Files
bj_power/bj_power_mes/internal/logic/dashboard_front.go
T

242 lines
7.9 KiB
Go
Raw Normal View History

package logic
import (
"context"
"strings"
"time"
)
/*
看板前端契约层(bj_power_dashboard/src/types.ts
MES 内部保留一套“富”聚合(DashboardSnapshot:质量/报警/绩效/AGV 等,供后续扩展),
而看板对外只暴露甲方约定的正面指标(见 types.ts 注释):
- 不直接下发合格率/不良/报警等负面字段
- 当前订单卡片:合同 / 负责人 / 工期 / 完成度 / 可追溯
- 设备一律以“在线台数”等正面口径
此文件把富快照映射为前端 JSON,字段名与 types.ts 一一对应。
*/
// FrontToolState 单把工具(扫码枪 / 拧紧枪)
type FrontToolState struct {
Status string `json:"status"` // online / offline / busy / alarm
CurrentSn string `json:"currentSn,omitempty"`
ProcessedCount int `json:"processedCount"`
}
// FrontStation 工位(前端 Station
type FrontStation struct {
ID int `json:"id"`
Name string `json:"name"`
Status string `json:"status"` // running / idle / alarm / offline
ScanGun FrontToolState `json:"scanGun"`
TighteningGun FrontToolState `json:"tighteningGun"`
CurrentSn string `json:"currentSn"`
CurrentOperator string `json:"currentOperator"`
}
// FrontProduction 产能总览(前端 ProductionOverview
type FrontProduction struct {
OutputToday int `json:"outputToday"`
TargetToday int `json:"targetToday"`
PassCount int `json:"passCount"` // 一次通过件数(替代合格率)
TorqueCount int `json:"torqueCount"`
InLine int `json:"inLine"`
DeviceOnline int `json:"deviceOnline"`
DeviceTotal int `json:"deviceTotal"`
StationSummary StationSummary `json:"stationSummary"`
}
// FrontMovement 出入库动态(实时事件流)
type FrontMovement struct {
ID string `json:"id"`
Time string `json:"time"`
Type string `json:"type"` // in / out
Material string `json:"material"`
Qty int `json:"qty"`
Operator string `json:"operator"`
}
// FrontWarehouse 仓储动态(前端 WarehouseOverview,数据源 WMS 8890
type FrontWarehouse struct {
TotalStock int `json:"totalStock"`
MaterialTypes int `json:"materialTypes"`
InboundToday int `json:"inboundToday"`
OutboundToday int `json:"outboundToday"`
Movements []FrontMovement `json:"movements"`
}
// FrontProgress 当前订单卡片(前端 ProductionProgress
type FrontProgress struct {
OrderNo string `json:"orderNo"`
ContractNo string `json:"contractNo"`
ProductName string `json:"productName"`
Owner string `json:"owner"`
PlanStart string `json:"planStart"`
PlanEnd string `json:"planEnd"`
RemainDays int `json:"remainDays"`
TotalQty int `json:"totalQty"`
DoneQty int `json:"doneQty"`
Progress float64 `json:"progress"`
ProcessDone int `json:"processDone"`
ProcessTotal int `json:"processTotal"`
CurrentProcess string `json:"currentProcess"`
Status string `json:"status"`
TraceStepCount int `json:"traceStepCount"`
Traceable bool `json:"traceable"`
}
// FrontTrends 近 7 日产量趋势(前端 TrendData{ production: [...] }
type FrontTrends struct {
Production []TrendPoint `json:"production"`
}
// FrontDashboard 前端全量快照(与 types.ts DashboardData 对齐;isDemo 由前端标注,不回传)
type FrontDashboard struct {
UpdatedAt string `json:"updatedAt"`
Production FrontProduction `json:"production"`
Warehouse FrontWarehouse `json:"warehouse"`
Equipment []FrontStation `json:"equipment"`
Progress FrontProgress `json:"progress"`
Traces []TraceItem `json:"traces"`
Trends FrontTrends `json:"trends"`
}
// DashboardFrontSnapshot 看板前端快照:富聚合 → 前端契约映射,走同一缓存(60s + 防击穿)。
func (s *Service) DashboardFrontSnapshot(ctx context.Context) (*FrontDashboard, error) {
return cachedTyped(ctx, s, "dashboard:front", dashboardTTL, func() (*FrontDashboard, error) {
rich, err := s.DashboardSnapshot(ctx)
if err != nil {
return nil, err
}
return mapFrontDashboard(rich), nil
})
}
// mapFrontDashboard 富快照 → 前端契约。
// 展示口径按甲方要求:负面字段(合格率/不良数/报警)一律不出现。
func mapFrontDashboard(r *DashboardSnapshot) *FrontDashboard {
f := &FrontDashboard{UpdatedAt: r.UpdatedAt}
// -------- production / equipment --------
onlineDevices := 0
summary := StationSummary{}
equipment := make([]FrontStation, 0, len(r.Equipment))
for _, st := range r.Equipment {
tool := "online"
switch st.Status {
case "running":
tool = "busy"
summary.Running++
case "alarm":
tool = "alarm"
summary.Alarm++
case "offline":
tool = "offline"
summary.Offline++
default:
summary.Idle++
}
if st.Status != "offline" {
onlineDevices += 2
}
equipment = append(equipment, FrontStation{
ID: st.StationNo,
Name: st.Name,
Status: st.Status,
ScanGun: FrontToolState{Status: tool, ProcessedCount: st.DoneToday},
TighteningGun: FrontToolState{Status: tool, CurrentSn: st.CurrentSn, ProcessedCount: st.DoneToday},
CurrentSn: st.CurrentSn,
CurrentOperator: st.CurrentOperator,
})
}
f.Equipment = equipment
f.Production = FrontProduction{
OutputToday: r.Production.OutputToday,
TargetToday: r.Production.TargetToday,
PassCount: r.Production.QualityOk,
TorqueCount: r.Production.TorqueTotal,
InLine: r.Production.InLine,
DeviceOnline: onlineDevices,
DeviceTotal: len(r.Equipment) * 2,
StationSummary: summary,
}
// -------- warehouse --------
f.Warehouse = FrontWarehouse{
TotalStock: r.Warehouse.TotalQty,
MaterialTypes: r.Warehouse.MaterialTypes,
InboundToday: r.Warehouse.InToday,
OutboundToday: r.Warehouse.OutToday,
Movements: []FrontMovement{},
}
// -------- 当前订单卡片 --------
pr := FrontProgress{ProcessTotal: 12, Status: "待排产"}
if c := r.Production.CurrentOrder; c != nil {
pr = FrontProgress{
OrderNo: c.OrderNo,
ProductName: c.ProductName,
TotalQty: c.TotalQty,
DoneQty: c.DoneQty,
Progress: c.Progress,
Status: c.Status,
CurrentProcess: c.CurrentProcess,
PlanStart: shortDate(c.PlanStart),
PlanEnd: shortDate(c.PlanEnd),
RemainDays: remainDays(c.PlanEnd),
ProcessTotal: 12,
}
}
// 追溯:取最近完工工件,汇总工序实绩条数 → “全程可追溯”
stepCount := 0
allOk := true
for _, t := range r.Trace.Recent {
stepCount += t.StepCount
if t.NgCount > 0 {
allOk = false
}
}
pr.TraceStepCount = stepCount
pr.Traceable = stepCount > 0 && allOk
f.Progress = pr
// -------- traces / trends --------
f.Traces = r.Trace.Recent
if f.Traces == nil {
f.Traces = []TraceItem{}
}
f.Trends = FrontTrends{Production: r.Trends}
if f.Trends.Production == nil {
f.Trends.Production = []TrendPoint{}
}
return f
}
// shortDate "2006-01-02 15:04:05" → "2006-01-02"
func shortDate(s string) string {
if len(s) >= 10 {
return s[:10]
}
return strings.TrimSpace(s)
}
// remainDays 距交期剩余天数:按自然日差(负数 = 已超期)。
// 工期为空的订单返回 0,前端显示“—”即可。
func remainDays(planEnd string) int {
layout := dashboardTimeLayout
if len(planEnd) == 10 {
layout = "2006-01-02"
}
t, err := time.ParseInLocation(layout, strings.TrimSpace(planEnd), time.Local)
if err != nil {
return 0
}
// 取日期差(跨过当天 0 点即视为到期)
now := time.Now()
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
end := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
return int(end.Sub(start).Hours() / 24)
}