572 lines
18 KiB
Go
572 lines
18 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// BackendCrmProductController CRM 产品管理(产品台账/目录)
|
|
type BackendCrmProductController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type crmProductPayload struct {
|
|
ProductNo string `json:"product_no"`
|
|
ProductName string `json:"product_name"`
|
|
Category string `json:"category"`
|
|
Unit string `json:"unit"`
|
|
Spec string `json:"spec"`
|
|
Price float64 `json:"price"`
|
|
CostPrice float64 `json:"cost_price"`
|
|
TaxRate float64 `json:"tax_rate"`
|
|
LineType string `json:"line_type"`
|
|
DevUnitPrice float64 `json:"dev_unit_price"`
|
|
DevMode string `json:"dev_mode"`
|
|
DevTree json.RawMessage `json:"dev_tree"`
|
|
Status int8 `json:"status"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// normalizeDevMode 规范软件开发「研发方式」:self=自行研发(默认)/ outsource=外协研发(别人帮我们研发)。
|
|
func normalizeDevMode(v string) string {
|
|
if strings.TrimSpace(v) == "outsource" {
|
|
return "outsource"
|
|
}
|
|
return "self"
|
|
}
|
|
|
|
// crmProductItem 项目产品清单中的单行(也用于项目生成时写入产品管理)。
|
|
type crmProductItem struct {
|
|
ProductNo string `json:"product_no"`
|
|
Name string `json:"name"`
|
|
Category string `json:"category"`
|
|
Spec string `json:"spec"`
|
|
Unit string `json:"unit"`
|
|
Quantity interface{} `json:"quantity"`
|
|
Price float64 `json:"price"`
|
|
CostPrice float64 `json:"cost_price"`
|
|
TaxRate float64 `json:"tax_rate"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// List GET /backend/crm/product/list
|
|
func (c *BackendCrmProductController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
category := strings.TrimSpace(c.GetString("category"))
|
|
status := strings.TrimSpace(c.GetString("status"))
|
|
projectID, _ := c.GetUint64("project_id")
|
|
|
|
tenantID := pipelineTenantID(claims)
|
|
cond := orm.NewCondition().And("tenant_id", tenantID).And("delete_time__isnull", true)
|
|
if keyword != "" {
|
|
kw := orm.NewCondition().
|
|
Or("product_name__contains", keyword).
|
|
Or("product_no__contains", keyword).
|
|
Or("spec__contains", keyword)
|
|
cond = cond.AndCond(kw)
|
|
}
|
|
if category != "" {
|
|
cond = cond.And("category", category)
|
|
}
|
|
if status != "" {
|
|
cond = cond.And("status", status)
|
|
}
|
|
if projectID > 0 {
|
|
cond = cond.And("project_id", projectID)
|
|
}
|
|
qs := models.Orm.QueryTable(new(models.TenantCrmProduct)).SetCond(cond)
|
|
|
|
total, _ := qs.Count()
|
|
var list []models.TenantCrmProduct
|
|
if total > 0 {
|
|
_, _ = qs.OrderBy("-id").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"list": list, "total": total, "page": page, "pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// Detail GET /backend/crm/product/:id
|
|
func (c *BackendCrmProductController) Detail() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
var p models.TenantCrmProduct
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).One(&p); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "产品未找到")
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, p)
|
|
}
|
|
|
|
// Create POST /backend/crm/product
|
|
func (c *BackendCrmProductController) Create() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p crmProductPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.ProductName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "产品名称不能为空")
|
|
return
|
|
}
|
|
status := p.Status
|
|
if status != 0 && status != 1 {
|
|
status = 1
|
|
}
|
|
lineType := strings.TrimSpace(p.LineType)
|
|
if lineType != "dev" {
|
|
lineType = "product"
|
|
}
|
|
// 非开发行不保留开发字段,避免残留(如 dev_tree="[]")导致清单调用时被误判为软件开发行
|
|
devUnitPrice := p.DevUnitPrice
|
|
devMode := normalizeDevMode(p.DevMode)
|
|
devTree := string(p.DevTree)
|
|
if lineType != "dev" {
|
|
devUnitPrice = 0
|
|
devMode = ""
|
|
devTree = ""
|
|
}
|
|
now := time.Now()
|
|
row := models.TenantCrmProduct{
|
|
TenantID: pipelineTenantID(claims),
|
|
ProductNo: strings.TrimSpace(p.ProductNo),
|
|
ProductName: strings.TrimSpace(p.ProductName),
|
|
Category: strings.TrimSpace(p.Category),
|
|
Unit: strings.TrimSpace(p.Unit),
|
|
Spec: strings.TrimSpace(p.Spec),
|
|
Price: p.Price,
|
|
CostPrice: p.CostPrice,
|
|
TaxRate: p.TaxRate,
|
|
LineType: lineType,
|
|
DevUnitPrice: devUnitPrice,
|
|
DevMode: devMode,
|
|
DevTree: devTree,
|
|
Status: status,
|
|
Remark: p.Remark,
|
|
CreateUserID: pipelineUID(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/product/:id
|
|
func (c *BackendCrmProductController) Update() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p crmProductPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.ProductName) == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "产品名称不能为空")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var row models.TenantCrmProduct
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "产品未找到")
|
|
return
|
|
}
|
|
status := p.Status
|
|
if status != 0 && status != 1 {
|
|
status = row.Status
|
|
}
|
|
lineType := strings.TrimSpace(p.LineType)
|
|
if lineType != "dev" {
|
|
lineType = "product"
|
|
}
|
|
// 非开发行清空开发字段,避免残留导致清单调用时被误判为软件开发行
|
|
devUnitPrice := p.DevUnitPrice
|
|
devMode := normalizeDevMode(p.DevMode)
|
|
devTree := string(p.DevTree)
|
|
if lineType != "dev" {
|
|
devUnitPrice = 0
|
|
devMode = ""
|
|
devTree = ""
|
|
}
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Update(orm.Params{
|
|
"product_no": strings.TrimSpace(p.ProductNo),
|
|
"product_name": strings.TrimSpace(p.ProductName),
|
|
"category": strings.TrimSpace(p.Category),
|
|
"unit": strings.TrimSpace(p.Unit),
|
|
"spec": strings.TrimSpace(p.Spec),
|
|
"price": p.Price,
|
|
"cost_price": p.CostPrice,
|
|
"tax_rate": p.TaxRate,
|
|
"line_type": lineType,
|
|
"dev_unit_price": devUnitPrice,
|
|
"dev_mode": devMode,
|
|
"dev_tree": devTree,
|
|
"status": status,
|
|
"remark": p.Remark,
|
|
"update_time": now,
|
|
})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/product/:id
|
|
func (c *BackendCrmProductController) Delete() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var row models.TenantCrmProduct
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "产品未找到")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Update(orm.Params{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// SyncFromProject POST /backend/crm/product/sync-from-project
|
|
// 项目生成(或保存)时,把项目产品清单里的产品参数写入产品管理(按 名称+分类 去重 upsert)。
|
|
func (c *BackendCrmProductController) SyncFromProject() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var body struct {
|
|
ProjectID uint64 `json:"project_id"`
|
|
ProjectName string `json:"project_name"`
|
|
Products []crmProductItem `json:"products"`
|
|
}
|
|
if err := json.Unmarshal(raw, &body); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
if body.ProjectID == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "缺少项目ID")
|
|
return
|
|
}
|
|
created, updated, err := SyncProjectProducts(pipelineTenantID(claims), body.ProjectID, strings.TrimSpace(body.ProjectName), body.Products)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "同步失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"created": created, "updated": updated})
|
|
}
|
|
|
|
// SyncProjectProducts 把项目产品清单写入产品管理:按 产品名称+分类 在同一租户下去重。
|
|
// - 已存在:刷新单价/成本/税率/规格/单位/来源项目/备注;
|
|
// - 不存在:新建产品档案,登记来源项目。
|
|
// 返回新建数、更新数。
|
|
func SyncProjectProducts(tenantID string, projectID uint64, projectName string, items []crmProductItem) (created int, updated int, err error) {
|
|
if len(items) == 0 {
|
|
return 0, 0, nil
|
|
}
|
|
now := time.Now()
|
|
for _, it := range items {
|
|
name := strings.TrimSpace(it.Name)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
cat := strings.TrimSpace(it.Category)
|
|
var exist models.TenantCrmProduct
|
|
e := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("tenant_id", tenantID).
|
|
Filter("product_name", name).
|
|
Filter("category", cat).
|
|
Filter("delete_time__isnull", true).
|
|
OrderBy("-id").Limit(1).One(&exist)
|
|
if e == nil && exist.ID > 0 {
|
|
// 已存在:更新参数
|
|
_, uerr := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", exist.ID).
|
|
Update(orm.Params{
|
|
"product_no": strings.TrimSpace(it.ProductNo),
|
|
"spec": strings.TrimSpace(it.Spec),
|
|
"unit": strings.TrimSpace(it.Unit),
|
|
"price": it.Price,
|
|
"cost_price": it.CostPrice,
|
|
"tax_rate": it.TaxRate,
|
|
"project_id": projectID,
|
|
"project_name": projectName,
|
|
"remark": it.Remark,
|
|
"update_time": now,
|
|
})
|
|
if uerr != nil {
|
|
err = uerr
|
|
return
|
|
}
|
|
updated++
|
|
continue
|
|
}
|
|
// 不存在:新建
|
|
row := models.TenantCrmProduct{
|
|
TenantID: tenantID,
|
|
ProductNo: strings.TrimSpace(it.ProductNo),
|
|
ProductName: name,
|
|
Category: cat,
|
|
Unit: strings.TrimSpace(it.Unit),
|
|
Spec: strings.TrimSpace(it.Spec),
|
|
Price: it.Price,
|
|
CostPrice: it.CostPrice,
|
|
TaxRate: it.TaxRate,
|
|
Status: 1,
|
|
ProjectID: &projectID,
|
|
ProjectName: projectName,
|
|
Remark: it.Remark,
|
|
CreateUserID: "",
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
if _, ierr := models.Orm.Insert(&row); ierr != nil {
|
|
err = ierr
|
|
return
|
|
}
|
|
created++
|
|
}
|
|
return created, updated, nil
|
|
}
|
|
|
|
// SyncContractProducts 把合同产品清单同步到产品管理(供合同保存时调用):
|
|
// - 已关联 product_id 或名称命中现有产品:回填 product_id,并将成本单价强制取产品管理
|
|
// (成本追溯产品管理;销售单价不回写,允许合同溢价);
|
|
// - 名称未命中(产品管理中不存在):新建产品档案,销售单价/成本单价/规格/单位/分类/税率取自合同行;
|
|
// 软件开发行(line_type=dev)同样建档,并写入统一人天单价与模块树,使该「开发包」可在产品管理中复用;
|
|
// - 软件开发行已关联到产品档案时,回写最新模块树与人天单价(价格仍由产品管理主导,不回写);
|
|
// - projectID 传入时回填为产品档案的「来源项目」。
|
|
//
|
|
// 返回(可能已回填 product_id 与成本单价)的清单 JSON,供合同落库。
|
|
func SyncContractProducts(tenantID, uid string, projectID uint64, projectName string, raw json.RawMessage) (json.RawMessage, error) {
|
|
var items []map[string]interface{}
|
|
if len(raw) == 0 || strings.TrimSpace(string(raw)) == "[]" || strings.TrimSpace(string(raw)) == "" {
|
|
return json.RawMessage("[]"), nil
|
|
}
|
|
if err := json.Unmarshal(raw, &items); err != nil {
|
|
return raw, err
|
|
}
|
|
now := time.Now()
|
|
for _, it := range items {
|
|
// 行类型:成品 product / 软件开发 dev,缺省按成品处理并回填给清单
|
|
lineType := normalizeLineType(productFieldStr(it["line_type"]))
|
|
it["line_type"] = lineType
|
|
|
|
name := productFieldStr(it["name"])
|
|
if name == "" {
|
|
continue
|
|
}
|
|
// 已关联产品:成本单价强制取产品管理,不回写销售单价
|
|
if pid := toUint64(it["product_id"]); pid > 0 {
|
|
var p models.TenantCrmProduct
|
|
if e := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("id", pid).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&p); e == nil && p.ID > 0 {
|
|
it["product_id"] = p.ID
|
|
it["cost_price"] = p.CostPrice
|
|
syncDevTreeToProduct(&p, it)
|
|
}
|
|
continue
|
|
}
|
|
// 按名称命中现有产品(未启用也命中,便于回填):需与当前行类型一致,避免成品/开发互相误匹配
|
|
var exist models.TenantCrmProduct
|
|
e := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("tenant_id", tenantID).Filter("product_name", name).
|
|
Filter("delete_time__isnull", true).OrderBy("-id").Limit(1).One(&exist)
|
|
if e == nil && exist.ID > 0 && normalizeLineType(exist.LineType) == lineType {
|
|
it["product_id"] = exist.ID
|
|
it["cost_price"] = exist.CostPrice
|
|
syncDevTreeToProduct(&exist, it)
|
|
continue
|
|
}
|
|
// 未命中:新建产品档案(成本/单价等取自合同行)
|
|
row := models.TenantCrmProduct{
|
|
TenantID: tenantID,
|
|
ProductNo: genProductNo(tenantID),
|
|
ProductName: name,
|
|
LineType: lineType,
|
|
Category: productFieldStr(it["category"]),
|
|
Unit: productFieldStr(it["unit"]),
|
|
Spec: productFieldStr(it["spec"]),
|
|
Price: toFloat64(it["price"]),
|
|
CostPrice: toFloat64(it["cost_price"]),
|
|
TaxRate: toFloat64(it["tax_rate"]),
|
|
Status: 1,
|
|
Remark: productFieldStr(it["remark"]),
|
|
CreateUserID: uid,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
// 软件开发行:写入统一人天单价、研发方式与模块树,作为可复用的「开发包」
|
|
if lineType == "dev" {
|
|
row.DevUnitPrice = toFloat64(it["dev_unit_price"])
|
|
row.DevMode = normalizeDevMode(productFieldStr(it["dev_mode"]))
|
|
if tree := it["dev_tree"]; tree != nil {
|
|
if b, me := json.Marshal(tree); me == nil {
|
|
row.DevTree = string(b)
|
|
}
|
|
}
|
|
if row.Category == "" {
|
|
row.Category = "软件开发"
|
|
}
|
|
if row.Unit == "" {
|
|
row.Unit = "项"
|
|
}
|
|
}
|
|
// 回填来源项目(仅项目合同有,无头合同为空)
|
|
if projectID > 0 {
|
|
pid := projectID
|
|
row.ProjectID = &pid
|
|
row.ProjectName = strings.TrimSpace(projectName)
|
|
}
|
|
id, ierr := models.Orm.Insert(&row)
|
|
if ierr != nil {
|
|
return raw, ierr
|
|
}
|
|
it["product_id"] = uint64(id)
|
|
}
|
|
out, err := json.Marshal(items)
|
|
if err != nil {
|
|
return raw, err
|
|
}
|
|
return json.RawMessage(out), nil
|
|
}
|
|
|
|
// normalizeLineType 规范化行类型:空值按成品处理。
|
|
func normalizeLineType(v string) string {
|
|
if strings.TrimSpace(v) == "" {
|
|
return "product"
|
|
}
|
|
return strings.TrimSpace(v)
|
|
}
|
|
|
|
// productFieldStr 安全取清单字段的字符串值:nil 时返回空串,避免 fmt 输出 "<nil>"。
|
|
func productFieldStr(v interface{}) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
|
}
|
|
|
|
// syncDevTreeToProduct 软件开发行引用到产品档案时,回写最新模块树、人天单价与研发方式
|
|
// (仅在有变化时更新;销售/成本单价不回写,仍由产品管理主导),
|
|
// 保证后续合同再选该产品能拿到最新的开发包。
|
|
func syncDevTreeToProduct(p *models.TenantCrmProduct, it map[string]interface{}) {
|
|
if p == nil || p.ID == 0 || normalizeLineType(p.LineType) != "dev" {
|
|
return
|
|
}
|
|
tree := it["dev_tree"]
|
|
if tree == nil {
|
|
return
|
|
}
|
|
b, me := json.Marshal(tree)
|
|
if me != nil {
|
|
return
|
|
}
|
|
newTree := string(b)
|
|
newUnitPrice := toFloat64(it["dev_unit_price"])
|
|
// 研发方式:清单行传了值就跟随回写(自行研发 / 外协研发)
|
|
newMode := p.DevMode
|
|
if m := productFieldStr(it["dev_mode"]); m != "" {
|
|
newMode = normalizeDevMode(m)
|
|
}
|
|
if newTree == p.DevTree && newUnitPrice == p.DevUnitPrice && newMode == p.DevMode {
|
|
return
|
|
}
|
|
p.DevTree = newTree
|
|
p.DevUnitPrice = newUnitPrice
|
|
p.DevMode = newMode
|
|
_, _ = models.Orm.Update(p, "DevTree", "DevUnitPrice", "DevMode")
|
|
}
|
|
|
|
// genProductNo 生成产品编号 P-yyyymmdd-4位序号,并保证本租户内不重复。
|
|
func genProductNo(tenantID string) string {
|
|
day := time.Now().Format("20060102")
|
|
for i := 0; i < 8; i++ {
|
|
no := fmt.Sprintf("P-%s-%04d", day, (time.Now().UnixNano()+int64(i)*37)%10000)
|
|
cnt, err := models.Orm.QueryTable(new(models.TenantCrmProduct)).
|
|
Filter("tenant_id", tenantID).Filter("product_no", no).Count()
|
|
if err == nil && cnt == 0 {
|
|
return no
|
|
}
|
|
}
|
|
return fmt.Sprintf("P-%s-%d", day, time.Now().UnixNano()%100000)
|
|
}
|
|
|
|
// toUint64 转为 uint64(依赖同包 toInt64)。
|
|
func toUint64(v interface{}) uint64 {
|
|
return uint64(toInt64(v))
|
|
}
|