226 lines
7.4 KiB
Go
226 lines
7.4 KiB
Go
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) }
|