feat: 迭代
This commit is contained in:
@@ -21,11 +21,20 @@ export class MerchantAccount {
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入(订单结算)' })
|
||||
total_income: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出(提现)' })
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出(提现+退款扣回)' })
|
||||
total_expense: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计服务费' })
|
||||
total_service_fee: number;
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计结算金额' })
|
||||
total_settlement: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计提现金额' })
|
||||
total_withdraw: number;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true, comment: '最后结算时间' })
|
||||
last_settlement_at: Date;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '待结算金额' })
|
||||
pending_settlement: number;
|
||||
|
||||
@VersionColumn({ comment: '乐观锁版本号' })
|
||||
version: number;
|
||||
|
||||
@@ -30,7 +30,7 @@ export class CouponController {
|
||||
@Post('receive')
|
||||
@ApiOperation({ summary: '领取优惠券' })
|
||||
async receive(@Body() dto: ReceiveCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.receive(user.id, dto.couponId);
|
||||
return this.couponService.receive(user.sub, dto.couponId);
|
||||
}
|
||||
|
||||
@Get('my')
|
||||
|
||||
@@ -78,9 +78,9 @@ export class QueryOrderDto {
|
||||
}
|
||||
|
||||
export class PayOrderDto {
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
orderId: number;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
orderNo: string;
|
||||
|
||||
@IsEnum(['wechat', 'alipay', 'balance'])
|
||||
paymentMethod: 'wechat' | 'alipay' | 'balance';
|
||||
|
||||
@@ -43,20 +43,20 @@ export class OrderController {
|
||||
return this.orderService.findByUser(userId, query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Get(':orderNo')
|
||||
@ApiOperation({ summary: '订单详情' })
|
||||
async findById(@Param('id') id: number) {
|
||||
return this.orderService.findById(id);
|
||||
async findByOrderNo(@Param('orderNo') orderNo: string) {
|
||||
return this.orderService.findByOrderNo(orderNo);
|
||||
}
|
||||
|
||||
@Put(':id/cancel')
|
||||
@Put(':orderNo/cancel')
|
||||
@ApiOperation({ summary: '取消订单' })
|
||||
async cancel(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Param('id') id: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
@Body('reason') reason: string,
|
||||
) {
|
||||
return this.orderService.cancel(userId, id, reason);
|
||||
return this.orderService.cancelByOrderNo(userId, orderNo, reason);
|
||||
}
|
||||
|
||||
@Post('pay')
|
||||
@@ -65,16 +65,16 @@ export class OrderController {
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: PayOrderDto,
|
||||
) {
|
||||
return this.orderService.pay(userId, dto.orderId, dto.paymentMethod);
|
||||
return this.orderService.payByOrderNo(userId, dto.orderNo, dto.paymentMethod);
|
||||
}
|
||||
|
||||
@Put(':id/refund')
|
||||
@Put(':orderNo/refund')
|
||||
@ApiOperation({ summary: '申请退款' })
|
||||
async refund(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Param('id') id: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
@Body('reason') reason: string,
|
||||
) {
|
||||
return this.orderService.refund(userId, id, reason);
|
||||
return this.orderService.refundByOrderNo(userId, orderNo, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +80,6 @@ export class OrderService {
|
||||
|
||||
const roomAmount = totalRoomPrice * roomCount;
|
||||
|
||||
// 计算平台服务费(基于房费)
|
||||
const serviceFeeRate = await this.configService.getServiceFeeRate();
|
||||
const serviceFee = Math.round(roomAmount * serviceFeeRate * 100) / 100;
|
||||
|
||||
// 订单总额 = 房费 + 服务费
|
||||
const totalAmount = roomAmount + serviceFee;
|
||||
|
||||
// 处理优惠券
|
||||
let couponDiscount = 0;
|
||||
let userCouponId: number | null = null;
|
||||
@@ -106,16 +99,22 @@ export class OrderService {
|
||||
throw new BadRequestException('优惠券不可用');
|
||||
}
|
||||
|
||||
// 计算优惠金额(优惠券只能抵扣房费部分,不能抵扣服务费)
|
||||
// 计算优惠金额(基于房费)
|
||||
couponDiscount = this.couponService.calculateDiscount(userCoupon.coupon, roomAmount);
|
||||
userCouponId = userCoupon.id;
|
||||
}
|
||||
|
||||
// 实付金额 = 订单总额 - 优惠券折扣
|
||||
// 订单总额 = 房费
|
||||
const totalAmount = roomAmount;
|
||||
// 用户实付 = 房费 - 优惠券
|
||||
const payAmount = totalAmount - couponDiscount;
|
||||
|
||||
// 商家收入 = 房费 - 优惠券折扣
|
||||
const merchantIncome = Math.round((roomAmount - couponDiscount) * 100) / 100;
|
||||
// 计算平台服务费(基于用户实付金额)
|
||||
const serviceFeeRate = await this.configService.getServiceFeeRate();
|
||||
const serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100;
|
||||
|
||||
// 商家收入 = 用户实付 - 服务费
|
||||
const merchantIncome = Math.round((payAmount - serviceFee) * 100) / 100;
|
||||
|
||||
const now = new Date();
|
||||
const orderNo = [
|
||||
@@ -212,7 +211,19 @@ export class OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户查询订单详情
|
||||
* 用户查询订单详情(通过订单号)
|
||||
*/
|
||||
async findByOrderNo(orderNo: string) {
|
||||
const order = await this.orderRepo.findOne({
|
||||
where: { orderNo },
|
||||
relations: ['room', 'merchant', 'user'],
|
||||
});
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户查询订单详情(通过ID,保留用于内部调用)
|
||||
*/
|
||||
async findById(id: number) {
|
||||
const order = await this.orderRepo.findOne({
|
||||
@@ -224,7 +235,44 @@ export class OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户取消订单
|
||||
* 用户取消订单(通过订单号)
|
||||
*/
|
||||
async cancelByOrderNo(userId: number, orderNo: string, reason: string) {
|
||||
const order = await this.findByOrderNo(orderNo);
|
||||
if (order.userId !== userId) throw new ForbiddenException('无权操作此订单');
|
||||
if (!['pending_pay', 'pending_confirm', 'pending_checkin'].includes(order.status)) {
|
||||
throw new BadRequestException('当前订单状态不可取消');
|
||||
}
|
||||
|
||||
// 恢复房态库存(所有可取消的订单都需要恢复库存)
|
||||
const checkIn = new Date(order.checkInDate);
|
||||
const checkOut = new Date(order.checkOutDate);
|
||||
for (let d = new Date(checkIn); d < checkOut; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
await this.calendarRepo.decrement(
|
||||
{ roomId: order.roomId, date: dateStr },
|
||||
'sold',
|
||||
order.roomCount,
|
||||
);
|
||||
}
|
||||
|
||||
// 已支付的订单(pending_confirm、pending_checkin)取消时需退款
|
||||
const needRefund = order.status !== 'pending_pay';
|
||||
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'cancelled',
|
||||
cancelReason: reason,
|
||||
cancelledAt: new Date(),
|
||||
...(needRefund ? {
|
||||
refundAmount: order.payAmount,
|
||||
refundAt: new Date(),
|
||||
} : {}),
|
||||
});
|
||||
return { message: needRefund ? '订单已取消,退款将原路返回' : '订单已取消' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户取消订单(通过ID,保留用于内部调用)
|
||||
*/
|
||||
async cancel(userId: number, id: number, reason: string) {
|
||||
const order = await this.findById(id);
|
||||
@@ -261,7 +309,44 @@ export class OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户支付订单
|
||||
* 用户支付订单(通过订单号)
|
||||
*/
|
||||
async payByOrderNo(userId: number, orderNo: string, paymentMethod: 'wechat' | 'alipay' | 'balance') {
|
||||
const order = await this.findByOrderNo(orderNo);
|
||||
if (order.userId !== userId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'pending_pay') {
|
||||
throw new BadRequestException('当前订单状态不可支付');
|
||||
}
|
||||
|
||||
// 模拟支付成功
|
||||
const paymentNo = `PAY${Date.now()}${Math.floor(Math.random() * 10000)}`;
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'pending_confirm',
|
||||
paymentMethod,
|
||||
paymentNo,
|
||||
paidAt: new Date(),
|
||||
});
|
||||
|
||||
// 扣减房态库存
|
||||
const checkIn = new Date(order.checkInDate);
|
||||
const checkOut = new Date(order.checkOutDate);
|
||||
for (let d = new Date(checkIn); d < checkOut; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const calendar = await this.calendarRepo.findOne({
|
||||
where: { roomId: order.roomId, date: dateStr },
|
||||
});
|
||||
if (calendar) {
|
||||
await this.calendarRepo.update(calendar.id, {
|
||||
sold: calendar.sold + order.roomCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { message: '支付成功', paymentNo };
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户支付订单(通过ID,保留用于内部调用)
|
||||
*/
|
||||
async pay(userId: number, id: number, paymentMethod: 'wechat' | 'alipay' | 'balance') {
|
||||
const order = await this.findById(id);
|
||||
@@ -298,7 +383,42 @@ export class OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户申请退款(待确认、待入住状态)- 直接退款,无需审核
|
||||
* 用户申请退款(通过订单号)
|
||||
*/
|
||||
async refundByOrderNo(userId: number, orderNo: string, reason: string) {
|
||||
const order = await this.findByOrderNo(orderNo);
|
||||
if (order.userId !== userId) throw new ForbiddenException('无权操作此订单');
|
||||
if (!['pending_confirm', 'pending_checkin'].includes(order.status)) {
|
||||
throw new BadRequestException('当前订单状态不可申请退款');
|
||||
}
|
||||
|
||||
// 恢复房态库存
|
||||
const checkIn = new Date(order.checkInDate);
|
||||
const checkOut = new Date(order.checkOutDate);
|
||||
for (let d = new Date(checkIn); d < checkOut; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const calendar = await this.calendarRepo.findOne({
|
||||
where: { roomId: order.roomId, date: dateStr },
|
||||
});
|
||||
if (calendar) {
|
||||
await this.calendarRepo.update(calendar.id, {
|
||||
sold: Math.max(0, calendar.sold - order.roomCount),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 RefundService 处理财务退款
|
||||
try {
|
||||
await this.refundService.processRefund(order, reason);
|
||||
return { message: '退款成功,款项将原路返回' };
|
||||
} catch (error) {
|
||||
// 退款失败,恢复订单状态(库存已恢复,需要手动回滚)
|
||||
throw new BadRequestException(`退款失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户申请退款(通过ID,保留用于内部调用)
|
||||
*/
|
||||
async refund(userId: number, id: number, reason: string) {
|
||||
const order = await this.findById(id);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IsString, IsNumber, IsOptional, IsBoolean, IsArray, Min, Max } from 'class-validator';
|
||||
import { IsString, IsNumber, IsOptional, IsBoolean, IsArray, Min, Max, IsNotEmpty } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateReviewDto {
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
orderId: number;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
orderNo: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ReviewController {
|
||||
@Request() req,
|
||||
@Body() dto: CreateReviewDto,
|
||||
) {
|
||||
return this.reviewService.create(req.user.sub, Number(dto.orderId), dto);
|
||||
return this.reviewService.createByOrderNo(req.user.sub, dto.orderNo, dto);
|
||||
}
|
||||
|
||||
// 查询评价列表(公开)
|
||||
@@ -35,9 +35,9 @@ export class ReviewController {
|
||||
}
|
||||
|
||||
// 检查订单是否已评价
|
||||
@Get('check/:orderId')
|
||||
async checkOrder(@Param('orderId') orderId: number) {
|
||||
const review = await this.reviewService.findByOrder(Number(orderId));
|
||||
@Get('check/:orderNo')
|
||||
async checkOrder(@Param('orderNo') orderNo: string) {
|
||||
const review = await this.reviewService.findByOrderNo(orderNo);
|
||||
return { reviewed: !!review, review };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,38 @@ export class ReviewService {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 用户提交评价
|
||||
* 用户提交评价(通过订单号)
|
||||
*/
|
||||
async createByOrderNo(userId: number, orderNo: string, dto: CreateReviewDto) {
|
||||
const order = await this.orderRepo.findOne({ where: { orderNo } });
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
if (order.userId !== userId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'completed') throw new BadRequestException('只有已完成的订单才能评价');
|
||||
|
||||
// 检查是否已评价
|
||||
const existing = await this.reviewRepo.findOne({ where: { orderId: order.id } });
|
||||
if (existing) throw new BadRequestException('该订单已评价');
|
||||
|
||||
if (dto.rating < 1 || dto.rating > 5) throw new BadRequestException('评分范围为1-5');
|
||||
|
||||
const review = this.reviewRepo.create({
|
||||
orderId: order.id,
|
||||
userId,
|
||||
merchantId: order.merchantId,
|
||||
roomId: order.roomId,
|
||||
rating: dto.rating,
|
||||
content: dto.content,
|
||||
images: dto.images,
|
||||
isAnonymous: dto.isAnonymous || false,
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
await this.reviewRepo.save(review);
|
||||
return { message: '评价已提交' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户提交评价(通过订单ID,保留用于内部调用)
|
||||
*/
|
||||
async create(userId: number, orderId: number, dto: CreateReviewDto) {
|
||||
const order = await this.orderRepo.findOne({ where: { id: orderId } });
|
||||
@@ -77,7 +108,16 @@ export class ReviewService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查订单是否已评价
|
||||
* 检查订单是否已评价(通过订单号)
|
||||
*/
|
||||
async findByOrderNo(orderNo: string) {
|
||||
const order = await this.orderRepo.findOne({ where: { orderNo } });
|
||||
if (!order) return null;
|
||||
return this.reviewRepo.findOne({ where: { orderId: order.id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查订单是否已评价(通过订单ID)
|
||||
*/
|
||||
async findByOrder(orderId: number) {
|
||||
return this.reviewRepo.findOne({ where: { orderId } });
|
||||
|
||||
@@ -180,4 +180,12 @@ export class QueryMerchantDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
city?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
checkInDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
checkOutDate?: string;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantFinanceController } from './finance.controller';
|
||||
import { MerchantFinanceService } from './finance.service';
|
||||
import { SettlementMerchantController } from './settlement-merchant.controller';
|
||||
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),
|
||||
],
|
||||
controllers: [
|
||||
MerchantFinanceController,
|
||||
SettlementMerchantController,
|
||||
WithdrawalMerchantController,
|
||||
TransactionSellerController,
|
||||
],
|
||||
controllers: [MerchantFinanceController],
|
||||
providers: [MerchantFinanceService],
|
||||
exports: [MerchantFinanceService],
|
||||
})
|
||||
|
||||
@@ -14,7 +14,26 @@ export class MerchantFinanceService {
|
||||
) {}
|
||||
|
||||
async getMerchantAccount(merchantId: number) {
|
||||
return this.merchantAccountRepo.findOne({ where: { merchant_id: merchantId } });
|
||||
let account = await this.merchantAccountRepo.findOne({ where: { merchant_id: merchantId } });
|
||||
|
||||
// 如果账户不存在,自动创建
|
||||
if (!account) {
|
||||
account = this.merchantAccountRepo.create({
|
||||
merchant_id: merchantId,
|
||||
balance: 0,
|
||||
frozen_balance: 0,
|
||||
debt_amount: 0,
|
||||
total_income: 0,
|
||||
total_expense: 0,
|
||||
total_settlement: 0,
|
||||
total_withdraw: 0,
|
||||
pending_settlement: 0,
|
||||
status: 'active',
|
||||
});
|
||||
await this.merchantAccountRepo.save(account);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
async getMerchantTransactions(params: {
|
||||
@@ -43,11 +62,11 @@ export class MerchantFinanceService {
|
||||
const queryBuilder = this.merchantTransactionRepo.createQueryBuilder('t');
|
||||
|
||||
if (merchantId !== undefined) {
|
||||
queryBuilder.andWhere('t.merchantId = :merchantId', { merchantId });
|
||||
queryBuilder.andWhere('t.merchant_id = :merchantId', { merchantId });
|
||||
}
|
||||
|
||||
if (accountId) {
|
||||
queryBuilder.andWhere('t.accountId = :accountId', { accountId });
|
||||
queryBuilder.andWhere('t.account_id = :accountId', { accountId });
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
@@ -55,21 +74,21 @@ export class MerchantFinanceService {
|
||||
}
|
||||
|
||||
if (transactionType) {
|
||||
queryBuilder.andWhere('t.transactionType = :transactionType', { transactionType });
|
||||
queryBuilder.andWhere('t.transaction_type = :transactionType', { transactionType });
|
||||
}
|
||||
|
||||
if (businessType) {
|
||||
queryBuilder.andWhere('t.businessType = :businessType', { businessType });
|
||||
queryBuilder.andWhere('t.business_type = :businessType', { businessType });
|
||||
}
|
||||
|
||||
if (startDate && endDate) {
|
||||
queryBuilder.andWhere('t.createdAt BETWEEN :startDate AND :endDate', {
|
||||
queryBuilder.andWhere('t.created_at BETWEEN :startDate AND :endDate', {
|
||||
startDate: `${startDate} 00:00:00`,
|
||||
endDate: `${endDate} 23:59:59`
|
||||
});
|
||||
}
|
||||
|
||||
queryBuilder.orderBy('t.createdAt', 'DESC');
|
||||
queryBuilder.orderBy('t.created_at', 'DESC');
|
||||
|
||||
const skip = (page - 1) * pageSize;
|
||||
queryBuilder.skip(skip).take(pageSize);
|
||||
@@ -111,11 +130,11 @@ export class MerchantFinanceService {
|
||||
queryBuilder.select('SUM(t.amount)', 'total');
|
||||
|
||||
if (merchantId !== undefined) {
|
||||
queryBuilder.andWhere('t.merchantId = :merchantId', { merchantId });
|
||||
queryBuilder.andWhere('t.merchant_id = :merchantId', { merchantId });
|
||||
}
|
||||
|
||||
if (accountId) {
|
||||
queryBuilder.andWhere('t.accountId = :accountId', { accountId });
|
||||
queryBuilder.andWhere('t.account_id = :accountId', { accountId });
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
@@ -123,11 +142,11 @@ export class MerchantFinanceService {
|
||||
}
|
||||
|
||||
if (transactionType) {
|
||||
queryBuilder.andWhere('t.transactionType = :transactionType', { transactionType });
|
||||
queryBuilder.andWhere('t.transaction_type = :transactionType', { transactionType });
|
||||
}
|
||||
|
||||
if (startDate && endDate) {
|
||||
queryBuilder.andWhere('t.createdAt BETWEEN :startDate AND :endDate', {
|
||||
queryBuilder.andWhere('t.created_at BETWEEN :startDate AND :endDate', {
|
||||
startDate: `${startDate} 00:00:00`,
|
||||
endDate: `${endDate} 23:59:59`
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import { MerchantService } from './merchant.service';
|
||||
import { StatisticsService } from './statistics.service';
|
||||
import { MerchantSellerController } from './merchant-seller.controller';
|
||||
import { MerchantAdminController } from './merchant-admin.controller';
|
||||
import { StatisticsSellerController } from './statistics-seller.controller';
|
||||
import { MerchantAuthModule } from './auth/auth.module';
|
||||
import { MerchantProfileModule } from './profile/profile.module';
|
||||
import { MerchantRoomModule } from './room/room.module';
|
||||
@@ -34,7 +33,6 @@ import { MerchantStatisticsModule } from './statistics/statistics.module';
|
||||
controllers: [
|
||||
MerchantSellerController,
|
||||
MerchantAdminController,
|
||||
StatisticsSellerController,
|
||||
],
|
||||
providers: [MerchantService, StatisticsService],
|
||||
exports: [MerchantService, StatisticsService],
|
||||
|
||||
@@ -67,7 +67,7 @@ export class MerchantService {
|
||||
}
|
||||
|
||||
async findPublic(query: QueryMerchantDto) {
|
||||
const { page = 1, pageSize = 10, keyword, city } = query;
|
||||
const { page = 1, pageSize = 10, keyword, city, checkInDate } = query;
|
||||
const safePage = Number(page) || 1;
|
||||
const safePageSize = Number(pageSize) || 10;
|
||||
const qb = this.merchantRepo
|
||||
@@ -86,10 +86,10 @@ export class MerchantService {
|
||||
|
||||
const [list, total] = await qb.getManyAndCount();
|
||||
|
||||
// 获取当天日期
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
// 使用传入的开始日期,如果没有则使用当天日期
|
||||
const targetDate = checkInDate || new Date().toISOString().split('T')[0];
|
||||
|
||||
// 为每个商家获取第一个房源的当天房价
|
||||
// 为每个商家获取第一个房源的指定日期房价
|
||||
const listWithPrice = await Promise.all(
|
||||
list.map(async (merchant) => {
|
||||
// 获取商家的第一个已审核通过且在售的房源
|
||||
@@ -104,11 +104,11 @@ export class MerchantService {
|
||||
|
||||
let minPrice = 0;
|
||||
if (firstRoom) {
|
||||
// 获取该房源当天的房价
|
||||
// 获取该房源指定日期的房价
|
||||
const calendar = await this.roomCalendarRepo.findOne({
|
||||
where: {
|
||||
roomId: firstRoom.id,
|
||||
date: today,
|
||||
date: targetDate,
|
||||
status: 'available',
|
||||
},
|
||||
});
|
||||
@@ -210,6 +210,9 @@ export class MerchantService {
|
||||
debt_amount: 0,
|
||||
total_income: 0,
|
||||
total_expense: 0,
|
||||
total_settlement: 0,
|
||||
total_withdraw: 0,
|
||||
pending_settlement: 0,
|
||||
status: 'active',
|
||||
});
|
||||
await this.merchantAccountRepo.save(account);
|
||||
|
||||
@@ -37,63 +37,78 @@ export class MerchantOrderController {
|
||||
return this.orderService.findByMerchant(merchant.id, query);
|
||||
}
|
||||
|
||||
@Post('detail')
|
||||
@Get(':orderNo')
|
||||
@ApiOperation({ summary: '商家订单详情' })
|
||||
async getDetail(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Body('orderId') orderId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
const order = await this.orderService.findById(orderId);
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
throw new NotFoundException('订单不存在');
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@Put(':id/confirm')
|
||||
@ApiOperation({ summary: '确认订单' })
|
||||
async confirm(
|
||||
@Post('detail')
|
||||
@ApiOperation({ summary: '商家订单详情(POST)' })
|
||||
async getDetailPost(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
@Body('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.confirm(merchant.id, id);
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
throw new NotFoundException('订单不存在');
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@Put(':id/reject')
|
||||
@Put(':orderNo/confirm')
|
||||
@ApiOperation({ summary: '确认订单' })
|
||||
async confirm(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.confirm(merchant.id, orderNo);
|
||||
}
|
||||
|
||||
@Put(':orderNo/reject')
|
||||
@ApiOperation({ summary: '拒绝订单' })
|
||||
async reject(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
@Body('reason') reason: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.reject(merchant.id, id, reason);
|
||||
return this.orderService.reject(merchant.id, orderNo, reason);
|
||||
}
|
||||
|
||||
@Put(':id/checkin')
|
||||
@Put(':orderNo/checkin')
|
||||
@ApiOperation({ summary: '办理入住' })
|
||||
async checkin(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkin(merchant.id, id);
|
||||
return this.orderService.checkin(merchant.id, orderNo);
|
||||
}
|
||||
|
||||
@Put(':id/checkout')
|
||||
@Put(':orderNo/checkout')
|
||||
@ApiOperation({ summary: '确认离店' })
|
||||
async checkout(
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkout(merchant.id, id);
|
||||
return this.orderService.checkout(merchant.id, orderNo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,37 +36,42 @@ export class MerchantOrderService {
|
||||
return { list, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
|
||||
}
|
||||
|
||||
async findById(id: number) {
|
||||
async findOne(identifier: string | number) {
|
||||
const whereCondition = typeof identifier === 'number'
|
||||
? { id: identifier }
|
||||
: { orderNo: identifier };
|
||||
|
||||
const order = await this.orderRepo.findOne({
|
||||
where: { id },
|
||||
where: whereCondition,
|
||||
relations: ['room', 'merchant', 'user'],
|
||||
});
|
||||
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
return order;
|
||||
}
|
||||
|
||||
async confirm(merchantId: number, id: number) {
|
||||
const order = await this.findById(id);
|
||||
async confirm(merchantId: number, orderNo: string) {
|
||||
const order = await this.findOne(orderNo);
|
||||
if (order.merchantId !== merchantId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'pending_confirm') {
|
||||
throw new BadRequestException('当前订单状态不可确认');
|
||||
}
|
||||
|
||||
await this.orderRepo.update(id, {
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'pending_checkin',
|
||||
confirmedAt: new Date(),
|
||||
});
|
||||
return { message: '订单已确认' };
|
||||
}
|
||||
|
||||
async reject(merchantId: number, id: number, reason: string) {
|
||||
const order = await this.findById(id);
|
||||
async reject(merchantId: number, orderNo: string, reason: string) {
|
||||
const order = await this.findOne(orderNo);
|
||||
if (order.merchantId !== merchantId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'pending_confirm') {
|
||||
throw new BadRequestException('当前订单状态不可拒绝');
|
||||
}
|
||||
|
||||
await this.orderRepo.update(id, {
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'cancelled',
|
||||
cancelReason: reason,
|
||||
cancelledAt: new Date(),
|
||||
@@ -74,22 +79,22 @@ export class MerchantOrderService {
|
||||
return { message: '订单已拒绝' };
|
||||
}
|
||||
|
||||
async checkin(merchantId: number, id: number) {
|
||||
const order = await this.findById(id);
|
||||
async checkin(merchantId: number, orderNo: string) {
|
||||
const order = await this.findOne(orderNo);
|
||||
if (order.merchantId !== merchantId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'pending_checkin') {
|
||||
throw new BadRequestException('当前订单状态不可办理入住');
|
||||
}
|
||||
|
||||
await this.orderRepo.update(id, {
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'checked_in',
|
||||
checkinAt: new Date(),
|
||||
});
|
||||
return { message: '已办理入住' };
|
||||
}
|
||||
|
||||
async checkout(merchantId: number, id: number) {
|
||||
const order = await this.findById(id);
|
||||
async checkout(merchantId: number, orderNo: string) {
|
||||
const order = await this.findOne(orderNo);
|
||||
if (order.merchantId !== merchantId) throw new ForbiddenException('无权操作此订单');
|
||||
if (order.status !== 'checked_in') {
|
||||
throw new BadRequestException('当前订单状态不可确认离店');
|
||||
@@ -101,7 +106,7 @@ export class MerchantOrderService {
|
||||
throw new BadRequestException('未到离店日期,不可确认离店');
|
||||
}
|
||||
|
||||
await this.orderRepo.update(id, {
|
||||
await this.orderRepo.update(order.id, {
|
||||
status: 'completed',
|
||||
checkoutAt: new Date(),
|
||||
});
|
||||
|
||||
@@ -79,6 +79,9 @@ export class MerchantProfileService {
|
||||
debt_amount: 0,
|
||||
total_income: 0,
|
||||
total_expense: 0,
|
||||
total_settlement: 0,
|
||||
total_withdraw: 0,
|
||||
pending_settlement: 0,
|
||||
status: 'active',
|
||||
});
|
||||
await this.merchantAccountRepo.save(account);
|
||||
|
||||
@@ -62,6 +62,9 @@ export class AccountService {
|
||||
debt_amount: 0,
|
||||
total_income: 0,
|
||||
total_expense: 0,
|
||||
total_settlement: 0,
|
||||
total_withdraw: 0,
|
||||
pending_settlement: 0,
|
||||
status: 'active',
|
||||
});
|
||||
await this.merchantAccountRepo.save(account);
|
||||
|
||||
Reference in New Issue
Block a user