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

+225
View File
@@ -0,0 +1,225 @@
package erp
import (
"fmt"
"strconv"
"strings"
"time"
"server/models"
"github.com/beego/beego/v2/client/orm"
)
// Settings 从租户「ERP基础设置」读取的运行时参数。
type Settings struct {
NegativeAllowed bool // negative_stock_allowed
AutoDocNo bool // auto_generate_doc_no
DefaultWarehouseID uint64 // default_warehouse(存的是名称,按名称匹配)
SalesPriceType string // sales_price_type:sale/wholesale/member
}
func GetSettings(tenantID string) *Settings {
s := &Settings{AutoDocNo: true, SalesPriceType: "sale"}
if v := models.GetErpNormalSettingByKey(tenantID, "negative_stock_allowed"); v != nil {
s.NegativeAllowed = strings.TrimSpace(v.ConfigValue) == "1"
}
if v := models.GetErpNormalSettingByKey(tenantID, "auto_generate_doc_no"); v != nil {
s.AutoDocNo = strings.TrimSpace(v.ConfigValue) != "0"
}
if v := models.GetErpNormalSettingByKey(tenantID, "sales_price_type"); v != nil {
if t := strings.TrimSpace(v.ConfigValue); t != "" {
s.SalesPriceType = t
}
}
if v := models.GetErpNormalSettingByKey(tenantID, "default_warehouse"); v != nil {
name := strings.TrimSpace(v.ConfigValue)
if name != "" {
var wh models.ErpWarehouse
if err := models.Orm.QueryTable(new(models.ErpWarehouse)).
Filter("tenant_id", tenantID).
Filter("warehouse_name", name).
Filter("delete_time__isnull", true).One(&wh); err == nil {
s.DefaultWarehouseID = wh.ID
}
}
}
if s.DefaultWarehouseID == 0 {
var wh models.ErpWarehouse
if err := models.Orm.QueryTable(new(models.ErpWarehouse)).
Filter("tenant_id", tenantID).
Filter("is_default", 1).
Filter("status", 1).
Filter("delete_time__isnull", true).One(&wh); err == nil {
s.DefaultWarehouseID = wh.ID
}
}
return s
}
// erpDocPrefixDefaults 各单据类型的取号前缀默认值(也是基础设置里可改的初值)。
// 必须一型一前缀:共用前缀会让采购入库/其他入库、采购退货/销售退货同号段,
// 单据号无法反推类型,日次序号也会互相穿插。
var erpDocPrefixDefaults = map[string]string{
models.ErpDocPurchaseOrder: "CGDD",
models.ErpDocPurchaseIn: "CGRK",
models.ErpDocPurchaseReturn: "CGTH",
models.ErpDocSalesOrder: "XSDD",
models.ErpDocSalesOut: "XSCK",
models.ErpDocSalesReturn: "XSTH",
models.ErpDocOtherIn: "QTRK",
models.ErpDocOtherOut: "QTCK",
models.ErpDocTransfer: "DB",
models.ErpDocStockTake: "PD",
}
// ErpDocPrefixKey 单据类型 → 基础设置键(document 分组,每型独立)
func ErpDocPrefixKey(docType string) string {
if _, ok := erpDocPrefixDefaults[docType]; !ok {
return ""
}
return "doc_prefix_" + docType
}
// ErpDocPrefixDefaults 供基础设置接口登记默认前缀
func ErpDocPrefixDefaults() map[string]string {
return erpDocPrefixDefaults
}
// ErpDocPrefixField 基础设置「单据设置」里一条可编辑的单据前缀
type ErpDocPrefixField struct {
Key string
Name string
Default string
}
// 表单展示顺序:按采购→销售→其他→调拨/盘点
var erpDocPrefixLabels = []struct {
docType string
name string
}{
{models.ErpDocPurchaseOrder, "采购订单前缀"},
{models.ErpDocPurchaseIn, "采购入库前缀"},
{models.ErpDocPurchaseReturn, "采购退货前缀"},
{models.ErpDocSalesOrder, "销售订单前缀"},
{models.ErpDocSalesOut, "销售出库前缀"},
{models.ErpDocSalesReturn, "销售退货前缀"},
{models.ErpDocOtherIn, "其他入库前缀"},
{models.ErpDocOtherOut, "其他出库前缀"},
{models.ErpDocTransfer, "调拨单前缀"},
{models.ErpDocStockTake, "盘点单前缀"},
}
// ErpDocPrefixFields 返回全部单据前缀配置项,供基础设置接口注册默认值
func ErpDocPrefixFields() []ErpDocPrefixField {
out := make([]ErpDocPrefixField, 0, len(erpDocPrefixLabels))
for _, l := range erpDocPrefixLabels {
out = append(out, ErpDocPrefixField{
Key: ErpDocPrefixKey(l.docType),
Name: l.name,
Default: erpDocPrefixDefaults[l.docType],
})
}
return out
}
// docNoPrefix 按单据类型取号前缀(基础设置 document 分组),缺省用类型缩写。
func docNoPrefix(tenantID, docType string) string {
if key := ErpDocPrefixKey(docType); key != "" {
if v := models.GetErpNormalSettingByKey(tenantID, key); v != nil {
if p := strings.ToUpper(strings.TrimSpace(v.ConfigValue)); p != "" {
return p
}
}
}
if p, ok := erpDocPrefixDefaults[docType]; ok {
return p
}
return "ERP"
}
// GenerateDocNo 生成单据号:前缀+YYYYMMDD+当日3位序号(按租户+类型+日期计数)。
// 依赖 yz_backend_erp_document 的 uk_tenant_docno 唯一索引兜底,调用方需处理重号重试。
func GenerateDocNo(o orm.QueryExecutor, tenantID, docType string) (string, error) {
prefix := docNoPrefix(tenantID, docType)
day := time.Now().Format("20060102")
var count int64
var err error
if o != nil {
count, err = o.QueryTable(new(models.ErpDocument)).
Filter("tenant_id", tenantID).Filter("doc_no__startswith", prefix+day).Count()
} else {
count, err = models.Orm.QueryTable(new(models.ErpDocument)).
Filter("tenant_id", tenantID).Filter("doc_no__startswith", prefix+day).Count()
}
if err != nil && err != orm.ErrNoRows {
return "", err
}
seq := count + 1
if seq > 999 {
return "", fmt.Errorf("当日单据编号已超过999,请先归档")
}
return fmt.Sprintf("%s%s%03d", prefix, day, seq), nil
}
// GenerateFundNo 收付款单号:前缀(SK/FK/QR/ZC)+日期+序号。
func GenerateFundNo(tenantID string, fundType int8) (string, error) {
prefixMap := map[int8]string{
models.ErpFundReceive: "SK", models.ErpFundPay: "FK",
models.ErpFundOtherIn: "QR", models.ErpFundExpense: "FY",
}
p, ok := prefixMap[fundType]
if !ok {
p = "ZJ"
}
day := time.Now().Format("20060102")
count, err := models.Orm.QueryTable(new(models.ErpFundBill)).
Filter("tenant_id", tenantID).Filter("fund_no__startswith", p+day).Count()
if err != nil && err != orm.ErrNoRows {
return "", err
}
return fmt.Sprintf("%s%s%03d", p, day, count+1), nil
}
// GenerateProductCode 商品编号:SP+日期+当日序号(租户内唯一由 uk_tenant_code 兜底)。
func GenerateProductCode(tenantID string) string {
day := time.Now().Format("20060102")
count, _ := models.Orm.QueryTable(new(models.ErpProduct)).
Filter("tenant_id", tenantID).Filter("product_code__startswith", "SP"+day).Count()
return fmt.Sprintf("SP%s%03d", day, count+1)
}
// GetPartnerPaymentDays 取往来单位账期天数(应收到期日计算)。
func GetPartnerPaymentDays(partnerType string, partnerID uint64) int {
if partnerID == 0 {
return 0
}
switch partnerType {
case "customer":
var row models.TenantCrmCustomer
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
Filter("id", partnerID).One(&row); err == nil {
return row.PaymentDays
}
case "supplier":
var row models.TenantCrmSupplier
if err := models.Orm.QueryTable(new(models.TenantCrmSupplier)).
Filter("id", partnerID).One(&row); err == nil {
return row.PaymentDays
}
}
return 0
}
// GetCustomerCreditLimit 客户信用额度,0=不限。
func GetCustomerCreditLimit(customerID uint64) float64 {
var row models.TenantCrmCustomer
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
Filter("id", customerID).One(&row); err != nil {
return 0
}
return row.CreditLimit
}
// 内部工具
func itoa(v uint64) string { return strconv.FormatUint(v, 10) }