Files
platforms/backend/api/internal/logic/delivery/order.go

407 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package delivery
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
platformgasorder "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform/gasorder"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func rewriteJSON(ctx *gin.Context, mutate func(map[string]any) bool) bool {
body, err := io.ReadAll(ctx.Request.Body)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return false
}
var values map[string]any
if json.Unmarshal(body, &values) != nil || !mutate(values) {
if !ctx.IsAborted() {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
}
return false
}
body, _ = json.Marshal(values)
ctx.Request.Body = io.NopCloser(bytes.NewReader(body))
return true
}
func scopedContract(ctx *gin.Context, identity string, pointID uint64) (models.GasorderContract, bool) {
var contract models.GasorderContract
if err := common.ActiveRecords(db()).Where("identity = ? AND delivery_basic_id = ?", identity, pointID).First(&contract).Error; err != nil {
common.RespondRecordError(ctx, err)
return contract, false
}
return contract, true
}
func ListContract(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
listScoped(ctx, &models.GasorderContract{}, common.ActiveRecords(db().Model(&models.GasorderContract{})).
Where("gas_basic_id = ? AND delivery_basic_id = ?", point.GasBasicID, point.ID), "gasorder_contract.created_at desc")
}
}
func GetContract(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
if _, valid := scopedContract(ctx, ctx.Param("identity"), point.ID); valid {
platformgasorder.GetGasorderContract(ctx)
}
}
}
func CreateContract(ctx *gin.Context) {
point, station, ok := currentScope(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
userIdentity, _ := values["user_account_identity"].(string)
if _, _, valid := scopedUser(ctx, userIdentity, point.ID); !valid {
return false
}
values["gas_basic_identity"] = station.Identity
values["delivery_basic_identity"] = point.Identity
return true
}) {
return
}
platformgasorder.CreateGasorderContract(ctx)
}
func withContract(ctx *gin.Context, handler gin.HandlerFunc) {
point, _, ok := currentScope(ctx)
if ok {
if _, valid := scopedContract(ctx, ctx.Param("identity"), point.ID); valid {
handler(ctx)
}
}
}
func UpdateContract(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if _, valid := scopedContract(ctx, ctx.Param("identity"), point.ID); !valid {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
values["delivery_basic_identity"] = point.Identity
return true
}) {
return
}
platformgasorder.UpdateGasorderContract(ctx)
}
func ActivateContract(ctx *gin.Context) { withContract(ctx, platformgasorder.ActivateGasorderContract) }
func RenewContract(ctx *gin.Context) { withContract(ctx, platformgasorder.RenewGasorderContract) }
func TerminateContract(ctx *gin.Context) {
withContract(ctx, platformgasorder.TerminateGasorderContract)
}
func ListContractProduct(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
query := common.ActiveRecords(db().Model(&models.GasorderContractProduct{})).
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract.delivery_basic_id = ?", point.ID)
listScoped(ctx, &models.GasorderContractProduct{}, query, "gasorder_contract_product.created_at desc")
}
func GetContractProduct(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var item models.GasorderContractProduct
query := common.ActiveRecords(db().Model(&models.GasorderContractProduct{})).
Select("gasorder_contract_product.*").
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract_product.identity = ? AND gasorder_contract.delivery_basic_id = ?", ctx.Param("identity"), point.ID)
respondRecord(ctx, query, &item)
}
func BindContractProduct(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
identity, _ := values["gasorder_contract_identity"].(string)
_, valid := scopedContract(ctx, identity, point.ID)
return valid
}) {
return
}
platformgasorder.BindGasorderContractProduct(ctx)
}
func UnbindContractProduct(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var count int64
err := db().Model(&models.GasorderContractProduct{}).
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract_product.identity = ? AND gasorder_contract.delivery_basic_id = ?", ctx.Param("identity"), point.ID).
Count(&count).Error
if err != nil || count != 1 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
platformgasorder.UnbindGasorderContractProduct(ctx)
}
func ListContractRevision(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
query := common.ActiveRecords(db().Model(&models.GasorderContractRevision{})).
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_revision.gasorder_contract_id").
Where("gasorder_contract.delivery_basic_id = ?", point.ID)
listScoped(ctx, &models.GasorderContractRevision{}, query, "gasorder_contract_revision.created_at desc")
}
func GetContractRevision(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var item models.GasorderContractRevision
query := common.ActiveRecords(db().Model(&models.GasorderContractRevision{})).
Select("gasorder_contract_revision.*").
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_revision.gasorder_contract_id").
Where("gasorder_contract_revision.identity = ? AND gasorder_contract.delivery_basic_id = ?", ctx.Param("identity"), point.ID)
respondRecord(ctx, query, &item)
}
func ListProductCandidate(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
query := common.ActiveRecords(db().Model(&models.ProductInfo{})).
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = product_info.user_account_id AND user_service_relation.status <> ?",
common.StatusArchived).Where("user_service_relation.delivery_basic_id = ?", point.ID)
listScoped(ctx, &models.ProductInfo{}, query, "product_info.created_at desc")
}
func GetProductCandidate(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var item models.ProductInfo
query := common.ActiveRecords(db().Model(&models.ProductInfo{})).
Select("product_info.*").
Joins("JOIN user_service_relation ON user_service_relation.user_account_id = product_info.user_account_id AND user_service_relation.status <> ?",
common.StatusArchived).
Where("product_info.identity = ? AND user_service_relation.delivery_basic_id = ?", ctx.Param("identity"), point.ID)
respondRecord(ctx, query, &item)
}
func scopedOrder(ctx *gin.Context, identity string, pointID uint64) (models.GasorderBasic, bool) {
var order models.GasorderBasic
if err := common.ActiveRecords(db()).Where("identity = ? AND delivery_basic_id = ?", identity, pointID).First(&order).Error; err != nil {
common.RespondRecordError(ctx, err)
return order, false
}
return order, true
}
func ListOrder(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
listScoped(ctx, &models.GasorderBasic{}, common.ActiveRecords(db().Model(&models.GasorderBasic{})).
Where("delivery_basic_id = ?", point.ID), "gasorder_basic.created_at desc")
}
}
func GetOrder(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if ok {
if _, valid := scopedOrder(ctx, ctx.Param("identity"), point.ID); valid {
platformgasorder.GetGasorderBasic(ctx)
}
}
}
func CreateOrder(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
identity, _ := values["gasorder_contract_identity"].(string)
if _, valid := scopedContract(ctx, identity, point.ID); !valid {
return false
}
values["creator_type"] = "delivery"
values["creator_identity"] = point.Identity
return true
}) {
return
}
platformgasorder.CreateGasorderBasic(ctx)
}
func AssignOrder(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
if _, valid := scopedOrder(ctx, ctx.Param("identity"), point.ID); !valid {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
staffIdentity, _ := values["staff_account_identity"].(string)
staff, valid := scopedStaff(ctx, staffIdentity, point)
if !valid || staff.WorkStatus != "on_duty" || staff.Status != common.StatusEnable {
return false
}
var credentialCount int64
err := common.ActiveRecords(db().Model(&models.StaffCredential{})).
Where("staff_account_id = ? AND (expired_at IS NULL OR expired_at > ?)", staff.ID, time.Now()).
Count(&credentialCount).Error
if err != nil || credentialCount == 0 {
return false
}
values["delivery_basic_identity"] = point.Identity
return true
}) {
return
}
platformgasorder.AssignGasorderBasic(ctx)
}
func withOrder(ctx *gin.Context, handler gin.HandlerFunc) {
point, _, ok := currentScope(ctx)
if ok {
if _, valid := scopedOrder(ctx, ctx.Param("identity"), point.ID); valid {
handler(ctx)
}
}
}
func OrderException(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderException) }
func OrderRecover(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderRecover) }
func OrderCancel(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderCancel) }
func ReclaimOrder(ctx *gin.Context) {
account, point, _, ok := CurrentDeliveryAccount(ctx)
if !ok {
return
}
var request struct {
Reason string `json:"reason" binding:"required,max=1000"`
}
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
err := db().Transaction(func(tx *gorm.DB) error {
var order models.GasorderBasic
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("identity = ? AND delivery_basic_id = ?", ctx.Param("identity"), point.ID).First(&order).Error; err != nil {
return err
}
if order.OrderStatus != common.StatusAssigned {
return errors.New("only assigned order can be reclaimed")
}
if err := tx.Model(&order).Updates(map[string]any{
"staff_account_id": 0, "order_status": common.StatusCreated,
}).Error; err != nil {
return err
}
return tx.Create(&models.GasorderStatus{
Entity: common.NewEntity(common.StatusEnable), GasorderBasicID: order.ID,
FromStatus: common.StatusAssigned, ToStatus: common.StatusCreated,
OperatorIdentity: account.Identity, OperatorName: account.DisplayName,
OccurredAt: time.Now(), Reason: request.Reason,
}).Error
})
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
infra.Response.Success(ctx, gin.H{"updated": true, "order_status": common.StatusCreated})
}
func AdjustOrderAmount(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
var request struct {
DeliveryFee int64 `json:"delivery_fee"`
DiscountAmount int64 `json:"discount_amount"`
Reason string `json:"reason" binding:"required,max=1000"`
}
if err := ctx.ShouldBindJSON(&request); err != nil || request.DeliveryFee < 0 || request.DiscountAmount < 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
account, _, _, _ := CurrentDeliveryAccount(ctx)
err := db().Transaction(func(tx *gorm.DB) error {
var order models.GasorderBasic
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("identity = ? AND delivery_basic_id = ?", ctx.Param("identity"), point.ID).First(&order).Error; err != nil {
return err
}
if order.OrderStatus != common.StatusCreated && order.OrderStatus != common.StatusAssigned {
return errors.New("order amount cannot be adjusted")
}
var paymentCount int64
if err := tx.Model(&models.GasorderPayment{}).
Joins("JOIN wallet_payment ON wallet_payment.id = gasorder_payment.wallet_payment_id").
Where("gasorder_payment.gasorder_basic_id = ? AND wallet_payment.payment_status = ?", order.ID, common.StatusPaid).
Count(&paymentCount).Error; err != nil || paymentCount > 0 {
return errors.New("paid order cannot be adjusted")
}
if order.ProductAmount > math.MaxInt64-request.DeliveryFee {
return errors.New("payable amount overflow")
}
payable := order.ProductAmount + request.DeliveryFee - request.DiscountAmount
if payable <= 0 {
return errors.New("payable amount must be positive")
}
if err := tx.Model(&order).Updates(map[string]any{
"delivery_fee": request.DeliveryFee, "discount_amount": request.DiscountAmount, "payable_amount": payable,
}).Error; err != nil {
return err
}
record := models.GasorderStatus{
Entity: common.NewEntity(common.StatusEnable), GasorderBasicID: order.ID,
FromStatus: order.OrderStatus, ToStatus: order.OrderStatus,
OperatorIdentity: account.Identity, OperatorName: account.DisplayName, OccurredAt: time.Now(),
Reason: fmt.Sprintf("%s配送费 %d→%d优惠金额 %d→%d应付金额 %d→%d",
request.Reason, order.DeliveryFee, request.DeliveryFee, order.DiscountAmount, request.DiscountAmount,
order.PayableAmount, payable),
}
return tx.Create(&record).Error
})
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}