本次迭代覆盖MES与WMS核心业务: 1. 新增接驳台托盘传感器读取与AGV对接能力 2. 完善工单排产、备料流程与权限体系拆分 3. 优化看板接口与前端路由、样式 4. 新增操作日志、库存盘点与角色保护逻辑 5. 修复代理地址、BOM保存等已知问题
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)
|
||
}
|