refactor platform domain states and permissions
This commit is contained in:
@@ -152,7 +152,7 @@ func CreateGasorderContract(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
contract := models.GasorderContract{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusDraft},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable}, ContractStatus: common.StatusDraft,
|
||||
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,
|
||||
@@ -192,7 +192,7 @@ func UpdateGasorderContract(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
result := impl.DBService.Model(&models.GasorderContract{}).
|
||||
Where("identity = ? AND status = ?", ctx.Param("identity"), common.StatusDraft).
|
||||
Where("identity = ? AND contract_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})
|
||||
@@ -228,13 +228,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 != common.StatusActive && contract.Status != common.StatusExpired && contract.Status != common.StatusTerminated {
|
||||
if contract.ContractStatus != common.StatusActive && contract.ContractStatus != common.StatusExpired && contract.ContractStatus != common.StatusTerminated {
|
||||
return errors.New("contract cannot be renewed")
|
||||
}
|
||||
if err := tx.Model(&contract).Updates(map[string]any{"status": common.StatusActive, "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt}).Error; err != nil {
|
||||
if err := tx.Model(&contract).Updates(map[string]any{"contract_status": common.StatusActive, "effective_at": request.EffectiveAt, "expired_at": request.ExpiredAt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
contract.Status, contract.EffectiveAt, contract.ExpiredAt = common.StatusActive, request.EffectiveAt, request.ExpiredAt
|
||||
contract.ContractStatus, contract.EffectiveAt, contract.ExpiredAt = common.StatusActive, request.EffectiveAt, request.ExpiredAt
|
||||
return tx.Create(contractRevision(contract, "renew", request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
@@ -258,10 +258,10 @@ func changeGasorderContract(ctx *gin.Context, action string, target int) {
|
||||
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 != common.StatusDraft && contract.Status != common.StatusTerminated {
|
||||
if action == "activate" && contract.ContractStatus != common.StatusDraft && contract.ContractStatus != common.StatusTerminated {
|
||||
return errors.New("contract cannot be activated")
|
||||
}
|
||||
if action == "terminate" && contract.Status != common.StatusActive {
|
||||
if action == "terminate" && contract.ContractStatus != common.StatusActive {
|
||||
return errors.New("contract cannot be terminated")
|
||||
}
|
||||
if target == common.StatusActive {
|
||||
@@ -275,23 +275,23 @@ func changeGasorderContract(ctx *gin.Context, action string, target int) {
|
||||
return errors.New("contract has no active product")
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&contract).Update("status", target).Error; err != nil {
|
||||
if err := tx.Model(&contract).Update("contract_status", target).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
contract.Status = target
|
||||
contract.ContractStatus = target
|
||||
return tx.Create(contractRevision(contract, action, request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": target})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "contract_status": target})
|
||||
}
|
||||
|
||||
func contractRevision(contract models.GasorderContract, action, reason, operatorIdentity, operatorName string) *models.GasorderContractRevision {
|
||||
return &models.GasorderContractRevision{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded},
|
||||
GasorderContractID: contract.ID, Action: action, ContractStatus: contract.Status,
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderContractID: contract.ID, Action: action, ContractStatus: contract.ContractStatus,
|
||||
EffectiveAt: contract.EffectiveAt, ExpiredAt: contract.ExpiredAt,
|
||||
OperatorIdentity: operatorIdentity, OperatorName: operatorName, OccurredAt: time.Now(), Reason: reason,
|
||||
}
|
||||
@@ -312,13 +312,13 @@ func BindGasorderContractProduct(ctx *gin.Context) {
|
||||
common.RespondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if contract.Status != common.StatusDraft && contract.Status != common.StatusActive {
|
||||
if contract.ContractStatus != common.StatusDraft && contract.ContractStatus != 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 == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
product.Status != common.StatusEnable || product.ProductStatus == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
@@ -328,7 +328,7 @@ func BindGasorderContractProduct(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
binding := models.GasorderContractProduct{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusBound},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderContractID: contract.ID, ProductInfoID: product.ID, ProductCode: product.Code,
|
||||
ProductTypeName: productType.Name, ProductParams: product.Params, UnitPrice: request.UnitPrice, BoundAt: time.Now(),
|
||||
}
|
||||
@@ -350,6 +350,7 @@ func UnbindGasorderContractProduct(ctx *gin.Context) {
|
||||
now := time.Now()
|
||||
result := impl.DBService.Model(&models.GasorderContractProduct{}).
|
||||
Where("identity = ? AND unbound_at IS NULL", ctx.Param("identity")).
|
||||
Where("NOT EXISTS (SELECT 1 FROM gasorder_item WHERE gasorder_item.gasorder_contract_product_id = gasorder_contract_product.id AND gasorder_item.active = true)").
|
||||
Updates(gasorderUnbindUpdates(request.Reason, now))
|
||||
if result.Error != nil || result.RowsAffected != 1 {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
@@ -389,11 +390,11 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
return err
|
||||
}
|
||||
var contract models.GasorderContract
|
||||
if err := tx.Where("identity = ?", request.ContractIdentity).First(&contract).Error; err != nil {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", request.ContractIdentity).First(&contract).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
if contract.Status != common.StatusActive || contract.EffectiveAt.After(now) || (contract.ExpiredAt != nil && !contract.ExpiredAt.After(now)) {
|
||||
if contract.ContractStatus != 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 {
|
||||
@@ -417,8 +418,8 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
var productAmount int64
|
||||
for _, binding := range bindings {
|
||||
var product models.ProductInfo
|
||||
if err := tx.Where("id = ?", binding.ProductInfoID).First(&product).Error; err != nil ||
|
||||
!product.IsEnabled || product.Status == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", binding.ProductInfoID).First(&product).Error; err != nil ||
|
||||
product.Status != common.StatusEnable || product.ProductStatus == common.StatusScrapped || product.UserAccountID != contract.UserAccountID {
|
||||
return errors.New("contract product is no longer eligible")
|
||||
}
|
||||
productAmount += binding.UnitPrice
|
||||
@@ -428,7 +429,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
return errors.New("invalid payable amount")
|
||||
}
|
||||
order = models.GasorderBasic{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusCreated},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable}, OrderStatus: common.StatusCreated,
|
||||
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,
|
||||
@@ -443,7 +444,7 @@ func CreateGasorderBasic(ctx *gin.Context) {
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
item := models.GasorderItem{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusOrdered},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderBasicID: order.ID, GasorderContractProductID: binding.ID, ProductInfoID: binding.ProductInfoID,
|
||||
Active: true,
|
||||
ProductCode: binding.ProductCode, ProductTypeName: binding.ProductTypeName,
|
||||
@@ -493,20 +494,20 @@ 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 != common.StatusCreated && order.Status != common.StatusAssigned {
|
||||
if order.OrderStatus != common.StatusCreated && order.OrderStatus != common.StatusAssigned {
|
||||
return errors.New("order cannot be assigned")
|
||||
}
|
||||
if !validGasorderAssignment(order, delivery, staff) {
|
||||
return errors.New("delivery or staff does not belong to the order organization")
|
||||
}
|
||||
previous := order.Status
|
||||
previous := order.OrderStatus
|
||||
if err := tx.Model(&order).Updates(map[string]any{
|
||||
"delivery_basic_id": delivery.ID, "staff_account_id": staff.ID, "status": common.StatusAssigned,
|
||||
"delivery_basic_id": delivery.ID, "staff_account_id": staff.ID, "order_status": common.StatusAssigned,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
assignment := models.GasorderAssign{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderBasicID: order.ID, GasBasicID: order.GasBasicID, DeliveryBasicID: delivery.ID,
|
||||
StaffAccountID: staff.ID, AssignerIdentity: operatorIdentity, AssignerName: operatorName,
|
||||
AssignedAt: time.Now(), Reason: request.Reason,
|
||||
@@ -523,7 +524,7 @@ func AssignGasorderBasic(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": common.StatusAssigned})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "order_status": common.StatusAssigned})
|
||||
}
|
||||
|
||||
func GasorderStartFilling(ctx *gin.Context) {
|
||||
@@ -561,14 +562,14 @@ func GasorderComplete(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 != common.StatusAwaitingConfirmation {
|
||||
if order.OrderStatus != common.StatusAwaitingConfirmation {
|
||||
return errors.New("order cannot be completed")
|
||||
}
|
||||
if err := tx.Model(&order).Update("status", common.StatusCompleted).Error; err != nil {
|
||||
if err := tx.Model(&order).Update("order_status", common.StatusCompleted).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
confirm := models.GasorderConfirm{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderBasicID: order.ID, ConfirmType: request.ConfirmType, RecipientName: request.RecipientName,
|
||||
RecipientPhone: request.RecipientPhone, ProofURI: request.ProofURI, ConfirmedAt: time.Now(), Remark: request.Remark,
|
||||
}
|
||||
@@ -578,13 +579,13 @@ func GasorderComplete(ctx *gin.Context) {
|
||||
if err := releaseGasorderProducts(tx, order.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(gasorderStatusRecord(order.ID, order.Status, common.StatusCompleted, request.Reason, operatorIdentity, operatorName)).Error
|
||||
return tx.Create(gasorderStatusRecord(order.ID, order.OrderStatus, common.StatusCompleted, request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": common.StatusCompleted})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "order_status": common.StatusCompleted})
|
||||
}
|
||||
|
||||
func GasorderException(ctx *gin.Context) {
|
||||
@@ -605,11 +606,11 @@ 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 != common.StatusException || order.PreviousStatus == common.StatusDraft {
|
||||
if order.OrderStatus != common.StatusException || order.PreviousOrderStatus == common.StatusDraft {
|
||||
return errors.New("order cannot recover")
|
||||
}
|
||||
target := order.PreviousStatus
|
||||
if err := tx.Model(&order).Updates(map[string]any{"status": target, "previous_status": common.StatusDraft}).Error; err != nil {
|
||||
target := order.PreviousOrderStatus
|
||||
if err := tx.Model(&order).Updates(map[string]any{"order_status": target, "previous_order_status": common.StatusDraft}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(gasorderStatusRecord(order.ID, common.StatusException, target, request.Reason, operatorIdentity, operatorName)).Error
|
||||
@@ -635,12 +636,12 @@ func transitionGasorder(ctx *gin.Context, target int) {
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", ctx.Param("identity")).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if !gasorderTransitionAllowed(order.Status, target) || (target == common.StatusFilling && order.DeliveryBasicID == 0) {
|
||||
if !gasorderTransitionAllowed(order.OrderStatus, target) || (target == common.StatusFilling && order.DeliveryBasicID == 0) {
|
||||
return errors.New("invalid order transition")
|
||||
}
|
||||
updates := map[string]any{"status": target}
|
||||
updates := map[string]any{"order_status": target}
|
||||
if target == common.StatusException {
|
||||
updates["previous_status"] = order.Status
|
||||
updates["previous_order_status"] = order.OrderStatus
|
||||
}
|
||||
if err := tx.Model(&order).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
@@ -652,7 +653,7 @@ func transitionGasorder(ctx *gin.Context, target int) {
|
||||
return err
|
||||
}
|
||||
track := models.GasorderTrack{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusActive},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderBasicID: order.ID, StaffAccountID: order.StaffAccountID,
|
||||
AttemptNo: attempt + 1, StartedAt: time.Now(),
|
||||
}
|
||||
@@ -674,13 +675,13 @@ func transitionGasorder(ctx *gin.Context, target int) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(gasorderStatusRecord(order.ID, order.Status, target, request.Reason, operatorIdentity, operatorName)).Error
|
||||
return tx.Create(gasorderStatusRecord(order.ID, order.OrderStatus, target, request.Reason, operatorIdentity, operatorName)).Error
|
||||
})
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "status": target})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true, "order_status": target})
|
||||
}
|
||||
|
||||
func gasorderTransitionAllowed(from, target int) bool {
|
||||
@@ -734,7 +735,7 @@ func deliveryBelongsToGas(deliveryID, gasID uint64) bool {
|
||||
|
||||
func gasorderStatusRecord(orderID uint64, from, to int, reason, operatorIdentity, operatorName string) *models.GasorderStatus {
|
||||
return &models.GasorderStatus{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusRecorded},
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable},
|
||||
GasorderBasicID: orderID, FromStatus: from, ToStatus: to,
|
||||
OperatorIdentity: operatorIdentity, OperatorName: operatorName, OccurredAt: time.Now(), Reason: reason,
|
||||
}
|
||||
|
||||
@@ -91,14 +91,14 @@ func TestGasorderStatusRecordIsImmutableSnapshot(t *testing.T) {
|
||||
if record.GasorderBasicID != 7 || record.FromStatus != common.StatusAssigned || record.ToStatus != common.StatusFilling {
|
||||
t.Fatalf("unexpected status record: %#v", record)
|
||||
}
|
||||
if record.Status != common.StatusRecorded || record.OccurredAt.IsZero() {
|
||||
if record.Status != common.StatusEnable || 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: common.StatusActive},
|
||||
Entity: models.Entity{ID: 9, Status: common.StatusEnable}, ContractStatus: common.StatusActive,
|
||||
}
|
||||
revision := contractRevision(contract, "renew", "annual renewal", "operator-a", "Operator")
|
||||
if revision.GasorderContractID != 9 || revision.Action != "renew" || revision.ContractStatus != common.StatusActive {
|
||||
|
||||
Reference in New Issue
Block a user