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) }