package controllers import ( "strconv" "strings" "time" erp "server/services/erp" ) // BackendErpFinanceController 往来账(应收/应付)、收付款与核销 // // 往来账由单据生效时自动生成,不开放手工新增/修改,保证「业务单据 → 往来账 → 收付款」 // 三段可追溯;收付款单可以手工开草稿,确认后按单核销(显式指定或按到期日 FIFO)。 // 查询与核销口径统一在 erp_finance_core.go,与移动端 /api/erp/finance* 共用。 type BackendErpFinanceController struct { BackendErpBaseController } // BillList GET /backend/erp/finance/bill/list 往来账列表 func (c *BackendErpFinanceController) BillList() { cl, err := c.claims() if c.denied(err) { return } page, pageSize := c.pageArgs() f := erpBillFilter{ BillType: strings.TrimSpace(c.GetString("bill_type")), PartnerID: erpUintParam(c.GetString("partner_id")), PartnerType: strings.TrimSpace(c.GetString("partner_type")), SourceDocID: erpUintParam(c.GetString("source_doc_id")), SettleState: strings.TrimSpace(c.GetString("settle_state")), Keyword: strings.TrimSpace(c.GetString("keyword")), DateFrom: strings.TrimSpace(c.GetString("date_from")), DateTo: strings.TrimSpace(c.GetString("date_to")), } list, total, summary, code, msg := erpBillListCore(erpNewActor(cl), f, page, pageSize) if msg != "" { c.fail(code, code, msg) return } c.ok(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, "summary": summary, }) } // BillDetail GET /backend/erp/finance/bill/:id 往来账详情(含核销记录与原单据) func (c *BackendErpFinanceController) BillDetail() { cl, err := c.claims() if c.denied(err) { return } data, code, msg := erpBillDetailCore(erpNewActor(cl), c.pathID()) if msg != "" { c.fail(code, code, msg) return } c.ok(data) } // Aging GET /backend/erp/finance/aging 账龄分析(按未结余额分桶) func (c *BackendErpFinanceController) Aging() { cl, err := c.claims() if c.denied(err) { return } data, code, msg := erpAgingCore(erpNewActor(cl)) if msg != "" { c.fail(code, code, msg) return } c.ok(data) } // FundList GET /backend/erp/finance/fund/list 收付款单列表 func (c *BackendErpFinanceController) FundList() { cl, err := c.claims() if c.denied(err) { return } page, pageSize := c.pageArgs() f := erpFundFilter{ FundType: strings.TrimSpace(c.GetString("fund_type")), Status: strings.TrimSpace(c.GetString("status")), PartnerID: erpUintParam(c.GetString("partner_id")), Keyword: strings.TrimSpace(c.GetString("keyword")), DateFrom: strings.TrimSpace(c.GetString("date_from")), DateTo: strings.TrimSpace(c.GetString("date_to")), } rows, total, code, msg := erpFundListCore(erpNewActor(cl), f, page, pageSize) if msg != "" { c.fail(code, code, msg) return } c.listOK(rows, total, page, pageSize) } // FundDetail GET /backend/erp/finance/fund/:id func (c *BackendErpFinanceController) FundDetail() { cl, err := c.claims() if c.denied(err) { return } data, code, msg := erpFundDetailCore(erpNewActor(cl), c.pathID()) if msg != "" { c.fail(code, code, msg) return } c.ok(data) } type erpFundPayload struct { ID uint64 `json:"id"` FundType int8 `json:"fund_type"` PartnerID uint64 `json:"partner_id"` Amount float64 `json:"amount"` PayMethod string `json:"pay_method"` PayAccount string `json:"pay_account"` BizDate string `json:"biz_date"` Remark string `json:"remark"` } // FundSave POST /backend/erp/finance/fund 新建/修改收付款草稿 func (c *BackendErpFinanceController) FundSave() { cl, err := c.claims() if c.denied(err) { return } var p erpFundPayload if !c.bindJSON(&p) { return } id, fundNo, code, msg := erpFundSaveCore(erpNewActor(cl), p) if msg != "" { c.fail(code, code, msg) return } c.ok(map[string]interface{}{"id": id, "fund_no": fundNo}) } // FundConfirm POST /backend/erp/finance/fund/:id/confirm // 确认收付款并核销;body 可选 settles=[{partner_bill_id,amount}],不传则按到期日自动 FIFO。 func (c *BackendErpFinanceController) FundConfirm() { cl, err := c.claims() if c.denied(err) { return } var body struct { Settles []erp.SettleItem `json:"settles"` } // 允许空 body(自动核销) _ = c.bindJSON(&body) code, msg := erpFundConfirmCore(erpNewActor(cl), c.pathID(), body.Settles) if msg != "" { c.fail(code, code, msg) return } c.ok(nil) } // FundVoid POST /backend/erp/finance/fund/:id/void func (c *BackendErpFinanceController) FundVoid() { cl, err := c.claims() if c.denied(err) { return } code, msg := erpFundVoidCore(erpNewActor(cl), c.pathID()) if msg != "" { c.fail(code, code, msg) return } c.ok(nil) } // FundDelete DELETE /backend/erp/finance/fund/:id 仅草稿可删 func (c *BackendErpFinanceController) FundDelete() { cl, err := c.claims() if c.denied(err) { return } code, msg := erpFundDeleteCore(erpNewActor(cl), c.pathID()) if msg != "" { c.fail(code, code, msg) return } c.ok(nil) } // FundOpenBills GET /backend/erp/finance/fund/openBills?fund_type=&partner_id= // 收款/付款时列出可核销的往来账,前端据此勾选。 func (c *BackendErpFinanceController) FundOpenBills() { cl, err := c.claims() if c.denied(err) { return } ft, _ := erpInt8Query(c.GetString("fund_type")) list, code, msg := erpFundOpenBillsCore(erpNewActor(cl), ft, erpUintParam(c.GetString("partner_id"))) if msg != "" { c.fail(code, code, msg) return } c.ok(map[string]interface{}{"list": list}) } // CashFlow GET /backend/erp/finance/cashflow 资金流水(含单据生成的往来账不在此列,只统计实际收付) func (c *BackendErpFinanceController) CashFlow() { cl, err := c.claims() if c.denied(err) { return } from := time.Now().AddDate(0, 0, -30) if d := strings.TrimSpace(c.GetString("date_from")); d != "" { if t, e := time.ParseInLocation("2006-01-02", d, time.Local); e == nil { from = t } } to := time.Now() if d := strings.TrimSpace(c.GetString("date_to")); d != "" { if t, e := time.ParseInLocation("2006-01-02", d, time.Local); e == nil { to = t } } data, code, msg := erpCashFlowCore(erpNewActor(cl), from, to) if msg != "" { c.fail(code, code, msg) return } c.ok(data) } // PartnerSummary GET /backend/erp/finance/partner 往来单位对账汇总 func (c *BackendErpFinanceController) PartnerSummary() { cl, err := c.claims() if c.denied(err) { return } data, code, msg := erpPartnerSummaryCore(erpNewActor(cl), strings.TrimSpace(c.GetString("partner_type"))) if msg != "" { c.fail(code, code, msg) return } c.ok(data) } func erpBillStatusLabel(s int8) string { switch s { case 1: return "部分核销" case 2: return "已结清" default: return "未核销" } } func erpInt8Query(v string) (int8, bool) { n, err := strconv.Atoi(strings.TrimSpace(v)) if err != nil { return 0, false } return int8(n), true }