lrf %!s(int64=2) %!d(string=hai) anos
pai
achega
3150768827
Modificáronse 2 ficheiros con 275 adicións e 33 borrados
  1. 72 23
      app/service/trade/order.js
  2. 203 10
      app/service/util/order.js

+ 72 - 23
app/service/trade/order.js

@@ -20,6 +20,7 @@ class OrderService extends CrudService {
     this.userCouponModel = this.ctx.model.User.UserCoupon;
     this.platformActModel = this.ctx.model.System.PlatformAct;
     this.gjaModel = this.ctx.model.Shop.GoodsJoinAct;
+    this.orderUtil = this.ctx.service.util.order;
     this.tran = new Transaction();
   }
 
@@ -210,21 +211,23 @@ class OrderService extends CrudService {
     }
     const { result, msg } = await this.ctx.service.util.trade.checkCanBuy(data, false);
     if (!result) throw new BusinessError(ErrorCode.DATA_INVALID, msg);
-    // 正常整理商品的内容
-    specsData = await this.getPageData(data);
+    // 本次订单 有关活动的数据
+    const actList = await this.getActList(data);
+    // 正常整理商品的内容,与活动结合
+    specsData = await this.getPageData(data, actList);
     // 组装页面的数据
     const user = this.ctx.user;
     const customer = _.get(user, '_id');
     if (!customer) throw new BusinessError(ErrorCode.NOT_LOGIN, '未找到用户信息');
     const pageData = {};
-    // 找到默认地址
-    const address = await this.addressModel.findOne({ customer, is_default: '1' });
-    pageData.address = address;
     // 商品总价,各店铺的价格明细
     specsData = this.ctx.service.util.order.makeOrder_computedShopTotal(specsData);
     const shopTotalDetail = this.ctx.service.util.order.makerOrder_computedOrderTotal(specsData);
     pageData.goodsData = specsData;
     pageData.orderTotal = shopTotalDetail;
+    // 找到默认地址
+    const address = await this.addressModel.findOne({ customer, is_default: '1' });
+    pageData.address = address;
     // 优惠券列表
     // 查询优惠券列表
     const couponList = await this.ctx.service.user.userCoupon.toMakeOrder_getList(specsData);
@@ -233,10 +236,31 @@ class OrderService extends CrudService {
     const inviter = data.find(f => ObjectId.isValid(f.inviter));
     pageData.inviter = inviter;
     // 活动部分
-    // 每个数据中都有act字段.这个字段表明商品参加了哪些活动
-    // await this.dealAct();
+    pageData.actList = actList.filter(f => ![ '2', '3' ].includes(f.platform_act_type));
     return pageData;
   }
+  /**
+   * 活动相关第一步: 处理规格和活动的问题
+   * @param {Array} goodsList 商店商品列表
+   * @param {Array} actList 活动列表
+   */
+  async dealAct(goodsList, actList) {
+    // 活动根据类型有优先级设置,需要按优先级进行处理
+    // 特价(3)>满减(5)>满折(6)>加价购(4); 买赠(2)无所谓
+    const spActs = actList.filter(f => f.platform_act_type === '3');
+    this.orderUtil.dealAct_sp(goodsList, spActs);
+    const dmActs = actList.filter(f => f.platform_act_type === '5');
+    await this.orderUtil.dealAct_discount(goodsList, dmActs);
+    const dpActs = actList.filter(f => f.platform_act_type === '6');
+    await this.orderUtil.dealAct_discount(goodsList, dpActs);
+    let plusActs = actList.filter(f => f.platform_act_type === '4');
+    await this.orderUtil.dealAct_plus(goodsList, plusActs);
+    plusActs = plusActs.filter(f => f.activity);
+    const giftActs = actList.filter(f => f.platform_act_type === '2');
+    await this.orderUtil.dealAct_gift(goodsList, giftActs);
+  }
+
+
   /**
    * 处理该订单活动部分
    * * 该商品可能会参加多个活动,活动之间有叠加问题
@@ -245,18 +269,20 @@ class OrderService extends CrudService {
    * * 加价购: 满足下限金额时,组织数据,允许前端进行加价购
    * * 满减/折: 针对整个订单而言. 满足就处理,不满足就是不处理
    * * 套装:暂不处理
-   * * ps: 特价与满减/折叠加, 按特价计算总价再看满减/折; 加价购不在满减/折的金额下限计算范围内
+   * * ps1: 特价与满减/折叠加, 按特价计算总价再看满减/折; 加价购不在满减/折的金额下限计算范围内
+   * * ps2: 满减与满折的优先级为:先满减,后满折(理论上不应该同时出现在一个商品上)
    * @param {Array} data 购物车数据(直接购买也会组织成购物车数据)
    */
-  async dealAct(data) {
+  async getActList(data) {
     const actList = [];
     for (const i of data) {
-      const { act = [], spec } = i;
+      const { act = [], goodsSpec: spec } = i;
       if (act.length <= 0) continue;
       for (const a of act) {
-        const platformAct = await this.platformActModel.findById(a);
+        let platformAct = await this.platformActModel.findById(a);
         // 没有找到活动,略过
         if (!platformAct) continue;
+        platformAct = JSON.parse(JSON.stringify(platformAct));
         // 活动未开启,略过
         if (_.get(platformAct, 'is_use') !== '0') continue;
         // 活动类型为 0&1不需要处理
@@ -277,31 +303,38 @@ class OrderService extends CrudService {
         if (type === '2') {
           // 买赠,直接去到活动中找到赠品
           const gja = await this.gjaModel.findOne({ platform_act: platformAct._id, 'spec._id': spec }, { config: 1 });
+          if (!gja) continue;
           const gift = _.get(gja, 'config.gift', []);
-          actList.push({ platform_act_type: type, spec, gift });
+          actList.push({ platform_act: platformAct._id, platform_act_type: type, spec, gift });
         } else if (type === '3') {
           // 特价,找出特价
           const gja = await this.gjaModel.findOne({ platform_act: platformAct._id, 'spec._id': spec }, { config: 1 });
+          if (!gja) continue;
           const sp_price = _.get(gja, 'config.sp_price');
+          actList.push({ platform_act: platformAct._id, platform_act_type: type, spec, sp_price });
         } else if (type === '4') {
           // 加价购,找出加价购下限;如果判断下限够了,那就可以让前端去加价购
           const plus_money = _.get(platformAct, 'config.plus_money', 0);
-        } else if (type === '5') {
-          // 满减
-          const discount = _.get(platformAct, 'discount', []);
-        } else if (type === '6') {
-          // 满折
-          const discount = _.get(platformAct, 'discount', []);
+          const obj = { platform_act: platformAct._id, platform_act_type: type, plus_money };
+          const r = actList.find(f => _.isEqual(f, obj));
+          if (!r) actList.push(obj);
+        } else if (type === '5' || type === '6') {
+          // 满减/折
+          const discount = _.get(platformAct, 'config.discount', []);
+          const obj = { platform_act: platformAct._id, platform_act_type: type, discount };
+          const r = actList.find(f => _.isEqual(f, obj));
+          if (!r) actList.push(obj);
         } else if (type === '7') {
           // 套装先不考虑
         }
       }
     }
+    return actList;
   }
 
   // 直接购买&购物车,这俩字段基本没差, 组织订单页商品数据
-  async getPageData(data) {
-    let arr = [];
+  async getPageData(data, actList) {
+    const arr = [];
     for (const i of data) {
       const { goodsSpec, num } = i;
       const d = await this.goodsSpecModel.aggregate([
@@ -346,15 +379,31 @@ class OrderService extends CrudService {
             file: '$goods.file',
             tags: '$goods.tags',
             act_tags: '$goods.act_tags',
+            price: { $toDouble: '$sell_money' },
           },
         },
       ]);
-      arr.push(...d);
+      let gs = _.head(d);
+      if (gs) gs = JSON.parse(JSON.stringify(gs));
+      arr.push(gs);
     }
-    arr = Object.values(_.groupBy(arr, 'shop'));
+    // 平铺数据后,需要处理活动相关部分
+    // 经过处理后的数据,会添加act字段,表明与活动有关的信息
+    await this.dealAct(arr, actList);
+    const result = await this.toMakeGroupData(arr);
+    return result;
+  }
+  /**
+   * 将平铺的数据按店铺分组形成
+   * * [  { shop:${value}, shop_name:${value}, goods:${value} }  ]
+   * 的形式
+   * @param {Array} list 平铺的数据集合
+   */
+  async toMakeGroupData(list) {
+    list = Object.values(_.groupBy(list, 'shop'));
     const result = [];
     // 按店铺分组
-    for (const i of arr) {
+    for (const i of list) {
       const head = _.head(i);
       const obj = { shop: _.get(head, 'shop'), shop_name: _.get(head, 'shop_name') };
       const goods = i.map(e => _.omit(e, [ 'shop', 'shop_name' ]));

+ 203 - 10
app/service/util/order.js

@@ -9,23 +9,27 @@ class OrderService extends CrudService {
   constructor(ctx) {
     super(ctx, 'order');
     this.orderModel = this.ctx.model.Trade.Order;
+    this.platformActModel = this.ctx.model.System.PlatformAct;
+    this.gjaModel = this.ctx.model.Shop.GoodsJoinAct;
   }
 
   // #region 下单前计算函数
   /**
    * 计算每个店铺的价格
-   * @param {Array} list 按店铺分组的商品列表
    * list是经过 getPageData 处理过的数据
-   * @param {String} type 订单类型码: 0常规单,1团购单
+   * @param {Array} list 按店铺分组的商品列表
    */
-  makeOrder_computedShopTotal(list, type = '0') {
-    let sell_money_key;
-    if (type === '1') sell_money_key = 'group_sell_money';
-    else sell_money_key = 'sell_money';
+  makeOrder_computedShopTotal(list) {
     for (const i of list) {
-      i.goods = i.goods.map((e) => ({ ...e, price: _.get(e, sell_money_key) }));
-      i.goods_total = i.goods.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(_.get(n, sell_money_key), n.num)), 0);
-      i.freight_total = i.goods.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(n.freight, n.num)), 0);
+      let goods_total = 0,
+        freight_total = 0;
+      for (const g of i.goods) {
+        // 如果有特价,那就使用特价,没有特价就是用正常销售价
+        goods_total = this.ctx.plus(goods_total, _.get(g, 'price'));
+        freight_total = this.ctx.plus(freight_total, _.get(g, 'freight'));
+      }
+      i.goods_total = goods_total;
+      i.freight_total = freight_total;
     }
     return list;
   }
@@ -57,7 +61,7 @@ class OrderService extends CrudService {
     // 解除店铺层
     const sd = Object.values(detail);
     // 取出规格层
-    const sgd = sd.map((i) => Object.values(i));
+    const sgd = sd.map(i => Object.values(i));
     // 将规格明细降维至一维
     const oneLevel = _.flattenDeep(sgd);
     // 根据订单类型,计算应付(优惠券部分已经按订单类型计算并分配完了.这地方只是复现)
@@ -174,6 +178,195 @@ class OrderService extends CrudService {
     }
     return result;
   }
+  /**
+   * 检查商品是否满足加价购
+   * @param {Array} goodsList 平铺的商品列表
+   * @param {Array} actList 活动
+   */
+  async dealAct_plus(goodsList, actList) {
+    for (const act of actList) {
+      const { platform_act, plus_money } = act;
+      const goodsInAct = await this.getGoodsInAct(goodsList, platform_act);
+      // 没有有关活动的商品,直接下个活动
+      if (goodsInAct.length <= 0) continue;
+      const total = goodsInAct.reduce((p, n) => {
+        const rp = this.getGoodsPayAfterAct(n);
+        return this.ctx.plus(p, rp);
+      }, 0);
+      // 商品,优惠过后的金额,大于等于 活动下限:活动可以进行
+      if (this.ctx.minus(total, plus_money) >= 0) act.activity = true;
+    }
+  }
+
+  /**
+   * 设置商品特价部分
+   * @param {Array} goodsList 平铺的商品列表
+   * @param {Array} actList 活动
+   */
+  dealAct_sp(goodsList, actList) {
+    for (const act of actList) {
+      const { spec, sp_price } = act;
+      if (!spec) continue;
+      const goods = goodsList.find(f => f.goodsSpec_id === spec);
+      if (goods) {
+        // 默认特价为商品金额
+        goods.sp_price = sp_price;
+        goods.price = sp_price;
+      }
+    }
+  }
+  /**
+   * 商品满减/折处理:主要区分在于 将折扣转换为金额,剩下都是按比例分配
+   * @param {Array} goodsList 平铺的商品列表
+   * @param {Array} actList 活动
+   */
+  async dealAct_discount(goodsList, actList) {
+    for (const act of actList) {
+      const { discount = [], platform_act, platform_act_type } = act;
+      // 整理出区间
+      const range = this.getDiscountRange(discount);
+      // 找到在当前活动的商品
+      const goodsInAct = await this.getGoodsInAct(goodsList, platform_act);
+      if (goodsInAct.length <= 0) continue;
+      // 计算总价格够不够线(因为活动有优先级问题,如果发生满减够, 而满减之后的价格就不足以满折, 那就不给满折,所以要重新计算)
+      const total = goodsInAct.reduce((p, n) => {
+        const rp = this.getGoodsPayAfterAct(n);
+        return this.ctx.plus(p, rp);
+      }, 0);
+      for (const r of range) {
+        const { ls, le, number, max } = r;
+        let res = false;
+        if (ls && le) res = _.inRange(total, ls, le);
+        else if (ls && !le) res = this.ctx.minus(total, ls) >= 0;
+        if (res) {
+          // 在区间中,处理钱的问题.
+          // 按比例分配金额; 分配完后,结果统一放回原数据中
+          const actResult = [];
+          let discountTotal = number;
+          if (platform_act_type === '6') {
+            // 满折:因为输入的是折扣,所以需要将折扣转换成具体金额,然后与优惠上限对比,决定最后优惠总金额
+            const dp = this.ctx.minus(1, this.ctx.divide(number, 10));
+            // 计算优惠的金额
+            discountTotal = this.ctx.multiply(dp, total);
+            // 如果超出上限,则使用上限值作为优惠金额
+            if (_.isNumber(max)) {
+              if (this.ctx.minus(discountTotal, max) > 0) discountTotal = max;
+            }
+          }
+          for (const gia of goodsInAct) {
+            const { goodsSpec_id } = gia;
+            // 不是最后一个
+            if (!_.isEqual(gia, _.last(goodsInAct))) {
+              const rp = this.getGoodsPayAfterAct(gia);
+              const percent = this.ctx.divide(rp, total);
+              const money = this.ctx.multiply(percent, discountTotal);
+              actResult.push({ platform_act, money, goodsSpec_id });
+            } else {
+              const allready = actResult.reduce((p, n) => this.ctx.plus(p, n.money), 0);
+              const el = this.ctx.minus(discountTotal, allready);
+              actResult.push({ platform_act, money: el, goodsSpec_id });
+            }
+          }
+          // 修改数据
+          for (const i of actResult) {
+            const { goodsSpec_id, ...others } = i;
+            const r = goodsList.find(f => f.goodsSpec_id === goodsSpec_id);
+            if (r) {
+              const { act = [] } = r;
+              act.push(others);
+              r.act = act;
+            }
+          }
+          // 修改活动数据
+          const text = `满${platform_act_type === '6' ? '折' : '减'}活动`;
+          const actData = await this.platformActModel.findById(platform_act);
+          act.title = _.get(actData, 'act_time.title', text);
+          act.discount = actResult.reduce((p, n) => this.ctx.plus(p, n.money), 0);
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * 商品买赠处理
+   * @param {Array} goodsList 平铺的商品列表
+   * @param {Array} actList 活动
+   */
+  async dealAct_gift(goodsList, actList) {
+    for (const act of actList) {
+      const { spec, gift, platform_act } = act;
+      const goodsInAct = await this.getGoodsInAct(goodsList, platform_act);
+      const actResult = [];
+      for (const goods of goodsInAct) {
+        const { goodsSpec_id } = goods;
+        if (spec === goodsSpec_id) actResult.push({ platform_act, gift, goodsSpec_id });
+      }
+      for (const i of actResult) {
+        const { goodsSpec_id, ...others } = i;
+        const r = goodsList.find(f => f.goodsSpec_id === goodsSpec_id);
+        if (r) {
+          const { act = [] } = r;
+          act.push(others);
+          r.act = act;
+        }
+      }
+    }
+  }
+
+  /**
+   * 查看商品是否在活动中
+   * @param {Array} goodsList 平铺商品列表
+   * @param {String} platform_act 活动id
+   */
+  async getGoodsInAct(goodsList, platform_act) {
+    const num = await this.platformActModel.count({ _id: platform_act, is_use: '0' });
+    if (num <= 0) return [];
+    // 查询商品是否参与活动
+    const goodsInAct = [];
+    for (const goods of goodsList) {
+      const { goodsSpec_id } = goods;
+      const gnum = await this.gjaModel.count({ 'spec._id': goodsSpec_id, platform_act });
+      if (gnum <= 0) continue;
+      goodsInAct.push(goods);
+    }
+    return goodsInAct;
+  }
+  /**
+   * 获取商品经活动后实付的价格
+   * @param {Object} goods 平铺的商品数据
+   */
+  getGoodsPayAfterAct(goods) {
+    const { act = [], price } = goods;
+    const actDiscount = act.reduce((p, n) => this.ctx.plus(p, n.money), 0);
+    const rp = this.ctx.minus(price, actDiscount);
+    return rp;
+  }
+
+  /**
+   * 获取满减/折区间范围
+   * @param {Array} discount 满减/折阶梯设置
+   */
+  getDiscountRange(discount) {
+    const range = [];
+    for (let i = 0; i < discount.length; i++) {
+      const e1 = _.get(discount, i);
+      const e2 = _.get(discount, i + 1);
+      if (e1 && e2) {
+        const { limit: ls, number, max } = e1;
+        const { limit: le } = e2;
+        const obj = { ls: this.ctx.toNumber(ls), le: this.ctx.toNumber(le), number: this.ctx.toNumber(number) };
+        if (max) obj.max = max;
+        range.push(obj);
+      } else if (e1 && !e2) {
+        const { limit: ls, number, max } = e1;
+        const obj = { ls: this.ctx.toNumber(ls), number: this.ctx.toNumber(number) };
+        if (max) obj.max = max;
+        range.push(obj);
+      }
+    }
+    return range;
+  }
 }
 
 module.exports = OrderService;