This commit is contained in:
hero920103 committed 2026-10-19 01:13:17 +08:00
1 parent 5718a00e31
commit 1850ac640e
226 files changed
+37772 -12071

No files matched your search

+652
View File
@@ -0,0 +1,652 @@
package controllers
import (
"fmt"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
approvalEngine "server/services/approval"
erp "server/services/erp"
"github.com/beego/beego/v2/client/orm"
)
// ERP 单据核心逻辑(两端共用)。
//
// 按项目的接口隔离约定,/backend/erp/document* 与 /api/erp/document* 必须各有独立的
// handler 与路由,但单据的校验、明细装配、落库事务与审批提交口径只能有一份实现:
// 这里就是那一份。两端 handler 只负责「解析自己的令牌 → 组装 erpActor → 调核心 →
// 按各自的响应信封输出」,业务规则不在两端各写一遍,避免移动端口径慢慢跑偏。
//
// 返回约定沿用 reimburse*Core:最后一个 string 为空即成功,非空时给出处置状态码与文案,
// 由调用方原样透出,保证抽取前后接口行为完全一致。
// erpActor ERP 操作人:由各自端的 JWT 解析而来,核心层只认这个结构,不认 beego 上下文
type erpActor struct {
Tid string // 租户ID字符串口径(ERP 表的 tenant_id 是 VARCHAR)
TidNum uint64
UID uint64
Name string
}
func erpNewActor(claims *jwtutil.Claims) erpActor {
return erpActor{
Tid: fmt.Sprintf("%d", claims.TenantId),
TidNum: uint64(claims.TenantId),
UID: uint64(claims.UserID),
Name: resolveUserName(claims),
}
}
// erpDocFilter 单据列表筛选条件(与 Web 端查询参数一一对应)
type erpDocFilter struct {
DocType string
DocTypes string // 逗号分隔的多类型
Status string
Keyword string
PartnerID uint64
WarehouseID uint64
SourceDocID uint64
DateFrom string
DateTo string
}
// erpDocListCore 单据分页查询
func erpDocListCore(a erpActor, f erpDocFilter, page, pageSize int) ([]models.ErpDocument, int64, int, string) {
if f.DocType != "" && !erpDocTypes[f.DocType] {
return nil, 0, 400, "未知的单据类型"
}
qs := models.Orm.QueryTable(new(models.ErpDocument)).
Filter("tenant_id", a.Tid).Filter("delete_time__isnull", true)
if f.DocType != "" {
qs = qs.Filter("doc_type", f.DocType)
}
if types := strings.TrimSpace(f.DocTypes); types != "" {
list := make([]string, 0, 4)
for _, t := range strings.Split(types, ",") {
if erpDocTypes[strings.TrimSpace(t)] {
list = append(list, strings.TrimSpace(t))
}
}
if len(list) > 0 {
qs = qs.Filter("doc_type__in", list)
}
}
if st := strings.TrimSpace(f.Status); st != "" {
qs = qs.Filter("status", st)
}
if kw := strings.TrimSpace(f.Keyword); kw != "" {
cond := orm.NewCondition().
Or("doc_no__contains", kw).
Or("partner_name__contains", kw).
Or("remark__contains", kw)
qs = qs.SetCond(qs.GetCond().AndCond(cond))
}
if f.PartnerID > 0 {
qs = qs.Filter("partner_id", f.PartnerID)
}
if f.WarehouseID > 0 {
qs = qs.Filter("warehouse_id", f.WarehouseID)
}
if f.SourceDocID > 0 {
qs = qs.Filter("source_doc_id", f.SourceDocID)
}
for _, d := range []struct {
raw string
cond string
}{{f.DateFrom, "biz_date__gte"}, {f.DateTo, "biz_date__lte"}} {
if strings.TrimSpace(d.raw) == "" {
continue
}
if t, e := time.ParseInLocation("2006-01-02", strings.TrimSpace(d.raw), time.Local); e == nil {
qs = qs.Filter(d.cond, t)
}
}
total, _ := qs.Count()
var list []models.ErpDocument
if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&list); err != nil {
return nil, 0, 500, "查询失败: " + err.Error()
}
erpFillDocNames(list)
for i := range list {
list[i].StatusLabel = models.ErpDocStatusLabel(list[i].Status)
}
return list, total, 200, ""
}
// erpDocDetailCore 单据详情(含明细、库存快照与审批实例摘要)
func erpDocDetailCore(a erpActor, id uint64) (*models.ErpDocument, int, string) {
if id == 0 {
return nil, 400, "无效的ID"
}
doc, err := erpLoadDoc(id, a.Tid)
if err != nil {
return nil, 404, "单据未找到"
}
doc.Items = erpDocItems(models.Orm, id)
erpFillItemStock(a.Tid, doc.Items, doc.WarehouseID)
single := []models.ErpDocument{*doc}
erpFillDocNames(single)
single[0].StatusLabel = models.ErpDocStatusLabel(single[0].Status)
if doc.InstanceID > 0 {
single[0].Instance = approvalEngine.InstanceBrief(doc.InstanceID)
}
return &single[0], 200, ""
}
// erpValidateDocHead 校验表头:仓库归属、往来单位匹配、调拨/盘点的仓库约束
func erpValidateDocHead(tid string, p *erpDocPayload) string {
needWh := models.ErpDocAffectsStock(p.DocType)
if needWh && p.WarehouseID == 0 && p.DocType != models.ErpDocStockTake {
return "请选择仓库"
}
if p.WarehouseID > 0 && !erpWarehouseOwned(tid, p.WarehouseID) {
return "仓库不存在或不属于当前租户"
}
if p.DocType == models.ErpDocTransfer {
if p.ToWarehouseID == 0 {
return "请选择调入仓库"
}
if p.ToWarehouseID == p.WarehouseID {
return "调入仓库不能与调出仓库相同"
}
if !erpWarehouseOwned(tid, p.ToWarehouseID) {
return "调入仓库不存在或不属于当前租户"
}
}
if pt := docTypePartner(p.DocType); pt != "" {
if p.PartnerID == 0 {
return "请选择" + erpPartnerLabel(pt)
}
}
if p.SourceDocID > 0 {
src, e := erpLoadDoc(p.SourceDocID, tid)
if e != nil {
return "来源单据不存在"
}
wants := docTypeSources(p.DocType)
if len(wants) > 0 {
allowed := false
labels := make([]string, 0, len(wants))
for _, w := range wants {
labels = append(labels, docTypeLabelOf(w))
if src.DocType == w {
allowed = true
}
}
if !allowed {
return "来源单据类型应为" + strings.Join(labels, "或")
}
if src.Status != models.ErpDocStatusApproved {
return "来源单据「" + src.DocNo + "」尚未生效,不能引用"
}
}
}
if p.DiscountAmount < 0 {
return "优惠金额不能为负"
}
return ""
}
// refItemIDs 收集明细行引用的来源单据行ID
func refItemIDs(raw []erpDocItemPayload) []uint64 {
out := make([]uint64, 0, len(raw))
for _, r := range raw {
if r.RefItemID > 0 {
out = append(out, r.RefItemID)
}
}
return out
}
// erpBuildItems 明细行规范化:补商品快照/单价(价格体系)、算金额、校验限价与批次
func erpBuildItems(tid string, doc *models.ErpDocument, raw []erpDocItemPayload) ([]models.ErpDocumentItem, string) {
ids := make([]uint64, 0, len(raw))
for _, r := range raw {
if r.ProductID > 0 {
ids = append(ids, r.ProductID)
}
}
pmap := erpProductMap(tid, ids)
// 来源订单明细行归属:草稿覆盖保存会重建明细(行 ID 变化),前端若仍拿着旧行 ID,
// 不在开单时就拦下,脏引用要拖到审核生效才报错,移动端更难定位。
refOwner := map[uint64]uint64{}
if refIDs := refItemIDs(raw); len(refIDs) > 0 {
var refs []models.ErpDocumentItem
_, _ = models.Orm.QueryTable(new(models.ErpDocumentItem)).
Filter("id__in", refIDs).Filter("tenant_id", tid).All(&refs, "id", "doc_id")
for _, r := range refs {
refOwner[r.ID] = r.DocID
}
}
settings := erp.GetSettings(tid)
out := make([]models.ErpDocumentItem, 0, len(raw))
for i, r := range raw {
p, okm := pmap[r.ProductID]
if !okm {
return nil, fmt.Sprintf("第%d行商品不存在", i+1)
}
if p.IsDraft == 1 || p.Status != 1 {
return nil, fmt.Sprintf("商品「%s」未启用,不能开单", p.ProductName)
}
if r.Quantity <= 0 {
return nil, fmt.Sprintf("商品「%s」数量必须大于0", p.ProductName)
}
unitPrice := r.UnitPrice
if unitPrice <= 0 {
unitPrice = erpPickPrice(p, doc.DocType, settings.SalesPriceType)
}
// 限价控制:低于最低售价直接拦截(除管理员放行的场景,这里不做例外)
if strings.HasPrefix(doc.DocType, "sales") && p.MinPrice > 0 && unitPrice < p.MinPrice-0.005 {
return nil, fmt.Sprintf("商品「%s」售价 %.2f 低于最低售价 %.2f", p.ProductName, unitPrice, p.MinPrice)
}
if doc.DocType == models.ErpDocPurchaseOrder || doc.DocType == models.ErpDocPurchaseIn {
if p.EnableBatch == 1 && strings.TrimSpace(r.BatchNo) == "" && doc.DocType == models.ErpDocPurchaseIn {
return nil, fmt.Sprintf("商品「%s」启用批次管理,请填写批次号", p.ProductName)
}
}
unit := strings.TrimSpace(r.Unit)
if unit == "" {
unit = p.BaseUnit
}
amount := r.Amount
if amount <= 0 {
amount = round2f(r.Quantity * unitPrice)
}
wh := r.WarehouseID
if wh > 0 && !erpWarehouseOwned(tid, wh) {
return nil, fmt.Sprintf("第%d行仓库无效", i+1)
}
if r.RefItemID > 0 && refOwner[r.RefItemID] != doc.SourceDocID {
return nil, fmt.Sprintf("第%d行关联的来源单据明细已失效,请重新选择来源单据", i+1)
}
it := models.ErpDocumentItem{
ProductID: p.ID, ProductName: p.ProductName, Spec: p.Spec, Unit: unit,
WarehouseID: wh, BatchNo: strings.TrimSpace(r.BatchNo),
Quantity: round4f(r.Quantity), BookQty: round4f(r.BookQty),
RefItemID: r.RefItemID, UnitPrice: unitPrice, Amount: round2f(amount),
Remark: r.Remark, SortOrder: r.SortOrder,
}
if it.SortOrder == 0 {
it.SortOrder = i + 1
}
if r.ProductionDate != "" {
if t, e := time.ParseInLocation("2006-01-02", r.ProductionDate, time.Local); e == nil {
it.ProductionDate = &t
if p.ShelfLifeDays > 0 {
ed := t.AddDate(0, 0, p.ShelfLifeDays)
it.ExpireDate = &ed
}
}
}
if r.ExpireDate != "" {
if t, e := time.ParseInLocation("2006-01-02", r.ExpireDate, time.Local); e == nil {
it.ExpireDate = &t
}
}
// 盘点行未带账面数时按当前结存补齐
if doc.DocType == models.ErpDocStockTake && it.BookQty == 0 && it.WarehouseID == 0 {
it.BookQty = erpStockQty(tid, p.ID, doc.WarehouseID, it.BatchNo)
if it.UnitPrice == 0 {
it.UnitPrice = erpStockCost(tid, p.ID, doc.WarehouseID, it.BatchNo)
}
}
if msg := erpLinePreCheck(tid, doc, settings, &it); msg != "" {
return nil, msg
}
out = append(out, it)
}
return out, ""
}
// erpDocSaveCore 新建或覆盖保存草稿(payload 带 id 即为编辑)。已生效/审批中的单据不允许改动。
func erpDocSaveCore(a erpActor, p erpDocPayload) (uint64, string, int, string) {
tid := a.Tid
if !erpDocTypes[p.DocType] {
return 0, "", 400, "未知的单据类型"
}
if len(p.Items) == 0 {
return 0, "", 400, "请至少添加一行明细"
}
if errMsg := erpValidateDocHead(tid, &p); errMsg != "" {
return 0, "", 400, errMsg
}
doc := &models.ErpDocument{TenantID: tid}
if p.ID > 0 {
exist, e := erpLoadDoc(p.ID, tid)
if e != nil {
return 0, "", 404, "单据未找到"
}
if exist.Status == models.ErpDocStatusApproved || exist.Status == models.ErpDocStatusPending {
return 0, "", 400, "审批中或已生效的单据不可编辑,请先撤回或作废"
}
if exist.Status == models.ErpDocStatusCancelled {
return 0, "", 400, "已作废单据不可编辑"
}
doc = exist
}
if doc.DocNo == "" {
no, e := erp.GenerateDocNo(nil, tid, p.DocType)
if e != nil {
return 0, "", 500, e.Error()
}
doc.DocNo = no
}
bizDate := time.Now()
if p.BizDate != "" {
if t, e := time.ParseInLocation("2006-01-02", p.BizDate, time.Local); e == nil {
bizDate = t
}
}
doc.DocType = p.DocType
doc.BizDate = bizDate
doc.WarehouseID = p.WarehouseID
doc.ToWarehouseID = p.ToWarehouseID
doc.SourceDocID = p.SourceDocID
doc.DiscountAmount = p.DiscountAmount
doc.Remark = p.Remark
doc.Status = models.ErpDocStatusDraft
if pt := docTypePartner(p.DocType); pt != "" {
doc.PartnerType = pt
doc.PartnerID = p.PartnerID
doc.PartnerName = erpPartnerName(pt, p.PartnerID)
if doc.PartnerName == "" {
return 0, "", 400, erpPartnerLabel(pt) + "未选择或不存在"
}
} else {
doc.PartnerType, doc.PartnerID, doc.PartnerName = "", 0, ""
}
if doc.CreateUserID == "" {
doc.CreateUserID = fmt.Sprintf("%d", a.UID)
doc.CreateUserName = a.Name
}
items, errMsg := erpBuildItems(tid, doc, p.Items)
if errMsg != "" {
return 0, "", 400, errMsg
}
doc.TotalQty = 0
doc.TotalAmount = 0
for _, it := range items {
doc.TotalQty += it.Quantity
doc.TotalAmount += it.Amount
}
doc.TotalQty = round4f(doc.TotalQty)
doc.TotalAmount = round2f(doc.TotalAmount)
doc.ActualAmount = round2f(doc.TotalAmount - doc.DiscountAmount)
if doc.ActualAmount < 0 {
return 0, "", 400, "优惠金额不能大于单据金额"
}
o := models.Orm
tx, err := o.Begin()
if err != nil {
return 0, "", 500, "保存失败"
}
rollback := true
defer func() { if rollback { _ = tx.Rollback() } }()
now := time.Now()
doc.UpdateTime = now
if doc.ID == 0 {
doc.CreateTime = now
id, e := tx.Insert(doc)
if e != nil {
return 0, "", 500, "保存失败: " + e.Error()
}
doc.ID = uint64(id)
} else {
if _, e := tx.QueryTable(new(models.ErpDocument)).Filter("id", doc.ID).
Update(orm.Params{
"doc_type": doc.DocType, "biz_date": doc.BizDate, "warehouse_id": doc.WarehouseID,
"to_warehouse_id": doc.ToWarehouseID, "partner_type": doc.PartnerType,
"partner_id": doc.PartnerID, "partner_name": doc.PartnerName,
"source_doc_id": doc.SourceDocID, "total_qty": doc.TotalQty,
"total_amount": doc.TotalAmount, "discount_amount": doc.DiscountAmount,
"actual_amount": doc.ActualAmount, "status": models.ErpDocStatusDraft,
"instance_id": 0, "remark": doc.Remark, "update_time": now,
}); e != nil {
return 0, "", 500, "保存失败: " + e.Error()
}
if _, e := tx.QueryTable(new(models.ErpDocumentItem)).Filter("doc_id", doc.ID).Delete(); e != nil {
return 0, "", 500, "保存失败: " + e.Error()
}
}
for i := range items {
items[i].DocID = doc.ID
items[i].TenantID = tid
items[i].ID = 0
if _, e := tx.Insert(&items[i]); e != nil {
return 0, "", 500, "保存明细失败: " + e.Error()
}
}
if err := tx.Commit(); err != nil {
return 0, "", 500, "保存失败: " + err.Error()
}
rollback = false
return doc.ID, doc.DocNo, 200, ""
}
// erpDocDeleteCore 删除单据(软删主表、硬删明细),仅草稿/驳回可删
func erpDocDeleteCore(a erpActor, id uint64) (int, string) {
doc, errMsg := erpLoadEditable(id, a.Tid, []int8{models.ErpDocStatusDraft, models.ErpDocStatusRejected})
if errMsg != "" {
return 400, errMsg
}
now := time.Now()
tx, err := models.Orm.Begin()
if err != nil {
return 500, "删除失败"
}
rollback := true
defer func() { if rollback { _ = tx.Rollback() } }()
if _, err = tx.QueryTable(new(models.ErpDocument)).Filter("id", doc.ID).
Update(orm.Params{"delete_time": now, "update_time": now}); err != nil {
return 500, "删除失败: " + err.Error()
}
// 明细随主表硬删:主表只软删,历史仍可追溯;明细无独立查询价值
if _, err = tx.QueryTable(new(models.ErpDocumentItem)).Filter("doc_id", doc.ID).Delete(); err != nil {
return 500, "删除失败: " + err.Error()
}
if err = tx.Commit(); err != nil {
return 500, "删除失败"
}
rollback = false
return 200, ""
}
// erpDocCancelCore 作废:草稿/驳回/审批中均可作废;已生效单据不做反向单据,直接拒绝,
// 保证「已生效=流水已入账」这一不可篡改前提。
func erpDocCancelCore(a erpActor, id uint64) (int, string) {
doc, errMsg := erpLoadEditable(id, a.Tid, []int8{
models.ErpDocStatusDraft, models.ErpDocStatusRejected, models.ErpDocStatusPending,
})
if errMsg != "" {
return 400, errMsg
}
now := time.Now()
params := orm.Params{"status": models.ErpDocStatusCancelled, "instance_id": 0, "update_time": now}
if _, err := models.Orm.QueryTable(new(models.ErpDocument)).Filter("id", doc.ID).Update(params); err != nil {
return 500, "作废失败: " + err.Error()
}
if doc.InstanceID > 0 {
approvalEngine.CancelInstance(doc.InstanceID, "单据已作废")
}
return 200, ""
}
// erpDocApproveCore 直接审核生效(未走审批流时使用)。库存/往来账效果在同一事务内落地。
func erpDocApproveCore(a erpActor, id uint64) (int, string) {
if id == 0 {
return 400, "无效的ID"
}
if err := erp.ApproveDocument(id, a.Tid, fmt.Sprintf("%d", a.UID), a.Name); err != nil {
return 400, err.Error()
}
return 200, ""
}
// erpDocSubmitCore 提交审批:flowID 为 0 时按单据类型自动选流。
// 流程未配置人工节点时引擎会直接结束实例,此时同步单据终态。
func erpDocSubmitCore(a erpActor, id, flowID uint64) (uint64, int, string) {
if id == 0 {
return 0, 400, "无效的ID"
}
doc, errMsg := erpLoadEditable(id, a.Tid, []int8{models.ErpDocStatusDraft, models.ErpDocStatusRejected})
if errMsg != "" {
return 0, 400, errMsg
}
if len(erpDocItems(models.Orm, doc.ID)) == 0 {
return 0, 400, "单据没有明细行,无法提交"
}
bizType := models.ErpDocApprovalBizType(doc.DocType)
deptID := erpUserOrgID(a.TidNum, a.UID)
if flowID == 0 {
flowID = erpPickFlowID(a.TidNum, deptID, bizType)
}
if flowID == 0 {
return 0, 400, "该单据类型尚未配置审批流程,可直接审核或到「审批流配置」中新增"
}
instID, err := approvalEngine.Submit(approvalEngine.SubmitInput{
Scope: models.ApprovalScopeTenant,
Tid: a.TidNum,
FlowID: flowID,
BizType: bizType,
BizID: doc.ID,
BizTitle: fmt.Sprintf("[%s] %s %s", docTypeLabelOf(doc.DocType), doc.DocNo, doc.PartnerName),
SubmitUID: a.UID,
SubmitName: a.Name,
DeptID: deptID,
FormData: map[string]interface{}{
"doc_type": doc.DocType,
"doc_no": doc.DocNo,
"total_amount": doc.TotalAmount,
"actual_amount": doc.ActualAmount,
"total_qty": doc.TotalQty,
"warehouse_id": doc.WarehouseID,
"partner_id": doc.PartnerID,
"partner_type": doc.PartnerType,
},
})
if err != nil {
return 0, 400, err.Error()
}
now := time.Now()
if _, err := models.Orm.QueryTable(new(models.ErpDocument)).Filter("id", doc.ID).
Update(orm.Params{"status": models.ErpDocStatusPending, "instance_id": instID, "update_time": now}); err != nil {
return 0, 500, "提交失败: " + err.Error()
}
approvalEngine.SyncBizResult(instID, approvalEngine.InstanceStatusOf(instID))
return instID, 200, ""
}
type erpFlowRow struct {
FlowID uint64 `json:"flow_id"`
FlowCode string `json:"flow_code"`
FlowName string `json:"flow_name"`
BizType string `json:"biz_type"`
}
// erpDocFlowsCore 该单据类型可用的审批流(先精确匹配 erp_xxx,再回落到模块级 erp)
func erpDocFlowsCore(a erpActor, docType string) (string, []erpFlowRow, int, string) {
if !erpDocTypes[docType] {
return "", nil, 400, "未知的单据类型"
}
bizType := models.ErpDocApprovalBizType(docType)
deptID := erpUserOrgID(a.TidNum, a.UID)
flows, err := approvalEngine.ListAvailableFlows(models.ApprovalScopeTenant, a.TidNum, bizType, deptID)
if err != nil {
return bizType, nil, 500, err.Error()
}
if len(flows) == 0 {
flows, _ = approvalEngine.ListAvailableFlows(models.ApprovalScopeTenant, a.TidNum, "erp", deptID)
}
out := make([]erpFlowRow, 0, len(flows))
for _, f := range flows {
out = append(out, erpFlowRow{f.FlowID, f.FlowCode, f.FlowName, f.BizType})
}
return bizType, out, 200, ""
}
// erpDocExecutableCore 取来源订单中尚可执行的明细行(入库/出库/退货按行带出,避免超收超发)
func erpDocExecutableCore(a erpActor, id uint64) (map[string]interface{}, int, string) {
doc, e := erpLoadDoc(id, a.Tid)
if e != nil {
return nil, 404, "单据未找到"
}
isReturn := doc.DocType == models.ErpDocPurchaseReturn || doc.DocType == models.ErpDocSalesReturn
items := erpDocItems(models.Orm, doc.ID)
out := make([]map[string]interface{}, 0, len(items))
for _, it := range items {
basis := it.Quantity
used := it.ExecutedQty
if isReturn {
used = erpReturnedQty(a.Tid, it.ID, 0)
}
remain := basis - used
if remain < -0.000001 {
remain = 0
}
out = append(out, map[string]interface{}{
"item_id": it.ID, "product_id": it.ProductID, "product_name": it.ProductName,
"spec": it.Spec, "unit": it.Unit, "batch_no": it.BatchNo,
"quantity": it.Quantity, "unit_price": it.UnitPrice, "amount": it.Amount,
"executed_qty": round4f(used), "remain_qty": round4f(remain),
})
}
return map[string]interface{}{"doc": doc, "items": out}, 200, ""
}
// erpTakeSheetCore 盘点表初始化:带出当前账面数量与加权成本,盘点员只改实盘数
func erpTakeSheetCore(a erpActor, warehouseID uint64) ([]map[string]interface{}, int, string) {
var stocks []models.ErpStock
_, _ = models.Orm.QueryTable(new(models.ErpStock)).
Filter("tenant_id", a.Tid).Filter("warehouse_id", warehouseID).
OrderBy("product_id", "batch_no").All(&stocks)
ids := make([]uint64, 0, len(stocks))
for _, s := range stocks {
ids = append(ids, s.ProductID)
}
pmap := erpProductMap(a.Tid, ids)
out := make([]map[string]interface{}, 0, len(stocks))
for _, s := range stocks {
p := pmap[s.ProductID]
out = append(out, map[string]interface{}{
"product_id": s.ProductID, "product_name": p.ProductName, "product_code": p.ProductCode,
"spec": p.Spec, "unit": p.BaseUnit, "batch_no": s.BatchNo,
"book_qty": s.Quantity, "cost_price": s.CostPrice,
})
}
return out, 200, ""
}
// erpDocMetaDict 开单页所需字典与默认值(两端共用同一份口径)
func erpDocMetaDict(a erpActor) map[string]interface{} {
s := erp.GetSettings(a.Tid)
types := make([]map[string]interface{}, 0, len(models.ErpDocTypeLabel))
for _, k := range []string{
models.ErpDocPurchaseOrder, models.ErpDocPurchaseIn, models.ErpDocPurchaseReturn,
models.ErpDocSalesOrder, models.ErpDocSalesOut, models.ErpDocSalesReturn,
models.ErpDocOtherIn, models.ErpDocOtherOut, models.ErpDocTransfer, models.ErpDocStockTake,
} {
types = append(types, map[string]interface{}{
"value": k, "label": models.ErpDocTypeLabel[k],
"affects_stock": models.ErpDocAffectsStock(k),
"partner_type": docTypePartner(k),
})
}
return map[string]interface{}{
"doc_types": types,
"statuses": []map[string]interface{}{
{"value": 0, "label": "草稿"}, {"value": 1, "label": "审批中"},
{"value": 2, "label": "已生效"}, {"value": 3, "label": "已驳回"}, {"value": 4, "label": "已作废"},
},
"default_warehouse_id": s.DefaultWarehouseID,
"sales_price_type": s.SalesPriceType,
"negative_stock": s.NegativeAllowed,
}
}