refactor platform menus and entity statuses
This commit is contained in:
@@ -148,7 +148,7 @@ func CreateGasorderContract(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
contract := models.GasorderContract{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "draft", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusDraft, Version: 1},
|
||||
ContractNo: request.ContractNo, UserAccountID: userID, GasBasicID: gasID, DeliveryBasicID: deliveryID,
|
||||
Title: request.Title, Terms: request.Terms, FileURI: request.FileURI, DefaultDeliveryFee: request.DefaultDeliveryFee,
|
||||
SignedAt: request.SignedAt, EffectiveAt: request.EffectiveAt, ExpiredAt: request.ExpiredAt,
|
||||
@@ -182,7 +182,7 @@ func UpdateGasorderContract(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
result := impl.DBService.Model(&models.GasorderContract{}).
|
||||
Where("identity = ? AND status = ?", ctx.Param("identity"), "draft").
|
||||
Where("identity = ? AND status = ?", ctx.Param("identity"), common.StatusDraft).
|
||||
Updates(map[string]any{"delivery_basic_id": deliveryID, "title": request.Title, "terms": request.Terms,
|
||||
"file_uri": request.FileURI, "default_delivery_fee": request.DefaultDeliveryFee,
|
||||
"signed_at": request.SignedAt, "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt})
|
||||
@@ -194,11 +194,11 @@ func UpdateGasorderContract(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
func ActivateGasorderContract(ctx *gin.Context) {
|
||||
changeGasorderContract(ctx, "activate", "active")
|
||||
changeGasorderContract(ctx, "activate", common.StatusActive)
|
||||
}
|
||||
|
||||
func TerminateGasorderContract(ctx *gin.Context) {
|
||||
changeGasorderContract(ctx, "terminate", "terminated")
|
||||
changeGasorderContract(ctx, "terminate", common.StatusTerminated)
|
||||
}
|
||||
|
||||
func RenewGasorderContract(ctx *gin.Context) {
|
||||
@@ -218,13 +218,13 @@ func RenewGasorderContract(ctx *gin.Context) {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&contract).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if contract.Status != "active" && contract.Status != "expired" && contract.Status != "terminated" {
|
||||
if contract.Status != common.StatusActive && contract.Status != common.StatusExpired && contract.Status != common.StatusTerminated {
|
||||
return errors.New("contract cannot be renewed")
|
||||
}
|
||||
if err := tx.Model(&contract).Updates(map[string]any{"status": "active", "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt}).Error; err != nil {
|
||||
if err := tx.Model(&contract).Updates(map[string]any{"status": common.StatusActive, "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
contract.Status, contract.EffectiveAt, contract.ExpiredAt = "active", request.EffectiveAt, request.ExpiredAt
|
||||
contract.Status, contract.EffectiveAt, contract.ExpiredAt = common.StatusActive, request.EffectiveAt, request.ExpiredAt
|
||||
return tx.Create(contractRevision(contract, "renew", request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
@@ -234,7 +234,7 @@ func RenewGasorderContract(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func changeGasorderContract(ctx *gin.Context, action, target string) {
|
||||
func changeGasorderContract(ctx *gin.Context, action string, target int) {
|
||||
var request struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
@@ -248,13 +248,13 @@ func changeGasorderContract(ctx *gin.Context, action, target string) {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&contract).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if action == "activate" && contract.Status != "draft" && contract.Status != "terminated" {
|
||||
if action == "activate" && contract.Status != common.StatusDraft && contract.Status != common.StatusTerminated {
|
||||
return errors.New("contract cannot be activated")
|
||||
}
|
||||
if action == "terminate" && contract.Status != "active" {
|
||||
if action == "terminate" && contract.Status != common.StatusActive {
|
||||
return errors.New("contract cannot be terminated")
|
||||
}
|
||||
if target == "active" {
|
||||
if target == common.StatusActive {
|
||||
now := time.Now()
|
||||
if contract.EffectiveAt.After(now) || (contract.ExpiredAt != nil && !contract.ExpiredAt.After(now)) {
|
||||
return errors.New("contract outside effective period")
|
||||
@@ -280,7 +280,7 @@ func changeGasorderContract(ctx *gin.Context, action, target string) {
|
||||
|
||||
func contractRevision(contract models.GasorderContract, action, reason, operatorIdentity, operatorName string) *models.GasorderContractRevision {
|
||||
return &models.GasorderContractRevision{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "recorded", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded, Version: 1},
|
||||
GasorderContractID: contract.ID, Action: action, ContractStatus: contract.Status,
|
||||
EffectiveAt: contract.EffectiveAt, ExpiredAt: contract.ExpiredAt,
|
||||
OperatorIdentity: operatorIdentity, OperatorName: operatorName, OccurredAt: time.Now(), Reason: reason,
|
||||
@@ -302,13 +302,13 @@ func BindGasorderContractProduct(ctx *gin.Context) {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if contract.Status != "draft" && contract.Status != "active" {
|
||||
if contract.Status != common.StatusDraft && contract.Status != common.StatusActive {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var product models.ProductInfo
|
||||
if err := impl.DBService.Where("identity = ?", request.ProductIdentity).First(&product).Error; err != nil ||
|
||||
!product.IsEnabled || product.Status == "scrapped" || product.UserAccountID != contract.UserAccountID {
|
||||
!product.IsEnabled || product.Status == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
@@ -318,7 +318,7 @@ func BindGasorderContractProduct(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
binding := models.GasorderContractProduct{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "bound", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusBound, Version: 1},
|
||||
GasorderContractID: contract.ID, ProductInfoID: product.ID, ProductCode: product.Code,
|
||||
ProductTypeName: productType.Name, ProductParams: product.Params, UnitPrice: request.UnitPrice, BoundAt: time.Now(),
|
||||
}
|
||||
@@ -376,7 +376,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
if contract.Status != "active" || contract.EffectiveAt.After(now) || (contract.ExpiredAt != nil && !contract.ExpiredAt.After(now)) {
|
||||
if contract.Status != common.StatusActive || contract.EffectiveAt.After(now) || (contract.ExpiredAt != nil && !contract.ExpiredAt.After(now)) {
|
||||
return errors.New("contract is not active")
|
||||
}
|
||||
if request.CreatorType == "user" && creatorID != contract.UserAccountID {
|
||||
@@ -401,13 +401,13 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
for _, binding := range bindings {
|
||||
var product models.ProductInfo
|
||||
if err := tx.Where("id = ?", binding.ProductInfoID).First(&product).Error; err != nil ||
|
||||
!product.IsEnabled || product.Status == "scrapped" || product.UserAccountID != contract.UserAccountID {
|
||||
!product.IsEnabled || product.Status == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
return errors.New("contract product is no longer eligible")
|
||||
}
|
||||
var activeOrderCount int64
|
||||
if err := tx.Table("gasorder_item").
|
||||
Joins("JOIN gasorder_basic ON gasorder_basic.id = gasorder_item.gasorder_basic_id").
|
||||
Where("gasorder_item.product_info_id = ? AND gasorder_basic.status NOT IN ?", binding.ProductInfoID, []string{"completed", "cancelled"}).
|
||||
Where("gasorder_item.product_info_id = ? AND gasorder_basic.status NOT IN ?", binding.ProductInfoID, []int{common.StatusCompleted, common.StatusCancelled}).
|
||||
Count(&activeOrderCount).Error; err != nil || activeOrderCount != 0 {
|
||||
return errors.New("contract product already has an active order")
|
||||
}
|
||||
@@ -418,7 +418,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
return errors.New("invalid payable amount")
|
||||
}
|
||||
order = models.GasorderBasic{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "created", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusCreated, Version: 1},
|
||||
OrderNo: models.NewIdentity(), RequestNo: request.RequestNo, GasorderContractID: contract.ID,
|
||||
UserAccountID: contract.UserAccountID, CreatorType: request.CreatorType, CreatorID: creatorID,
|
||||
CreatorIdentity: request.CreatorIdentity, GasBasicID: contract.GasBasicID, DeliveryBasicID: contract.DeliveryBasicID,
|
||||
@@ -433,7 +433,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
item := models.GasorderItem{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "ordered", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusOrdered, Version: 1},
|
||||
GasorderBasicID: order.ID, GasorderContractProductID: binding.ID, ProductInfoID: binding.ProductInfoID,
|
||||
ProductCode: binding.ProductCode, ProductTypeName: binding.ProductTypeName,
|
||||
ProductParams: binding.ProductParams, UnitPrice: binding.UnitPrice,
|
||||
@@ -442,7 +442,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(gasorderStatusRecord(order.ID, "", "created", "order created", operatorIdentity, operatorName)).Error
|
||||
return tx.Create(gasorderStatusRecord(order.ID, common.StatusDraft, common.StatusCreated, "order created", operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
@@ -462,13 +462,13 @@ func AssignGasorderBasic(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
var delivery models.DeliveryBasic
|
||||
if err := impl.DBService.Where("identity = ?", request.DeliveryIdentity).First(&delivery).Error; err != nil || delivery.Status != "enabled" {
|
||||
if err := impl.DBService.Where("identity = ?", request.DeliveryIdentity).First(&delivery).Error; err != nil || delivery.Status != common.StatusEnable {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var staff models.StaffAccount
|
||||
if err := impl.DBService.Where("identity = ?", request.StaffIdentity).First(&staff).Error; err != nil ||
|
||||
staff.Status != "enabled" || staff.WorkStatus == "off_duty" {
|
||||
staff.Status != common.StatusEnable || staff.WorkStatus == "off_duty" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
@@ -478,17 +478,17 @@ func AssignGasorderBasic(ctx *gin.Context) {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "created" && order.Status != "assigned" {
|
||||
if order.Status != common.StatusCreated && order.Status != common.StatusAssigned {
|
||||
return errors.New("order cannot be assigned")
|
||||
}
|
||||
previous := order.Status
|
||||
if err := tx.Model(&order).Updates(map[string]any{
|
||||
"delivery_basic_id": delivery.ID, "staff_account_id": staff.ID, "status": "assigned",
|
||||
"delivery_basic_id": delivery.ID, "staff_account_id": staff.ID, "status": common.StatusAssigned,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
assignment := models.GasorderAssign{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "recorded", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded, Version: 1},
|
||||
GasorderBasicID: order.ID, GasBasicID: order.GasBasicID, DeliveryBasicID: delivery.ID,
|
||||
StaffAccountID: staff.ID, AssignerIdentity: operatorIdentity, AssignerName: operatorName,
|
||||
AssignedAt: time.Now(), Reason: request.Reason,
|
||||
@@ -496,8 +496,8 @@ func AssignGasorderBasic(ctx *gin.Context) {
|
||||
if err := tx.Create(&assignment).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if previous != "assigned" {
|
||||
return tx.Create(gasorderStatusRecord(order.ID, previous, "assigned", request.Reason, operatorIdentity, operatorName)).Error
|
||||
if previous != common.StatusAssigned {
|
||||
return tx.Create(gasorderStatusRecord(order.ID, previous, common.StatusAssigned, request.Reason, operatorIdentity, operatorName)).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -505,21 +505,24 @@ func AssignGasorderBasic(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": "assigned"})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": common.StatusAssigned})
|
||||
}
|
||||
|
||||
func GasorderStartFilling(ctx *gin.Context) {
|
||||
transitionGasorder(ctx, "filling", map[string]bool{"assigned": true})
|
||||
transitionGasorder(ctx, common.StatusFilling, map[int]bool{common.StatusAssigned: true})
|
||||
}
|
||||
func GasorderReady(ctx *gin.Context) {
|
||||
transitionGasorder(ctx, "ready", map[string]bool{"filling": true})
|
||||
transitionGasorder(ctx, common.StatusReady, map[int]bool{common.StatusFilling: true})
|
||||
}
|
||||
func GasorderCancel(ctx *gin.Context) {
|
||||
transitionGasorder(ctx, "cancelled", map[string]bool{"created": true, "assigned": true})
|
||||
transitionGasorder(ctx, common.StatusCancelled, map[int]bool{common.StatusCreated: true, common.StatusAssigned: true})
|
||||
}
|
||||
|
||||
func GasorderException(ctx *gin.Context) {
|
||||
transitionGasorder(ctx, "exception", map[string]bool{"filling": true, "ready": true, "delivering": true, "awaiting_confirmation": true})
|
||||
transitionGasorder(ctx, common.StatusException, map[int]bool{
|
||||
common.StatusFilling: true, common.StatusReady: true,
|
||||
common.StatusDelivering: true, common.StatusAwaitingConfirmation: true,
|
||||
})
|
||||
}
|
||||
|
||||
func GasorderRecover(ctx *gin.Context) {
|
||||
@@ -536,14 +539,14 @@ func GasorderRecover(ctx *gin.Context) {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "exception" || order.PreviousStatus == "" {
|
||||
if order.Status != common.StatusException || order.PreviousStatus == common.StatusDraft {
|
||||
return errors.New("order cannot recover")
|
||||
}
|
||||
target := order.PreviousStatus
|
||||
if err := tx.Model(&order).Updates(map[string]any{"status": target, "previous_status": ""}).Error; err != nil {
|
||||
if err := tx.Model(&order).Updates(map[string]any{"status": target, "previous_status": common.StatusDraft}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(gasorderStatusRecord(order.ID, "exception", target, request.Reason, operatorIdentity, operatorName)).Error
|
||||
return tx.Create(gasorderStatusRecord(order.ID, common.StatusException, target, request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
@@ -552,7 +555,7 @@ func GasorderRecover(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func transitionGasorder(ctx *gin.Context, target string, allowed map[string]bool) {
|
||||
func transitionGasorder(ctx *gin.Context, target int, allowed map[int]bool) {
|
||||
var request struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
@@ -566,11 +569,11 @@ func transitionGasorder(ctx *gin.Context, target string, allowed map[string]bool
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if !allowed[order.Status] || (target == "filling" && order.DeliveryBasicID == 0) {
|
||||
if !allowed[order.Status] || (target == common.StatusFilling && order.DeliveryBasicID == 0) {
|
||||
return errors.New("invalid order transition")
|
||||
}
|
||||
updates := map[string]any{"status": target}
|
||||
if target == "exception" {
|
||||
if target == common.StatusException {
|
||||
updates["previous_status"] = order.Status
|
||||
}
|
||||
if err := tx.Model(&order).Updates(updates).Error; err != nil {
|
||||
@@ -585,9 +588,9 @@ func transitionGasorder(ctx *gin.Context, target string, allowed map[string]bool
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": target})
|
||||
}
|
||||
|
||||
func gasorderStatusRecord(orderID uint64, from, to, reason, operatorIdentity, operatorName string) *models.GasorderStatus {
|
||||
func gasorderStatusRecord(orderID uint64, from, to int, reason, operatorIdentity, operatorName string) *models.GasorderStatus {
|
||||
return &models.GasorderStatus{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "recorded", Version: 1},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded, Version: 1},
|
||||
GasorderBasicID: orderID, FromStatus: from, ToStatus: to,
|
||||
OperatorIdentity: operatorIdentity, OperatorName: operatorName, OccurredAt: time.Now(), Reason: reason,
|
||||
}
|
||||
|
||||
@@ -19,21 +19,21 @@ func TestGasorderCreatorTypesCoverEveryConfirmedOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGasorderStatusRecordIsImmutableSnapshot(t *testing.T) {
|
||||
record := gasorderStatusRecord(7, "assigned", "filling", "start filling", "operator-a", "Operator")
|
||||
if record.GasorderBasicID != 7 || record.FromStatus != "assigned" || record.ToStatus != "filling" {
|
||||
record := gasorderStatusRecord(7, common.StatusAssigned, common.StatusFilling, "start filling", "operator-a", "Operator")
|
||||
if record.GasorderBasicID != 7 || record.FromStatus != common.StatusAssigned || record.ToStatus != common.StatusFilling {
|
||||
t.Fatalf("unexpected status record: %#v", record)
|
||||
}
|
||||
if record.Status != "recorded" || record.OccurredAt.IsZero() {
|
||||
if record.Status != common.StatusRecorded || record.OccurredAt.IsZero() {
|
||||
t.Fatalf("status record lacks immutable metadata: %#v", record)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContractRevisionKeepsSingleContractHistory(t *testing.T) {
|
||||
contract := models.GasorderContract{
|
||||
Entity: models.Entity{ID: 9, Status: "active"},
|
||||
Entity: models.Entity{ID: 9, Status: common.StatusActive},
|
||||
}
|
||||
revision := contractRevision(contract, "renew", "annual renewal", "operator-a", "Operator")
|
||||
if revision.GasorderContractID != 9 || revision.Action != "renew" || revision.ContractStatus != "active" {
|
||||
if revision.GasorderContractID != 9 || revision.Action != "renew" || revision.ContractStatus != common.StatusActive {
|
||||
t.Fatalf("unexpected contract revision: %#v", revision)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user