chore(init): 初始化解忧商行项目并完成 Phase 0 基座
This commit is contained in:
@@ -0,0 +1,469 @@
|
||||
package pickup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
"hfb_sys/backend/internal/middleware"
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
// Create 创建提号订单(管理员)
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
var req CreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数格式错误")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Create(c.Request.Context(), req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// Complete 完成提号(管理员)
|
||||
func (h *Handler) Complete(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req CompleteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "结算金额不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Complete(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// MarkOfflineSettlement 确认平台代管提号已完成实际线下打款(管理员)。
|
||||
func (h *Handler) MarkOfflineSettlement(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req OfflineSettlementRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "线下结算备注格式不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.MarkOfflineSettlement(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// UpdateProfit 修改提号中的线下利润(管理员)
|
||||
func (h *Handler) UpdateProfit(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpdateProfitRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "利润金额不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.UpdateProfit(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// UpdateThirdPartyOrderNo 补录或修改第三方单号(管理员)
|
||||
func (h *Handler) UpdateThirdPartyOrderNo(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpdateThirdPartyOrderNoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "第三方单号不能为空且长度不能超过128位")
|
||||
return
|
||||
}
|
||||
item, err := h.service.UpdateThirdPartyOrderNo(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// CreateFinancialAdjustment 为已完成提号创建利润或打款调整。
|
||||
func (h *Handler) CreateFinancialAdjustment(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req FinancialAdjustmentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "调整金额或原因不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.CreateFinancialAdjustment(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// ListFinancialAdjustments 返回提号完成后的财务调整历史。
|
||||
func (h *Handler) ListFinancialAdjustments(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := h.service.ListFinancialAdjustments(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, items)
|
||||
}
|
||||
|
||||
func (h *Handler) ProcessEvents(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := h.service.ProcessEvents(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// SettleFinancialAdjustment 确认代管提号的补款或追回调整已线下处理。
|
||||
func (h *Handler) SettleFinancialAdjustment(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req FinancialAdjustmentSettlementRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "确认备注格式不正确")
|
||||
return
|
||||
}
|
||||
if err := h.service.SettleFinancialAdjustment(c.Request.Context(), id, req, adminID, auditMeta(c)); err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"settled": true})
|
||||
}
|
||||
|
||||
// Cancel 取消提号(管理员)
|
||||
func (h *Handler) Cancel(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req CancelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "取消原因不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.Cancel(c.Request.Context(), id, req.Reason, adminID, auditMeta(c)); err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"cancelled": true})
|
||||
}
|
||||
|
||||
// List 提号订单列表(管理员)
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
query := parseAdminQuery(c)
|
||||
result, err := h.service.ListAdmin(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// ShopOptions 返回后台提号已使用的店铺名称。
|
||||
func (h *Handler) ShopOptions(c *gin.Context) {
|
||||
items, err := h.service.ListShopNames(c.Request.Context(), c.Query("platform"))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, items)
|
||||
}
|
||||
|
||||
// Detail 提号订单详情(管理员)
|
||||
func (h *Handler) Detail(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
item, err := h.service.FindByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// AvailableListings 可提号的 listing 搜索(管理员)
|
||||
func (h *Handler) AvailableListings(c *gin.Context) {
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListAvailableListings(c.Request.Context(), AvailableListingQuery{
|
||||
Keyword: strings.TrimSpace(c.Query("keyword")),
|
||||
SourceType: strings.TrimSpace(c.Query("source_type")),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// ListForSeller 卖家自己的提号记录
|
||||
func (h *Handler) ListForSeller(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
query := parseSellerQuery(c)
|
||||
result, err := h.service.ListForSeller(c.Request.Context(), userID, query)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func writePickupError(c *gin.Context, err error) {
|
||||
response.RecordError(c, err)
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrListingUnavailable):
|
||||
response.BadRequest(c, "账号不可用或已在交易中")
|
||||
case errors.Is(err, ErrDuplicatePickup):
|
||||
response.BadRequest(c, "该账号已有进行中的提号订单")
|
||||
case errors.Is(err, ErrPickupNotFound):
|
||||
response.Error(c, http.StatusNotFound, "pickup_not_found", "提号订单不存在")
|
||||
case errors.Is(err, ErrPickupNotPickingUp):
|
||||
response.BadRequest(c, "提号订单状态不允许该操作")
|
||||
case errors.Is(err, ErrInvalidAmount):
|
||||
response.BadRequest(c, "结算金额不正确")
|
||||
case errors.Is(err, ErrInvalidProfit):
|
||||
response.BadRequest(c, "利润金额不正确")
|
||||
case errors.Is(err, ErrOfflineSettlementCannotMark):
|
||||
response.BadRequest(c, "当前提号不可确认线下结算")
|
||||
case errors.Is(err, ErrFinancialAdjustmentInvalid):
|
||||
response.BadRequest(c, "调整金额、状态或原因不正确")
|
||||
case errors.Is(err, ErrFinancialAdjustmentNotFound):
|
||||
response.Error(c, http.StatusNotFound, "pickup_financial_adjustment_not_found", "财务调整记录不存在")
|
||||
case errors.Is(err, ErrFinancialAdjustmentCannotSettle):
|
||||
response.BadRequest(c, "当前调整不可确认处理")
|
||||
case errors.Is(err, ErrPickupShopNotFound):
|
||||
response.Error(c, http.StatusNotFound, "pickup_shop_not_found", "店铺不存在")
|
||||
case errors.Is(err, ErrPickupShopNameExists):
|
||||
response.Error(c, http.StatusConflict, "pickup_shop_name_exists", "店铺名称已存在")
|
||||
case errors.Is(err, ErrPickupCancelled):
|
||||
response.BadRequest(c, "提号订单已取消,不能补录单号")
|
||||
case errors.Is(err, ErrInvalidThirdPartyOrderNo):
|
||||
response.BadRequest(c, "第三方单号不能为空且长度不能超过128位")
|
||||
default:
|
||||
response.Error(c, http.StatusInternalServerError, "pickup_error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ListShops 获取店铺配置列表(管理员)
|
||||
func (h *Handler) ListShops(c *gin.Context) {
|
||||
query := parsePickupShopQuery(c)
|
||||
items, err := h.service.ListShops(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, items)
|
||||
}
|
||||
|
||||
// CreateShop 创建店铺配置(管理员)
|
||||
func (h *Handler) CreateShop(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
var req CreatePickupShopRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "店铺参数格式不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.CreateShop(c.Request.Context(), req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// UpdateShop 更新店铺配置(管理员)
|
||||
func (h *Handler) UpdateShop(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpdatePickupShopRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "店铺参数格式不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.UpdateShop(c.Request.Context(), id, req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
// DeleteShop 删除店铺配置(管理员)
|
||||
func (h *Handler) DeleteShop(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteShop(c.Request.Context(), id, adminID, auditMeta(c)); err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
// GetHistoricalShopStats 获取历史店铺聚合统计及映射情况(管理员)
|
||||
func (h *Handler) GetHistoricalShopStats(c *gin.Context) {
|
||||
stats, err := h.service.GetHistoricalShopStats(c.Request.Context())
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, stats)
|
||||
}
|
||||
|
||||
// MapHistoricalShops 批量映射历史店铺并清洗提号单(管理员)
|
||||
func (h *Handler) MapHistoricalShops(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
var req MapHistoricalShopsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "映射参数格式不正确")
|
||||
return
|
||||
}
|
||||
res, err := h.service.MapHistoricalShops(c.Request.Context(), req, adminID, auditMeta(c))
|
||||
if err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, res)
|
||||
}
|
||||
|
||||
// DeleteShopMapping 删除历史店铺映射规则并还原提号单(管理员)
|
||||
func (h *Handler) DeleteShopMapping(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
sourceName := c.Query("source_name")
|
||||
if sourceName == "" {
|
||||
response.BadRequest(c, "缺少 source_name 参数")
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteShopMapping(c.Request.Context(), sourceName, adminID, auditMeta(c)); err != nil {
|
||||
writePickupError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
func auditMeta(c *gin.Context) auditlog.Meta {
|
||||
return auditlog.Meta{
|
||||
IP: c.ClientIP(),
|
||||
UserAgent: c.GetHeader("User-Agent"),
|
||||
RequestID: middleware.GetRequestID(c),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user