lrf 2 years ago
parent
commit
c3f8be6e09

+ 42 - 0
app/controller/group/config/.group.js

@@ -0,0 +1,42 @@
+module.exports = {
+  create: {
+    requestBody: ['person_limit', 'shop', 'goods', 'goodsSpec', 'leader', 'persons', 'status'],
+  },
+  destroy: {
+    params: ['!id'],
+    service: 'delete',
+  },
+  update: {
+    params: ['!id'],
+    requestBody: ['person_limit', 'shop', 'goods', 'goodsSpec', 'leader', 'persons', 'status'],
+  },
+  show: {
+    parameters: {
+      params: ['!id'],
+    },
+    service: 'fetch',
+  },
+  index: {
+    parameters: {
+      query: {
+        'meta.createdAt@start': 'meta.createdAt@start',
+        'meta.createdAt@end': 'meta.createdAt@end',
+        shop: 'shop',
+        goods: 'goods',
+        goodsSpec: 'goodsSpec',
+        leader: 'leader',
+        status: 'status',
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: 'query',
+    options: {
+      query: ['skip', 'limit'],
+      sort: ['meta.createdAt'],
+      desc: true,
+      count: true,
+    },
+  },
+};

+ 13 - 0
app/controller/group/group.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./config/.group.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 
+class GroupController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.group.group;
+  }
+}
+module.exports = CrudController(GroupController, meta);

+ 3 - 2
app/controller/shop/config/.goodsSpec.js

@@ -1,6 +1,6 @@
 module.exports = {
   create: {
-    requestBody: ['sell_money', 'flow_money', 'freight', 'goods', 'name', 'num', 'status'],
+    requestBody: ['can_group', 'group_config', 'sell_money', 'flow_money', 'freight', 'goods', 'name', 'num', 'status'],
   },
   destroy: {
     params: ['!id'],
@@ -8,7 +8,7 @@ module.exports = {
   },
   update: {
     params: ['!id'],
-    requestBody: ['sell_money', 'flow_money', 'freight', 'goods', 'name', 'num', 'status'],
+    requestBody: ['can_group', 'group_config', 'sell_money', 'flow_money', 'freight', 'goods', 'name', 'num', 'status'],
   },
   show: {
     parameters: {
@@ -24,6 +24,7 @@ module.exports = {
         goods: 'goods',
         name: 'name',
         status: 'status',
+        can_group: 'can_group',
       },
       // options: {
       //   "meta.state": 0 // 默认条件

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

@@ -1,6 +1,6 @@
 module.exports = {
   create: {
-    requestBody: ['customer', 'address', 'goods', 'total_detail', 'buy_time', 'pay', 'no', 'status', 'coupon'],
+    requestBody: ['type', 'group', 'customer', 'address', 'goods', 'total_detail', 'buy_time', 'pay', 'no', 'status', 'coupon'],
   },
   destroy: {
     params: ['!id'],
@@ -8,7 +8,7 @@ module.exports = {
   },
   update: {
     params: ['!id'],
-    requestBody: ['customer', 'address', 'goods', 'total_detail', 'buy_time', 'pay', 'no', 'status'],
+    requestBody: ['type', 'group', 'customer', 'address', 'goods', 'total_detail', 'buy_time', 'pay', 'no', 'status'],
   },
   show: {
     parameters: {
@@ -29,6 +29,8 @@ module.exports = {
         shop: 'goods.shop',
         no: 'no',
         status: 'status',
+        type: 'type',
+        group: 'group',
       },
       // options: {
       //   "meta.state": 0 // 默认条件

+ 3 - 1
app/controller/trade/config/.orderDetail.js

@@ -8,7 +8,7 @@ module.exports = {
   },
   update: {
     params: ['!id'],
-    requestBody: ['remarks', 'order', 'shop', 'customer', 'address', 'no', 'transport', 'goods', 'total_detail', 'buy_time', 'pay_time', 'discount', 'status'],
+    requestBody: ['type', 'group', 'remarks', 'order', 'shop', 'customer', 'address', 'no', 'transport', 'goods', 'total_detail', 'buy_time', 'pay_time', 'discount', 'status'],
   },
   show: {
     parameters: {
@@ -31,6 +31,8 @@ module.exports = {
         'buy_time@start': 'buy_time@start',
         'buy_time@end': 'buy_time@end',
         status: 'status',
+        type: 'type',
+        group: 'group',
       },
       // options: {
       //   "meta.state": 0 // 默认条件

+ 34 - 0
app/model/group/group.js

@@ -0,0 +1,34 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const persons = [{
+  customer: '用户',
+  status: '状态', // 0:入团;1:退团
+  join_time: '入团时间',
+  out_time: '退团时间',
+}];
+// 团表
+const group = {
+  shop: { type: String, required: false, zh: '店铺', ref: 'Shop.Shop' }, //
+  goods: { type: String, required: false, zh: '商品' }, //
+  goodsSpec: { type: String, required: false, zh: '规格' }, //
+  leader: { type: String, required: false, zh: '团长', ref: 'User.User' }, //
+  persons: { type: Array, required: false, zh: '团员', ref: 'User.User' }, //
+  status: { type: String, required: false, default: '0', zh: '状态' }, // 字典:group_status
+  person_limit: { type: Number, required: false, zh: '人数限制' }, //
+};
+const schema = new Schema(group, { toJSON: { getters: true, virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.index({ shop: 1 });
+schema.index({ goods: 1 });
+schema.index({ goodsSpec: 1 });
+schema.index({ leader: 1 });
+schema.index({ status: 1 });
+
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Group', schema, 'group');
+};

+ 3 - 0
app/model/shop/goodsSpec.js

@@ -10,6 +10,8 @@ const goodsSpec = {
   name: { type: String, required: false, zh: '规格名称' }, //
   num: { type: Number, required: false, zh: '库存' }, //
   status: { type: String, required: false, default: '0', zh: '状态' }, // 字典status
+  can_group: { type: String, required: false, zh: '是否可以团购' }, // 字典:tf
+  group_config: { type: Object, required: false, zh: '团购设置' }, // 团购模块的设置:团购金额:money,需要人数:need_person
 };
 const schema = new Schema(goodsSpec, { toJSON: { getters: true, virtuals: true } });
 schema.index({ id: 1 });
@@ -17,6 +19,7 @@ schema.index({ 'meta.createdAt': 1 });
 schema.index({ goods: 1 });
 schema.index({ name: 1 });
 schema.index({ status: 1 });
+schema.index({ can_group: 1 });
 
 schema.plugin(metaPlugin);
 schema.plugin(MoneyPlugin({ zh: '实际销售价格', required: false, key: 'sell_money' }));

+ 7 - 4
app/model/trade/order.js

@@ -9,21 +9,24 @@ const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
  */
 const order = {
   customer: { type: String, required: false, zh: '顾客', ref: 'User.User' }, //
-  address: { type: Object, required: false, zh: '邮寄地址' }, //
-  goods: { type: Array, required: false, zh: '商品' }, // 按店铺分组,快照过来
-  total_detail: { type: Object, required: false, zh: '总金额明细' }, // 优惠,活动;商品总额和运费由商品而来
+  address: { type: Object, required: false, zh: '邮寄地址' }, // 快照
+  goods: { type: Array, required: false, zh: '商品' }, // 按店铺分组,快照过来,带上备注[{店铺信息,商品列表:[{商品规格:{商品信息}}]}]
+  total_detail: { type: Object, required: false, zh: '总金额明细' }, // 优惠,活动;商品总额和运费由商品而来:{货物总价,运费总价,优惠详情:{优惠券id:{规格id:优惠金额}}}
   buy_time: { type: String, required: false, zh: '下单时间' }, //
   no: { type: String, required: false, zh: '订单号' }, //
   status: { type: String, required: false, zh: '订单状态' }, // 字典:order_process
   pay: { type: Object, required: false, zh: '支付数据' }, // 有关支付时间,支付方式,支付订单号内容全都写在这里面
+  type: { type: String, required: false, default: '0', zh: '订单类型' }, // 字典:order_type
+  group: { type: String, required: false, zh: '所属团', ref: 'Group.Group' }, //
 };
 const schema = new Schema(order, { toJSON: { getters: true, virtuals: true } });
 schema.index({ id: 1 });
 schema.index({ 'meta.createdAt': 1 });
 schema.index({ customer: 1 });
 schema.index({ buy_time: 1 });
-schema.index({ pay_id: 1 });
 schema.index({ no: 1 });
+schema.index({ type: 1 });
+schema.index({ group: 1 });
 
 schema.plugin(metaPlugin);
 

+ 11 - 5
app/model/trade/orderDetail.js

@@ -1,10 +1,12 @@
 'use strict';
-const Schema = require('mongoose').Schema;
-const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
 const transport = {
   shop_transport_no: '运单号',
   shop_transport_type: '快递公司类型',
 };
+
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+
 // 订单详情
 const orderDetail = {
   order: { type: String, required: false, zh: '总订单', ref: 'Trade.order' }, //
@@ -12,14 +14,16 @@ const orderDetail = {
   customer: { type: String, required: false, zh: '顾客', ref: 'User.User' }, //
   address: { type: Object, required: false, zh: '邮寄地址' }, //
   no: { type: String, required: false, zh: '订单号' }, //
-  transport: { type: Object, required: false, zh: '快递' }, //
-  goods: { type: Array, required: false, zh: '商品快照清单' }, // 下单时,商品的属性设置
-  total_detail: { type: Object, required: false, zh: '金额明细' }, //
+  transport: { type: Object, required: false, zh: '快递' }, // {no:运单号,type:快递公司编码}
+  goods: { type: Array, required: false, zh: '商品快照清单' }, // 下单时,商品的属性设置:[{商品规格,商品信息}]
+  total_detail: { type: Object, required: false, zh: '金额明细' }, // 本单的:{货物总价,运费总价,优惠详情:{优惠券id:{规格id:优惠金额}}}
   buy_time: { type: String, required: false, zh: '下单时间' }, //
   pay_time: { type: String, required: false, zh: '支付时间' }, //
   discount: { type: Array, required: false, zh: '优惠' }, //
   status: { type: String, required: false, zh: '订单状态' }, // 字典:order_process
   remarks: { type: String, required: false, zh: '订单备注' }, //
+  type: { type: String, required: false, default: '0', zh: '订单类型' }, // 字典:order_type
+  group: { type: String, required: false, zh: '所属团', ref: 'Group.Group' }, //
 };
 const schema = new Schema(orderDetail, { toJSON: { getters: true, virtuals: true } });
 schema.index({ id: 1 });
@@ -32,6 +36,8 @@ schema.index({ no: 1 });
 schema.index({ buy_time: 1 });
 schema.index({ pay_time: 1 });
 schema.index({ status: 1 });
+schema.index({ type: 1 });
+schema.index({ group: 1 });
 
 schema.plugin(metaPlugin);
 

+ 1 - 0
app/router.js

@@ -11,6 +11,7 @@ module.exports = app => {
   require('./z_router/system/index')(app); // 系统部分
   require('./z_router/shop/index')(app); // 店铺部分
   require('./z_router/trade/index')(app); // 交易部分
+  require('./z_router/group/index')(app); // 团购部分
 
   require('./z_router/view/index')(app); // 视图部分
   require('./z_router/util')(app); // 工具接口

+ 101 - 0
app/service/group/group.js

@@ -0,0 +1,101 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+const moment = require('moment');
+
+//
+class GroupService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'group');
+    this.model = this.ctx.model.Group.Group;
+    this.goodsModel = this.ctx.model.Shop.Goods;
+    this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
+  }
+
+  /**
+   * 生成团
+   * @param {Object} orderDetail 订单详情数据
+   * @param {Transaction} tran 数据库事务实例
+   */
+  async create(orderDetail, tran) {
+    const { goods: goodsInfo, customer, shop } = orderDetail;
+    const goods_id = _.get(goodsInfo, 'goods._id');
+    const goods = await this.goodsModel.findById(goods_id);
+    const goodsSpec_id = _.get(goodsInfo, '_id');
+    const goodsSpec = await this.goodsSpecModel.findById(goodsSpec_id);
+    const person_limit = _.get(goodsSpec, 'group_config.need_person');
+    const leader = customer;
+    const persons = [{ customer, status: '0', join_time: moment().format('YYYY-MM-DD HH:mm:ss') }];
+    const obj = { shop, goods, goodsSpec, leader, persons, person_limit };
+    const id = tran.insert('Group', obj);
+    return id;
+  }
+
+  /**
+   * 加入团
+   * @param {String} customer 用户id
+   * @param {String} group_id 团id
+   * @param {Transaction} tran 数据库事务实例
+   */
+  async join(customer, group_id, tran) {
+    const result = await this.checkGroupCanJoin({ id: group_id });
+    if (!result.result) throw new BusinessError(ErrorCode.DATA_INVALID, result.msg);
+    const data = await this.model.findById(group_id);
+    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到团信息');
+    const { persons = [], person_limit } = data;
+    const nps = JSON.parse(JSON.stringify(persons));
+    nps.push({ customer, status: '0', join_time: moment().format('YYYY-MM-DD HH:mm:ss') });
+    const updateData = { persons: nps };
+    if (person_limit <= nps.length) updateData.status = '1';
+    tran.update('Group', group_id, updateData);
+  }
+
+  // 检查是否可以加入团
+  async checkGroupCanJoin({ id }) {
+    const data = await this.model.findById(id);
+    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到团数据');
+    const { person_limit, persons = [], status } = data;
+    if (status === '1') return { result: false, msg: '当前团已结束' };
+    else if (status === '-1') return { result: false, msg: '当前团已关闭' };
+    // 为0是正常的团员
+    const realPersons = persons.filter(f => f.status === '0');
+    if (realPersons.length < person_limit) return { result: true };
+    return { result: false, msg: '当前参团人数已足够' };
+  }
+
+  /**
+   * 团购退货(订单取消&售后退款/退货)
+   * @param {Object} params 参数
+   * @param params.group 团id
+   * @param params.customer 用户id
+   * @param tran
+   */
+  async refund({ group, customer }, tran) {
+    const groupData = await this.model.findById(group);
+    if (!groupData) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到拼团数据');
+    const { persons, leader } = groupData;
+    const newPersons = JSON.parse(JSON.stringify(persons));
+    const findPersonCondition = (c1, c2) => c1 === c2;
+    const p = newPersons.find(f => findPersonCondition(f.customer, customer));
+    if (!p) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到要退团的用户');
+    const i = newPersons.findIndex(f => findPersonCondition(f.customer, customer));
+    p.status = '1';
+    newPersons[i] = p;
+    const updateData = { newPersons };
+    // 团长退团了,根据入团时间降序,顺位成为团长
+    if (leader === customer) {
+      let newLeader;
+      const orderList = _.orderBy(newPersons, [ 'status', 'join_time' ], [ 'asc', 'asc' ]);
+      const head = _.head(orderList);
+      if (head && head.status === '0') {
+        newLeader = _.get(head, 'customer');
+        updateData.leader = newLeader;
+      }
+    }
+    tran.update('Group', group, updateData);
+  }
+}
+
+module.exports = GroupService;

+ 41 - 36
app/service/trade/afterSale.js

@@ -18,8 +18,6 @@ class AfterSaleService extends CrudService {
   async create({ order_detail, goods_id, ...others }) {
     const orderDetail = await this.orderDetailModel.findById(order_detail);
     if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
-    // 查看订单是否签收
-
     // 查看该商品是否已经申请售后
     const hasData = await this.model.count({ order_detail, 'goods._id': goods_id, type: [ '0', '1', '2', '3' ] });
     if (hasData > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '该商品已有正在处理中的售后申请.请勿重复申请');
@@ -29,7 +27,6 @@ class AfterSaleService extends CrudService {
     const { shop, customer } = orderDetail;
     const apply_time = moment().format('YYYY-MM-DD HH:mm:ss');
     const obj = { order_detail, customer, shop, goods, ...others, apply_time, status: '0' };
-    console.log(obj);
     await this.model.create(obj);
   }
 
@@ -51,7 +48,7 @@ class AfterSaleService extends CrudService {
       await this.tran.run();
       const type = _.get(entity, 'type');
       const status = _.get(update, 'status');
-      // 同意退款,则直接进行退款,然后再将状态修改为已退款
+      // 同意退款/退货,则直接进行退款,然后再将状态修改为已退款
       if (type !== '2' && status === '1') {
         await this.toRefund({ afterSale_id: entity._id, goods_id: _.get(entity, 'goods._id') }, this.tran);
       }
@@ -80,24 +77,17 @@ class AfterSaleService extends CrudService {
     const { populate } = this.ctx.service.trade.orderDetail.getRefMods();
     const orderDetail = await this.orderDetailModel.findById(data.order_detail).populate(populate);
     if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后信息的订单');
-    // 计算商品原价,运费
-    let money = this.ctx.multiply(_.get(data, 'goods.sell_money'), _.get(data, 'goods.buy_num'));
-    const freight = this.ctx.multiply(_.get(data, 'goods.buy_num'), _.get(data, 'goods.freight'));
-    money = this.ctx.plus(money, freight);
     const reason = _.get(data, 'desc');
     const order_no = _.get(orderDetail, 'order.pay.pay_no');
     if (!order_no) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单号');
-
-    // 查查优惠中,是否有这个商品:将这个商品的每一个优惠券的优惠部分都加一起,然后用原价-优惠价=实付价
-    const discount_detail = _.get(orderDetail, 'total_detail.discount_detail');
-    if (discount_detail) {
-      let discountMoney = 0;
-      for (const uc_id in discount_detail) {
-        const detail = discount_detail[uc_id];
-        discountMoney = this.ctx.plus(discountMoney, detail[goods_id]);
-      }
-      money = this.ctx.minus(money, discountMoney);
-    }
+    // 用工具函数,获取退货商品的实际支付价格(常规/团购)
+    const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
+    const goodsMoneyDetail = _.get(moneyDetail, goods_id, {});
+    const type = _.get(orderDetail, 'type', '0');
+    let priceKey;
+    if (type === '1') priceKey = 'ggrp';
+    else priceKey = 'grp';
+    const money = _.get(goodsMoneyDetail, priceKey, 0);
 
     // 取出商品输入的价格
     const needRefund = _.get(data, 'money');
@@ -109,7 +99,6 @@ class AfterSaleService extends CrudService {
       // 部分退,部分退是不退优惠券的
       refundMoney = needRefund;
     }
-
     // 找下当前这个订单有多少次售后记录
     let num = await this.model.count({ order_detail: data.order_detail });
     num += 2;
@@ -122,8 +111,15 @@ class AfterSaleService extends CrudService {
       if (res.errcode && res.errcode !== 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, res.errmsg);
     }
     if (data.status === '1') tran.update('AfterSale', afterSale_id, { status: '-1' });
-    await tran.run();
-
+    // #region 团购部分
+    const status = _.get(orderDetail, 'status');
+    // 团购单,且未收货的单子,才需要走退团逻辑,否则不需要走退团逻辑
+    if (type === '1' && status !== '3') {
+      // 团购单,走团购退货逻辑补充
+      const { group, customer } = orderDetail;
+      await this.ctx.service.group.group.refund({ group, customer }, tran);
+    }
+    // #endregion
     // 检查优惠券是否都退了
     //  查看支付订单-优惠明细中,该优惠券影响的商品是否都有退款/退货成功的记录,且退款成功的记录为退全款的.退部分是不退优惠券的
     // 如果有,就说明该优惠券可以退了,没有影响任何一单了
@@ -214,23 +210,32 @@ class AfterSaleService extends CrudService {
     // 查询要退的订单
     const orderDetail = await this.orderDetailModel.findById(order_detail);
     if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
-    const { goods, customer } = orderDetail;
+    const { customer } = orderDetail;
     const basic = { order_detail, customer, type: '1', desc };
-    const discount_detail = _.get(orderDetail, 'total_detail.discount_detail', {});
-    // 组织数据
-    for (const g of goods) {
-      let money = this.ctx.multiply(g.buy_num, g.sell_money);
-      let dmt = 0;
-      for (const dd in discount_detail) {
-        const detail = _.get(discount_detail, dd, {});
-        const dm = _.get(detail, g._id);
-        dmt = this.ctx.plus(dmt, dm);
-      }
-      money = this.ctx.minus(money, dmt);
-      if (money <= 0) money = 0;
-      const obj = { ...basic, goods_id: g._id, money };
+    const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
+    let priceKey;
+    if (_.get(orderDetail, 'type') === '1') priceKey = 'ggrp';
+    else priceKey = 'grp';
+    for (const goods_id in moneyDetail) {
+      const d = _.get(moneyDetail, goods_id, {});
+      const money = _.get(d, priceKey, 0);
+      const obj = { ...basic, goods_id, money };
       await this.create(obj);
     }
+    // 组织数据
+    // for (const g of goods) {
+    //   let money = this.ctx.multiply(g.buy_num, g.sell_money);
+    //   let dmt = 0;
+    //   for (const dd in discount_detail) {
+    //     const detail = _.get(discount_detail, dd, {});
+    //     const dm = _.get(detail, g._id);
+    //     dmt = this.ctx.plus(dmt, dm);
+    //   }
+    //   money = this.ctx.minus(money, dmt);
+    //   if (money <= 0) money = 0;
+    //   const obj = { ...basic, goods_id: g._id, money };
+    //   await this.create(obj);
+    // }
   }
 
   async fetch(filter) {

+ 37 - 34
app/service/trade/order.js

@@ -32,7 +32,7 @@ class OrderService extends CrudService {
       const user = this.ctx.user;
       const customer = _.get(user, '_id');
       if (!customer) throw new BusinessError(ErrorCode.NOT_LOGIN, '未找到用户信息');
-      const { address, goods, total_detail, coupon = [] } = body;
+      const { address, goods, total_detail, coupon = [], type = '0', group } = body;
       if (coupon.length > 1) throw new BusinessError(ErrorCode.DATA_INVALID, '目前只允许使用1张优惠券');
       // 检测商品是否可以下单
       for (const i of goods) {
@@ -58,17 +58,17 @@ class OrderService extends CrudService {
         const qp = [];
         for (const g of goodsList) {
           const { goodsSpec_id, num, cart_id } = g;
-          let d = await this.goodsSpecModel.findById(goodsSpec_id).populate(populate);
-          if (!d) continue;
-          d = JSON.parse(JSON.stringify(d));
+          let goodsSpec = await this.goodsSpecModel.findById(goodsSpec_id).populate(populate);
+          if (!goodsSpec) continue;
+          goodsSpec = JSON.parse(JSON.stringify(goodsSpec));
           // 将商店内容剔除
-          const { goods: gd, ...dOthers } = d;
+          const { goods: gd, ...dOthers } = goodsSpec;
           const gdOthers = _.omit(gd, [ 'shop' ]);
           const obj = { ...dOthers, goods: gdOthers, buy_num: num, tags: _.get(gdOthers, 'tags') };
           if (cart_id) obj.cart_id = cart_id;
           qp.push(obj);
 
-          goodsSpecs.push({ id: goodsSpec_id, buy_num: num, sell_money: obj.sell_money });
+          goodsSpecs.push({ id: goodsSpec_id, buy_num: num, sell_money: obj.sell_money, group_sell_money: _.get(obj, 'group_config.money'), type });
         }
         goodsData.push({ ...others, goods: qp });
       }
@@ -89,13 +89,19 @@ class OrderService extends CrudService {
       orderData.no = `${moment().format('YYYYMMDDHHmmss')}-${str}`;
       // 4.状态
       orderData.status = '0';
+      // #region 是否是团购
+      orderData.type = type;
+      if (type === '1' || group) {
+        orderData.group = group;
+      }
+      // #endregion
+
       // 生成数据
-      // const order = await this.model.create(orderData);
       const order_id = this.tran.insert('Order', orderData);
       // 处理库存,删除购物车
       await this.dealGoodsNum(goodsData);
       // 处理优惠券,改为使用过
-      await this.ctx.service.user.userCoupon.useCoupon(coupon, this.tran);
+      if (coupon.length > 1) await this.ctx.service.user.userCoupon.useCoupon(coupon, this.tran);
       await this.tran.run();
       return order_id;
     } catch (error) {
@@ -204,9 +210,14 @@ class OrderService extends CrudService {
     // 找到默认地址
     const address = await this.addressModel.findOne({ customer, is_default: '1' });
     pageData.address = address;
+    // #region 团购部分
+    const { type, group } = data;
+    pageData.type = type; // 新添字段,订单类型
+    pageData.group = group; // 新添字段,团购的团id
+    // #endregion
     // 商品总价,各店铺的价格明细
-    specsData = this.computedShopTotal(specsData);
-    const shopTotalDetail = this.computedAllTotal(specsData);
+    specsData = this.ctx.service.util.order.makeOrder_computedShopTotal(specsData, type);
+    const shopTotalDetail = this.ctx.service.util.order.makerOrder_computedOrderTotal(specsData);
     pageData.goodsData = specsData;
     pageData.orderTotal = shopTotalDetail;
     // 优惠券列表
@@ -215,29 +226,6 @@ class OrderService extends CrudService {
     // 查询优惠券列表
     return pageData;
   }
-  /**
-   * 计算商店的结算
-   * @param {Array} list 按店铺分组的商品列表
-   */
-  computedShopTotal(list) {
-    for (const i of list) {
-      i.goods_total = i.goods.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(n.money, n.num)), 0);
-      i.freight_total = i.goods.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(n.freight, n.num)), 0);
-    }
-    return list;
-  }
-
-  /**
-   * 计算订单的结算
-   * @param {Array} list 按店铺分组的商品列表
-   */
-  computedAllTotal(list) {
-    const obj = {
-      goods_total: list.reduce((p, n) => this.ctx.plus(p, n.goods_total), 0),
-      freight_total: list.reduce((p, n) => this.ctx.plus(p, n.freight_total), 0),
-    };
-    return obj;
-  }
 
   /**
    * 单商品整理数据,剩下的可以简略
@@ -261,6 +249,10 @@ class OrderService extends CrudService {
       goods.num = num;
       goods.file = _.get(i.goods, 'file');
       goods.tags = _.get(i.goods, 'tags');
+      // #region 团购部分
+      // 团购价
+      goods.group_sell_money = _.get(i, 'group_config.money');
+      // #endregion
       obj.goods = [ goods ];
       arr.push(obj);
     }
@@ -292,6 +284,10 @@ class OrderService extends CrudService {
         goods.num = _.get(i, 'num');
         goods.file = _.get(i.goods, 'file', []);
         goods.tags = _.get(i.goods, 'tags');
+        // #region 团购部分
+        // 团购价
+        goods.group_sell_money = _.get(i.goodsSpec, 'group_config.money');
+        // #endregion
         goodsList.push(goods);
       }
       obj.goods = goodsList;
@@ -304,7 +300,14 @@ class OrderService extends CrudService {
     data = JSON.parse(JSON.stringify(data));
     for (const i of data) {
       const { goods } = i;
-      const buy_num_total = goods.reduce((p, n) => this.ctx.plus(p, n.goods.reduce((np, ng) => this.ctx.plus(np, ng.buy_num), 0)), 0);
+      const buy_num_total = goods.reduce(
+        (p, n) =>
+          this.ctx.plus(
+            p,
+            n.goods.reduce((np, ng) => this.ctx.plus(np, ng.buy_num), 0)
+          ),
+        0
+      );
       i.buy_num_total = buy_num_total;
       i.real_pay = _.get(i, 'pay.pay_money');
     }

+ 20 - 6
app/service/trade/orderDetail.js

@@ -33,22 +33,20 @@ class OrderDetailService extends CrudService {
    */
   async create({ order_id }, tran) {
     assert(order_id, '缺少支付订单信息');
-    // if (!tran) throw new BusinessError(ErrorCode.DATA_INVALID, '支付成功添加积分:缺少事务参数');
     const order = await this.orderModel.findById(order_id);
     if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单的数据');
-    const { customer, address, goods, no, status, buy_time, pay, total_detail: otd } = order;
+    const { customer, address, goods: shopGoods, no, status, buy_time, pay, total_detail: otd } = order;
     if (status === '0') throw new BusinessError(ErrorCode.DATA_INVALID, '订单未支付');
     const orderDetailData = { customer, address, order: order_id, buy_time, pay_time: _.get(pay, 'pay_time'), status };
-    // 补全 shop, goods, total_detail;
-    // const arr = [];
+    const shopTotals = this.ctx.service.util.order.shopMoneyDetail(order);
     // 分订单计数器
     let noTimes = 1;
-    for (const s of goods) {
+    for (const s of shopGoods) {
       const shop = _.get(s, 'shop');
       const remarks = _.get(s, 'remarks');
       const goodsList = _.get(s, 'goods', []);
       const detailNo = `${no}-${noTimes}`;
-      const total_detail = this.getTotalDetail(goodsList);
+      const total_detail = _.get(shopTotals, shop, 0);
       // 优惠部分分割
       if (_.get(otd, 'discount_detail')) {
         // 如果有优惠部分,那就得找,优惠里面有没有对应的商品规格
@@ -57,6 +55,22 @@ class OrderDetailService extends CrudService {
       }
       noTimes++;
       const obj = { ...orderDetailData, shop, goods: goodsList, no: detailNo, total_detail, remarks };
+      // #region 团购
+      const type = _.get(order, 'type');
+      if (type === '1') {
+        // 说明是团购,需要找订单中有没有团的id
+        let group = _.get(order, 'group');
+        if (!group) {
+          // 需要创建团,这是团长支付的单子,开团
+          group = await this.ctx.service.group.group.create(obj, tran);
+        } else {
+          // 需要参团操作,这是团员的单子,将人加入团中
+          await this.ctx.service.group.group.join(obj, tran);
+        }
+        obj.type = '1';
+        obj.group = group;
+      }
+      // #endregion
       tran.insert('OrderDetail', obj);
       // arr.push(obj);
     }

+ 3 - 3
app/service/trade/pay.js

@@ -52,7 +52,7 @@ class PayService extends CrudService {
     const arr = no.split('-');
     // 订单中pay的信息
     const payObject = { pay_type: type, pay_no: `${_.last(arr)}-${str}-${rePayTimes}` };
-    const totalMoney = this.getOrderNeedPay(order);
+    const totalMoney = this.ctx.service.util.order.payOrder_RealPay(order);
     payObject.pay_money = totalMoney;
     let payData;
     let res;
@@ -108,7 +108,7 @@ class PayService extends CrudService {
    */
   async callBackPayOrder({ result }) {
     const { out_trade_no, payer } = result;
-    // TODO: 没有需要报警,没有订单号就出问题了
+    // 没有需要报警,没有订单号就出问题了
     if (!out_trade_no) {
       console.error('没有支付订单号');
       return;
@@ -116,7 +116,7 @@ class PayService extends CrudService {
     const openid = _.get(payer, 'openid');
     const query = { 'pay.pay_no': new RegExp(`${out_trade_no}`), 'pay.openid': openid };
     let orderData = await this.orderModel.findOne(query);
-    // TODO: 没找到订单也是有问题的,需要报警
+    // 没找到订单也是有问题的,需要报警
     if (!orderData) {
       console.error('没有找到订单');
       return;

+ 22 - 10
app/service/user/userCoupon.js

@@ -235,19 +235,19 @@ class UserCouponService extends CrudService {
     const goods_total = shopGoods.reduce((p, n) => this.ctx.plus(this.ctx.plus(p, n.goods_total), n.freight_total), 0);
     if (this.ctx.minus(goods_total, limit) >= 0) return true;
     return false;
-
   }
   /**
    * 计算订单使用优惠券的明细
    * @param {Array} couponIds 订单使用的用户领取的优惠券数据id数组
-   * @param {Array} goodsSpecs 订单所有商品规格的数据:{id,buy_num}
+   * @param {Array} goodsSpecs 订单所有商品规格的数据:{id,buy_num,sell_money,group_sell_money}
+   * @param {String} type 订单类型:0:常规单;1:团购单; 常规单,用销售金额算,团购单用团购金额算
    */
-  async computedOrderCouponDiscount(couponIds, goodsSpecs) {
+  async computedOrderCouponDiscount(couponIds, goodsSpecs, type = '0') {
     const result = {}; // 最终结果: 以 couponId: { goodsSpecId: ${money} } 为明细格式
     const goodsSpecIds = goodsSpecs.map(i => i.id);
     const { populate: gp } = this.ctx.service.shop.goodsSpec.getRefMods();
     let goodsSpecList = await this.goodsSpecModel.find({ _id: goodsSpecIds }).populate(gp);
-    goodsSpecList = this.resetGoodsSpecData(goodsSpecList, goodsSpecs);
+    goodsSpecList = this.resetGoodsSpecData(goodsSpecList, goodsSpecs, type);
     const { populate: cp } = this.getRefMods();
     let userCouponList = await this.model.find({ _id: couponIds }, { customer: 0 }).populate(cp);
     // 先查下有没有不能用的,有不能用的需要返回
@@ -294,7 +294,7 @@ class UserCouponService extends CrudService {
       // 有最低消费的限制,就需要看看下满足条件的商品够不够这个金额
       // 消费下限 大于 满足优惠券使用的商品的消费总额,则不能使用这个券,消费的钱不够
       // 且这个地方应该报错
-      if (parseFloat(limit) > goodsTotal) throw new BusinessError(ErrorCode.DATA_INVALID, `消费券:${coupon.name}不满足使用条件`);
+      if (this.ctx.minus(limit, goodsTotal) > 0) throw new BusinessError(ErrorCode.DATA_INVALID, `消费券:${coupon.name}不满足使用条件`);
     }
     // 减免设置转换成 实际减免总金额:
     // 满减则直接就是 min(设置的满x减y的y);
@@ -304,21 +304,20 @@ class UserCouponService extends CrudService {
     let discountRealMoney = 0;
     let outLimit = true; // 是否超过金额上限
     //
-    if (discount_type === 'min') discountRealMoney = parseFloat(min);
+    if (discount_type === 'min') discountRealMoney = this.ctx.toNumber(min);
     else {
       // 需要计算是否超过上限
       // 没有上限,那就不会超过
       if (!max) outLimit = false;
       else {
         // 有上限,则需要计算
-        // const percent = 1 - parseFloat(min) / 10;
         const percent = this.ctx.minus(1, this.ctx.divide(min, 10));
         // 计算实际折扣 减了 多少钱
         const discountMoney = this.ctx.multiply(goodsTotal, percent);
         // 最后看下 不限制减的钱 是不是 超过了 上限
         outLimit = this.ctx.minus(discountMoney, max) > 0;
         // 超过了,就将上限的金额拿去按比例分配
-        if (outLimit) discountRealMoney = parseFloat(max);
+        if (outLimit) discountRealMoney = this.ctx.toNumber(max);
       }
     }
 
@@ -399,8 +398,12 @@ class UserCouponService extends CrudService {
    * 将goods,shop和goodsSpec重组为3个属性在object同一级上
    * @param {Array} list 商品规格列表
    * @param {Array} orderInfo 有关订单的信息 {id,buy_num}
+   * @param {String} type 订单类型:0:常规单-sell_money;1:团购单,group_config.money
    */
-  resetGoodsSpecData(list, orderInfo) {
+  resetGoodsSpecData(list, orderInfo, type) {
+    let priceKey;
+    if (type === '1') priceKey = 'group_config.money';
+    else priceKey = 'sell_money';
     const nouse = [ 'meta', '__v' ];
     const arr = list.map(i => {
       const obj = {};
@@ -413,7 +416,16 @@ class UserCouponService extends CrudService {
       const r = orderInfo.find(f => ObjectId(f.id).equals(goodsSpec._id));
       if (r) obj.buy_num = r.buy_num;
       if (obj.buy_num) {
-        obj.goods_total = this.ctx.multiply(obj.buy_num, _.get(obj.goodsSpec, 'sell_money'));
+        // #region 团购部分
+        const group_sell_money = _.get(obj.goodsSpec, 'group_config.money');
+        if (group_sell_money) {
+          obj.group_sell_money = group_sell_money;
+          obj.group_goods_total = this.ctx.multiply(obj.buy_num, group_sell_money);
+        }
+        // #endregion
+        // 计算 常规/团购 商品总价
+        obj.goods_total = this.ctx.multiply(obj.buy_num, _.get(obj.goodsSpec, priceKey));
+
       }
       return obj;
     });

+ 151 - 0
app/service/util/order.js

@@ -0,0 +1,151 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+//
+class OrderService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'order');
+    this.orderModel = this.ctx.model.Trade.Order;
+  }
+
+  // #region 下单前计算函数
+  /**
+   * 计算每个店铺的价格
+   * @param {Array} list 按店铺分组的商品列表
+   * list是经过 setCartsGoodsToPageData/setGoodsToPageData 处理过的数据
+   * @param {String} type 订单类型码: 0常规单,1团购单
+   */
+  makeOrder_computedShopTotal(list, type = '0') {
+    let sell_money_key;
+    if (type === '1') sell_money_key = 'group_sell_money';
+    else sell_money_key = 'money';
+    for (const i of list) {
+      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);
+    }
+    return list;
+  }
+  /**
+   * 计算整个订单的价格
+   * @param {Array} list 按店铺分组的商品列表
+   * list是经过 setCartsGoodsToPageData/setGoodsToPageData 处理过的数据
+   */
+  makerOrder_computedOrderTotal(list) {
+    const obj = {
+      goods_total: list.reduce((p, n) => this.ctx.plus(p, n.goods_total), 0),
+      freight_total: list.reduce((p, n) => this.ctx.plus(p, n.freight_total), 0),
+    };
+    return obj;
+  }
+
+  // #endregion
+
+  /**
+   * 计算需要支付的金额
+   * @param {Object} order 支付订单信息
+   * @return {Number} 订单实付金额
+   */
+  payOrder_RealPay(order) {
+    let priceKey;
+    if (_.get(order, 'type', '0') === '1') priceKey = 'ggrp';
+    else priceKey = 'grp';
+    const detail = this.moneyDetail(order);
+    // 解除店铺层
+    const sd = Object.values(detail);
+    // 取出规格层
+    const sgd = sd.map(i => Object.values(i));
+    // 将规格明细降维至一维
+    const oneLevel = _.flattenDeep(sgd);
+    // 根据订单类型,计算应付(优惠券部分已经按订单类型计算并分配完了.这地方只是复现)
+    const realPay = oneLevel.reduce((p, n) => this.ctx.plus(p, n[priceKey]), 0);
+    return realPay;
+  }
+  /**
+   * 计算需要支付的金额
+   * @param {Object} order 支付订单信息
+   * @return {Object} 返回:{
+   *  店铺id:金额
+   * }
+   */
+  shopMoneyDetail(order) {
+    let priceKey;
+    if (_.get(order, 'type', '0') === '1') priceKey = 'ggrp';
+    else priceKey = 'grp';
+    const detail = this.moneyDetail(order);
+    const result = {};
+    for (const s in detail) {
+      const values = Object.values(_.get(detail, s, {}));
+      const realPay = values.reduce((p, n) => this.ctx.plus(p, n[priceKey]), 0);
+      result[s] = realPay;
+    }
+    return result;
+  }
+  /**
+   * 按店铺-商品 计算该订单的价格明细
+   * @param {Object} data 支付订单数据
+   * @return {Object} 返回:{
+   *  店铺id:{
+   *    规格id:{
+   *      sm: 规格正常销售价格(sell_money),
+   *      f: 规格运费(freight),
+   *      bn: 购买数量(buy_num),
+   *      st: 规格正常销售总价(sell_total: sm * bn)
+   *      ft: 规格运费总价(freight_total: f * bn)
+   *      gt: 商品支付原价(goods_total: st + ft)
+   *      dd: {
+   *        key:优惠券id,
+   *        value:优惠价格
+   *      },
+   *      dt: 优惠总价(d_detail的value之和)
+   *      grp:商品实际支付价格(goods_real_pay: gt - dt)
+   *      gsm:规格团购销售价格(group_sell_money)
+   *      gst: 规格团购销售总价(group_sell_total: gsm * bn)
+   *      ggt: 商品团购支付原价(goods_group_total: gst + ft)
+   *      ggrp: 商品团购实际支付价格(goods_group_real_pay: ggt - dt)
+   *    }
+   *  }
+   * }
+   */
+  moneyDetail(data) {
+    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    // 优惠部分
+    const ddt = _.get(data, 'total_detail.discount_detail', {});
+    // 店铺规格商品数据
+    const shopGoods = _.get(data, 'goods', []);
+    const result = {};
+    for (const s of shopGoods) {
+      const { goods, shop } = s;
+      const shopResult = {};
+      for (const g of goods) {
+        const { sell_money: sm, freight: f, buy_num: bn, group_config, _id } = g;
+        const st = this.ctx.multiply(sm, bn);
+        const ft = this.ctx.multiply(f, bn);
+        const gt = this.ctx.plus(st, ft);
+        const dd = {};
+        for (const uc_id in ddt) {
+          const detail = _.get(ddt, uc_id, {});
+          const value = detail[_id];
+          if (value) dd[uc_id] = value;
+        }
+        const dt = Object.values(dd).reduce((p, n) => this.ctx.plus(p, n), 0);
+        const grp = this.ctx.minus(gt, dt);
+        let obj = { sm, f, bn, st, ft, gt, dd, dt, grp };
+        const gsm = _.get(group_config, 'money');
+        if (gsm) {
+          const gst = this.ctx.multiply(gsm, bn);
+          const ggt = this.ctx.plus(gst, ft);
+          const ggrp = this.ctx.minus(ggt, dt);
+          obj = { ...obj, gsm, gst, ggt, ggrp };
+        }
+        shopResult[_id] = obj;
+      }
+      result[shop] = shopResult;
+    }
+    return result;
+  }
+}
+
+module.exports = OrderService;

+ 64 - 18
app/service/util/orderDetail.js

@@ -8,31 +8,77 @@ const assert = require('assert');
 class OrderDetailService extends CrudService {
   constructor(ctx) {
     super(ctx, 'orderdetail');
+    this.orderDetailModel = this.ctx.model.Trade.OrderDetail;
   }
   /**
    * 计算订单详情实际支付金额
    * @param {Object} data 订单详情数据
    * @return {Number} 实际支付金额
    */
-  async computedRealPay(data) {
-    const { goods = [], total_detail = {} } = data;
-    let total = goods.reduce((p, n) => {
-      const price = this.ctx.plus(n.sell_money, n.freight);
-      const goodsTotal = this.ctx.multiply(price, n.buy_num);
-      return this.ctx.plus(p, goodsTotal);
-    }, 0);
-    let dmt = 0;
-    const discount_detail = _.get(total_detail, 'discount_detail', {});
-    for (const uc_id in discount_detail) {
-      const detail = _.get(discount_detail, uc_id, {});
-      const values = Object.values(detail);
-      const dm = values.reduce((p, n) => this.ctx.plus(p, n), 0);
-      dmt = this.ctx.plus(dmt, dm);
-
+  computedRealPay(data) {
+    let priceKey;
+    if (_.get(data, 'type', '0') === '1') priceKey = 'ggrp';
+    else priceKey = 'grp';
+    let detail = this.moneyDetail(data);
+    detail = Object.values(detail);
+    const realPay = detail.reduce((p, n) => this.ctx.plus(p, _.get(n, priceKey, 0)), 0);
+    return realPay;
+  }
+  /**
+   * 按商品计算该订单详情的价格明细
+   * @param {Object} data 订单详情数据
+   * @return {Object} 返回{
+   *  规格id:{
+   *    sm: 规格正常销售价格(sell_money),
+   *    f: 规格运费(freight),
+   *    bn: 购买数量(buy_num),
+   *    st: 规格正常销售总价(sell_total: sm * bn)
+   *    ft: 规格运费总价(freight_total: f * bn)
+   *    gt: 商品支付原价(goods_total: st + ft)
+   *    dd: {
+   *      key:优惠券id,
+   *      value:优惠价格
+   *    },
+   *    dt: 优惠总价(d_detail的value之和)
+   *    grp:商品实际支付价格(goods_real_pay: gt - dt)
+   *    gsm:规格团购销售价格(group_sell_money)
+   *    gst: 规格团购销售总价(group_sell_total: gsm * bn)
+   *    ggt: 商品团购支付原价(goods_group_total: gst + ft)
+   *    ggrp: 商品团购实际支付价格(goods_group_real_pay: ggt - dt)
+   *
+   *  }
+   * }
+   */
+  moneyDetail(data) {
+    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    // 优惠部分
+    const ddt = _.get(data, 'total_detail.discount_detail', {});
+    const goods = _.get(data, 'goods', []);
+    const result = {};
+    for (const g of goods) {
+      const { sell_money: sm, freight: f, buy_num: bn, group_config, _id } = g;
+      const st = this.ctx.multiply(sm, bn);
+      const ft = this.ctx.multiply(f, bn);
+      const gt = this.ctx.plus(st, ft);
+      const dd = {};
+      for (const uc_id in ddt) {
+        const detail = _.get(ddt, uc_id, {});
+        const value = detail[_id];
+        if (value) dd[uc_id] = value;
+      }
+      const dt = Object.values(dd).reduce((p, n) => this.ctx.plus(p, n), 0);
+      const grp = this.ctx.minus(gt, dt);
+      let obj = { sm, f, bn, st, ft, gt, dd, dt, grp };
+      const gsm = _.get(group_config, 'money');
+      if (gsm) {
+        const gst = this.ctx.multiply(gsm, bn);
+        const ggt = this.ctx.plus(gst, ft);
+        const ggrp = this.ctx.minus(ggt, dt);
+        obj = { ...obj, gsm, gst, ggt, ggrp };
+      }
+      result[_id] = obj;
     }
-    total = this.ctx.minus(total, dmt);
-    if (total <= 0) total = 0;
-    return total;
+    return result;
   }
 }
 

+ 13 - 2
app/service/util/trade.js

@@ -48,9 +48,11 @@ class TradeService extends CrudService {
    * @param param.goods 商品id
    * @param param.goodsSpec 商品规格id
    * @param param.num 购买数量
+   * @param param.type 订单类型: 0:常规单 1:团购单,需要按团购价计算
+   * @param param.group 团id
    * @param makeCache 生成缓存
    */
-  async checkCanBuy({ shop, goods, goodsSpec, num }, makeCache = true) {
+  async checkCanBuy({ shop, goods, goodsSpec, num, type = '0', group }, makeCache = true) {
     assert(shop, '缺少店铺信息');
     assert(goods, '缺少商品信息');
     assert(goodsSpec, '缺少商品规格信息');
@@ -68,8 +70,17 @@ class TradeService extends CrudService {
     const goodsSpecData = await this.goodsSpecModel.findById(goodsSpec);
     const gsRes = this.checkGoodsSpec(goodsSpecData, num);
     if (gsRes.result !== true) return gsRes;
+    // #region 团购部分
+    // 4.检查是否是团购单
+    if (type === '1' && group) {
+      // 为团购单,但是没有团id,团长开团.不需要检查
+      // 为团购单,且有团id: 团员参团
+      const groupRes = await this.ctx.service.group.group.checkGroupCanJoin({ id: group });
+      if (groupRes.result !== true) return groupRes;
+    }
+    // #endregion
     if (makeCache) {
-      const key = await this.makeOrderKey({ shop, goods, goodsSpec, num });
+      const key = await this.makeOrderKey({ shop, goods, goodsSpec, num, type });
       result.key = key;
     }
 

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

@@ -22,7 +22,7 @@ class GoodsService extends CrudService {
     let goods = await this.goodsModel.findById(id, { file: 1, tags: 1, name: 1, shot_brief: 1, brief: 1, send_time: 1, shop: 1, view_num: 1 }).populate(populate);
     if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到商品数据');
     goods = JSON.parse(JSON.stringify(goods));
-    let specs = await this.goodsSpecModel.find({ goods: id, status: '0' }, { sell_money: 1, flow_money: 1, freight: 1, name: 1, num: 1 });
+    let specs = await this.goodsSpecModel.find({ goods: id, status: '0' }, { sell_money: 1, flow_money: 1, freight: 1, name: 1, num: 1, can_group: 1, group_config: 1 });
     specs = JSON.parse(JSON.stringify(specs));
     goods = _.omit(goods, [ 'meta', '__v' ]);
     const shop = _.pick(goods.shop, [ 'logo', 'name', 'person', 'phone', '_id' ]);

+ 19 - 0
app/z_router/group/group.js

@@ -0,0 +1,19 @@
+'use strict';
+// 路由配置
+const path = require('path');
+const regPath = path.resolve('app', 'public', 'routerRegister');
+const routerRegister = require(regPath);
+const rkey = 'group';
+const ckey = 'group.group';
+const keyZh = '团';
+const routes = [
+  { method: 'get', path: `${rkey}`, controller: `${ckey}.index`, name: `${ckey}Query`, zh: `${keyZh}列表查询` },
+  { method: 'get', path: `${rkey}/:id`, controller: `${ckey}.show`, name: `${ckey}Show`, zh: `${keyZh}查询` },
+  // { method: 'post', path: `${rkey}`, controller: `${ckey}.create`, name: `${ckey}Create`, zh: `创建${keyZh}` },
+  { method: 'post', path: `${rkey}/:id`, controller: `${ckey}.update`, name: `${ckey}Update`, zh: `修改${keyZh}` },
+  // { method: 'delete', path: `${rkey}/:id`, controller: `${ckey}.destroy`, name: `${ckey}Delete`, zh: `删除${keyZh}` },
+];
+
+module.exports = app => {
+  routerRegister(app, routes, keyZh, rkey, ckey);
+};

+ 6 - 0
app/z_router/group/index.js

@@ -0,0 +1,6 @@
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  require('./group')(app); // 团
+};