@@ -7,12 +7,35 @@ import { orderStatusLogWhere } from '../../common/event/event.helpers';
|
||||
import { mapOrderCompat, mapStatusLogCompat } from '../../common/compat/v31-compat';
|
||||
import { TradeService } from '../trade/trade.service';
|
||||
import { AdminXiaofeixiaService } from './admin-xiaofeixia.service';
|
||||
import type { AdminOrdersQueryDto } from './dto/admin-query.dto';
|
||||
import type { AdminOrdersExportDto, AdminOrdersQueryDto } from './dto/admin-query.dto';
|
||||
import type { AdminShipOrderDto, HqLogisticsShipDto } from './dto/admin-mutate.dto';
|
||||
import type { XiaofeixiaCreateShipmentDto } from './dto/admin-courier.dto';
|
||||
import { FulfillmentService } from '../fulfillment/fulfillment.service';
|
||||
import { FulfillmentProviderService } from '../fulfillment/fulfillment-provider.service';
|
||||
import { AdminRedeemService } from './admin-redeem.service';
|
||||
import {
|
||||
buildExportFilename,
|
||||
buildOrdersPdf,
|
||||
buildOrdersXlsx,
|
||||
mapOrderToExportRow,
|
||||
} from './admin-order-export.util';
|
||||
|
||||
const ORDER_EXPORT_MAX = 5000;
|
||||
|
||||
type OrderFilterInput = Pick<
|
||||
AdminOrdersQueryDto,
|
||||
| 'orderNo'
|
||||
| 'status'
|
||||
| 'orderType'
|
||||
| 'userId'
|
||||
| 'cityId'
|
||||
| 'receiverPhone'
|
||||
| 'fulfillmentHold'
|
||||
| 'createdFrom'
|
||||
| 'createdTo'
|
||||
| 'excludeTest'
|
||||
| 'deliveryType'
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class AdminOrdersService {
|
||||
@@ -60,23 +83,7 @@ export class AdminOrdersService {
|
||||
async list(query: AdminOrdersQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
const where: Prisma.OrderWhereInput = {};
|
||||
|
||||
if (query.orderNo) where.orderNo = { contains: query.orderNo };
|
||||
if (query.status) where.status = query.status as Prisma.EnumOrderStatusFilter['equals'];
|
||||
if (query.orderType) where.orderType = query.orderType as Prisma.EnumOrderTypeFilter['equals'];
|
||||
if (query.userId) where.userId = BigInt(query.userId);
|
||||
if (query.cityId) where.cityId = BigInt(query.cityId);
|
||||
if (query.receiverPhone) where.receiverPhone = { contains: query.receiverPhone };
|
||||
if (query.fulfillmentHold === true || query.fulfillmentHold === 'true') {
|
||||
where.fulfillmentHold = true;
|
||||
}
|
||||
if (query.createdFrom || query.createdTo) {
|
||||
where.createdAt = {};
|
||||
if (query.createdFrom) where.createdAt.gte = new Date(query.createdFrom);
|
||||
if (query.createdTo) where.createdAt.lte = new Date(query.createdTo);
|
||||
}
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
const where = this.buildOrderWhere(query);
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.order.findMany({
|
||||
@@ -89,6 +96,9 @@ export class AdminOrdersService {
|
||||
delivery: { select: { provider: true, trackingNo: true, providerOrderNo: true } },
|
||||
city: { select: { id: true, name: true, code: true } },
|
||||
fulfillmentWarehouse: { select: { id: true, name: true } },
|
||||
benefitCoupon: {
|
||||
select: { couponNo: true, totalAmount: true, usedAmount: true, balance: true, status: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
this.prisma.order.count({ where }),
|
||||
@@ -97,6 +107,114 @@ export class AdminOrdersService {
|
||||
return serializeBigInt({ items, total, page, pageSize });
|
||||
}
|
||||
|
||||
async previewExport(dto: AdminOrdersExportDto) {
|
||||
const count = await this.countExportOrders(dto);
|
||||
return { count, max: ORDER_EXPORT_MAX, exceeds: count > ORDER_EXPORT_MAX };
|
||||
}
|
||||
|
||||
async exportOrders(dto: AdminOrdersExportDto) {
|
||||
const count = await this.countExportOrders(dto);
|
||||
if (count > ORDER_EXPORT_MAX) {
|
||||
throw new BadRequestException(`超过 ${ORDER_EXPORT_MAX} 条,请缩小日期或筛选条件`);
|
||||
}
|
||||
if (count === 0) {
|
||||
throw new BadRequestException('没有可导出的订单');
|
||||
}
|
||||
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: this.buildExportWhere(dto),
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: ORDER_EXPORT_MAX,
|
||||
include: {
|
||||
city: { select: { name: true } },
|
||||
delivery: { select: { trackingNo: true } },
|
||||
benefitCoupon: {
|
||||
select: { totalAmount: true, usedAmount: true, balance: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const rows = orders.map((order) => mapOrderToExportRow(order));
|
||||
const buffer =
|
||||
dto.format === 'pdf' ? await buildOrdersPdf(rows) : await buildOrdersXlsx(rows);
|
||||
const filename = buildExportFilename(dto.format, rows.length);
|
||||
const mimeType =
|
||||
dto.format === 'pdf'
|
||||
? 'application/pdf'
|
||||
: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
|
||||
return {
|
||||
filename,
|
||||
mimeType,
|
||||
contentBase64: buffer.toString('base64'),
|
||||
count: rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
private buildOrderWhere(query: OrderFilterInput): Prisma.OrderWhereInput {
|
||||
const where: Prisma.OrderWhereInput = {};
|
||||
|
||||
if (query.orderNo) where.orderNo = { contains: query.orderNo };
|
||||
if (query.status) where.status = query.status as Prisma.EnumOrderStatusFilter['equals'];
|
||||
if (query.orderType) where.orderType = query.orderType as Prisma.EnumOrderTypeFilter['equals'];
|
||||
if (query.userId) where.userId = BigInt(query.userId);
|
||||
if (query.cityId) where.cityId = BigInt(query.cityId);
|
||||
if (query.receiverPhone) where.receiverPhone = { contains: query.receiverPhone };
|
||||
if (query.deliveryType) {
|
||||
where.deliveryType = query.deliveryType as Prisma.EnumDeliveryTypeFilter['equals'];
|
||||
}
|
||||
if (query.fulfillmentHold === true || query.fulfillmentHold === 'true') {
|
||||
where.fulfillmentHold = true;
|
||||
}
|
||||
if (query.createdFrom || query.createdTo) {
|
||||
where.createdAt = {};
|
||||
if (query.createdFrom) where.createdAt.gte = new Date(query.createdFrom);
|
||||
if (query.createdTo) where.createdAt.lte = this.endOfDay(query.createdTo);
|
||||
}
|
||||
if (query.excludeTest) where.isTest = false;
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
private buildExportWhere(dto: AdminOrdersExportDto): Prisma.OrderWhereInput {
|
||||
if (dto.scope === 'selected') {
|
||||
const ids = (dto.ids ?? []).map((id) => BigInt(id));
|
||||
if (!ids.length) {
|
||||
throw new BadRequestException('请先勾选要导出的订单');
|
||||
}
|
||||
return { id: { in: ids } };
|
||||
}
|
||||
|
||||
const normalized = this.normalizeExportFilter(dto);
|
||||
return this.buildOrderWhere(normalized);
|
||||
}
|
||||
|
||||
private async countExportOrders(dto: AdminOrdersExportDto): Promise<number> {
|
||||
return this.prisma.order.count({ where: this.buildExportWhere(dto) });
|
||||
}
|
||||
|
||||
private normalizeExportFilter(dto: AdminOrdersExportDto): OrderFilterInput {
|
||||
const createdFrom = dto.createdFrom;
|
||||
const createdTo = dto.createdTo;
|
||||
if (!createdFrom && !createdTo) {
|
||||
const from = new Date();
|
||||
from.setDate(from.getDate() - 30);
|
||||
from.setHours(0, 0, 0, 0);
|
||||
return {
|
||||
...dto,
|
||||
createdFrom: from.toISOString().slice(0, 10),
|
||||
createdTo: new Date().toISOString().slice(0, 10),
|
||||
};
|
||||
}
|
||||
return { ...dto, createdFrom, createdTo };
|
||||
}
|
||||
|
||||
private endOfDay(dateStr: string): Date {
|
||||
const end = new Date(dateStr);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
return end;
|
||||
}
|
||||
|
||||
async getOrderTrack(id: bigint) {
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id },
|
||||
|
||||
Reference in New Issue
Block a user