Decouple aftersales customers from company management
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,` 前缀,解码后 200B–500KB
|
- 签名校验:必须为 `data:image/png;base64,` 或 `data:image/jpeg;base64,` 前缀,解码后 200B–500KB
|
||||||
|
|||||||
+2
-3
@@ -277,9 +277,8 @@ type AftersalesOrder struct {
|
|||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
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 创建售后工单请求数据
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user