lrf преди 2 години
родител
ревизия
f4c60303e5
променени са 6 файла, в които са добавени 274 реда и са изтрити 92 реда
  1. 4 2
      app/controller/trade/config/.cart.js
  2. 4 0
      app/model/trade/cart.js
  3. 84 26
      app/service/trade/cart.js
  4. 52 49
      app/service/trade/order.js
  5. 104 14
      app/service/util/trade.js
  6. 26 1
      app/service/view/goods.js

+ 4 - 2
app/controller/trade/config/.cart.js

@@ -1,6 +1,6 @@
 module.exports = {
   create: {
-    requestBody: ['customer', 'shop', 'goods', 'goodsSpec', 'num', 'act'],
+    requestBody: ['customer', 'shop', 'goods', 'goodsSpec', 'num', 'act', 'is_set', 'set_id'],
   },
   destroy: {
     params: ['!id'],
@@ -8,7 +8,7 @@ module.exports = {
   },
   update: {
     params: ['!id'],
-    requestBody: ['customer', 'shop', 'goods', 'goodsSpec', 'num', 'act'],
+    requestBody: ['customer', 'shop', 'goods', 'goodsSpec', 'num', 'act', 'is_set', 'set_id'],
   },
   show: {
     parameters: {
@@ -25,6 +25,8 @@ module.exports = {
         shop: 'shop',
         goods: 'goods',
         goodsSpec: 'goodsSpec',
+        is_set: 'is_set',
+        set_id: 'set_id',
       },
       // options: {
       //   "meta.state": 0 // 默认条件

+ 4 - 0
app/model/trade/cart.js

@@ -9,6 +9,8 @@ const cart = {
   goodsSpec: { type: String, required: false, zh: '商品规格', ref: 'Shop.GoodsSpec' }, //
   act: { type: Array, zh: '活动相关' },
   num: { type: Number, required: false, zh: '数量' }, //
+  is_set: { type: String, required: false, default: '1', zh: '是否是套装' }, // 字典:is_use,默认不是
+  set_id: { type: String, required: false, zh: '套装id', ref: 'Shop.GoodsSet' }, //
 };
 const schema = new Schema(cart, { toJSON: { getters: true, virtuals: true } });
 schema.index({ id: 1 });
@@ -17,6 +19,8 @@ schema.index({ customer: 1 });
 schema.index({ shop: 1 });
 schema.index({ goods: 1 });
 schema.index({ goodsSpec: 1 });
+schema.index({ is_set: 1 });
+schema.index({ set_id: 1 });
 
 schema.plugin(metaPlugin);
 

+ 84 - 26
app/service/trade/cart.js

@@ -12,6 +12,7 @@ class CartService extends CrudService {
     this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
     this.platformActModel = this.ctx.model.System.PlatformAct;
     this.gjaModel = this.ctx.model.Shop.GoodsJoinAct;
+    this.setModel = this.ctx.model.Shop.GoodsSet;
   }
 
   /**
@@ -46,22 +47,24 @@ class CartService extends CrudService {
     const platformActList = await this.platformActModel.find({ is_use: '0' });
     const platform_act = platformActList.map(i => ObjectId(i._id).toString());
     for (const cart of data) {
-      const { goodsSpec: spec_id, goods: goods_id, act = [] } = cart;
-      const gjaList = await this.gjaModel.find({ platform_act, 'goods._id': ObjectId(goods_id).toString(), 'spec._id': ObjectId(spec_id).toString() });
-      for (const gja of gjaList) {
-        const { platform_act_type: type, platform_act } = gja;
-        // 加价购需要额外判断是否是基础商品
-        if (type === '4') {
-          const goods_type = _.get(gja, 'config.goods_type');
-          if (goods_type === 'basic') act.push(platform_act);
-        } else act.push(platform_act);
+      const { goodsSpec: spec_id, goods: goods_id, act = [], is_set = '1', set_id } = cart;
+      if (is_set === '1' || !set_id) {
+        // 非套装处理, 套装不参与其他活动
+        const gjaList = await this.gjaModel.find({ platform_act, 'goods._id': ObjectId(goods_id).toString(), 'spec._id': ObjectId(spec_id).toString() });
+        for (const gja of gjaList) {
+          const { platform_act_type: type, platform_act } = gja;
+          // 加价购需要额外判断是否是基础商品
+          if (type === '4') {
+            const goods_type = _.get(gja, 'config.goods_type');
+            if (goods_type === 'basic') act.push(platform_act);
+          } else act.push(platform_act);
+        }
+        cart.act = _.uniq(act);
       }
-      cart.act = _.uniq(act);
     }
     return data;
   }
 
-
   /**
    ** 创建购物车信息,若找到该店的该商品规格.进行库存校验
    ** 数量进行合并;
@@ -74,24 +77,79 @@ class CartService extends CrudService {
    * @param body.num 数量
    */
   async create({ num, ...body }) {
-    const { customer, shop, goods, goodsSpec } = body;
+    const { customer, shop, goods, goodsSpec, is_set = '1', set_id } = body;
     assert(customer, '缺少顾客信息');
-    assert(shop, '缺少店铺信息');
-    assert(goods, '缺少商品信息');
-    assert(goodsSpec, '缺少商品规格信息');
-    const query = _.pick(body, [ 'customer', 'shop', 'goods', 'goodsSpec' ]);
-    const data = await this.model.findOne(query);
-    if (data) {
-      const buyNum = this.ctx.plus(data.num, num);
-      const { enough, msg } = await this.checkGoodsNum({ goodsSpecId: goodsSpec, num: buyNum });
-      if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
-      data.num = buyNum;
-      await data.save();
+    if (is_set === '1') {
+      assert(shop, '缺少店铺信息');
+      assert(goods, '缺少商品信息');
+      assert(goodsSpec, '缺少商品规格信息');
+    } else assert(set_id, '缺少套装id');
+    if (is_set === '1') {
+      // 非套装 添加购物车
+      const query = _.pick(body, [ 'customer', 'shop', 'goods', 'goodsSpec' ]);
+      const data = await this.model.findOne(query);
+      if (data) {
+        const buyNum = this.ctx.plus(data.num, num);
+        const { enough, msg } = await this.checkGoodsNum({ goodsSpecId: goodsSpec, num: buyNum });
+        if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
+        data.num = buyNum;
+        await data.save();
+      } else {
+        const { enough, msg } = await this.checkGoodsNum({ goodsSpecId: goodsSpec, num });
+        if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
+        await this.model.create({ num, ...body });
+      }
     } else {
-      const { enough, msg } = await this.checkGoodsNum({ goodsSpecId: goodsSpec, num });
-      if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
-      await this.model.create({ num, ...body });
+      // 套装添加购物车
+      const query = _.pick(body, [ 'customer', 'set_id' ]);
+      const data = await this.model.findOne(query);
+      if (data) {
+        // 计算套装购买数量
+        const buyNum = this.ctx.plus(data.num, num);
+        const { enough, msg } = await this.checkSetGoodsNum(data, buyNum);
+        if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
+        data.num = buyNum;
+        await data.save();
+      } else {
+        const { enough, msg } = await this.checkSetGoodsNum(data, num);
+        if (!enough) throw new BusinessError(ErrorCode.SERVICE_FAULT, msg);
+        await this.model.create({ num, ...body });
+      }
+    }
+  }
+
+  /**
+   * 检查套装购物车库存是否足够
+   * @param {Object} data 套装的购物车数据
+   * @param {Number} buyNum 套装购买数量
+   */
+  async checkSetGoodsNum(data, buyNum) {
+    const { set_id } = data;
+    const setData = await this.setModel.findById(set_id).lean();
+    const { set, single_stock = '1', stock } = setData;
+    const stockList = [];
+    for (const sd of set) {
+      const { shop, goods, spec, set_num } = sd;
+      const shopRes = this.ctx.service.util.trade.checkShop(shop);
+      if (shopRes.result !== true) throw new BusinessError(ErrorCode.DATA_INVALID, shopRes.msg);
+      const goodsRes = this.ctx.service.util.trade.checkGoods(goods);
+      if (goodsRes.result !== true) throw new BusinessError(ErrorCode.DATA_INVALID, goodsRes.msg);
+      if (single_stock === '1') {
+        const goodsSpecData = await this.goodsSpecModel.findById(spec);
+        const setGoodsNum = this.ctx.multiply(buyNum, set_num);
+        const gsRes = this.ctx.service.util.trade.checkGoodsSpec(goodsSpecData, setGoodsNum, '0');
+        if (gsRes.result !== true) throw new BusinessError(ErrorCode.DATA_INVALID, gsRes.msg);
+        const { num: gsnum = 0 } = goodsSpecData;
+        stockList.push(gsnum);
+      }
+    }
+    if (single_stock !== '1') {
+      // 走单独的库存
+      if (this.ctx.minus(stock, buyNum) < 0) throw new BusinessError(ErrorCode.DATA_INVALID, '套装库存不足');
+      stockList.push(stock);
     }
+    const total = _.min(stockList);
+    return { enough: true, total };
   }
 
   /**

+ 52 - 49
app/service/trade/order.js

@@ -341,58 +341,61 @@ class OrderService extends CrudService {
   async getPageData(data, actList) {
     const arr = [];
     for (const i of data) {
-      const { goodsSpec, num, cart_id } = i;
-      const d = await this.goodsSpecModel.aggregate([
-        { $match: { _id: ObjectId(goodsSpec) } },
-        // #region 处理店铺与商品部分
-        { $addFields: { goods_id: { $toObjectId: '$goods' } } },
-        {
-          $lookup: {
-            from: 'goods',
-            localField: 'goods_id',
-            foreignField: '_id',
-            pipeline: [
-              { $addFields: { shop_id: { $toObjectId: '$shop' } } },
-              {
-                $lookup: {
-                  from: 'shop',
-                  localField: 'shop_id',
-                  foreignField: '_id',
-                  pipeline: [{ $project: { name: 1 } }],
-                  as: 'shop',
+      const { goodsSpec, num, cart_id, is_set = '1', set_id } = i;
+      if (is_set === '1' || !set_id) {
+        const d = await this.goodsSpecModel.aggregate([
+          { $match: { _id: ObjectId(goodsSpec) } },
+          // #region 处理店铺与商品部分
+          { $addFields: { goods_id: { $toObjectId: '$goods' } } },
+          {
+            $lookup: {
+              from: 'goods',
+              localField: 'goods_id',
+              foreignField: '_id',
+              pipeline: [
+                { $addFields: { shop_id: { $toObjectId: '$shop' } } },
+                {
+                  $lookup: {
+                    from: 'shop',
+                    localField: 'shop_id',
+                    foreignField: '_id',
+                    pipeline: [{ $project: { name: 1 } }],
+                    as: 'shop',
+                  },
                 },
-              },
-              { $project: { name: 1, file: 1, tag: 1, act_tag: 1, shop: { $first: '$shop' } } },
-            ],
-            as: 'goods',
+                { $project: { name: 1, file: 1, tag: 1, act_tag: 1, shop: { $first: '$shop' } } },
+              ],
+              as: 'goods',
+            },
           },
-        },
-        { $unwind: '$goods' },
-        // #endregion
-        {
-          $project: {
-            _id: 0,
-            shop: '$goods.shop._id',
-            shop_name: '$goods.shop.name',
-            goods_id: '$goods._id',
-            goods_name: '$goods.name',
-            goodsSpec_id: '$_id',
-            goodsSpec_name: '$name',
-            freight: { $toString: '$freight' },
-            sell_money: { $toString: '$sell_money' },
-            leader_price: { $toString: '$leader_price' },
-            num: { $toDouble: num },
-            file: '$goods.file',
-            tags: '$goods.tags',
-            act_tags: '$goods.act_tags',
-            price: { $toDouble: '$sell_money' },
+          { $unwind: '$goods' },
+          // #endregion
+          {
+            $project: {
+              _id: 0,
+              shop: '$goods.shop._id',
+              shop_name: '$goods.shop.name',
+              goods_id: '$goods._id',
+              goods_name: '$goods.name',
+              goodsSpec_id: '$_id',
+              goodsSpec_name: '$name',
+              freight: { $toString: '$freight' },
+              sell_money: { $toString: '$sell_money' },
+              leader_price: { $toString: '$leader_price' },
+              num: { $toDouble: num },
+              file: '$goods.file',
+              tags: '$goods.tags',
+              act_tags: '$goods.act_tags',
+              price: { $toDouble: '$sell_money' },
+            },
           },
-        },
-      ]);
-      let gs = _.head(d);
-      if (gs) gs = JSON.parse(JSON.stringify(gs));
-      if (cart_id) gs.cart_id = cart_id;
-      arr.push(gs);
+        ]);
+        let gs = _.head(d);
+        if (gs) gs = JSON.parse(JSON.stringify(gs));
+        if (cart_id) gs.cart_id = cart_id;
+        arr.push(gs);
+      }
+
     }
     // 检测是否是团长,使用团长价格
     const user = _.get(this.ctx, 'user');

+ 104 - 14
app/service/util/trade.js

@@ -15,6 +15,7 @@ class TradeService extends CrudService {
     this.goodsModel = this.ctx.model.Shop.Goods;
     this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
     this.cartModel = this.ctx.model.Trade.Cart;
+    this.setModel = this.ctx.model.Shop.GoodsSet;
   }
   /**
    * 检查选中的购物车是否符合购买的条件
@@ -51,13 +52,46 @@ class TradeService extends CrudService {
    * @param param.type 订单类型: 0:常规单 1:团购单,需要按团购价计算
    * @param param.group 团id
    * @param param.inviter 邀请用户id 返现部分
+   * @param param.is_set 是否是套装,默认 1 不是套装
+   * @param params.set_id 套装id
    * @param makeCache 生成缓存
    */
   async checkCanBuy(data, makeCache = true) {
     if (!_.isArray(data)) data = [ data ];
     let result = { result: true };
     for (const i of data) {
-      const { shop, goods, goodsSpec, num } = i;
+      const { is_set = '1' } = i;
+      if (is_set === '1') {
+        // 非套装,使用正常的判断
+        result = await this.checkGoodsCanBuy(i);
+      } else {
+        // 套装,使用套装判断
+        result = await this.checkSetCanBuy(i);
+      }
+      if (result.result !== true) break;
+    }
+
+    // #endregion
+    if (result.result && makeCache) {
+      const key = await this.makeOrderKey(data);
+      result.key = key;
+    }
+
+    return result;
+  }
+
+  /**
+   * 套装判断是否可以购买
+   * @param {Object} data 购物车/直接购买 数据对象
+   */
+  async checkSetCanBuy(data) {
+    const { set_id, num } = data;
+    const setData = await this.setModel.findById(set_id).lean();
+    let result = { result: true };
+    const { set, single_stock = '1', stock, is_use } = setData;
+    if (is_use === '1') return { result: false, msg: '套装已下架' };
+    for (const sd of set) {
+      const { shop, goods, spec, set_num } = sd;
       if (!shop) {
         result.result = false;
         result.msg = '缺少店铺信息';
@@ -68,7 +102,7 @@ class TradeService extends CrudService {
         result.msg = '缺少商品信息';
         break;
       }
-      if (!goodsSpec) {
+      if (!spec) {
         result.result = false;
         result.msg = '缺少商品规格信息';
         break;
@@ -92,21 +126,75 @@ class TradeService extends CrudService {
         result = goodsRes;
         break;
       }
-      // 3.检验该规格是否可以购买
-      const goodsSpecData = await this.goodsSpecModel.findById(goodsSpec);
-      const gsRes = this.checkGoodsSpec(goodsSpecData, num);
-      if (gsRes.result !== true) {
-        result = gsRes;
-        break;
+
+      // 3.根据套装库存设置判断是走各个商品库存还是走套装库存
+      if (single_stock === '1') {
+        // 走各个商品库存
+        const goodsSpecData = await this.goodsSpecModel.findById(spec);
+        // 单独计算下商品数量 = 购买数量 * 组成套装的数量
+        const setGoodsNum = this.ctx.multiply(num, set_num);
+        const gsRes = this.checkGoodsSpec(goodsSpecData, setGoodsNum, '0');
+        if (gsRes.result !== true) {
+          result = gsRes;
+          break;
+        }
       }
     }
-
-    // #endregion
-    if (result.result && makeCache) {
-      const key = await this.makeOrderKey(data);
-      result.key = key;
+    if (single_stock !== '1') {
+      // 走单独的库存
+      if (this.ctx.minus(stock, num) < 0) result = { result: false, msg: '套装库存不足' };
     }
+    return result;
+  }
 
+  /**
+   * 常规商品判断是否可以购买
+   * @param {Object} data 购物车/直接购买 数据对象
+   */
+  async checkGoodsCanBuy(data) {
+    let result = { result: true };
+    const { shop, goods, goodsSpec, num } = data;
+    if (!shop) {
+      result.result = false;
+      result.msg = '缺少店铺信息';
+      return result;
+    }
+    if (!goods) {
+      result.result = false;
+      result.msg = '缺少商品信息';
+      return result;
+    }
+    if (!goodsSpec) {
+      result.result = false;
+      result.msg = '缺少商品规格信息';
+      return result;
+    }
+    if (!num) {
+      result.result = false;
+      result.msg = '缺少购买数量';
+      return result;
+    }
+    // 1.检查商店是否正常运行
+    const shopData = await this.shopModel.findById(shop);
+    const shopRes = this.checkShop(shopData);
+    if (shopRes.result !== true) {
+      result = shopRes;
+      return result;
+    }
+    // 2.检查商品是否可以购买
+    const goodsData = await this.goodsModel.findById(goods);
+    const goodsRes = this.checkGoods(goodsData);
+    if (goodsRes.result !== true) {
+      result = goodsRes;
+      return result;
+    }
+    // 3.检验该规格是否可以购买
+    const goodsSpecData = await this.goodsSpecModel.findById(goodsSpec);
+    const gsRes = this.checkGoodsSpec(goodsSpecData, num);
+    if (gsRes.result !== true) {
+      result = gsRes;
+      return result;
+    }
     return result;
   }
   /**
@@ -150,8 +238,9 @@ class TradeService extends CrudService {
    * 检查商品的规格是否满足购买条件
    * @param {Object} goodsSpecData 商品规格的数据
    * @param {Number} num 购买数量
+   * @param {String} is_set 是否是套装;套装不需要检查购买限制
    */
-  checkGoodsSpec(goodsSpecData, num) {
+  checkGoodsSpec(goodsSpecData, num, is_set = '1') {
     const result = { result: true };
     if (!goodsSpecData) {
       result.msg = '未找到商品的指定规格';
@@ -168,6 +257,7 @@ class TradeService extends CrudService {
       result.result = false;
       return result;
     }
+    if (is_set !== '1') return result;
     // 判断是否有购买限制
     if (_.get(goodsSpecData, 'buy_limit', '0') !== '0') {
       // 有购买限制,需要检测购买限制

+ 26 - 1
app/service/view/goods.js

@@ -18,6 +18,7 @@ class GoodsService extends CrudService {
     this.actTagsModel = this.ctx.model.System.ActTags;
     this.goodsConfigModel = this.ctx.model.Shop.GoodsConfig;
     this.userModel = this.ctx.model.User.User;
+    this.setModel = this.ctx.model.Shop.GoodsSet;
   }
   /**
    *
@@ -182,9 +183,34 @@ class GoodsService extends CrudService {
     } else {
       data.specs = data.specs.map(i => _.omit(i, [ 'leader_price' ]));
     }
+    const sets = await this.getGoodsSetList(data);
+    data.sets = sets;
     return data;
   }
 
+  async getGoodsSetList(data) {
+    const goods = _.get(data, 'goods');
+    if (!goods) return [];
+    const goodsSetList = await this.setModel.find({ goods: goods._id, is_use: '0' }).lean();
+    const arr = [];
+    for (const sd of goodsSetList) {
+      const { _id, set = [], sell_money, name } = sd;
+      const obj = { _id, name, sell_money, goods_total: set.length };
+      const newSet = [];
+      for (const s of set) {
+        const { goods, goods_name, spec, spec_name, set_num } = s;
+        const goodsData = await this.goodsModel.findById(goods, { file: 1 }).lean();
+        const specData = await this.goodsSpecModel.findById(spec, { file: 1 }).lean();
+        const file = [ ..._.get(specData, 'file', []), ..._.get(goodsData, 'file', []) ];
+        const newSetData = { goods_name, spec_name, file };
+        newSet.push(newSetData);
+      }
+      obj.set = newSet;
+      arr.push(obj);
+    }
+    return arr;
+  }
+
   async indexGoodsList(condition, { skip = 0, limit = 20 } = {}) {
     condition = this.dealFilter(condition);
     const pipeline = [{ $match: { status: { $ne: '0' } } }]; // { $sort: { sort: 1 } },
@@ -279,7 +305,6 @@ class GoodsService extends CrudService {
         specs = _.orderBy(specs, [ 'leader_price' ], [ 'asc' ]);
         const head = _.head(specs);
         i.leader_price = this.ctx.toNumber(_.get(head, 'leader_price', 0));
-
       }
     }
     const tpipeline = _.cloneDeep(pipeline);