chore: 完成多系统功能迭代与优化
1. 通用优化: - 统一系统标题为"库房客户端"/"MES产线控制",移除北自所前缀 - 调整内置账号密码为统一123456,优化密码校验逻辑 - 新增数据库自动创建逻辑,简化部署流程 - 修复日志时间格式配置,优化日志输出 - 新增纯数字密码安全提示 2. MES系统优化: - 新增日排产管理功能,支持增删改查与自动算料生成备料单 - 重构BOM模块,改为按产品编码维护而非工单维度 - 新增工艺参数模板配置与手动报工页面 - 新增产品类型自动编码功能 - 优化菜单结构,调整工单管理、扫码报工等页面名称与路由 - 新增删除日排产接口与权限保护 - 修复物料清单查询逻辑,适配新BOM结构 - 新增refreshToken支持,优化登录会话管理 3. WMS系统优化: - 新增基础数据维护页面,支持区域与物料档案管理 - 新增工单列表下拉接口,对接MES获取未完成工单优先展示 - 优化入库管理提示文案,替换英文提示为中文 - 修复精密件入库校验逻辑,优化错误提示 - 优化Excel导入功能,新增备注字段支持 - 优化登录页面与菜单文案,统一备料台账名称 - 新增自动打开浏览器功能,优化客户端启动体验 - 修复备料出库页面查询提示文案 - 新增WMS与MES对接配置,完善内部API调用逻辑 4. 其他优化: - 删除冗余的旧版静态资源文件,更新资源引用路径 - 新增帮助文档,完善基础数据模块说明 - 修复多处文案不统一、英文残留问题
This commit is contained in:
@@ -6,34 +6,45 @@ import (
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
// PostgresConfig PostgreSQL 连接配置(go-zero 按 json tag + 字段名小写匹配 yaml)
|
||||
type PostgresConfig struct {
|
||||
Host string `yaml:"Host"`
|
||||
Port int `yaml:"Port"`
|
||||
User string `yaml:"User"`
|
||||
Password string `yaml:"Password"`
|
||||
DBName string `yaml:"DBName"`
|
||||
SSLMode string `yaml:"SSLMode"`
|
||||
Host string `json:",default=127.0.0.1"`
|
||||
Port int `json:",default=5432"`
|
||||
User string `json:",default=postgres"`
|
||||
Password string `json:",default=postgres"`
|
||||
DBName string `json:",default=bj_power_wms"`
|
||||
SSLMode string `json:",default=disable"`
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
AccessSecret string `yaml:"AccessSecret"`
|
||||
AccessExpire int64 `yaml:"AccessExpire"`
|
||||
AccessSecret string `json:",default=Hardman_2026"`
|
||||
AccessExpire int64 `json:",default=1800"`
|
||||
}
|
||||
|
||||
// InternalConfig 项目间内部 API 配置
|
||||
type InternalConfig struct {
|
||||
Token string `yaml:"Token"` // 写死的项目间 API token(X-API-TOKEN)
|
||||
Token string `json:",default=Hardman_2026"` // 写死的项目间 API token(X-API-TOKEN)
|
||||
}
|
||||
|
||||
// MesConfig 下游 MES 服务对接(备料/台账需取工单与 BOM)
|
||||
type MesConfig struct {
|
||||
BaseURL string `json:",default=http://127.0.0.1:8888"` // MES 服务地址
|
||||
Token string `json:",default=Hardman_2026"` // 调 MES 内部接口的 X-API-TOKEN
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Postgres PostgresConfig `yaml:"Postgres"`
|
||||
Auth AuthConfig `yaml:"Auth"`
|
||||
Internal InternalConfig `yaml:"Internal"`
|
||||
Redis struct {
|
||||
Host string `yaml:"Host"`
|
||||
Type string `yaml:"Type"`
|
||||
} `yaml:"Redis"`
|
||||
Postgres PostgresConfig
|
||||
Auth AuthConfig
|
||||
Internal InternalConfig
|
||||
Redis RedisConf
|
||||
Mes MesConfig
|
||||
}
|
||||
|
||||
// RedisConf Redis 连接配置
|
||||
type RedisConf struct {
|
||||
Host string `json:",default=127.0.0.1:6379"`
|
||||
Type string `json:",default=node"`
|
||||
}
|
||||
|
||||
// DSN 返回 PostgreSQL 连接串
|
||||
|
||||
@@ -22,7 +22,7 @@ func MustNewDB(c DatabaseConf) *ent.Client {
|
||||
}
|
||||
|
||||
func NewDB(c DatabaseConf) (*ent.Client, error) {
|
||||
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s",
|
||||
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
|
||||
c.User, c.Password, c.Host, c.Port, c.Dbname,
|
||||
)
|
||||
|
||||
@@ -39,6 +39,34 @@ func NewDB(c DatabaseConf) (*ent.Client, error) {
|
||||
return ent.NewClient(ent.Driver(drv)), nil
|
||||
}
|
||||
|
||||
// EnsureDB 若目标数据库不存在则自动创建(先连 postgres 维护库做检查/创建)
|
||||
func EnsureDB(c DatabaseConf) error {
|
||||
maintenanceDSN := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
|
||||
c.User, c.Password, c.Host, c.Port, "postgres")
|
||||
sqlDB, err := sql.Open("pgx", maintenanceDSN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
var exists bool
|
||||
if err := sqlDB.QueryRow(
|
||||
`SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)`, c.Dbname,
|
||||
).Scan(&exists); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := sqlDB.Exec(`CREATE DATABASE "` + c.Dbname + `"`); err != nil {
|
||||
return err
|
||||
}
|
||||
slog.Info("已自动创建数据库 " + c.Dbname)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoMigrate 启动时自动建表/迁移
|
||||
func AutoMigrate(client *ent.Client) error {
|
||||
ctx := contextBackdrop()
|
||||
|
||||
@@ -31,10 +31,10 @@ func SeedIfEmpty(client *ent.Client) error {
|
||||
slog.Info("seed: zones created")
|
||||
}
|
||||
|
||||
// 2. 账号
|
||||
// 2. 账号(全部统一初始密码 123456)
|
||||
users, _ := client.User.Query().Count(ctx)
|
||||
if users == 0 {
|
||||
adminHash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
||||
adminHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
||||
opHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
||||
client.User.Create().
|
||||
SetUsername("admin").
|
||||
@@ -56,17 +56,17 @@ func SeedIfEmpty(client *ent.Client) error {
|
||||
SetRole("inspector").
|
||||
SetDept("质检").
|
||||
SaveX(ctx)
|
||||
slog.Info("seed: users created (admin/admin123, store1/123456, insp1/123456)")
|
||||
slog.Info("seed: users created (admin/123456, store1/123456, insp1/123456)")
|
||||
}
|
||||
|
||||
// 3. 演示物料与库存
|
||||
materials, _ := client.Material.Query().Count(ctx)
|
||||
if materials == 0 {
|
||||
mats := []struct {
|
||||
code, name, spec, unit string
|
||||
mode int
|
||||
mtype string
|
||||
isBatch, isSerial bool
|
||||
code, name, spec, unit string
|
||||
mode int
|
||||
mtype string
|
||||
isBatch, isSerial bool
|
||||
}{
|
||||
{"GJ-BAN-001", "10mm钢板(结构件)", "2000x1000x10", "张", 1, "raw", true, false},
|
||||
{"GJ-GANGGUAN-002", "无缝钢管114x8", "114x8x6000", "根", 1, "raw", true, false},
|
||||
@@ -115,7 +115,7 @@ func SeedIfEmpty(client *ent.Client) error {
|
||||
for i := 1; i <= 5; i++ {
|
||||
sn := "SN20260601001" + time.Now().Format("02")
|
||||
client.SerialNumber.Create().
|
||||
SetSnCode(sn+itoaPad(i)).
|
||||
SetSnCode(sn + itoaPad(i)).
|
||||
SetMaterialCode("JM-ZHOUCHENG-6020").
|
||||
SetStatus("在库").
|
||||
SetQualityStatus("合格").
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// excelInboundHandler Excel 批量导入入库(结构件)
|
||||
// 列头(第一行忽略):物料编码 | 批次号(可空,自动生成) | 数量 | 生产日期 | 供应商 | 区域
|
||||
// 列头(第一行忽略):物料编码 | 批次号(可空,自动生成) | 数量 | 生产日期 | 供应商 | 区域 | 备注
|
||||
func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(20 << 20); err != nil {
|
||||
@@ -67,6 +67,7 @@ func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
prodDate := cell(3)
|
||||
supplier := cell(4)
|
||||
zone := cell(5)
|
||||
remark := cell(6)
|
||||
|
||||
res := resultRow{Row: i + 1, Code: code, BatchNo: batchNo, Qty: qty}
|
||||
|
||||
@@ -116,6 +117,7 @@ func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
SetManageMode(1).
|
||||
SetNillableZoneCode(strPtr(zone)).
|
||||
SetQuantity(qty).
|
||||
SetNillableRemark(strPtr(remark)).
|
||||
SetOperator("excel-import").
|
||||
SaveX(ctx0())
|
||||
res.BatchNo = batchNo
|
||||
|
||||
@@ -37,7 +37,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
if req.MaterialCode == "" {
|
||||
fail(w, http.StatusBadRequest, "materialCode 必填")
|
||||
fail(w, http.StatusBadRequest, "物料编码必填")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
qty := req.Quantity
|
||||
if qty <= 0 {
|
||||
fail(w, http.StatusBadRequest, "结构件入库 quantity 必填且 > 0")
|
||||
fail(w, http.StatusBadRequest, "结构件入库 数量必填且大于0")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
// 精密件(SN 管理)
|
||||
if m.ManageMode == 2 {
|
||||
if len(req.SnList) == 0 {
|
||||
fail(w, http.StatusBadRequest, "精密件入库 snList 必填")
|
||||
fail(w, http.StatusBadRequest, "精密件入库 序列号(SN)必填")
|
||||
return
|
||||
}
|
||||
for _, sn := range req.SnList {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"bj_power_wms/internal/svc"
|
||||
)
|
||||
|
||||
// 视为"已完成"的工单状态(查询时排到最后,未完成的优先显示)
|
||||
var mesDoneStatuses = map[string]bool{
|
||||
"FINISHED": true, "COMPLETED": true, "DONE": true,
|
||||
"CLOSED": true, "已完结": true, "已完成": true,
|
||||
}
|
||||
|
||||
type mesOrder struct {
|
||||
WorkOrderNo string `json:"workOrderNo"`
|
||||
ProductCode string `json:"productCode"`
|
||||
ProductName string `json:"productName"`
|
||||
Quantity int `json:"quantity"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ordersHandler 调用 MES 内部接口获取工单列表,把未完成工单排前面,供备料出库下拉选择
|
||||
func ordersHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
url := ctx.Config.Mes.BaseURL + "/api/internal/order/query?orderNo=" + r.URL.Query().Get("orderNo")
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "构造 MES 请求失败")
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-API-TOKEN", ctx.Config.Mes.Token)
|
||||
cli := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "无法连接 MES 服务")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fail(w, http.StatusBadGateway, "MES 返回异常")
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Data []mesOrder `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
fail(w, http.StatusBadGateway, "解析 MES 响应失败")
|
||||
return
|
||||
}
|
||||
list := body.Data
|
||||
if list == nil {
|
||||
list = []mesOrder{}
|
||||
}
|
||||
sort.SliceStable(list, func(i, j int) bool {
|
||||
di := mesDoneStatuses[list[i].Status]
|
||||
dj := mesDoneStatuses[list[j].Status]
|
||||
if di != dj {
|
||||
return !di // 未完成在前
|
||||
}
|
||||
return list[i].WorkOrderNo < list[j].WorkOrderNo
|
||||
})
|
||||
ok(w, list)
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) {
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{Method: http.MethodGet, Path: "/api/ledger/query", Handler: queryLedgerHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/orders", Handler: ordersHandler(ctx)},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -12,14 +12,20 @@ type ServiceContext struct {
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
entClient := db.MustNewDB(db.DatabaseConf{
|
||||
conf := db.DatabaseConf{
|
||||
Host: c.Postgres.Host,
|
||||
Port: c.Postgres.Port,
|
||||
User: c.Postgres.User,
|
||||
Password: c.Postgres.Password,
|
||||
Dbname: c.Postgres.DBName,
|
||||
Debug: false,
|
||||
})
|
||||
}
|
||||
|
||||
// 目标数据库不存在则自动创建
|
||||
if err := db.EnsureDB(conf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
entClient := db.MustNewDB(conf)
|
||||
|
||||
// 启动时自动建表
|
||||
if err := db.AutoMigrate(entClient); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user