feat(pickup): support platform managed offline settlement
This commit is contained in:
@@ -91,6 +91,7 @@ func (r *Repository) Create(ctx context.Context, req CreateRequest, adminID uint
|
||||
ShopName: strings.TrimSpace(req.ShopName),
|
||||
AccountSource: accountSource,
|
||||
SourceChannel: sourceChannel,
|
||||
SettlementMode: pickupSettlementMode(listing.SettlementMode, accountSource),
|
||||
ListingPriceCent: priceSnapshot.ListingPriceCent,
|
||||
OwnerPriceCent: priceSnapshot.OwnerPriceCent,
|
||||
WebsiteProfitCent: priceSnapshot.WebsiteProfitCent,
|
||||
@@ -168,7 +169,7 @@ func (r *Repository) Create(ctx context.Context, req CreateRequest, adminID uint
|
||||
return r.FindByID(ctx, createdID)
|
||||
}
|
||||
|
||||
// Complete 完成提号:给卖家加可用余额,listing 置 completed(售出终态,不可再上架)。
|
||||
// Complete 完成提号。站内上传结算至卖家钱包;平台代管提号转为待线下结算。
|
||||
func (r *Repository) Complete(ctx context.Context, pickupID uint64, req CompleteRequest, adminID uint64, meta auditlog.Meta) (*PickupDTO, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
@@ -233,28 +234,44 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wallet.AppendEntries(tx, wallet.Entry{
|
||||
UserID: pickup.OwnerID,
|
||||
Direction: "in",
|
||||
AmountCent: req.SettleAmountCent,
|
||||
BalanceType: "available",
|
||||
BizType: "admin_pickup_settle",
|
||||
BizNo: pickup.PickupNo,
|
||||
Remark: fmt.Sprintf("管理员提号结算:%s", pickup.PickupNo),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bid := pickup.ID
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
UserID: pickup.OwnerID,
|
||||
Type: "pickup",
|
||||
Title: "提号已完成,已结算到账",
|
||||
Content: fmt.Sprintf("账号「%s」提号完成,已结算 %s 到您的钱包。", account.Title, money.FormatWithSymbol(req.SettleAmountCent)),
|
||||
BizType: "pickup",
|
||||
BizID: &bid,
|
||||
}); err != nil {
|
||||
return err
|
||||
if pickupRequiresOfflineSettlement(pickup) {
|
||||
pickup.OfflineSettlementStatus = OfflineSettlementStatusPending
|
||||
if err := tx.Save(&pickup).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
UserID: pickup.OwnerID,
|
||||
Type: "pickup",
|
||||
Title: "提号已完成,待线下结算",
|
||||
Content: fmt.Sprintf("账号「%s」提号完成,结算金额 %s 将通过线下方式支付。", account.Title, money.FormatWithSymbol(req.SettleAmountCent)),
|
||||
BizType: "pickup",
|
||||
BizID: &bid,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := wallet.AppendEntries(tx, wallet.Entry{
|
||||
UserID: pickup.OwnerID,
|
||||
Direction: "in",
|
||||
AmountCent: req.SettleAmountCent,
|
||||
BalanceType: "available",
|
||||
BizType: "admin_pickup_settle",
|
||||
BizNo: pickup.PickupNo,
|
||||
Remark: fmt.Sprintf("管理员提号结算:%s", pickup.PickupNo),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
UserID: pickup.OwnerID,
|
||||
Type: "pickup",
|
||||
Title: "提号已完成,已结算到账",
|
||||
Content: fmt.Sprintf("账号「%s」提号完成,已结算 %s 到您的钱包。", account.Title, money.FormatWithSymbol(req.SettleAmountCent)),
|
||||
BizType: "pickup",
|
||||
BizID: &bid,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := auditlog.Append(tx, auditlog.Entry{
|
||||
@@ -265,9 +282,11 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete
|
||||
BizID: &bid,
|
||||
Meta: meta,
|
||||
Detail: map[string]any{
|
||||
"pickup_no": pickup.PickupNo,
|
||||
"settle_amount_cent": req.SettleAmountCent,
|
||||
"profit_amount_cent": pickup.ProfitAmountCent,
|
||||
"pickup_no": pickup.PickupNo,
|
||||
"settle_amount_cent": req.SettleAmountCent,
|
||||
"profit_amount_cent": pickup.ProfitAmountCent,
|
||||
"settlement_mode": pickup.SettlementMode,
|
||||
"offline_settlement_status": pickup.OfflineSettlementStatus,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -280,6 +299,56 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete
|
||||
return r.FindByID(ctx, pickupID)
|
||||
}
|
||||
|
||||
// MarkOfflineSettlement 确认平台代管提号已完成实际线下打款。
|
||||
func (r *Repository) MarkOfflineSettlement(ctx context.Context, pickupID uint64, req OfflineSettlementRequest, adminID uint64, meta auditlog.Meta) (*PickupDTO, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var pickup model.AdminPickup
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&pickup, pickupID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrPickupNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !pickupRequiresOfflineSettlement(pickup) || pickup.Status != StatusCompleted ||
|
||||
pickup.OfflineSettlementStatus != OfflineSettlementStatusPending || pickup.SettleAmountCent <= 0 {
|
||||
return ErrOfflineSettlementCannotMark
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
pickup.OfflineSettlementStatus = OfflineSettlementStatusSettled
|
||||
pickup.OfflineSettlementRemark = strings.TrimSpace(req.Remark)
|
||||
pickup.OfflineSettledBy = &adminID
|
||||
pickup.OfflineSettledAt = &now
|
||||
if err := tx.Save(&pickup).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bid := pickup.ID
|
||||
return auditlog.Append(tx, auditlog.Entry{
|
||||
ActorType: "admin",
|
||||
ActorID: adminID,
|
||||
Action: "pickup_offline_settlement",
|
||||
BizType: "admin_pickup",
|
||||
BizID: &bid,
|
||||
Meta: meta,
|
||||
Detail: map[string]any{
|
||||
"pickup_no": pickup.PickupNo,
|
||||
"settle_amount_cent": pickup.SettleAmountCent,
|
||||
"before_offline_settlement_status": OfflineSettlementStatusPending,
|
||||
"after_offline_settlement_status": pickup.OfflineSettlementStatus,
|
||||
"remark": pickup.OfflineSettlementRemark,
|
||||
},
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindByID(ctx, pickupID)
|
||||
}
|
||||
|
||||
// UpdateProfit 修改提号中的线下利润,已完成和已取消的提号不可修改。
|
||||
func (r *Repository) UpdateProfit(ctx context.Context, pickupID uint64, req UpdateProfitRequest, adminID uint64, meta auditlog.Meta) (*PickupDTO, error) {
|
||||
if r == nil || r.db == nil {
|
||||
@@ -566,38 +635,54 @@ type pickupRow struct {
|
||||
|
||||
func (row pickupRow) toDTO() PickupDTO {
|
||||
return PickupDTO{
|
||||
ID: row.ID,
|
||||
PickupNo: row.PickupNo,
|
||||
ListingID: row.ListingID,
|
||||
ListingNo: row.ListingNo,
|
||||
AccountID: row.AccountID,
|
||||
AccountTitle: row.AccountTitle,
|
||||
ServerRegion: row.ServerRegion,
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
OwnerID: row.OwnerID,
|
||||
OwnerPhone: row.OwnerPhone,
|
||||
AdminID: row.AdminID,
|
||||
Platform: row.Platform,
|
||||
ShopName: row.ShopName,
|
||||
AccountSource: row.AccountSource,
|
||||
SourceChannel: row.SourceChannel,
|
||||
ListingPriceCent: row.ListingPriceCent,
|
||||
OwnerPriceCent: row.OwnerPriceCent,
|
||||
WebsiteProfitCent: row.WebsiteProfitCent,
|
||||
ProfitAmountCent: row.ProfitAmountCent,
|
||||
SellerRatio: row.SellerRatio,
|
||||
BuyerRatio: row.BuyerRatio,
|
||||
AccountSnapshot: row.AccountSnapshot,
|
||||
SettleAmountCent: row.SettleAmountCent,
|
||||
Status: row.Status,
|
||||
Remark: row.Remark,
|
||||
CompleteRemark: row.CompleteRemark,
|
||||
CreatedAt: row.CreatedAt,
|
||||
CompletedAt: row.CompletedAt,
|
||||
CancelledAt: row.CancelledAt,
|
||||
ID: row.ID,
|
||||
PickupNo: row.PickupNo,
|
||||
ListingID: row.ListingID,
|
||||
ListingNo: row.ListingNo,
|
||||
AccountID: row.AccountID,
|
||||
AccountTitle: row.AccountTitle,
|
||||
ServerRegion: row.ServerRegion,
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
OwnerID: row.OwnerID,
|
||||
OwnerPhone: row.OwnerPhone,
|
||||
AdminID: row.AdminID,
|
||||
Platform: row.Platform,
|
||||
ShopName: row.ShopName,
|
||||
AccountSource: row.AccountSource,
|
||||
SourceChannel: row.SourceChannel,
|
||||
SettlementMode: row.SettlementMode,
|
||||
ListingPriceCent: row.ListingPriceCent,
|
||||
OwnerPriceCent: row.OwnerPriceCent,
|
||||
WebsiteProfitCent: row.WebsiteProfitCent,
|
||||
ProfitAmountCent: row.ProfitAmountCent,
|
||||
SellerRatio: row.SellerRatio,
|
||||
BuyerRatio: row.BuyerRatio,
|
||||
AccountSnapshot: row.AccountSnapshot,
|
||||
SettleAmountCent: row.SettleAmountCent,
|
||||
Status: row.Status,
|
||||
OfflineSettlementStatus: row.OfflineSettlementStatus,
|
||||
OfflineSettlementRemark: row.OfflineSettlementRemark,
|
||||
OfflineSettledBy: row.OfflineSettledBy,
|
||||
OfflineSettledAt: row.OfflineSettledAt,
|
||||
Remark: row.Remark,
|
||||
CompleteRemark: row.CompleteRemark,
|
||||
CreatedAt: row.CreatedAt,
|
||||
CompletedAt: row.CompletedAt,
|
||||
CancelledAt: row.CancelledAt,
|
||||
}
|
||||
}
|
||||
|
||||
func pickupSettlementMode(listingMode, accountSource string) string {
|
||||
if listingMode == SettlementModePlatformManaged || accountSource == AccountSourceExternalPlatformManaged {
|
||||
return SettlementModePlatformManaged
|
||||
}
|
||||
return SettlementModeOwnerWallet
|
||||
}
|
||||
|
||||
func pickupRequiresOfflineSettlement(pickup model.AdminPickup) bool {
|
||||
return pickup.SettlementMode == SettlementModePlatformManaged
|
||||
}
|
||||
|
||||
type availableListingRow struct {
|
||||
ID uint64
|
||||
ListingNo string
|
||||
|
||||
Reference in New Issue
Block a user