fix;提交、

This commit is contained in:
ljy
2026-07-19 22:16:55 +08:00
parent 76270b20bf
commit 792b543ba8
34 changed files with 2310 additions and 398 deletions
@@ -478,23 +478,293 @@ export class TradeService {
}
async createRefundRequest(userId: bigint, orderId: bigint, remark?: string) {
const order = await this.prisma.order.findFirst({ where: { id: orderId, userId } });
if (!order) throw new NotFoundException('订单不存在');
if (!['PENDING_SHIP', 'PENDING_RECEIVE', 'COMPLETED', 'OUT_WAREHOUSE', 'SHIPPING'].includes(order.status)) {
throw new BadRequestException('当前订单不可申请退款');
}
await this.prisma.order.update({
where: { id: orderId },
data: { status: 'REFUNDING', payStatus: 'REFUNDING' },
});
return this.ticketService.create({
return this.createAfterSaleTicket(userId, orderId, {
ticketType: 'REFUND',
refType: 'ORDER',
refId: orderId.toString(),
remark: remark ?? '用户申请退款',
});
}
async createAfterSaleTicket(
userId: bigint,
orderId: bigint,
body: { ticketType: string; remark?: string; evidenceUrls?: string[] },
) {
const order = await this.prisma.order.findFirst({ where: { id: orderId, userId } });
if (!order) throw new NotFoundException('订单不存在');
if (order.status === 'PENDING_PAY' || order.status === 'CANCELLED') {
throw new BadRequestException('当前订单不可申请售后');
}
if (['REFUNDING', 'REFUNDED'].includes(order.status)) {
throw new BadRequestException('订单已在退款流程中');
}
const pending = await this.prisma.commonTicket.findFirst({
where: {
ticketType: body.ticketType as never,
refType: 'ORDER',
refId: orderId,
status: { in: ['PENDING', 'OPEN'] },
},
});
if (pending) throw new BadRequestException('该类型售后工单已在处理中');
const evidenceUrls = (body.evidenceUrls ?? []).filter((u) => typeof u === 'string' && u.trim());
return this.ticketService.create({
ticketType: body.ticketType,
refType: 'ORDER',
refId: orderId.toString(),
remark: body.remark ?? '',
extraJson: evidenceUrls.length ? { evidenceUrls } : undefined,
});
}
async listAfterSaleTickets(userId: bigint, page = 1, pageSize = 20) {
const orders = await this.prisma.order.findMany({
where: { userId },
select: { id: true, orderNo: true },
});
const orderIds = orders.map((o) => o.id);
const orderNoMap = new Map(orders.map((o) => [o.id.toString(), o.orderNo]));
if (!orderIds.length) {
return serializeBigInt({ items: [], total: 0, page, pageSize });
}
const where = {
refType: 'ORDER',
refId: { in: orderIds },
ticketType: { in: ['REFUND', 'RESHIPMENT', 'DAMAGE_RETURN', 'RETURN_REFUND'] as never[] },
};
const [items, total] = await Promise.all([
this.prisma.commonTicket.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.commonTicket.count({ where }),
]);
return serializeBigInt({
items: items.map((t) => ({
...t,
orderNo: orderNoMap.get(t.refId.toString()) ?? null,
})),
total,
page,
pageSize,
});
}
async getAfterSaleTicket(userId: bigint, ticketId: bigint) {
const ticket = await this.prisma.commonTicket.findUnique({ where: { id: ticketId } });
if (!ticket || ticket.refType !== 'ORDER') throw new NotFoundException('工单不存在');
const order = await this.prisma.order.findFirst({
where: { id: ticket.refId, userId },
select: { id: true, orderNo: true, status: true },
});
if (!order) throw new NotFoundException('工单不存在');
return serializeBigInt({ ...ticket, orderNo: order.orderNo, orderStatus: order.status });
}
private generateInvoiceNo() {
return `INV${Date.now()}${Math.floor(Math.random() * 900 + 100)}`;
}
async createInvoice(
userId: bigint,
orderId: bigint,
body: {
titleType: string;
invoiceKind: string;
titleName: string;
taxNo?: string;
addressPhone?: string;
bankAccount?: string;
email: string;
phone: string;
remark?: string;
},
) {
const order = await this.prisma.order.findFirst({ where: { id: orderId, userId } });
if (!order) throw new NotFoundException('订单不存在');
if (order.status !== 'COMPLETED') {
throw new BadRequestException('仅已完成订单可申请发票');
}
const existing = await this.prisma.userInvoice.findFirst({
where: { orderId, status: { in: ['PENDING', 'ISSUED'] } },
});
if (existing) throw new BadRequestException('该订单已有进行中或已开具的发票申请');
if (body.titleType === 'ENTERPRISE' && !body.taxNo?.trim()) {
throw new BadRequestException('企业抬头须填写税号');
}
if (body.invoiceKind === 'SPECIAL') {
if (body.titleType !== 'ENTERPRISE') {
throw new BadRequestException('专用发票仅支持企业抬头');
}
if (!body.taxNo?.trim() || !body.addressPhone?.trim() || !body.bankAccount?.trim()) {
throw new BadRequestException('专用发票须填写税号、地址电话与开户行账号');
}
}
const invoice = await this.prisma.userInvoice.create({
data: {
invoiceNo: this.generateInvoiceNo(),
orderId,
userId,
titleType: body.titleType as never,
invoiceKind: body.invoiceKind as never,
titleName: body.titleName.trim(),
taxNo: body.taxNo?.trim() || null,
addressPhone: body.addressPhone?.trim() || null,
bankAccount: body.bankAccount?.trim() || null,
email: body.email.trim(),
phone: body.phone.trim(),
remark: body.remark?.trim() || null,
},
});
return serializeBigInt({ ...invoice, orderNo: order.orderNo });
}
async listInvoices(userId: bigint, page = 1, pageSize = 20) {
const where = { userId };
const [items, total] = await Promise.all([
this.prisma.userInvoice.findMany({
where,
include: { order: { select: { orderNo: true } } },
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.userInvoice.count({ where }),
]);
return serializeBigInt({
items: items.map((inv) => ({
...inv,
orderNo: inv.order.orderNo,
order: undefined,
})),
total,
page,
pageSize,
});
}
async getInvoice(userId: bigint, invoiceId: bigint) {
const invoice = await this.prisma.userInvoice.findFirst({
where: { id: invoiceId, userId },
include: { order: { select: { orderNo: true, payAmount: true } } },
});
if (!invoice) throw new NotFoundException('发票不存在');
return serializeBigInt({
...invoice,
orderNo: invoice.order.orderNo,
payAmount: invoice.order.payAmount,
order: undefined,
});
}
/** 工作日差(粗略:排除周六日) */
private businessDaysSince(from: Date, to = new Date()): number {
let days = 0;
const cur = new Date(from);
cur.setHours(0, 0, 0, 0);
const end = new Date(to);
end.setHours(0, 0, 0, 0);
while (cur < end) {
cur.setDate(cur.getDate() + 1);
const w = cur.getDay();
if (w !== 0 && w !== 6) days += 1;
}
return days;
}
async adminListInvoices(query: { status?: string; page?: number; pageSize?: number }) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: { status?: never } = {};
if (query.status) where.status = query.status as never;
const [items, total] = await Promise.all([
this.prisma.userInvoice.findMany({
where,
include: { order: { select: { orderNo: true, payAmount: true } }, user: { select: { phone: true } } },
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.userInvoice.count({ where }),
]);
return serializeBigInt({
items: items.map((inv) => ({
...inv,
orderNo: inv.order.orderNo,
payAmount: inv.order.payAmount,
userPhone: inv.user.phone,
overdue: inv.status === 'PENDING' && this.businessDaysSince(inv.createdAt) > 2,
order: undefined,
user: undefined,
})),
total,
page,
pageSize,
});
}
async adminGetInvoice(id: bigint) {
const invoice = await this.prisma.userInvoice.findUnique({
where: { id },
include: { order: { select: { orderNo: true, payAmount: true, productName: true } }, user: { select: { phone: true, nickname: true } } },
});
if (!invoice) throw new NotFoundException('发票不存在');
return serializeBigInt({
...invoice,
orderNo: invoice.order.orderNo,
payAmount: invoice.order.payAmount,
productName: invoice.order.productName,
userPhone: invoice.user.phone,
overdue: invoice.status === 'PENDING' && this.businessDaysSince(invoice.createdAt) > 2,
});
}
async adminIssueInvoice(
id: bigint,
operatorId: bigint,
body: { fileUrl: string; resourceId?: string; remark?: string },
) {
const invoice = await this.prisma.userInvoice.findUnique({ where: { id } });
if (!invoice) throw new NotFoundException('发票不存在');
if (invoice.status !== 'PENDING') throw new BadRequestException('当前状态不可开票');
const updated = await this.prisma.userInvoice.update({
where: { id },
data: {
status: 'ISSUED',
fileUrl: body.fileUrl,
resourceId: body.resourceId ? BigInt(body.resourceId) : null,
issuedAt: new Date(),
operatorId,
remark: body.remark ?? invoice.remark,
},
});
return serializeBigInt(updated);
}
async adminRejectInvoice(id: bigint, operatorId: bigint, remark?: string) {
const invoice = await this.prisma.userInvoice.findUnique({ where: { id } });
if (!invoice) throw new NotFoundException('发票不存在');
if (invoice.status !== 'PENDING') throw new BadRequestException('当前状态不可驳回');
const updated = await this.prisma.userInvoice.update({
where: { id },
data: {
status: 'REJECTED',
operatorId,
remark: remark ?? '驳回',
issuedAt: new Date(),
},
});
return serializeBigInt(updated);
}
async listPartnerReshipments(partnerAccountId: bigint) {
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
const orderWhere = await this.partnerCityService.buildPartnerOrderWhere(primary.id);