242 lines
7.9 KiB
Go
242 lines
7.9 KiB
Go
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)
|
|||
|
|
}
|