186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
package adminfinance
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/url"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"hfb_sys/backend/internal/auditlog"
|
|
)
|
|
|
|
var (
|
|
ErrDependencyUnavailable = errors.New("dependency unavailable")
|
|
ErrInvalidManualDisbursement = errors.New("invalid manual disbursement")
|
|
ErrManualDisbursementNotFound = errors.New("manual disbursement not found")
|
|
ErrManualDisbursementNotPaid = errors.New("manual disbursement is not paid")
|
|
ErrInvalidOperatingExpense = errors.New("invalid operating expense")
|
|
ErrOperatingExpenseNotFound = errors.New("operating expense not found")
|
|
ErrOperatingExpenseNotPaid = errors.New("operating expense is not paid")
|
|
)
|
|
|
|
type Service struct {
|
|
repo *Repository
|
|
}
|
|
|
|
func NewService(repo *Repository) *Service {
|
|
return &Service{repo: repo}
|
|
}
|
|
|
|
func (s *Service) Dashboard(ctx context.Context, query DashboardQuery) (*DashboardDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.Dashboard(ctx, query)
|
|
}
|
|
|
|
func (s *Service) Details(ctx context.Context, query DetailQuery) (*PaginatedResult, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if query.Page < 1 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize < 1 {
|
|
query.PageSize = 20
|
|
}
|
|
if query.PageSize > 100 {
|
|
query.PageSize = 100
|
|
}
|
|
return s.repo.Details(ctx, query)
|
|
}
|
|
|
|
func (s *Service) Disbursements(ctx context.Context, query DisbursementQuery) (*DisbursementListDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if query.Page < 1 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize < 1 {
|
|
query.PageSize = 20
|
|
}
|
|
if query.PageSize > 100 {
|
|
query.PageSize = 100
|
|
}
|
|
return s.repo.Disbursements(ctx, query)
|
|
}
|
|
|
|
func (s *Service) OperatingExpenses(ctx context.Context, query OperatingExpenseQuery) (*OperatingExpenseListDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if query.Page < 1 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize < 1 {
|
|
query.PageSize = 20
|
|
}
|
|
if query.PageSize > 100 {
|
|
query.PageSize = 100
|
|
}
|
|
return s.repo.OperatingExpenses(ctx, query)
|
|
}
|
|
|
|
func (s *Service) CreateManualDisbursement(
|
|
ctx context.Context,
|
|
req CreateManualDisbursementRequest,
|
|
adminID uint64,
|
|
meta auditlog.Meta,
|
|
) (*ManualDisbursementDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
req.Category = strings.TrimSpace(req.Category)
|
|
req.CustomCategoryName = strings.TrimSpace(req.CustomCategoryName)
|
|
req.PayeeName = strings.TrimSpace(req.PayeeName)
|
|
req.Remark = strings.TrimSpace(req.Remark)
|
|
req.VoucherURL = strings.TrimSpace(req.VoucherURL)
|
|
if !validManualDisbursementCategory(req.Category) ||
|
|
!validManualDisbursementCustomCategoryName(req.Category, req.CustomCategoryName) ||
|
|
utf8.RuneCountInString(req.PayeeName) < 1 || utf8.RuneCountInString(req.PayeeName) > 100 ||
|
|
req.AmountCent <= 0 || req.PaidAt.IsZero() ||
|
|
utf8.RuneCountInString(req.Remark) < 1 || utf8.RuneCountInString(req.Remark) > 500 ||
|
|
!validManualVoucherURL(req.VoucherURL) {
|
|
return nil, ErrInvalidManualDisbursement
|
|
}
|
|
return s.repo.CreateManualDisbursement(ctx, req, adminID, meta)
|
|
}
|
|
|
|
func (s *Service) VoidManualDisbursement(
|
|
ctx context.Context,
|
|
id uint64,
|
|
reason string,
|
|
adminID uint64,
|
|
meta auditlog.Meta,
|
|
) (*ManualDisbursementDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
reason = strings.TrimSpace(reason)
|
|
if id == 0 || utf8.RuneCountInString(reason) < 1 || utf8.RuneCountInString(reason) > 255 {
|
|
return nil, ErrInvalidManualDisbursement
|
|
}
|
|
return s.repo.VoidManualDisbursement(ctx, id, reason, adminID, meta)
|
|
}
|
|
|
|
func (s *Service) CreateOperatingExpense(ctx context.Context, req CreateOperatingExpenseRequest, adminID uint64, meta auditlog.Meta) (*OperatingExpenseDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
req.Category = strings.TrimSpace(req.Category)
|
|
req.PayeeName = strings.TrimSpace(req.PayeeName)
|
|
req.Remark = strings.TrimSpace(req.Remark)
|
|
req.VoucherURL = strings.TrimSpace(req.VoucherURL)
|
|
if utf8.RuneCountInString(req.Category) < 1 || utf8.RuneCountInString(req.Category) > 50 ||
|
|
utf8.RuneCountInString(req.PayeeName) < 1 || utf8.RuneCountInString(req.PayeeName) > 100 ||
|
|
req.AmountCent <= 0 || req.OccurredAt.IsZero() ||
|
|
utf8.RuneCountInString(req.Remark) < 1 || utf8.RuneCountInString(req.Remark) > 500 ||
|
|
!validManualVoucherURL(req.VoucherURL) {
|
|
return nil, ErrInvalidOperatingExpense
|
|
}
|
|
return s.repo.CreateOperatingExpense(ctx, req, adminID, meta)
|
|
}
|
|
|
|
func (s *Service) VoidOperatingExpense(ctx context.Context, id uint64, reason string, adminID uint64, meta auditlog.Meta) (*OperatingExpenseDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
reason = strings.TrimSpace(reason)
|
|
if id == 0 || utf8.RuneCountInString(reason) < 1 || utf8.RuneCountInString(reason) > 255 {
|
|
return nil, ErrInvalidOperatingExpense
|
|
}
|
|
return s.repo.VoidOperatingExpense(ctx, id, reason, adminID, meta)
|
|
}
|
|
|
|
func validManualDisbursementCategory(category string) bool {
|
|
switch category {
|
|
case "user_compensation", "seller_supplement", "operating_expense", "channel_fee", "duoduo_deposit_refund", "other", "custom":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validManualDisbursementCustomCategoryName(category, name string) bool {
|
|
if category != "custom" {
|
|
return name == ""
|
|
}
|
|
return utf8.RuneCountInString(name) >= 1 && utf8.RuneCountInString(name) <= 50
|
|
}
|
|
|
|
func validManualVoucherURL(raw string) bool {
|
|
if raw == "" {
|
|
return true
|
|
}
|
|
if len(raw) > 500 {
|
|
return false
|
|
}
|
|
parsed, err := url.Parse(raw)
|
|
if err != nil || parsed.IsAbs() || parsed.Path != "/api/files/object" {
|
|
return false
|
|
}
|
|
return strings.HasPrefix(parsed.Query().Get("key"), "manual-disbursement/")
|
|
}
|