Decouple aftersales customers from company management

This commit is contained in:
Frudrax Cheng
2026-05-28 10:29:17 +08:00
parent 2aab9203a0
commit d1d189528c
5 changed files with 35 additions and 28 deletions
+4
View File
@@ -107,6 +107,10 @@ backend-go/
- Creating an employee through `/api/employees` creates employee master data and automatically generates one employee serial bound by `employeeId`. - Creating an employee through `/api/employees` creates employee master data and automatically generates one employee serial bound by `employeeId`.
- `admin` / `technician` creation requires an initial password; `employee` creation must not require one. - `admin` / `technician` creation requires an initial password; `employee` creation must not require one.
### Business Boundaries
- Company management (`Company`, `/api/companies`) is for authorized agents/company-code verification.
- Aftersales order `companyName` is customer information stored on the order only; creating or updating an aftersales order must not create or link a `Company` record.
### Import Organization ### Import Organization
Standard imports followed by third-party imports, then project imports (sorted alphabetically): Standard imports followed by third-party imports, then project imports (sorted alphabetically):
```go ```go
+3
View File
@@ -263,6 +263,8 @@ go run github.com/swaggo/swag/cmd/swag@v1.16.6 init -g main.go
### 企业管理 ### 企业管理
企业管理用于维护授权代理商/企业码主体,供客户扫码查询代理商授权状态;它不是售后客户名录。
| 方法 | 路径 | 描述 | 需要认证 | 角色 | | 方法 | 路径 | 描述 | 需要认证 | 角色 |
| ------ | ----------------------------- | ------------ | -------- | ------ | | ------ | ----------------------------- | ------------ | -------- | ------ |
| GET | `/api/companies/stats/overview` | 获取企业统计概览 | 是 | 管理员 | | GET | `/api/companies/stats/overview` | 获取企业统计概览 | 是 | 管理员 |
@@ -319,6 +321,7 @@ go run github.com/swaggo/swag/cmd/swag@v1.16.6 init -g main.go
- 服务类型枚举:`software`(软件故障)、`hardware`(硬件故障)、`maintenance`(售后维保) - 服务类型枚举:`software`(软件故障)、`hardware`(硬件故障)、`maintenance`(售后维保)
- 工单号格式: `zjbf-sh-YYMMDDNN`(年份后 2 位 + 月份 2 位 + 日期 2 位 + 当天序号至少 2 位,例:`zjbf-sh-26052801` - 工单号格式: `zjbf-sh-YYMMDDNN`(年份后 2 位 + 月份 2 位 + 日期 2 位 + 当天序号至少 2 位,例:`zjbf-sh-26052801`
- 序号按天重置,软删除工单不释放编号(避免回收造成混淆) - 序号按天重置,软删除工单不释放编号(避免回收造成混淆)
- 工单里的企业名称是售后客户信息,只保存在工单中,不会自动创建或关联企业管理记录
- 二维码扫码后客户在网页签名(canvas)后点「已授权」确认;选择「未授权」需填写退回原因 - 二维码扫码后客户在网页签名(canvas)后点「已授权」确认;选择「未授权」需填写退回原因
- 签名以 PNG dataURL 形式持久化到工单(`signature` 字段),管理员详情页可查看留底 - 签名以 PNG dataURL 形式持久化到工单(`signature` 字段),管理员详情页可查看留底
- 签名校验:必须为 `data:image/png;base64,``data:image/jpeg;base64,` 前缀,解码后 200B500KB - 签名校验:必须为 `data:image/png;base64,``data:image/jpeg;base64,` 前缀,解码后 200B500KB
-1
View File
@@ -279,7 +279,6 @@ type AftersalesOrder struct {
Technician *User `gorm:"foreignKey:TechnicianID" json:"technician,omitempty"` Technician *User `gorm:"foreignKey:TechnicianID" json:"technician,omitempty"`
Creator *User `gorm:"foreignKey:CreatedBy" json:"creator,omitempty"` Creator *User `gorm:"foreignKey:CreatedBy" json:"creator,omitempty"`
Company *Company `gorm:"foreignKey:CompanyName;references:CompanyName" json:"company,omitempty"`
} }
// CreateAftersalesOrderDTO 创建售后工单请求数据 // CreateAftersalesOrderDTO 创建售后工单请求数据
-13
View File
@@ -142,19 +142,6 @@ func (s *AftersalesService) generateUniqueSerial() (string, error) {
// Create 创建售后工单 // Create 创建售后工单
func (s *AftersalesService) Create(dto models.CreateAftersalesOrderDTO, userId uint) (*models.AftersalesOrder, error) { func (s *AftersalesService) Create(dto models.CreateAftersalesOrderDTO, userId uint) (*models.AftersalesOrder, error) {
// 确保公司存在
var company models.Company
result := database.DB.Where("company_name = ?", dto.CompanyName).First(&company)
if result.Error != nil {
company = models.Company{
CompanyName: dto.CompanyName,
IsActive: true,
}
if err := database.DB.Create(&company).Error; err != nil {
return nil, fmt.Errorf("创建公司失败: %w", err)
}
}
serialNumber, err := s.generateUniqueSerial() serialNumber, err := s.generateUniqueSerial()
if err != nil { if err != nil {
return nil, err return nil, err
+26 -12
View File
@@ -79,7 +79,32 @@ func TestAftersalesService_Create_GeneratesYYMMDDNNSerial(t *testing.T) {
assert.Equal(t, user.ID, *order.TechnicianID) assert.Equal(t, user.ID, *order.TechnicianID)
database.DB.Unscoped().Delete(order) database.DB.Unscoped().Delete(order)
database.DB.Unscoped().Where("company_name = ?", "AftersalesSerialCo").Delete(&models.Company{}) }
func TestAftersalesService_Create_DoesNotCreateManagedCompany(t *testing.T) {
user := seedTechnician(t, "aftersales_no_company_tech")
defer database.DB.Unscoped().Delete(&user)
companyName := "AftersalesCustomerOnlyCo"
database.DB.Unscoped().Where("company_name = ?", companyName).Delete(&models.Company{})
svc := AftersalesService{}
order, err := svc.Create(models.CreateAftersalesOrderDTO{
CompanyName: companyName,
CompanyAddress: "客户现场地址",
ContactName: "王五",
ContactPhone: "13800001235",
ServiceType: "software",
IssueDescription: "售后客户问题",
}, user.ID)
assert.NoError(t, err)
assert.NotNil(t, order)
defer database.DB.Unscoped().Delete(order)
var count int64
database.DB.Model(&models.Company{}).Where("company_name = ?", companyName).Count(&count)
assert.Equal(t, int64(0), count)
} }
func TestAftersalesService_Create_SerialIncrementsWithinDay(t *testing.T) { func TestAftersalesService_Create_SerialIncrementsWithinDay(t *testing.T) {
@@ -105,7 +130,6 @@ func TestAftersalesService_Create_SerialIncrementsWithinDay(t *testing.T) {
database.DB.Unscoped().Delete(first) database.DB.Unscoped().Delete(first)
database.DB.Unscoped().Delete(second) database.DB.Unscoped().Delete(second)
database.DB.Unscoped().Where("company_name = ?", "SeqCo").Delete(&models.Company{})
} }
func TestAftersalesService_SubmitForConfirmation_OwnerOnly(t *testing.T) { func TestAftersalesService_SubmitForConfirmation_OwnerOnly(t *testing.T) {
@@ -116,7 +140,6 @@ func TestAftersalesService_SubmitForConfirmation_OwnerOnly(t *testing.T) {
order := createOrderFor(t, owner, "13800003000") order := createOrderFor(t, owner, "13800003000")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
@@ -141,7 +164,6 @@ func TestAftersalesService_SubmitForConfirmation_RejectsClosed(t *testing.T) {
order := createOrderFor(t, owner, "13800004000") order := createOrderFor(t, owner, "13800004000")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
@@ -163,7 +185,6 @@ func TestAftersalesService_PublicQuery_MasksPhoneAndSetsScannedAt(t *testing.T)
order := createOrderFor(t, owner, "13800005000") order := createOrderFor(t, owner, "13800005000")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
view, err := svc.PublicQuery(order.SerialNumber) view, err := svc.PublicQuery(order.SerialNumber)
@@ -186,7 +207,6 @@ func TestAftersalesService_CustomerConfirm_Authorize(t *testing.T) {
order := createOrderFor(t, owner, "13800006789") order := createOrderFor(t, owner, "13800006789")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
@@ -217,7 +237,6 @@ func TestAftersalesService_CustomerConfirm_AuthorizeRejectsEmptySignature(t *tes
order := createOrderFor(t, owner, "13800007777") order := createOrderFor(t, owner, "13800007777")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
@@ -239,7 +258,6 @@ func TestAftersalesService_CustomerConfirm_AuthorizeRejectsInvalidSignature(t *t
order := createOrderFor(t, owner, "13800007788") order := createOrderFor(t, owner, "13800007788")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
@@ -271,7 +289,6 @@ func TestAftersalesService_CustomerConfirm_RejectRequiresReason(t *testing.T) {
order := createOrderFor(t, owner, "13800008877") order := createOrderFor(t, owner, "13800008877")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
@@ -293,7 +310,6 @@ func TestAftersalesService_CustomerConfirm_RejectIncrementsCount(t *testing.T) {
order := createOrderFor(t, owner, "13800008888") order := createOrderFor(t, owner, "13800008888")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
@@ -328,7 +344,6 @@ func TestAftersalesService_CustomerConfirm_RejectsWrongStatus(t *testing.T) {
order := createOrderFor(t, owner, "13800009999") order := createOrderFor(t, owner, "13800009999")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
// 未提交客户确认,工单仍是 created,应该拒绝 // 未提交客户确认,工单仍是 created,应该拒绝
@@ -345,7 +360,6 @@ func TestAftersalesService_ForceClose_AdminOverride(t *testing.T) {
order := createOrderFor(t, owner, "13800001111") order := createOrderFor(t, owner, "13800001111")
defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Delete(order)
defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{})
svc := AftersalesService{} svc := AftersalesService{}
updated, err := svc.ForceClose(order.SerialNumber) updated, err := svc.ForceClose(order.SerialNumber)