package controllers import ( "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" beego "github.com/beego/beego/v2/server/web" ) // BackendErpSupplierController ERP供应商管理控制器 type BackendErpSupplierController struct { beego.Controller } func (c *BackendErpSupplierController) supplierClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *BackendErpSupplierController) supplierJsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendErpSupplierController) supplierOk(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } type supplierFormPayload struct { SupplierName string `json:"supplier_name"` SupplierType string `json:"supplier_type"` ContactPerson string `json:"contact_person"` ContactPhone string `json:"contact_phone"` ContactEmail string `json:"contact_email"` SupplierLevel string `json:"supplier_level"` Industry string `json:"industry"` RegisteredCapital string `json:"registered_capital"` PaidCapital string `json:"paid_capital"` EstablishDate string `json:"establish_date"` AdministrativeDivision string `json:"administrative_division"` EnterpriseType string `json:"enterprise_type"` TaxpayerQualification string `json:"taxpayer_qualification"` BusinessScope string `json:"business_scope"` Address string `json:"address"` RegisterTime string `json:"register_time"` ExpireTime string `json:"expire_time"` Status string `json:"status"` IsDraft int8 `json:"is_draft"` Remark string `json:"remark"` InvoiceTitle string `json:"invoice_title"` TaxNumber string `json:"tax_number"` BankName string `json:"bank_name"` BankAccount string `json:"bank_account"` RegisteredAddress string `json:"registered_address"` RegisteredPhone string `json:"registered_phone"` } // List GET /backend/erp/supplier/list func (c *BackendErpSupplierController) List() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 20) keyword := strings.TrimSpace(c.GetString("keyword")) supplierType := strings.TrimSpace(c.GetString("supplier_type")) supplierLevel := strings.TrimSpace(c.GetString("supplier_level")) status := strings.TrimSpace(c.GetString("status")) isDraft := strings.TrimSpace(c.GetString("is_draft")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } qs := models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("supplier_name__contains", keyword) } if supplierType != "" { qs = qs.Filter("supplier_type", supplierType) } if supplierLevel != "" { qs = qs.Filter("supplier_level", supplierLevel) } if status != "" { qs = qs.Filter("status", status) } if isDraft != "" { if isDraft == "1" { qs = qs.Filter("is_draft", 1) } else { qs = qs.Filter("is_draft", 0) } } total, _ := qs.Count() var list []models.TenantCrmSupplier _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&list) if err != nil { c.supplierJsonErr(500, 500, "查询失败: "+err.Error()) return } // 标记「同时也是客户」:同一家单位可能既是供应商又是客户,列表上直接可见,避免重复转化 alsoCustomer := partyNameExists(fmt.Sprintf("%d", claims.TenantId), "customer", collectSupplierNames(list)) rows := make([]supplierListRow, 0, len(list)) for _, r := range list { rows = append(rows, supplierListRow{ TenantCrmSupplier: r, AlsoCustomer: alsoCustomer[strings.TrimSpace(r.SupplierName)], }) } c.supplierOk(map[string]interface{}{ "list": rows, "total": total, "page": page, "pageSize": pageSize, }) } // Detail GET /backend/erp/supplier/:id func (c *BackendErpSupplierController) Detail() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.supplierJsonErr(400, 400, "无效的ID") return } var supplier models.TenantCrmSupplier err = models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). One(&supplier) if err != nil { c.supplierJsonErr(404, 404, "供应商未找到") return } c.supplierOk(supplier) } // ConvertToCustomer POST /backend/erp/supplier/:id/convert-customer // 供应商转客户:把供应商档案(含联系人)复制一套到客户库。 // 同一家单位可能既是客户又是供应商,因此原供应商记录保留、不删除; // 客户库已存在同名企业时不重复建档,只把联系人补同步过去。 func (c *BackendErpSupplierController) ConvertToCustomer() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if id == 0 { c.supplierJsonErr(400, 400, "无效的ID") return } tenantID := fmt.Sprintf("%d", claims.TenantId) var supplier models.TenantCrmSupplier if err := models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", tenantID). Filter("delete_time__isnull", true). One(&supplier); err != nil { c.supplierJsonErr(404, 404, "供应商未找到") return } existed, customerID, inserted, merged, cerr := convertSupplierToCustomer(tenantID, &supplier, claims) if cerr != nil { c.supplierJsonErr(500, 500, cerr.Error()) return } c.supplierOk(map[string]interface{}{ "id": customerID, "existed": existed, "contacts": map[string]interface{}{ "inserted": inserted, "merged": merged, }, }) } // Create POST /backend/erp/supplier func (c *BackendErpSupplierController) Create() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.supplierJsonErr(400, 400, "参数错误") return } var p supplierFormPayload if err := json.Unmarshal(raw, &p); err != nil { c.supplierJsonErr(400, 400, "参数错误") return } if strings.TrimSpace(p.SupplierName) == "" { c.supplierJsonErr(400, 400, "供应商名称不能为空") return } // 正式保存时验证联系电话,草稿不强制 if p.IsDraft == 0 && strings.TrimSpace(p.ContactPhone) == "" { c.supplierJsonErr(400, 400, "联系人电话不能为空") return } supplier := models.TenantCrmSupplier{ TenantID: fmt.Sprintf("%d", claims.TenantId), SupplierName: strings.TrimSpace(p.SupplierName), SupplierType: p.SupplierType, ContactPerson: strings.TrimSpace(p.ContactPerson), ContactPhone: strings.TrimSpace(p.ContactPhone), ContactEmail: strings.TrimSpace(p.ContactEmail), SupplierLevel: p.SupplierLevel, Industry: strings.TrimSpace(p.Industry), RegisteredCapital: strings.TrimSpace(p.RegisteredCapital), PaidCapital: strings.TrimSpace(p.PaidCapital), AdministrativeDivision: strings.TrimSpace(p.AdministrativeDivision), EnterpriseType: strings.TrimSpace(p.EnterpriseType), TaxpayerQualification: strings.TrimSpace(p.TaxpayerQualification), BusinessScope: strings.TrimSpace(p.BusinessScope), Address: strings.TrimSpace(p.Address), Status: p.Status, IsDraft: p.IsDraft, Remark: p.Remark, InvoiceTitle: strings.TrimSpace(p.InvoiceTitle), TaxNumber: strings.TrimSpace(p.TaxNumber), BankName: strings.TrimSpace(p.BankName), BankAccount: strings.TrimSpace(p.BankAccount), RegisteredAddress: strings.TrimSpace(p.RegisteredAddress), RegisteredPhone: strings.TrimSpace(p.RegisteredPhone), CreateUserID: fmt.Sprintf("%d", claims.UserID), CreateTime: time.Now(), UpdateTime: time.Now(), } if p.RegisterTime != "" { if t, err := time.ParseInLocation("2006-01-02", p.RegisterTime, time.Local); err == nil { supplier.RegisterTime = &t } } if p.ExpireTime != "" { if t, err := time.ParseInLocation("2006-01-02", p.ExpireTime, time.Local); err == nil { supplier.ExpireTime = &t } } if p.EstablishDate != "" { if t, err := time.ParseInLocation("2006-01-02", p.EstablishDate, time.Local); err == nil { supplier.EstablishDate = &t } } if supplier.SupplierLevel == "" { supplier.SupplierLevel = "3" } if supplier.Status == "" { supplier.Status = "1" } id, err := models.Orm.Insert(&supplier) if err != nil { c.supplierJsonErr(500, 500, "创建失败: "+err.Error()) return } c.supplierOk(map[string]interface{}{"id": id}) } // Update PUT /backend/erp/supplier/:id func (c *BackendErpSupplierController) Update() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.supplierJsonErr(400, 400, "无效的ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.supplierJsonErr(400, 400, "参数错误") return } var p supplierFormPayload if err := json.Unmarshal(raw, &p); err != nil { c.supplierJsonErr(400, 400, "参数错误") return } var supplier models.TenantCrmSupplier err = models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). One(&supplier) if err != nil { c.supplierJsonErr(404, 404, "供应商未找到") return } supplier.SupplierName = strings.TrimSpace(p.SupplierName) supplier.SupplierType = p.SupplierType supplier.ContactPerson = strings.TrimSpace(p.ContactPerson) supplier.ContactPhone = strings.TrimSpace(p.ContactPhone) supplier.ContactEmail = strings.TrimSpace(p.ContactEmail) supplier.SupplierLevel = p.SupplierLevel supplier.Industry = strings.TrimSpace(p.Industry) supplier.RegisteredCapital = strings.TrimSpace(p.RegisteredCapital) supplier.PaidCapital = strings.TrimSpace(p.PaidCapital) supplier.AdministrativeDivision = strings.TrimSpace(p.AdministrativeDivision) supplier.EnterpriseType = strings.TrimSpace(p.EnterpriseType) supplier.TaxpayerQualification = strings.TrimSpace(p.TaxpayerQualification) supplier.BusinessScope = strings.TrimSpace(p.BusinessScope) supplier.Address = strings.TrimSpace(p.Address) supplier.Status = p.Status supplier.IsDraft = p.IsDraft supplier.Remark = p.Remark supplier.InvoiceTitle = strings.TrimSpace(p.InvoiceTitle) supplier.TaxNumber = strings.TrimSpace(p.TaxNumber) supplier.BankName = strings.TrimSpace(p.BankName) supplier.BankAccount = strings.TrimSpace(p.BankAccount) supplier.RegisteredAddress = strings.TrimSpace(p.RegisteredAddress) supplier.RegisteredPhone = strings.TrimSpace(p.RegisteredPhone) supplier.UpdateTime = time.Now() if p.RegisterTime != "" { if t, err := time.ParseInLocation("2006-01-02", p.RegisterTime, time.Local); err == nil { supplier.RegisterTime = &t } } else { supplier.RegisterTime = nil } if p.ExpireTime != "" { if t, err := time.ParseInLocation("2006-01-02", p.ExpireTime, time.Local); err == nil { supplier.ExpireTime = &t } } else { supplier.ExpireTime = nil } if p.EstablishDate != "" { if t, err := time.ParseInLocation("2006-01-02", p.EstablishDate, time.Local); err == nil { supplier.EstablishDate = &t } } else { supplier.EstablishDate = nil } _, err = models.Orm.Update(&supplier) if err != nil { c.supplierJsonErr(500, 500, "更新失败: "+err.Error()) return } c.supplierOk(nil) } // Delete DELETE /backend/erp/supplier/:id func (c *BackendErpSupplierController) Delete() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.supplierJsonErr(400, 400, "无效的ID") return } // 删除权限:仅平台管理员、租户管理员或创建人本人可删除 var supplier models.TenantCrmSupplier if err := models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). Filter("delete_time__isnull", true). One(&supplier); err != nil { c.supplierJsonErr(404, 404, "供应商未找到") return } if !canDeleteCrmRecord(claims, supplier.CreateUserID) { c.supplierJsonErr(403, 403, "只有创建人、租户管理员或平台管理员可以删除该供应商") return } // 软删除:仅置 delete_time,不物理删除 now := time.Now() _, err = models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). Update(map[string]interface{}{ "delete_time": now, "update_time": now, }) if err != nil { c.supplierJsonErr(500, 500, "删除失败: "+err.Error()) return } c.supplierOk(nil) } // ToggleStatus POST /backend/erp/supplier/:id/status func (c *BackendErpSupplierController) ToggleStatus() { claims, err := c.supplierClaims() if err != nil { c.supplierJsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.supplierJsonErr(400, 400, "无效的ID") return } raw, _ := io.ReadAll(c.Ctx.Request.Body) var p struct { Status string `json:"status"` } _ = json.Unmarshal(raw, &p) _, err = models.Orm.QueryTable(new(models.TenantCrmSupplier)). Filter("id", id). Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)). Update(map[string]interface{}{ "status": p.Status, "update_time": time.Now(), }) if err != nil { c.supplierJsonErr(500, 500, "操作失败: "+err.Error()) return } c.supplierOk(nil) }