Files
junhong_cmp_fiber/internal/handler/admin/employee_collection.go
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

296 lines
11 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 admin
import (
"strings"
"time"
"github.com/gofiber/fiber/v2"
employeecollectionapp "github.com/break/junhong_cmp_fiber/internal/application/employeecollection"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
employeecollectionquery "github.com/break/junhong_cmp_fiber/internal/query/employeecollection"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// EmployeeCollectionHandler 处理员工代收款账单、核销申请与线下收款方式字典请求。
// 边界只做绑定、路径参数校验与统一响应,状态、金额与权限不变量由应用用例判断。
type EmployeeCollectionHandler struct {
paymentMethod *employeecollectionapp.PaymentMethodService
paymentMethodQuery *employeecollectionquery.PaymentMethodQuery
billClose *employeecollectionapp.BillCloseService
billQuery *employeecollectionquery.BillQuery
application *employeecollectionapp.ApplicationService
applicationQuery *employeecollectionquery.ApplicationQuery
}
// NewEmployeeCollectionHandler 创建员工代收款处理器。
func NewEmployeeCollectionHandler(
service *employeecollectionapp.PaymentMethodService,
billClose *employeecollectionapp.BillCloseService,
application *employeecollectionapp.ApplicationService,
) *EmployeeCollectionHandler {
return &EmployeeCollectionHandler{paymentMethod: service, billClose: billClose, application: application}
}
// SetApplicationQuery 注入核销申请只读投影。
func (h *EmployeeCollectionHandler) SetApplicationQuery(query *employeecollectionquery.ApplicationQuery) {
h.applicationQuery = query
}
// SetPaymentMethodQuery 注入线下收款方式字典只读投影。
func (h *EmployeeCollectionHandler) SetPaymentMethodQuery(query *employeecollectionquery.PaymentMethodQuery) {
h.paymentMethodQuery = query
}
// SetBillQuery 注入员工代收款账单只读投影。
func (h *EmployeeCollectionHandler) SetBillQuery(query *employeecollectionquery.BillQuery) {
h.billQuery = query
}
// CreatePaymentMethod 创建线下收款方式。
// POST /api/admin/employee-collection-payment-methods
func (h *EmployeeCollectionHandler) CreatePaymentMethod(c *fiber.Ctx) error {
var request dto.CreateEmployeeCollectionPaymentMethodRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.paymentMethod.Create(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// UpdatePaymentMethod 更新线下收款方式,支持修改名称、排序、启停、备注与未被引用时的稳定编码。
// PUT /api/admin/employee-collection-payment-methods/:id
func (h *EmployeeCollectionHandler) UpdatePaymentMethod(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
var request dto.UpdateEmployeeCollectionPaymentMethodRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.paymentMethod.Update(c.UserContext(), id, request)
if err != nil {
return err
}
return response.Success(c, result)
}
// DeletePaymentMethod 删除未被核销申请引用的线下收款方式。
// DELETE /api/admin/employee-collection-payment-methods/:id
func (h *EmployeeCollectionHandler) DeletePaymentMethod(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
if err := h.paymentMethod.Delete(c.UserContext(), id); err != nil {
return err
}
return response.Success(c, nil)
}
// ListPaymentMethods 分页查询线下收款方式。
// GET /api/admin/employee-collection-payment-methods
func (h *EmployeeCollectionHandler) ListPaymentMethods(c *fiber.Ctx) error {
var request dto.EmployeeCollectionPaymentMethodListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.paymentMethodQuery.List(c.UserContext(), request)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
// ListBills 分页查询员工代收款账单。
// GET /api/admin/employee-collection-bills
func (h *EmployeeCollectionHandler) ListBills(c *fiber.Ctx) error {
if err := rejectLegacyBillTimeParams(c); err != nil {
return err
}
var request dto.EmployeeCollectionBillListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.billQuery.List(c.UserContext(), request)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
// StatisticsBills 汇总员工代收款账单金额与待处理数量。
// GET /api/admin/employee-collection-bills/statistics
func (h *EmployeeCollectionHandler) StatisticsBills(c *fiber.Ctx) error {
if err := rejectLegacyBillTimeParams(c); err != nil {
return err
}
var request dto.EmployeeCollectionBillStatisticsRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.billQuery.Statistics(c.UserContext(), request)
if err != nil {
return err
}
return response.Success(c, result)
}
// billLegacyTimeParams 是本次被统一时间筛选替换的旧参数名。
var billLegacyTimeParams = []string{"created_from", "created_to"}
// rejectLegacyBillTimeParams 拒绝携带旧时间参数名的账单列表与统计请求。
//
// 时间筛选已统一为 start_time/end_time严格 RFC3339 带时区秒级闭区间),
// 旧参数名 MUST NOT 被静默忽略或保留别名:静默忽略会让调用方以为筛选生效而拿到全量数据。
func rejectLegacyBillTimeParams(c *fiber.Ctx) error {
for _, name := range billLegacyTimeParams {
if c.Context().QueryArgs().Has(name) {
return errors.New(errors.CodeInvalidParam,
"账单时间筛选参数已统一为 start_time 与 end_time不再接受 "+name)
}
}
return nil
}
// GetBill 查询员工代收款账单详情。
// GET /api/admin/employee-collection-bills/:id
func (h *EmployeeCollectionHandler) GetBill(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
result, err := h.billQuery.Detail(c.UserContext(), id)
if err != nil {
return err
}
return response.Success(c, result)
}
// CloseBill 关闭员工代收款账单,仅超级管理员可操作。
// POST /api/admin/employee-collection-bills/:id/close
func (h *EmployeeCollectionHandler) CloseBill(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
var request dto.CloseEmployeeCollectionBillRequest
if err := c.BodyParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
bill, err := h.billClose.Close(c.UserContext(), id, request.Reason)
if err != nil {
return err
}
result, err := employeecollectionquery.ProjectBill(bill)
if err != nil {
return err
}
return response.Success(c, result)
}
// CreateApplication 为本人可见账单创建核销申请。
// POST /api/admin/employee-collection-applications
func (h *EmployeeCollectionHandler) CreateApplication(c *fiber.Ctx) error {
request, err := bindApplicationRequest(c)
if err != nil {
return err
}
result, err := h.application.Create(c.UserContext(), request)
if err != nil {
return err
}
projected, err := employeecollectionquery.ProjectApplicationSubmit(employeecollectionquery.ApplicationSubmitProjection{
Application: result.Application, Attempt: result.Attempt, Allocations: result.Allocations,
Bills: result.Bills, InstanceID: result.InstanceID, InstanceStatus: result.InstanceStatus,
})
if err != nil {
return err
}
return response.Success(c, projected)
}
// ResubmitApplication 修改并重提已驳回的核销申请。
// PUT /api/admin/employee-collection-applications/:id
func (h *EmployeeCollectionHandler) ResubmitApplication(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
request, err := bindApplicationRequest(c)
if err != nil {
return err
}
result, err := h.application.Resubmit(c.UserContext(), id, request)
if err != nil {
return err
}
projected, err := employeecollectionquery.ProjectApplicationSubmit(employeecollectionquery.ApplicationSubmitProjection{
Application: result.Application, Attempt: result.Attempt, Allocations: result.Allocations,
Bills: result.Bills, InstanceID: result.InstanceID, InstanceStatus: result.InstanceStatus,
})
if err != nil {
return err
}
return response.Success(c, projected)
}
// ListApplications 分页查询核销申请。
// GET /api/admin/employee-collection-applications
func (h *EmployeeCollectionHandler) ListApplications(c *fiber.Ctx) error {
var request dto.EmployeeCollectionApplicationListRequest
if err := c.QueryParser(&request); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.applicationQuery.List(c.UserContext(), request)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize)
}
// GetApplication 查询核销申请详情,含分摊与全部审批尝试历史。
// GET /api/admin/employee-collection-applications/:id
func (h *EmployeeCollectionHandler) GetApplication(c *fiber.Ctx) error {
id, err := pathID(c)
if err != nil {
return err
}
result, err := h.applicationQuery.Detail(c.UserContext(), id)
if err != nil {
return err
}
return response.Success(c, result)
}
// bindApplicationRequest 绑定并转换创建或重提核销申请请求。
// 付款时间按带时区的 RFC3339 解析;其余边界与业务校验由应用用例统一判断。
func bindApplicationRequest(c *fiber.Ctx) (employeecollectionapp.SubmitApplicationCommand, error) {
var request dto.SubmitEmployeeCollectionApplicationRequest
if err := c.BodyParser(&request); err != nil {
return employeecollectionapp.SubmitApplicationCommand{}, errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
paidAt, err := time.Parse(time.RFC3339, strings.TrimSpace(request.PaidAt))
if err != nil {
return employeecollectionapp.SubmitApplicationCommand{}, errors.New(errors.CodeInvalidParam, "付款时间必须为带时区的 RFC3339 格式")
}
allocations := make([]employeecollectionapp.ApplicationAllocationCommand, 0, len(request.Allocations))
for _, item := range request.Allocations {
allocations = append(allocations, employeecollectionapp.ApplicationAllocationCommand{
BillID: item.BillID, Amount: item.Amount,
})
}
return employeecollectionapp.SubmitApplicationCommand{
PaymentMethodID: request.PaymentMethodID, PaidAmount: request.PaidAmount,
PayerName: request.PayerName, PaidAt: paidAt,
ExternalTransactionNo: request.ExternalTransactionNo,
PaymentVoucherKeys: request.PaymentVoucherKeys, Remark: request.Remark,
ActingReason: request.ActingReason, Allocations: allocations,
}, nil
}