chore: 批量完成项目多模块迭代优化
1. 废弃半成品库存表并统一库存主表 2. 修复列表排序与搜索大小写不敏感问题 3. 新增用户最近登录时间、工单BOM名称字段 4. 完善角色管理、工位选择器功能 5. 增加拧紧数据补录、成品自动回流WMS能力 6. 统一导出时间范围校验规则 7. 丰富物料/备料单筛选条件 8. 调整菜单与权限命名适配产品型号业务
This commit is contained in:
@@ -2,11 +2,16 @@ package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// atoiDefault 解析整型字符串,非法返回默认值
|
||||
@@ -110,7 +115,10 @@ func StationReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
func WorkloadInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("from"), q.Get("to"))
|
||||
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("from"), q.Get("to"),
|
||||
atoiDefault(q.Get("opPage"), 1), atoiDefault(q.Get("opPageSize"), 20),
|
||||
atoiDefault(q.Get("stPage"), 1), atoiDefault(q.Get("stPageSize"), 20),
|
||||
atoiDefault(q.Get("detPage"), 1), atoiDefault(q.Get("detPageSize"), 20))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2105, err.Error())
|
||||
return
|
||||
@@ -165,3 +173,91 @@ func StationCheckinInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc
|
||||
httpx.OkMessage(w, "暂存退库已登记", nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// PerformanceExportHandler GET /performance/export 绩效报表导出(一个 xlsx 三个 sheet:按人/按工位/明细)
|
||||
// 执行全项目统一导出时间硬规则:起止必填(默认近3个月)、间隔≤1年
|
||||
func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
startDate, endDate, rerr := normalizeExportRange(q.Get("startDate"), q.Get("endDate"))
|
||||
if rerr != nil {
|
||||
httpx.Fail(w, 2106, rerr.Error())
|
||||
return
|
||||
}
|
||||
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), startDate, endDate,
|
||||
1, 1<<30, 1, 1<<30, 1, 1<<30)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2105, err.Error())
|
||||
return
|
||||
}
|
||||
f := excelize.NewFile()
|
||||
type view struct {
|
||||
name string
|
||||
key string
|
||||
headers []string
|
||||
cols func(m map[string]any) []any
|
||||
}
|
||||
rowOf := func(v any) map[string]any { m, _ := v.(map[string]any); return m }
|
||||
views := []view{
|
||||
{"按人", "byOperator", []string{"操作人", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["operator"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
}},
|
||||
{"按工位", "byStation", []string{"工位", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["stationNo"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
}},
|
||||
{"明细", "detail", []string{"操作人", "工位", "日期", "工序", "工序名", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["operator"], m["stationNo"], m["date"], m["processCode"], m["processName"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
}},
|
||||
}
|
||||
for i, vw := range views {
|
||||
sheet := vw.name
|
||||
if i == 0 {
|
||||
f.SetSheetName("Sheet1", sheet)
|
||||
} else {
|
||||
f.NewSheet(sheet)
|
||||
}
|
||||
for c, h := range vw.headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(c+1, 1)
|
||||
_ = f.SetCellValue(sheet, cell, h)
|
||||
}
|
||||
vm, _ := data[vw.key].(map[string]any)
|
||||
list, _ := vm["list"].([]any)
|
||||
for rIdx, it := range list {
|
||||
m := rowOf(it)
|
||||
for c := range vw.headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(c+1, rIdx+2)
|
||||
_ = f.SetCellValue(sheet, cell, vw.cols(m)[c])
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape("绩效报表.xlsx"))
|
||||
_, _ = f.WriteTo(w)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// normalizeExportRange MES 侧导出时间硬规则(与 WMS 同一套):
|
||||
// ① 起止都空 → 默认近 3 个月;② 只传一个 → 报错;③ 间隔 > 1 年 → 报错;④ 结束早于起始 → 报错。
|
||||
func normalizeExportRange(startDate, endDate string) (string, string, error) {
|
||||
if startDate == "" && endDate == "" {
|
||||
now := time.Now()
|
||||
return now.AddDate(0, -3, 0).Format("2006-01-02"), now.Format("2006-01-02"), nil
|
||||
}
|
||||
if startDate == "" || endDate == "" {
|
||||
return "", "", errors.New("导出必须同时提供起始时间与结束时间")
|
||||
}
|
||||
st, err1 := time.ParseInLocation("2006-01-02", startDate, time.Local)
|
||||
et, err2 := time.ParseInLocation("2006-01-02", endDate, time.Local)
|
||||
if err1 != nil || err2 != nil {
|
||||
return "", "", errors.New("时间格式应为 YYYY-MM-DD")
|
||||
}
|
||||
if et.Before(st) {
|
||||
return "", "", errors.New("结束时间不能早于起始时间")
|
||||
}
|
||||
if et.Sub(st) > 366*24*time.Hour {
|
||||
return "", "", errors.New("导出时间间隔不能超过 1 年")
|
||||
}
|
||||
return startDate, endDate, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user