feat: 迭代
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# 周结算接口修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
调用 `/api/admin/finance/settlements/execute-weekly` 接口时,即使没有实际执行结算,也会返回成功消息 `"周结算任务已执行完成"`,导致用户误以为结算成功。
|
||||
|
||||
## 问题原因
|
||||
|
||||
1. **Controller 层**:没有捕获 Service 层抛出的异常,也没有返回详细的执行结果
|
||||
2. **Service 层**:在以下情况会抛出 `BadRequestException`:
|
||||
- 该周期已经结算过
|
||||
- 没有需要结算的订单
|
||||
- 所有订单都已结算
|
||||
|
||||
这些异常导致接口返回 400 错误,但前端可能没有正确处理,或者异常被中间件捕获后返回了成功状态。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. Controller 层改进
|
||||
|
||||
**修改前:**
|
||||
```typescript
|
||||
@Post('execute-weekly')
|
||||
async executeWeeklySettlement() {
|
||||
await this.settlementService.handleWeeklySettlement();
|
||||
return { message: '周结算任务已执行完成' };
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```typescript
|
||||
@Post('execute-weekly')
|
||||
async executeWeeklySettlement() {
|
||||
const result = await this.settlementService.handleWeeklySettlement();
|
||||
return {
|
||||
message: '周结算任务已执行完成',
|
||||
...result
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service 层改进
|
||||
|
||||
将抛出异常改为返回明确的结果对象:
|
||||
|
||||
**修改前:**
|
||||
```typescript
|
||||
if (existingSettlements > 0) {
|
||||
throw new BadRequestException(`该周期已经结算过,无法重复结算`);
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```typescript
|
||||
if (existingSettlements > 0) {
|
||||
this.logger.warn(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,跳过`);
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: `该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 返回值结构
|
||||
|
||||
### 成功执行结算
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 5,
|
||||
"failCount": 0,
|
||||
"totalOrders": 120,
|
||||
"skipped": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 跳过结算(已结算过)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 0,
|
||||
"failCount": 0,
|
||||
"totalOrders": 0,
|
||||
"skipped": true,
|
||||
"reason": "该周期 2026-05-19 ~ 2026-05-25 已经结算过"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 跳过结算(无订单)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 0,
|
||||
"failCount": 0,
|
||||
"totalOrders": 0,
|
||||
"skipped": true,
|
||||
"reason": "该周期内没有需要结算的订单"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 前端处理建议
|
||||
|
||||
前端应该根据返回的 `skipped` 字段判断是否真正执行了结算:
|
||||
|
||||
```typescript
|
||||
const response = await executeWeeklySettlement();
|
||||
|
||||
if (response.skipped) {
|
||||
// 显示警告信息
|
||||
message.warning(response.reason);
|
||||
} else if (response.successCount > 0) {
|
||||
// 显示成功信息
|
||||
message.success(`结算成功:${response.successCount} 个商家,共 ${response.totalOrders} 个订单`);
|
||||
|
||||
if (response.failCount > 0) {
|
||||
message.warning(`${response.failCount} 个商家结算失败,请查看日志`);
|
||||
}
|
||||
} else {
|
||||
message.info('没有需要结算的数据');
|
||||
}
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. **首次执行**:调用接口,应该返回 `skipped: false` 和实际的结算数据
|
||||
2. **重复执行**:再次调用接口,应该返回 `skipped: true` 和原因说明
|
||||
3. **无订单场景**:在没有已完成订单的情况下调用,应该返回相应的提示
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [settlement-admin.controller.ts](../src/modules/admin/finance/settlement-admin.controller.ts)
|
||||
- [settlement.service.ts](../src/modules/shared/finance/settlement.service.ts)
|
||||
@@ -9,8 +9,14 @@
|
||||
```
|
||||
finance/
|
||||
├── entities/
|
||||
│ ├── account.entity.ts # 账户实体
|
||||
│ ├── transaction.entity.ts # 交易流水实体
|
||||
│ ├── user-account.entity.ts # 用户账户实体
|
||||
│ ├── merchant-account.entity.ts # 商家账户实体
|
||||
│ ├── platform-account.entity.ts # 平台账户实体
|
||||
│ ├── system-account.entity.ts # 系统总账户实体
|
||||
│ ├── user-transaction.entity.ts # 用户交易流水实体
|
||||
│ ├── merchant-transaction.entity.ts # 商家交易流水实体
|
||||
│ ├── platform-transaction.entity.ts # 平台交易流水实体
|
||||
│ ├── system-transaction.entity.ts # 系统总账户交易流水实体
|
||||
│ ├── settlement.entity.ts # 结算单实体
|
||||
│ ├── settlement-item.entity.ts # 结算明细实体
|
||||
│ ├── user-withdrawal.entity.ts # 用户提现实体
|
||||
@@ -203,8 +209,14 @@ finance/
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| `accounts` | 账户表(用户/商家/平台) |
|
||||
| `transactions` | 交易流水表(复式记账) |
|
||||
| `system_accounts` | 系统总账户表 |
|
||||
| `system_transactions` | 系统总账户交易流水表 |
|
||||
| `platform_accounts` | 平台账户表 |
|
||||
| `platform_transactions` | 平台交易流水表 |
|
||||
| `merchant_accounts` | 商家账户表 |
|
||||
| `merchant_transactions` | 商家交易流水表 |
|
||||
| `user_accounts` | 用户账户表 |
|
||||
| `user_transactions` | 用户交易流水表 |
|
||||
| `settlements` | 结算单表 |
|
||||
| `settlement_items` | 结算明细表 |
|
||||
| `user_withdrawals` | 用户提现表 |
|
||||
@@ -216,12 +228,13 @@ finance/
|
||||
|
||||
## 技术特性
|
||||
|
||||
1. **复式记账**: 每笔转账生成两条交易流水(支出+收入)
|
||||
1. **分表设计**: 账户和交易流水按角色分表(用户/商家/平台/系统)
|
||||
2. **乐观锁**: 账户余额更新使用版本号防止并发问题
|
||||
3. **事务保证**: 所有涉及金额变动的操作都在事务中执行
|
||||
4. **冻结机制**: 提现时先冻结余额,审核通过后扣减
|
||||
5. **自动对账**: 每日自动检查账户余额和交易流水一致性
|
||||
6. **定时结算**: 每周自动生成商家结算单
|
||||
7. **资金守恒**: 系统总账户 = 商家账户 + 用户账户 + 平台账户
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index, VersionColumn } from 'typeorm';
|
||||
|
||||
@Entity('accounts')
|
||||
@Index(['account_type', 'owner_id'], { unique: true })
|
||||
export class Account {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['user', 'merchant', 'platform'],
|
||||
comment: '账户类型'
|
||||
})
|
||||
@Index()
|
||||
account_type: 'user' | 'merchant' | 'platform';
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '所有者ID(user_id/merchant_id/platform固定为0)' })
|
||||
owner_id: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '可用余额' })
|
||||
balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '冻结余额(提现中、退款中)' })
|
||||
frozen_balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入' })
|
||||
total_income: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出' })
|
||||
total_expense: number;
|
||||
|
||||
@VersionColumn({ comment: '乐观锁版本号' })
|
||||
version: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['active', 'frozen', 'closed'],
|
||||
default: 'active',
|
||||
comment: '状态'
|
||||
})
|
||||
@Index()
|
||||
status: 'active' | 'frozen' | 'closed';
|
||||
|
||||
@CreateDateColumn({ comment: '创建时间' })
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn({ comment: '更新时间' })
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-activity.entity.ts
|
||||
export { MktActivity as InviteActivity } from './mkt-activity.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-cashback.entity.ts
|
||||
export { MktCashback as InviteCashback } from './mkt-cashback.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-invitation.entity.ts
|
||||
export { MktInvitation as InviteRecord } from './mkt-invitation.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 platform-config.entity.ts
|
||||
export { PlatformConfig as SystemConfig } from './platform-config.entity';
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm';
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, unique: true, comment: '交易流水号(全局唯一)' })
|
||||
transaction_no: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '账户ID' })
|
||||
@Index()
|
||||
account_id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['user', 'merchant', 'platform'],
|
||||
comment: '账户类型'
|
||||
})
|
||||
account_type: 'user' | 'merchant' | 'platform';
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '账户所有者ID' })
|
||||
owner_id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['income', 'expense'],
|
||||
comment: '方向:income-收入/expense-支出'
|
||||
})
|
||||
direction: 'income' | 'expense';
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '金额(正数)' })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '交易前余额' })
|
||||
balance_before: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '交易后余额' })
|
||||
balance_after: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: '交易类型' })
|
||||
@Index()
|
||||
transaction_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: '业务类型:order/refund/settlement/cashback/withdraw' })
|
||||
business_type: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, nullable: true, comment: '业务ID(订单ID/提现ID等)' })
|
||||
business_id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, nullable: true, comment: '业务单号(订单号/提现单号等)' })
|
||||
business_no: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, nullable: true, comment: '对方账户ID(复式记账关联)' })
|
||||
related_account_id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 500, nullable: true, comment: '备注' })
|
||||
remark: string;
|
||||
|
||||
@CreateDateColumn({ comment: '创建时间' })
|
||||
@Index()
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
@Index(['business_type', 'business_id'])
|
||||
export class TransactionIndex {}
|
||||
@@ -1,57 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { CouponService } from '@/modules/shared/coupon/coupon.service';
|
||||
import { JwtAuthGuard, RolesGuard } from '@/common';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { CreateCouponDto, UpdateCouponDto, QueryCouponDto } from './dto/coupon.dto';
|
||||
|
||||
@ApiTags('优惠券管理(管理员)')
|
||||
@Controller('admin/coupons')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ApiBearerAuth()
|
||||
export class CouponController {
|
||||
constructor(private readonly couponService: CouponService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建优惠券' })
|
||||
async create(@Body() dto: CreateCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.create(dto, user.id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '更新优惠券' })
|
||||
async update(@Param('id') id: number, @Body() dto: UpdateCouponDto) {
|
||||
return this.couponService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除优惠券' })
|
||||
async delete(@Param('id') id: number) {
|
||||
await this.couponService.delete(id);
|
||||
return { message: '删除成功' };
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '查询优惠券列表' })
|
||||
async findAll(@Query() dto: QueryCouponDto) {
|
||||
return this.couponService.findAll(dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取优惠券详情' })
|
||||
async findOne(@Param('id') id: number) {
|
||||
return this.couponService.findOne(id);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CouponModule } from '@/modules/shared/coupon/coupon.module';
|
||||
import { CouponController } from './coupon.controller';
|
||||
import { CouponAdminController } from './coupon-admin.controller';
|
||||
|
||||
@Module({
|
||||
imports: [CouponModule],
|
||||
controllers: [CouponController],
|
||||
controllers: [CouponAdminController],
|
||||
})
|
||||
export class AdminCouponModule {}
|
||||
|
||||
@@ -33,12 +33,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getPlatformAccountDetail(id);
|
||||
}
|
||||
|
||||
@Get('platform/:id/balance')
|
||||
@ApiOperation({ summary: '查询平台账户余额' })
|
||||
async getPlatformAccountBalance(@Param('id') id: number) {
|
||||
return this.accountService.getPlatformAccountBalance(id);
|
||||
}
|
||||
|
||||
// ==================== 用户账户管理 ====================
|
||||
|
||||
@Get('users')
|
||||
@@ -47,12 +41,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getUserAccounts(dto);
|
||||
}
|
||||
|
||||
@Get('users/:userId')
|
||||
@ApiOperation({ summary: '查询用户账户详情' })
|
||||
async getUserAccountDetail(@Param('userId') userId: number) {
|
||||
return this.accountService.getUserAccountDetail(userId);
|
||||
}
|
||||
|
||||
@Get('users/summary')
|
||||
@ApiOperation({ summary: '用户账户汇总统计' })
|
||||
async getUserAccountsSummary() {
|
||||
@@ -67,12 +55,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getMerchantAccounts(dto);
|
||||
}
|
||||
|
||||
@Get('merchants/:merchantId')
|
||||
@ApiOperation({ summary: '查询商家账户详情' })
|
||||
async getMerchantAccountDetail(@Param('merchantId') merchantId: number) {
|
||||
return this.accountService.getMerchantAccountDetail(merchantId);
|
||||
}
|
||||
|
||||
@Get('merchants/summary')
|
||||
@ApiOperation({ summary: '商家账户汇总统计' })
|
||||
async getMerchantAccountsSummary() {
|
||||
|
||||
@@ -89,8 +89,11 @@ export class SettlementAdminController {
|
||||
@Post('execute-weekly')
|
||||
@ApiOperation({ summary: '手动执行周结算(所有商家)' })
|
||||
async executeWeeklySettlement() {
|
||||
await this.settlementService.handleWeeklySettlement();
|
||||
return { message: '周结算任务已执行完成' };
|
||||
const result = await this.settlementService.handleWeeklySettlement();
|
||||
return {
|
||||
message: '周结算任务已执行完成',
|
||||
...result
|
||||
};
|
||||
}
|
||||
|
||||
@Put(':id/approve')
|
||||
|
||||
@@ -40,13 +40,6 @@ export class TransactionAdminController {
|
||||
});
|
||||
}
|
||||
|
||||
@Get('platform/:id')
|
||||
@ApiOperation({ summary: '查询平台交易详情' })
|
||||
async getPlatformTransactionDetail(@Param('id') id: number) {
|
||||
// TODO: 实现根据ID查询交易详情
|
||||
return { message: '功能开发中' };
|
||||
}
|
||||
|
||||
@Get('platform/export')
|
||||
@ApiOperation({ summary: '导出平台交易流水' })
|
||||
async exportPlatformTransactions(@Query() dto: QueryPlatformTransactionDto) {
|
||||
@@ -71,24 +64,6 @@ export class TransactionAdminController {
|
||||
});
|
||||
}
|
||||
|
||||
@Get('users/:userId')
|
||||
@ApiOperation({ summary: '查询指定用户交易流水' })
|
||||
async getUserTransactionsByUserId(
|
||||
@Param('userId') userId: number,
|
||||
@Query() dto: QueryUserTransactionDto,
|
||||
) {
|
||||
return this.transactionService.getUserTransactions({
|
||||
userId,
|
||||
direction: dto.direction,
|
||||
transactionType: dto.transactionType,
|
||||
businessType: dto.businessType,
|
||||
startDate: dto.startDate,
|
||||
endDate: dto.endDate,
|
||||
page: dto.page,
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 商家交易流水 ====================
|
||||
|
||||
@Get('merchants')
|
||||
@@ -105,22 +80,4 @@ export class TransactionAdminController {
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('merchants/:merchantId')
|
||||
@ApiOperation({ summary: '查询指定商家交易流水' })
|
||||
async getMerchantTransactionsByMerchantId(
|
||||
@Param('merchantId') merchantId: number,
|
||||
@Query() dto: QueryMerchantTransactionDto,
|
||||
) {
|
||||
return this.transactionService.getMerchantTransactions({
|
||||
merchantId,
|
||||
direction: dto.direction,
|
||||
transactionType: dto.transactionType,
|
||||
businessType: dto.businessType,
|
||||
startDate: dto.startDate,
|
||||
endDate: dto.endDate,
|
||||
page: dto.page,
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Query,
|
||||
UseGuards,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { CouponService } from './coupon.service';
|
||||
import { JwtAuthGuard } from '@/common';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { ReceiveCouponDto, QueryUserCouponDto, QueryCouponDto } from './dto/coupon.dto';
|
||||
|
||||
@ApiTags('优惠券(用户)')
|
||||
@Controller('app/coupons')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
export class CouponController {
|
||||
constructor(private readonly couponService: CouponService) {}
|
||||
|
||||
@Get('available')
|
||||
@ApiOperation({ summary: '查询可领取的优惠券' })
|
||||
async findAvailable(@Query() dto: QueryCouponDto) {
|
||||
// 只返回active状态的优惠券
|
||||
return this.couponService.findAll({ ...dto, status: 'active' });
|
||||
}
|
||||
|
||||
@Post('receive')
|
||||
@ApiOperation({ summary: '领取优惠券' })
|
||||
async receive(@Body() dto: ReceiveCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.receive(user.sub, dto.couponId);
|
||||
}
|
||||
|
||||
@Get('my')
|
||||
@ApiOperation({ summary: '查询我的优惠券' })
|
||||
async findMyCoupons(@Query() dto: QueryUserCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.findUserCoupons(user.id, dto);
|
||||
}
|
||||
|
||||
@Get('usable/:orderId')
|
||||
@ApiOperation({ summary: '查询订单可用优惠券' })
|
||||
async findUsableCoupons(
|
||||
@Param('orderId') orderId: number,
|
||||
@Query('orderAmount') orderAmount: number,
|
||||
@Query('merchantId') merchantId: number,
|
||||
@Query('roomId') roomId: number,
|
||||
@CurrentUser() user: any,
|
||||
) {
|
||||
return this.couponService.findAvailableCoupons(
|
||||
user.id,
|
||||
orderAmount,
|
||||
merchantId,
|
||||
roomId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CouponController } from './coupon.controller';
|
||||
import { CouponUserController } from './coupon-user.controller';
|
||||
import { CouponService } from './coupon.service';
|
||||
import { Coupon } from '@/entities/coupon.entity';
|
||||
import { UserCoupon } from '@/entities/user-coupon.entity';
|
||||
@@ -9,7 +9,7 @@ import { UserCoupon } from '@/entities/user-coupon.entity';
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Coupon, UserCoupon]),
|
||||
],
|
||||
controllers: [CouponController],
|
||||
controllers: [CouponUserController],
|
||||
providers: [CouponService],
|
||||
exports: [CouponService],
|
||||
})
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Body,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { UserService } from './user.service';
|
||||
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import {
|
||||
UpdateProfileDto,
|
||||
ChangePasswordDto,
|
||||
VerifyIdentityDto,
|
||||
} from './dto/user.dto';
|
||||
|
||||
@ApiTags('用户')
|
||||
@Controller('app')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
export class UserUserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Get('profile')
|
||||
@ApiOperation({ summary: '获取个人信息' })
|
||||
async getProfile(@CurrentUser('sub') userId: number) {
|
||||
return this.userService.findById(userId);
|
||||
}
|
||||
|
||||
@Post('profile')
|
||||
@ApiOperation({ summary: '更新个人信息' })
|
||||
async updateProfile(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
) {
|
||||
return this.userService.updateProfile(userId, dto);
|
||||
}
|
||||
|
||||
@Post('avatar')
|
||||
@ApiOperation({ summary: '上传头像' })
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadAvatar(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
return this.userService.uploadAvatar(userId, file);
|
||||
}
|
||||
|
||||
@Put('profile')
|
||||
@ApiOperation({ summary: '更新个人信息(旧接口,保留兼容)' })
|
||||
async updateProfilePut(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
) {
|
||||
return this.userService.updateProfile(userId, dto);
|
||||
}
|
||||
|
||||
@Put('password')
|
||||
@ApiOperation({ summary: '修改密码' })
|
||||
async changePassword(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: ChangePasswordDto,
|
||||
) {
|
||||
return this.userService.changePassword(userId, dto);
|
||||
}
|
||||
|
||||
@Post('verify')
|
||||
@ApiOperation({ summary: '实名认证' })
|
||||
async verifyIdentity(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: VerifyIdentityDto,
|
||||
) {
|
||||
return this.userService.verifyIdentity(userId, dto);
|
||||
}
|
||||
|
||||
@Get('verify/status')
|
||||
@ApiOperation({ summary: '获取实名认证状态' })
|
||||
async getVerifyStatus(@CurrentUser('sub') userId: number) {
|
||||
return this.userService.getVerifyStatus(userId);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantFinanceService } from './finance.service';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { QueryTransactionDto } from './dto/finance.dto';
|
||||
|
||||
@ApiTags('商家财务管理')
|
||||
@@ -19,11 +19,11 @@ import { QueryTransactionDto } from './dto/finance.dto';
|
||||
export class MerchantFinanceController {
|
||||
constructor(
|
||||
private readonly financeService: MerchantFinanceService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
private async getMerchantId(sellerId: number): Promise<number> {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return merchant.id;
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import { WithdrawalMerchantController } from './withdrawal-merchant.controller';
|
||||
import { TransactionSellerController } from './transaction-seller.controller';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
import { FinanceModule } from '@/modules/shared/finance/finance.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([MerchantAccount, MerchantTransaction]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
forwardRef(() => FinanceModule),
|
||||
],
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Put,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantService } from './merchant.service';
|
||||
import { JwtAuthGuard, RolesGuard } from '@/common';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { QueryMerchantDto } from './dto/merchant.dto';
|
||||
|
||||
@ApiTags('商家管理(管理员)')
|
||||
@Controller('admin/merchants')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ApiBearerAuth()
|
||||
export class MerchantAdminController {
|
||||
constructor(private readonly merchantService: MerchantService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取商家列表' })
|
||||
async findAll(@Query() query: QueryMerchantDto) {
|
||||
return this.merchantService.findAll(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取商家详情' })
|
||||
async findById(@Param('id') id: number) {
|
||||
return this.merchantService.findById(id);
|
||||
}
|
||||
|
||||
@Put(':id/approve')
|
||||
@ApiOperation({ summary: '审核通过' })
|
||||
async approve(@Param('id') id: number) {
|
||||
return this.merchantService.approve(id);
|
||||
}
|
||||
|
||||
@Put(':id/reject')
|
||||
@ApiOperation({ summary: '审核拒绝' })
|
||||
async reject(@Param('id') id: number, @Body('reason') reason: string) {
|
||||
return this.merchantService.reject(id, reason);
|
||||
}
|
||||
|
||||
@Put(':id/freeze')
|
||||
@ApiOperation({ summary: '冻结店铺' })
|
||||
async freeze(@Param('id') id: number) {
|
||||
return this.merchantService.freeze(id);
|
||||
}
|
||||
|
||||
@Put(':id/unfreeze')
|
||||
@ApiOperation({ summary: '解冻店铺' })
|
||||
async unfreeze(@Param('id') id: number) {
|
||||
return this.merchantService.unfreeze(id);
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,14 @@ import { Review } from '@/entities/review.entity';
|
||||
import { MerchantService } from './merchant.service';
|
||||
import { StatisticsService } from './statistics.service';
|
||||
import { MerchantSellerController } from './merchant-seller.controller';
|
||||
import { MerchantAdminController } from './merchant-admin.controller';
|
||||
import { MerchantAuthModule } from './auth/auth.module';
|
||||
import { MerchantProfileModule } from './profile/profile.module';
|
||||
import { MerchantRoomModule } from './room/room.module';
|
||||
import { MerchantRoomCalendarModule } from './room-calendar/room-calendar.module';
|
||||
import { MerchantOrderModule } from './order/order.module';
|
||||
import { MerchantReviewModule } from './review/review.module';
|
||||
import { MerchantFinanceModule } from './finance/finance.module';
|
||||
import { MerchantStatisticsModule } from './statistics/statistics.module';
|
||||
import { MerchantProfileModule } from './profile/profile.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -33,7 +32,6 @@ import { MerchantStatisticsModule } from './statistics/statistics.module';
|
||||
],
|
||||
controllers: [
|
||||
MerchantSellerController,
|
||||
MerchantAdminController,
|
||||
],
|
||||
providers: [MerchantService, StatisticsService],
|
||||
exports: [MerchantService, StatisticsService],
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantOrderService } from './order.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { QueryOrderDto } from './dto/order.dto';
|
||||
|
||||
@ApiTags('订单管理(商家)')
|
||||
@@ -23,7 +23,7 @@ import { QueryOrderDto } from './dto/order.dto';
|
||||
export class MerchantOrderController {
|
||||
constructor(
|
||||
private readonly orderService: MerchantOrderService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -32,7 +32,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query() query: QueryOrderDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.findByMerchant(merchant.id, query);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
@@ -58,7 +58,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Body('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
@@ -73,7 +73,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.confirm(merchant.id, orderNo);
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export class MerchantOrderController {
|
||||
@Param('orderNo') orderNo: string,
|
||||
@Body('reason') reason: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.reject(merchant.id, orderNo, reason);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkin(merchant.id, orderNo);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkout(merchant.id, orderNo);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import { MerchantOrderService } from './order.service';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { Room } from '@/entities/room.entity';
|
||||
import { RoomCalendar } from '@/entities/room-calendar.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
import { UserActivityModule } from '@/modules/app/activity/activity.module';
|
||||
import { FinanceModule } from '@/modules/shared/finance/finance.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Order, Room, RoomCalendar]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
forwardRef(() => UserActivityModule),
|
||||
forwardRef(() => FinanceModule),
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantReviewService } from './review.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
|
||||
@ApiTags('评价管理(商家)')
|
||||
@Controller('merchant/reviews')
|
||||
@@ -18,7 +18,7 @@ import { MerchantProfileService } from '../profile/profile.service';
|
||||
export class MerchantReviewController {
|
||||
constructor(
|
||||
private readonly reviewService: MerchantReviewService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -29,7 +29,7 @@ export class MerchantReviewController {
|
||||
@Query('limit') limit: string = '10',
|
||||
@Query('roomId') roomId?: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
|
||||
return this.reviewService.getSellerReviews(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantReviewController } from './review.controller';
|
||||
import { MerchantReviewService } from './review.service';
|
||||
import { Review } from '@/entities/review.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Review]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantReviewController],
|
||||
providers: [MerchantReviewService],
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantRoomService } from './room.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { CreateRoomDto, UpdateRoomDto, QueryRoomDto } from './dto/room.dto';
|
||||
|
||||
@ApiTags('房源管理(商家)')
|
||||
@@ -24,7 +24,7 @@ import { CreateRoomDto, UpdateRoomDto, QueryRoomDto } from './dto/room.dto';
|
||||
export class MerchantRoomController {
|
||||
constructor(
|
||||
private readonly roomService: MerchantRoomService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -33,7 +33,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query() query: QueryRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.findByMerchant(Number(merchant.id), query);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.findByIdAndMerchant(
|
||||
Number(id),
|
||||
@@ -58,7 +58,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Body() dto: CreateRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.create(Number(merchant.id), dto);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export class MerchantRoomController {
|
||||
@Param('id') id: number,
|
||||
@Body() dto: UpdateRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.update(Number(id), Number(merchant.id), dto);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.remove(Number(id), Number(merchant.id));
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantRoomController } from './room.controller';
|
||||
import { MerchantRoomService } from './room.service';
|
||||
import { Room } from '@/entities/room.entity';
|
||||
import { RoomCalendar } from '@/entities/room-calendar.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Room, RoomCalendar]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantRoomController],
|
||||
providers: [MerchantRoomService],
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '@/common/guards/roles.guard';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { StatisticsService } from './statistics.service';
|
||||
|
||||
@ApiTags('商家统计')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('merchant')
|
||||
@Controller('merchant/statistics')
|
||||
export class StatisticsSellerController {
|
||||
constructor(private readonly statisticsService: StatisticsService) {}
|
||||
|
||||
@Get('overview')
|
||||
@ApiOperation({ summary: '获取数据概览' })
|
||||
async getOverview(@CurrentUser() user: any) {
|
||||
return this.statisticsService.getOverview(user.merchantId);
|
||||
}
|
||||
|
||||
@Get('income-trend')
|
||||
@ApiOperation({ summary: '获取收入趋势' })
|
||||
async getIncomeTrend(
|
||||
@CurrentUser() user: any,
|
||||
@Query('type') type: 'day' | 'week' | 'month' = 'day',
|
||||
) {
|
||||
return this.statisticsService.getIncomeTrend(user.merchantId, type);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantStatisticsService } from './statistics.service';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
|
||||
@ApiTags('商家统计')
|
||||
@ApiBearerAuth()
|
||||
@@ -12,13 +12,13 @@ import { MerchantProfileService } from '../profile/profile.service';
|
||||
export class MerchantStatisticsController {
|
||||
constructor(
|
||||
private readonly statisticsService: MerchantStatisticsService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get('overview')
|
||||
@ApiOperation({ summary: '获取数据概览' })
|
||||
async getOverview(@CurrentSeller('sub') sellerId: number) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.statisticsService.getOverview(merchant.id);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export class MerchantStatisticsController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query('type') type: 'day' | 'week' | 'month' = 'day',
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.statisticsService.getIncomeTrend(merchant.id, type);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantStatisticsController } from './statistics.controller';
|
||||
import { MerchantStatisticsService } from './statistics.service';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Order]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantStatisticsController],
|
||||
providers: [MerchantStatisticsService],
|
||||
|
||||
@@ -318,6 +318,9 @@ export class AccountService {
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
// 先确保商家账户存在(如果不存在会自动创建)
|
||||
await this.getMerchantAccount(merchantId);
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
@@ -48,7 +48,13 @@ export class SettlementService {
|
||||
|
||||
if (existingSettlements > 0) {
|
||||
this.logger.warn(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,跳过`);
|
||||
throw new BadRequestException(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,无法重复结算`);
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: `该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过`
|
||||
};
|
||||
}
|
||||
|
||||
// 查询所有已完成且截止到上周末的订单
|
||||
@@ -68,7 +74,13 @@ export class SettlementService {
|
||||
|
||||
if (allOrders.length === 0) {
|
||||
this.logger.log('没有需要结算的订单');
|
||||
throw new BadRequestException('该周期内没有需要结算的订单');
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: '该周期内没有需要结算的订单'
|
||||
};
|
||||
}
|
||||
|
||||
// 批量查询已结算的订单ID
|
||||
@@ -109,11 +121,18 @@ export class SettlementService {
|
||||
|
||||
if (Object.keys(ordersByMerchant).length === 0) {
|
||||
this.logger.log('没有需要结算的订单(所有订单都已结算)');
|
||||
throw new BadRequestException('没有需要结算的订单');
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: '没有需要结算的订单(所有订单都已结算)'
|
||||
};
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const errors: Array<{ merchantId: number; error: string }> = [];
|
||||
|
||||
for (const [merchantIdStr, merchantOrders] of Object.entries(ordersByMerchant)) {
|
||||
const merchantId = Number(merchantIdStr);
|
||||
@@ -123,13 +142,26 @@ export class SettlementService {
|
||||
this.logger.log(`商家 ${merchantId} 结算完成,订单数:${merchantOrders.length}`);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
this.logger.error(`商家 ${merchantId} 结算失败:${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorStack = error instanceof Error ? error.stack : '';
|
||||
this.logger.error(`商家 ${merchantId} 结算失败:${errorMessage}`);
|
||||
this.logger.error(`错误堆栈:${errorStack}`);
|
||||
failCount++;
|
||||
errors.push({
|
||||
merchantId,
|
||||
error: errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`周结算任务执行完成,成功:${successCount},失败:${failCount}`);
|
||||
return { successCount, failCount, totalOrders: allOrders.length - skippedCount };
|
||||
return {
|
||||
successCount,
|
||||
failCount,
|
||||
totalOrders: allOrders.length - skippedCount,
|
||||
skipped: false,
|
||||
errors: errors.length > 0 ? errors : undefined
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`周结算任务执行失败:${error.message}`);
|
||||
throw error;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MerchantWithdrawal } from '@/entities/merchant-withdrawal.entity';
|
||||
import { PlatformWithdrawal } from '@/entities/platform-withdrawal.entity';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { AccountService } from './account.service';
|
||||
import { TransactionService } from './transaction.service';
|
||||
|
||||
@@ -18,6 +19,8 @@ export class WithdrawalService {
|
||||
private merchantWithdrawalRepo: Repository<MerchantWithdrawal>,
|
||||
@InjectRepository(PlatformWithdrawal)
|
||||
private platformWithdrawalRepo: Repository<PlatformWithdrawal>,
|
||||
@InjectRepository(MerchantTransaction)
|
||||
private merchantTransactionRepo: Repository<MerchantTransaction>,
|
||||
private accountService: AccountService,
|
||||
private transactionService: TransactionService,
|
||||
private dataSource: DataSource,
|
||||
@@ -423,22 +426,51 @@ export class WithdrawalService {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
// 扣减商家账户冻结金额
|
||||
const account = await queryRunner.manager.findOne(MerchantAccount, {
|
||||
where: { merchant_id: withdrawal.merchantId },
|
||||
lock: { mode: 'pessimistic_write' }
|
||||
});
|
||||
|
||||
// 扣减商家账户余额
|
||||
await this.accountService.deductMerchantBalance(
|
||||
withdrawal.merchantId,
|
||||
Number(withdrawal.actualAmount),
|
||||
transactionNo,
|
||||
'withdraw',
|
||||
withdrawal.id,
|
||||
withdrawal.withdrawNo,
|
||||
`商家提现 - ${withdrawal.bankName}`
|
||||
);
|
||||
if (!account) {
|
||||
throw new NotFoundException('商家账户不存在');
|
||||
}
|
||||
|
||||
const frozenBefore = Number(account.frozen_balance);
|
||||
const amount = Number(withdrawal.actualAmount);
|
||||
|
||||
if (frozenBefore < amount) {
|
||||
throw new BadRequestException('冻结金额不足');
|
||||
}
|
||||
|
||||
account.frozen_balance = frozenBefore - amount;
|
||||
account.total_withdraw = Number(account.total_withdraw) + amount;
|
||||
account.total_expense = Number(account.total_expense) + amount;
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
// 记录交易流水
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
const transaction = this.merchantTransactionRepo.create({
|
||||
transaction_no: transactionNo,
|
||||
merchant_id: withdrawal.merchantId,
|
||||
account_id: account.id,
|
||||
direction: 'expense',
|
||||
amount,
|
||||
balance_before: Number(account.balance),
|
||||
balance_after: Number(account.balance),
|
||||
transaction_type: '提现',
|
||||
business_type: 'withdraw',
|
||||
business_id: withdrawal.id,
|
||||
business_no: withdrawal.withdrawNo,
|
||||
remark: `商家提现 - ${withdrawal.bankName}`
|
||||
});
|
||||
await queryRunner.manager.save(transaction);
|
||||
|
||||
// 记录系统总账户提现
|
||||
await this.accountService.addSystemWithdrawal(
|
||||
Number(withdrawal.actualAmount),
|
||||
amount,
|
||||
transactionNo,
|
||||
'merchant_withdraw',
|
||||
withdrawal.id,
|
||||
@@ -611,7 +643,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -648,7 +680,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -680,7 +712,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
|
||||
Reference in New Issue
Block a user