package controllers import ( "strings" "server/models" "github.com/beego/beego/v2/client/orm" ) // ERP 档案读取核心(两端共用)。 // // 商品/分类/仓库/客商在 /backend/erp/** 与 /api/erp/** 各有独立 handler 与路由(接口隔离规范), // 但「按租户过滤 + 关键字命中字段 + 列表补库存与状态」的口径只能有一份实现,放在这里。 // 返回值约定与 erp_*_core.go 一致:最后一个 string 为空表示成功,非空时配套 http 状态码。 // ---- 商品 ---- type erpProductFilter struct { Keyword string CategoryID uint64 Brand string Status string IsDraft string OnlyEnabled bool } // erpProductListCore 商品列表:出参已补分类名、全仓库存合计与预警状态。 func erpProductListCore(a erpActor, f erpProductFilter, page, pageSize int) ([]models.ErpProduct, int64, int, string) { qs := models.Orm.QueryTable(new(models.ErpProduct)). Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true) if kw := strings.TrimSpace(f.Keyword); kw != "" { cond := orm.NewCondition(). Or("product_name__contains", kw). Or("product_code__contains", kw). Or("barcode__contains", kw). Or("spec__contains", kw). Or("brand__contains", kw) qs = qs.SetCond(qs.GetCond().AndCond(cond)) } if f.CategoryID > 0 { qs = qs.Filter("category_id", f.CategoryID) } if b := strings.TrimSpace(f.Brand); b != "" { qs = qs.Filter("brand", b) } if st := strings.TrimSpace(f.Status); st != "" { qs = qs.Filter("status", st) } if d := strings.TrimSpace(f.IsDraft); d != "" { qs = qs.Filter("is_draft", d) } if f.OnlyEnabled { qs = qs.Filter("status", 1).Filter("is_draft", 0) } total, _ := qs.Count() var list []models.ErpProduct if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&list); err != nil { return nil, 0, 500, "查询失败: " + err.Error() } cats := erpCategoryMap(a.Tid) ids := make([]uint64, 0, len(list)) for _, p := range list { ids = append(ids, p.ID) } agg := erpStockAggregate(a.Tid, ids) for i := range list { list[i].CategoryName = cats[list[i].CategoryID] list[i].TotalStock = agg[list[i].ID].qty list[i].SafetyState = erpSafetyState(list[i].SafetyQty, list[i].TotalStock, list[i].MaxQty) } return list, total, 200, "" } // erpProductDetailCore 商品详情:含分类名与分仓/分批次结存。 func erpProductDetailCore(a erpActor, id uint64) (*models.ErpProduct, int, string) { if id == 0 { return nil, 400, "无效的ID" } var p models.ErpProduct if err := models.Orm.QueryTable(new(models.ErpProduct)). Filter("id", id).Filter("tenant_id", a.Tid). Filter("delete_time__isnull", true).One(&p); err != nil { return nil, 404, "商品未找到" } p.CategoryName = erpCategoryMap(a.Tid)[p.CategoryID] p.Stocks = erpProductStocks(a.Tid, id) return &p, 200, "" } // ---- 商品分类 ---- // erpCategoryListCore 分类列表 + 树,并统计每个分类下的商品数。 func erpCategoryListCore(a erpActor) ([]map[string]interface{}, []map[string]interface{}, int, string) { var list []models.ErpProductCategory _, _ = models.Orm.QueryTable(new(models.ErpProductCategory)). Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true). OrderBy("sort_order", "id").All(&list) counts := map[uint64]int64{} var ps []models.ErpProduct _, _ = models.Orm.QueryTable(new(models.ErpProduct)). Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true). All(&ps, "id", "category_id") for _, p := range ps { counts[p.CategoryID]++ } rows := make([]map[string]interface{}, 0, len(list)) for _, r := range list { rows = append(rows, map[string]interface{}{ "id": r.ID, "category_name": r.CategoryName, "parent_id": r.ParentID, "sort_order": r.SortOrder, "status": r.Status, "remark": r.Remark, "product_count": counts[r.ID], }) } return rows, erpCategoryTree(rows, 0), 200, "" } // ---- 仓库 ---- // erpWarehouseListCore 仓库列表:每仓在库品种数与货值,列表直接反映仓库占用情况。 func erpWarehouseListCore(a erpActor, keyword, status string, onlyEnabled bool) ([]map[string]interface{}, int, string) { qs := models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true) if kw := strings.TrimSpace(keyword); kw != "" { qs = qs.Filter("warehouse_name__contains", kw) } if st := strings.TrimSpace(status); st != "" { qs = qs.Filter("status", st) } if onlyEnabled { qs = qs.Filter("status", 1) } var list []models.ErpWarehouse if _, err := qs.OrderBy("-is_default", "id").All(&list); err != nil { return nil, 500, "查询失败: " + err.Error() } type agg struct { kinds int value float64 } amap := map[uint64]*agg{} var stocks []models.ErpStock _, _ = models.Orm.QueryTable(new(models.ErpStock)). Filter("tenant_id", a.Tid).All(&stocks, "warehouse_id", "quantity", "cost_price") for _, s := range stocks { w := amap[s.WarehouseID] if w == nil { w = &agg{} amap[s.WarehouseID] = w } if s.Quantity > 0.000001 { w.kinds++ } w.value += s.Quantity * s.CostPrice } rows := make([]map[string]interface{}, 0, len(list)) for _, wh := range list { v := amap[wh.ID] if v == nil { v = &agg{} } rows = append(rows, map[string]interface{}{ "warehouse": wh, "stock_kinds": v.kinds, "stock_value": round2f(v.value), }) } return rows, 200, "" } // ---- 客商(供应商/客户统一出参)---- // erpPartnerRow 移动端开单/对账用的客商精简行:供应商与客户共用一套字段, // 前端按 partner_type 决定「应收/应付」口径,不必分别处理两张表。 type erpPartnerRow struct { PartnerID uint64 `json:"partner_id"` PartnerType string `json:"partner_type"` PartnerName string `json:"partner_name"` ContactPerson string `json:"contact_person"` ContactPhone string `json:"contact_phone"` Level string `json:"level"` PaymentDays int `json:"payment_days"` CreditLimit float64 `json:"credit_limit"` OpenAmount float64 `json:"open_amount"` } // erpPartnerListCore 客商列表(partner_type=supplier|customer),附带未结往来余额。 func erpPartnerListCore(a erpActor, partnerType, keyword string, onlyEnabled bool, page, pageSize int) ([]erpPartnerRow, int64, int, string) { nameField := "customer_name" var qTable interface{} = new(models.TenantCrmCustomer) if partnerType == "supplier" { nameField = "supplier_name" qTable = new(models.TenantCrmSupplier) } else { partnerType = "customer" } qs := models.Orm.QueryTable(qTable). Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true) if kw := strings.TrimSpace(keyword); kw != "" { qs = qs.Filter(nameField+"__contains", kw) } if onlyEnabled { qs = qs.Filter("status", "1").Filter("is_draft", 0) } if partnerType == "customer" { // 公海客户没有归属,不参与开单 qs = qs.Filter("in_pool", 0) } total, _ := qs.Count() billType := int8(models.ErpBillReceivable) if partnerType == "supplier" { billType = models.ErpBillPayable } type brief struct { id uint64 name string contact string phone string level string payDays int credit float64 } var briefs []brief if partnerType == "supplier" { var list []models.TenantCrmSupplier if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize). All(&list, "id", "supplier_name", "contact_person", "contact_phone", "supplier_level", "payment_days"); err != nil { return nil, 0, 500, "查询失败: " + err.Error() } for _, s := range list { briefs = append(briefs, brief{s.ID, s.SupplierName, s.ContactPerson, s.ContactPhone, s.SupplierLevel, s.PaymentDays, 0}) } } else { var list []models.TenantCrmCustomer if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize). All(&list, "id", "customer_name", "contact_person", "contact_phone", "customer_level", "payment_days", "credit_limit"); err != nil { return nil, 0, 500, "查询失败: " + err.Error() } for _, c := range list { briefs = append(briefs, brief{c.ID, c.CustomerName, c.ContactPerson, c.ContactPhone, c.CustomerLevel, c.PaymentDays, c.CreditLimit}) } } ids := make([]uint64, 0, len(briefs)) for _, b := range briefs { ids = append(ids, b.id) } open := map[uint64]float64{} if len(ids) > 0 { var bills []models.ErpPartnerBill _, _ = models.Orm.QueryTable(new(models.ErpPartnerBill)). Filter("tenant_id", a.Tid).Filter("bill_type", billType). Filter("status__lt", 2).Filter("partner_id__in", ids). All(&bills, "partner_id", "amount", "settled_amount") for _, b := range bills { open[b.PartnerID] += b.Amount - b.SettledAmount } } out := make([]erpPartnerRow, 0, len(briefs)) for _, b := range briefs { out = append(out, erpPartnerRow{ PartnerID: b.id, PartnerType: partnerType, PartnerName: strings.TrimSpace(b.name), ContactPerson: b.contact, ContactPhone: b.phone, Level: b.level, PaymentDays: b.payDays, CreditLimit: b.credit, OpenAmount: round2f(open[b.id]), }) } return out, total, 200, "" } // erpPartnerDetailCore 客商详情(按 partner_type 回原始档案,字段与 Web 端一致)。 func erpPartnerDetailCore(a erpActor, partnerType string, id uint64) (interface{}, int, string) { if id == 0 { return nil, 400, "无效的ID" } if partnerType == "supplier" { var s models.TenantCrmSupplier if err := models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id).Filter("tenant_id", a.Tid). Filter("delete_time__isnull", true).One(&s); err != nil { return nil, 404, "供应商未找到" } return s, 200, "" } var c models.TenantCrmCustomer if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)). Filter("id", id).Filter("tenant_id", a.Tid). Filter("delete_time__isnull", true).One(&c); err != nil { return nil, 404, "客户未找到" } return c, 200, "" }