|
@@ -1,9 +1,178 @@
|
|
|
import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
|
-import { DataSource } from 'typeorm';
|
|
|
|
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
import { Order } from './order.entity';
|
|
import { Order } from './order.entity';
|
|
|
|
|
+import { OrderGoods } from './order-goods.entity';
|
|
|
|
|
+import { Goods } from '@/server/modules/goods/goods.entity';
|
|
|
|
|
+import { DeliveryAddress } from '@/server/modules/delivery-address/delivery-address.entity';
|
|
|
|
|
+import type { CreateOrderRequest } from './dto/create-order-request.dto';
|
|
|
|
|
|
|
|
export class OrderService extends GenericCrudService<Order> {
|
|
export class OrderService extends GenericCrudService<Order> {
|
|
|
|
|
+ private orderGoodsRepository: Repository<OrderGoods>;
|
|
|
|
|
+ private goodsRepository: Repository<Goods>;
|
|
|
|
|
+ private deliveryAddressRepository: Repository<DeliveryAddress>;
|
|
|
|
|
+
|
|
|
constructor(dataSource: DataSource) {
|
|
constructor(dataSource: DataSource) {
|
|
|
super(dataSource, Order);
|
|
super(dataSource, Order);
|
|
|
|
|
+ this.orderGoodsRepository = dataSource.getRepository(OrderGoods);
|
|
|
|
|
+ this.goodsRepository = dataSource.getRepository(Goods);
|
|
|
|
|
+ this.deliveryAddressRepository = dataSource.getRepository(DeliveryAddress);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建订单
|
|
|
|
|
+ * @param data 创建订单请求数据
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @returns 创建的订单信息
|
|
|
|
|
+ */
|
|
|
|
|
+ async createOrder(data: CreateOrderRequest, userId: number): Promise<{
|
|
|
|
|
+ orderId: number;
|
|
|
|
|
+ orderNo: string;
|
|
|
|
|
+ amount: number;
|
|
|
|
|
+ payAmount: number;
|
|
|
|
|
+ }> {
|
|
|
|
|
+ const queryRunner = this.dataSource.createQueryRunner();
|
|
|
|
|
+ await queryRunner.connect();
|
|
|
|
|
+ await queryRunner.startTransaction();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { addressId, productOwn, consumeFrom, products } = data;
|
|
|
|
|
+
|
|
|
|
|
+ // 验证商品信息并计算总价
|
|
|
|
|
+ let totalAmount = 0;
|
|
|
|
|
+ let totalCostAmount = 0;
|
|
|
|
|
+ const goodsInfo = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (const item of products) {
|
|
|
|
|
+ const goods = await this.goodsRepository.findOne({
|
|
|
|
|
+ where: { id: item.id }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!goods) {
|
|
|
|
|
+ throw new Error(`商品ID ${item.id} 不存在`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (goods.stock < item.num) {
|
|
|
|
|
+ throw new Error(`商品 ${goods.name} 库存不足`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const itemAmount = goods.price * item.num;
|
|
|
|
|
+ const itemCostAmount = goods.costPrice * item.num;
|
|
|
|
|
+
|
|
|
|
|
+ totalAmount += itemAmount;
|
|
|
|
|
+ totalCostAmount += itemCostAmount;
|
|
|
|
|
+
|
|
|
|
|
+ goodsInfo.push({
|
|
|
|
|
+ goods,
|
|
|
|
|
+ quantity: item.num,
|
|
|
|
|
+ amount: itemAmount,
|
|
|
|
|
+ costAmount: itemCostAmount
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取收货地址信息
|
|
|
|
|
+ let deliveryAddress = null;
|
|
|
|
|
+ if (addressId) {
|
|
|
|
|
+ deliveryAddress = await this.deliveryAddressRepository.findOne({
|
|
|
|
|
+ where: { id: addressId, userId }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!deliveryAddress) {
|
|
|
|
|
+ throw new Error('收货地址不存在');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 生成订单号
|
|
|
|
|
+ const orderNo = this.generateOrderNo();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建订单
|
|
|
|
|
+ const order = this.repository.create({
|
|
|
|
|
+ orderNo,
|
|
|
|
|
+ userId,
|
|
|
|
|
+ amount: totalAmount,
|
|
|
|
|
+ costAmount: totalCostAmount,
|
|
|
|
|
+ payAmount: totalAmount, // 这里可以根据优惠规则计算实际支付金额
|
|
|
|
|
+ orderType: 1, // 实物订单
|
|
|
|
|
+ payType: consumeFrom === '积分兑换' ? 1 : 2, // 根据消费来源设置支付类型
|
|
|
|
|
+ payState: 0, // 未支付
|
|
|
|
|
+ state: 0, // 未发货
|
|
|
|
|
+ addressId: addressId || 0,
|
|
|
|
|
+ merchantId: goodsInfo[0]?.goods.merchantId || 0,
|
|
|
|
|
+ supplierId: goodsInfo[0]?.goods.supplierId || 0,
|
|
|
|
|
+ createdBy: userId,
|
|
|
|
|
+ updatedBy: userId,
|
|
|
|
|
+ // 设置收货地址信息(如果提供)
|
|
|
|
|
+ ...(deliveryAddress && {
|
|
|
|
|
+ receiverMobile: deliveryAddress.phone,
|
|
|
|
|
+ recevierName: deliveryAddress.name,
|
|
|
|
|
+ recevierProvince: deliveryAddress.provinceId,
|
|
|
|
|
+ recevierCity: deliveryAddress.cityId,
|
|
|
|
|
+ recevierDistrict: deliveryAddress.districtId,
|
|
|
|
|
+ recevierTown: deliveryAddress.townId,
|
|
|
|
|
+ address: deliveryAddress.address
|
|
|
|
|
+ })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const savedOrder = await queryRunner.manager.save(order);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建订单商品明细
|
|
|
|
|
+ const orderGoodsList = goodsInfo.map(info => ({
|
|
|
|
|
+ orderId: savedOrder.id,
|
|
|
|
|
+ orderNo,
|
|
|
|
|
+ goodsId: info.goods.id,
|
|
|
|
|
+ goodsName: info.goods.name,
|
|
|
|
|
+ goodsImage: info.goods.image,
|
|
|
|
|
+ goodsType: info.goods.goodsType,
|
|
|
|
|
+ supplierId: info.goods.supplierId,
|
|
|
|
|
+ costPrice: info.goods.costPrice,
|
|
|
|
|
+ price: info.goods.price,
|
|
|
|
|
+ num: info.quantity,
|
|
|
|
|
+ freightAmount: 0,
|
|
|
|
|
+ state: 0,
|
|
|
|
|
+ createdBy: userId,
|
|
|
|
|
+ updatedBy: userId
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ await queryRunner.manager.save(OrderGoods, orderGoodsList);
|
|
|
|
|
+
|
|
|
|
|
+ // 更新商品库存
|
|
|
|
|
+ for (const item of goodsInfo) {
|
|
|
|
|
+ await queryRunner.manager.update(Goods, item.goods.id, {
|
|
|
|
|
+ stock: () => `stock - ${item.quantity}`,
|
|
|
|
|
+ sales: () => `sales + ${item.quantity}`
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await queryRunner.commitTransaction();
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ orderId: savedOrder.id,
|
|
|
|
|
+ orderNo: savedOrder.orderNo,
|
|
|
|
|
+ amount: savedOrder.amount,
|
|
|
|
|
+ payAmount: savedOrder.payAmount
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ await queryRunner.rollbackTransaction();
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ await queryRunner.release();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成订单号
|
|
|
|
|
+ * 格式:年月日时分秒 + 6位随机数
|
|
|
|
|
+ */
|
|
|
|
|
+ private generateOrderNo(): string {
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const year = now.getFullYear();
|
|
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
+ const day = String(now.getDate()).padStart(2, '0');
|
|
|
|
|
+ const hour = String(now.getHours()).padStart(2, '0');
|
|
|
|
|
+ const minute = String(now.getMinutes()).padStart(2, '0');
|
|
|
|
|
+ const second = String(now.getSeconds()).padStart(2, '0');
|
|
|
|
|
+ const random = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
|
|
|
|
|
+
|
|
|
|
|
+ return `ORD${year}${month}${day}${hour}${minute}${second}${random}`;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|