Add responsible signature to aftersales confirmation

This commit is contained in:
Frudrax Cheng
2026-06-02 10:38:29 +08:00
parent d1d189528c
commit 1ebec18869
3 changed files with 83 additions and 44 deletions
+4
View File
@@ -272,6 +272,7 @@ type AftersalesOrder struct {
ConfirmedAt *time.Time `json:"confirmedAt"` ConfirmedAt *time.Time `json:"confirmedAt"`
RejectCount int `gorm:"default:0" json:"rejectCount"` RejectCount int `gorm:"default:0" json:"rejectCount"`
Signature string `gorm:"type:text" json:"signature,omitempty"` Signature string `gorm:"type:text" json:"signature,omitempty"`
ResponsibleSignature string `gorm:"type:text" json:"responsibleSignature,omitempty"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
@@ -310,10 +311,12 @@ type SubmitForConfirmationDTO struct {
// CustomerConfirmDTO 客户确认请求 // CustomerConfirmDTO 客户确认请求
// Signature 为客户在网页上手写签名的 base64 PNG dataURL,仅 authorize 时必填 // Signature 为客户在网页上手写签名的 base64 PNG dataURL,仅 authorize 时必填
// ResponsibleSignature 为负责人在网页上手写签名的 base64 PNG dataURL,仅 authorize 时必填
// RejectReason 为客户拒绝的原因,仅 reject 时必填 // RejectReason 为客户拒绝的原因,仅 reject 时必填
type CustomerConfirmDTO struct { type CustomerConfirmDTO struct {
Action string `json:"action" validate:"required,oneof=authorize reject"` Action string `json:"action" validate:"required,oneof=authorize reject"`
Signature string `json:"signature,omitempty" validate:"required_if=Action authorize"` Signature string `json:"signature,omitempty" validate:"required_if=Action authorize"`
ResponsibleSignature string `json:"responsibleSignature,omitempty" validate:"required_if=Action authorize"`
RejectReason string `json:"rejectReason,omitempty" validate:"required_if=Action reject"` RejectReason string `json:"rejectReason,omitempty" validate:"required_if=Action reject"`
} }
@@ -337,4 +340,5 @@ type AftersalesPublicView struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
ConfirmedAt *time.Time `json:"confirmedAt"` ConfirmedAt *time.Time `json:"confirmedAt"`
Signature string `json:"signature,omitempty"` Signature string `json:"signature,omitempty"`
ResponsibleSignature string `json:"responsibleSignature,omitempty"`
} }
+5
View File
@@ -341,6 +341,7 @@ func (s *AftersalesService) PublicQuery(serialNumber string) (*models.Aftersales
CreatedAt: order.CreatedAt, CreatedAt: order.CreatedAt,
ConfirmedAt: order.ConfirmedAt, ConfirmedAt: order.ConfirmedAt,
Signature: order.Signature, Signature: order.Signature,
ResponsibleSignature: order.ResponsibleSignature,
} }
if order.Technician != nil { if order.Technician != nil {
view.TechnicianName = order.Technician.Name view.TechnicianName = order.Technician.Name
@@ -372,10 +373,14 @@ func (s *AftersalesService) CustomerConfirm(serialNumber string, dto models.Cust
if err := validateSignature(dto.Signature); err != nil { if err := validateSignature(dto.Signature); err != nil {
return nil, err return nil, err
} }
if err := validateSignature(dto.ResponsibleSignature); err != nil {
return nil, err
}
order.WorkOrderStatus = WorkOrderStatusClosed order.WorkOrderStatus = WorkOrderStatusClosed
order.AuthorizationStatus = AuthorizationStatusAuthorized order.AuthorizationStatus = AuthorizationStatusAuthorized
order.ConfirmedAt = &now order.ConfirmedAt = &now
order.Signature = strings.TrimSpace(dto.Signature) order.Signature = strings.TrimSpace(dto.Signature)
order.ResponsibleSignature = strings.TrimSpace(dto.ResponsibleSignature)
case "reject": case "reject":
reason := strings.TrimSpace(dto.RejectReason) reason := strings.TrimSpace(dto.RejectReason)
if reason == "" { if reason == "" {
+30
View File
@@ -214,9 +214,11 @@ func TestAftersalesService_CustomerConfirm_Authorize(t *testing.T) {
}, owner) }, owner)
sig := validSignatureFixture() sig := validSignatureFixture()
responsibleSig := validSignatureFixture()
view, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ view, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize", Action: "authorize",
Signature: sig, Signature: sig,
ResponsibleSignature: responsibleSig,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, view) assert.NotNil(t, view)
@@ -224,10 +226,12 @@ func TestAftersalesService_CustomerConfirm_Authorize(t *testing.T) {
assert.Equal(t, AuthorizationStatusAuthorized, view.AuthorizationStatus) assert.Equal(t, AuthorizationStatusAuthorized, view.AuthorizationStatus)
assert.NotNil(t, view.ConfirmedAt) assert.NotNil(t, view.ConfirmedAt)
assert.Equal(t, sig, view.Signature) assert.Equal(t, sig, view.Signature)
assert.Equal(t, responsibleSig, view.ResponsibleSignature)
var refreshed models.AftersalesOrder var refreshed models.AftersalesOrder
database.DB.Where("serial_number = ?", order.SerialNumber).First(&refreshed) database.DB.Where("serial_number = ?", order.SerialNumber).First(&refreshed)
assert.Equal(t, sig, refreshed.Signature) assert.Equal(t, sig, refreshed.Signature)
assert.Equal(t, responsibleSig, refreshed.ResponsibleSignature)
} }
func TestAftersalesService_CustomerConfirm_AuthorizeRejectsEmptySignature(t *testing.T) { func TestAftersalesService_CustomerConfirm_AuthorizeRejectsEmptySignature(t *testing.T) {
@@ -246,6 +250,29 @@ func TestAftersalesService_CustomerConfirm_AuthorizeRejectsEmptySignature(t *tes
_, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ _, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize", Action: "authorize",
Signature: "", Signature: "",
ResponsibleSignature: validSignatureFixture(),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "签名")
}
func TestAftersalesService_CustomerConfirm_AuthorizeRejectsEmptyResponsibleSignature(t *testing.T) {
confirmTest(t)
owner := seedTechnician(t, "aftersales_empty_responsible_sig_owner")
defer database.DB.Unscoped().Delete(&owner)
order := createOrderFor(t, owner, "13800007778")
defer database.DB.Unscoped().Delete(order)
svc := AftersalesService{}
_, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{
ResolutionNote: "done",
}, owner)
_, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize",
Signature: validSignatureFixture(),
ResponsibleSignature: "",
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "签名") assert.Contains(t, err.Error(), "签名")
@@ -268,6 +295,7 @@ func TestAftersalesService_CustomerConfirm_AuthorizeRejectsInvalidSignature(t *t
_, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ _, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize", Action: "authorize",
Signature: "not-a-data-url", Signature: "not-a-data-url",
ResponsibleSignature: validSignatureFixture(),
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "签名格式") assert.Contains(t, err.Error(), "签名格式")
@@ -277,6 +305,7 @@ func TestAftersalesService_CustomerConfirm_AuthorizeRejectsInvalidSignature(t *t
_, err = svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ _, err = svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize", Action: "authorize",
Signature: tiny, Signature: tiny,
ResponsibleSignature: validSignatureFixture(),
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "过短") assert.Contains(t, err.Error(), "过短")
@@ -350,6 +379,7 @@ func TestAftersalesService_CustomerConfirm_RejectsWrongStatus(t *testing.T) {
_, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ _, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{
Action: "authorize", Action: "authorize",
Signature: validSignatureFixture(), Signature: validSignatureFixture(),
ResponsibleSignature: validSignatureFixture(),
}) })
assert.Error(t, err) assert.Error(t, err)
} }