Browse Source

Merge branch 'dev'

lrf 2 years ago
parent
commit
ed6c6ed312

+ 11 - 13
app/controller/trade/config/.afterSale.js

@@ -1,10 +1,8 @@
 module.exports = {
   create: {
     requestBody: [
-      'deal_person',
       'money',
       'order_detail',
-      'goods_id',
       'customer',
       'shop',
       'goods',
@@ -16,7 +14,8 @@ module.exports = {
       'apply_time',
       'end_time',
       'status',
-      'result',
+      'deal_person',
+      'out_bill',
     ],
   },
   destroy: {
@@ -26,8 +25,6 @@ module.exports = {
   update: {
     params: ['!id'],
     requestBody: [
-      'out_bill',
-      'deal_person',
       'money',
       'order_detail',
       'customer',
@@ -41,7 +38,8 @@ module.exports = {
       'apply_time',
       'end_time',
       'status',
-      'result',
+      'deal_person',
+      'out_bill',
     ],
   },
   show: {
@@ -56,15 +54,12 @@ module.exports = {
         'meta.createdAt@start': 'meta.createdAt@start',
         'meta.createdAt@end': 'meta.createdAt@end',
         type: 'type',
-        customer: 'customer',
-        'apply_time@start': 'apply_time@start',
-        'apply_time@end': 'apply_time@end',
-        'end_time@start': 'end_time@start',
-        'end_time@end': 'end_time@end',
-        status: 'status',
-        shop: 'shop',
+        apply_time: 'apply_time',
+        end_time: 'end_time',
         deal_person: 'deal_person',
         out_bill: 'out_bill',
+        customer: 'customer',
+        goods: '%goods.goods.name%',
       },
       // options: {
       //   "meta.state": 0 // 默认条件
@@ -85,4 +80,7 @@ module.exports = {
   orderCancel: {
     requestBody: ['order_detail', 'desc'],
   },
+  getTransportInfo: {
+    params: ['!id'],
+  },
 };

+ 5 - 1
app/middleware/errorEmail.js

@@ -19,7 +19,11 @@ module.exports = options => {
         emailData.errmsg = e.stack;
       }
       emailData.errcode = errcode;
-      console.log(emailData.errmsg);
+      const request = ctx.request;
+      const url = _.get(request, 'url');
+      let body = _.get(request, 'body');
+      if (body) body = JSON.stringify(body, null, 1);
+      emailData.errmsg = `${url}\n${body}\n${emailData.errmsg}`;
       // 未定义的错误需要发邮件,定义的错误不需要
       if (!_.isNumber(e.errcode)) {
         // 非开发模式发送邮件

+ 9 - 6
app/model/trade/afterSale.js

@@ -2,10 +2,14 @@
 const Schema = require('mongoose').Schema;
 const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
 const MoneyPlugin = require('naf-framework-mongoose-free/lib/model/type-money-plugin');
-
-/**
- * 与订单不同,此处是1订单1规格模式,对完账就对完了
- */
+const transport = {
+  customer_transport_no: { type: String, zh: '客户的快递单号' },
+  customer_transport_type: { type: String, zh: '客户的快递公司' },
+  customer_receive: { type: Boolean, zh: '客户是否签收' },
+  shop_transport_no: { type: String, zh: '店家的快递单号' },
+  shop_transport_type: { type: String, zh: '店家的快递公司' },
+  shop_receive: { type: Boolean, zh: '店铺是否签收' },
+};
 // 售后进度
 const afterSale = {
   order_detail: { type: String, required: false, zh: '订单详情', ref: 'Trade.OrderDetail' }, //
@@ -16,11 +20,10 @@ const afterSale = {
   reason: { type: String, required: false, zh: '申请理由' }, // 字典:afterSale_reason
   desc: { type: String, required: false, zh: '申请售后描述' }, //
   file: { type: Array, required: false, zh: '凭证图片' }, //
-  transport: { type: Object, required: false, zh: '快递信息' }, //
+  transport: { type: Object, required: false, zh: '快递信息' }, // 双方都可能快递单号,也都有可能有收货字段
   apply_time: { type: String, required: false, zh: '售后申请时间' }, //
   end_time: { type: String, required: false, zh: '售后结束时间' }, //
   status: { type: String, required: false, zh: '售后状态' }, // 字典:afterSale_status
-  result: { type: Object, required: false, zh: '售后结果' }, // 售后完成后需要补充的内容,例如:退款的金额
   deal_person: { type: String, required: false, zh: '处理人', ref: 'User.Admin' }, //
   out_bill: { type: String, required: false, default: '1', zh: '是否出账' }, // 字典:tf
 };

+ 350 - 131
app/service/trade/afterSale.js

@@ -13,59 +13,146 @@ class AfterSaleService extends CrudService {
     super(ctx, 'aftersale');
     this.model = this.ctx.model.Trade.AfterSale;
     this.orderDetailModel = this.ctx.model.Trade.OrderDetail;
+    this.orderModel = this.ctx.model.Trade.Order;
+    this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
     this.tran = new Transaction();
   }
-  async create({ order_detail, goods_id, ...others }) {
+  /**
+   * 售后申请
+   ** 售后申请有几种:取消订单; 拒收; 退款; 退货; 换货
+   ** 取消订单:发货之前,付款之后: 买家申请, 卖家同意之后,直接处理退款即可(退回库存,券影响的所有商品全额退款,则退券)
+   ** 拒收: 发货之后,收货之前;买家申请,卖家同意. 直到卖家 确认结束 售后,钱才退回给买家;(退回库存,券影响的所有商品全额退款,则退券)
+   ** 退款:收货之后;买家申请,卖家同意.当即退钱,自动结束(退钱,券影响的所有商品全额退款,则退券)
+   ** 退货:收货之后;买家申请,卖家同意.直到卖家 确认结束 售后,钱才退回给买家(退钱,退库存,券影响的所有商品全额退款,则退券)
+   ** 换货:收货之后;买家申请,卖家同意.双方填写各自的单号. 买卖双方确认收货 后 售后结束(没什么变化?库存-x?)
+   * @param {Object} body 售后信息
+   */
+  async create(body) {
+    const { order_detail, goods: goods_id, type, ...others } = body;
+    if (!order_detail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
     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' ] });
+    let goods;
+    const hasData = await this.model.count({ order_detail, 'goods._id': goods_id, status: { $nin: [ '!1', '!2', '!3', '!4', '!5' ] } });
     if (hasData > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '该商品已有正在处理中的售后申请.请勿重复申请');
-    const { goods: goodsList } = orderDetail;
-    const goods = goodsList.find(f => ObjectId(f._id).equals(goods_id));
-    if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未在当前订单中搜索到要售后的商品');
+    if (type !== '4' && type !== '5') {
+      const { goods: goodsList } = orderDetail;
+      goods = goodsList.find(f => {
+        return ObjectId(f._id).equals(goods_id);
+      });
+      if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未在当前订单中搜索到要售后的商品');
+    } else {
+      const realPay = await this.ctx.service.util.orderDetail.computedRealPay(orderDetail);
+      others.money = realPay;
+      others.desc = type === '4' ? '取消订单' : '拒收商品';
+    }
     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' };
+    const obj = { order_detail, customer, shop, goods, type, ...others, apply_time, status: '0' };
     await this.model.create(obj);
   }
 
   async update(filter, update, { projection } = {}) {
     assert(filter);
     assert(update);
-    const beforeUpdateResult = await this.beforeUpdate(filter, update);
-    filter = beforeUpdateResult.filter;
-    update = beforeUpdateResult.update;
     const { _id, id } = filter;
     if (_id || id) filter = { _id: ObjectId(_id || id) };
     // 检查数据是否存在
     const entity = await this.model.findOne(filter).exec();
     if (!entity) throw new BusinessError(ErrorCode.DATA_NOT_EXIST);
-
-    // 修改数据
     try {
-      let refundInfo;
-      this.tran.update('AfterSale', entity._id, update);
-      await this.tran.run();
-      const type = _.get(entity, 'type');
-      const status = _.get(update, 'status');
-      // 同意退款/退货,则直接进行退款,然后再将状态修改为已退款
-      if (type !== '2' && (status === '1' || status === '2')) {
-        refundInfo = await this.toRefund({ afterSale_id: entity._id, goods_id: _.get(entity, 'goods._id') }, this.tran);
+      // 先处理数据,数据处理对了,需要退钱再退钱
+      const { type } = entity;
+      const { status } = update;
+      // 没有修改状态,那就直接走修改返回
+      if (!status) {
+        entity.set(update);
+        await entity.save();
+        return;
       }
-      // 2022-10-17 需求8:标记处理售后的人
-      if (entity.status === '0' && update.status !== '0') {
-        // 将状态从 审核中 变为不是 审核中的操作人
+      // 退款信息,有内容就是要退款,没有内容(undefined)就是不需要退款
+      let refundInfo;
+      // 要修改成的状态
+      const uStatus = _.get(update, 'status');
+      // 根据类型不同,处理的函数不一样.
+      if (type === '1') {
+        // 仅退款,退优惠券,退款
+        // 如果不是处理中,则不进行退款
+        if (_.get(update, 'status') === '1') {
+          // 1.检验并组织退款信息
+          refundInfo = await this.toReturnMoney(entity, update, this.tran);
+          // 2.检查是否退优惠券,该退就退
+          await this.toReturnCoupons(entity, update, this.tran);
+          // 3.修改数据, 直接修改成退完款的状态.如果后面退款失败了.直接回滚了
+          update.status = '-1';
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        } else if (_.get(update, 'status') === '!1') {
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        }
+        // 4.修改订单状态
+        await this.refundOrder(entity, this.tran);
+      } else if (type === '2') {
+        // 退货(退库存),退款,退优惠券
+        // 做记录,但是不是结束状态,都不退
+        if (uStatus === '-2') {
+          // 1.检验并组织退款信息
+          refundInfo = await this.toReturnMoney(entity, update, this.tran);
+          // 2.检查是否退优惠券
+          await this.toReturnCoupons(entity, update, this.tran);
+          // 3.修改订单状态
+          await this.refundOrder(entity, this.tran);
+          // 结束时间
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        } else if (uStatus === '!2') {
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        }
+      } else if (type === '3') {
+        // 换货,不需要退款
+        // 但需要检查买卖双方的快递是否签收,都签收的话,需要将状态改为结束
+        const uto = _.get(update, 'transport', {});
+        const eto = _.get(entity, 'transport', {});
+        // 有关快递的字段整合,将传来的和以前的放在一起.然后找是否签收
+        const to = { ...eto, ...uto };
+        const cr = _.get(to, 'customer_receive');
+        const sr = _.get(to, 'shop_receive');
+        if (cr && sr) {
+          update.status = '-3';
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        } else if (uStatus === '!3') {
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        }
+      } else if (type === '4') {
+        // 取消订单: 退钱,退货(退库存)----不过不需要有快递信息,没发货,退优惠券
+        // 没有商品,只有拆分的订单号,用这个去退
+        if (uStatus === '4') {
+          refundInfo = await this.returnOrder(entity, this.tran);
+          update.status = '-4';
+        }
+        update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+      } else if (type === '5') {
+        // 拒收: 退钱, 退货
+        // 如果状态不是完成,那就不退,只是数据修改
+        if (uStatus === '-5') {
+          refundInfo = await this.returnOrder(entity, this.tran);
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        } else if (uStatus === '!5') {
+          update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
+        }
+      } else throw new BusinessError(ErrorCode.DATA_INVALID, '未知的售后类型,无法处理');
+      // 售后处理人的添加
+      if (update.status !== '0') {
         const admin = this.ctx.admin;
-        if (!admin) throw new BusinessError(ErrorCode.DATA_INVALID, '未找到管理人员的信息,无法进行操作');
-        this.tran.update('AfterSale', entity._id, { deal_person: admin._id });
+        update.deal_person = admin._id;
       }
+      // 修改数据
+      this.tran.update('AfterSale', entity._id, update);
       await this.tran.run();
-      if (refundInfo) {
-        // 退款请求, 将请求退款滞后,所有的信息处理完之后,再去退款,避免退款成功但是数据处理失败的问题
-        const res = await this.ctx.service.trade.pay.refund(refundInfo);
-        if (res.errcode && res.errcode !== 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, res.errmsg);
-      }
+      // 退钱
+      if (!refundInfo) return;
+      // console.log(refundInfo);
+      const res = await this.ctx.service.trade.pay.refund(refundInfo);
+      if (res.errcode && res.errcode !== 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, res.errmsg);
     } catch (error) {
       console.error(error);
       await this.tran.rollback();
@@ -73,104 +160,178 @@ class AfterSaleService extends CrudService {
     } finally {
       this.tran.clean();
     }
-    const reSearchData = await this.model.findOne(filter, projection).exec();
-    return reSearchData;
   }
 
   /**
-   * 退款
-   * @param {Object} param 参数
-   * @param param.afterSale_id 售后申请id
-   * @param param.goods_id 商品规格id
-   * @param {Transaction} tran 事务的实例
+   * 售后退钱
+   **组织退钱的请求参数即可,没啥需要改的
+   * @param {Object} data 售后修改前的数据
+   * @param {Object} update 要修改的数据
+   * @param {Transaction} tran 数据库事务
    */
-  async toRefund({ afterSale_id, goods_id }, tran) {
-    let refundInfo = {};
-    const data = await this.model.findById(afterSale_id);
-    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后信息');
-    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, '未找到售后信息的订单');
-    const reason = _.get(data, 'desc');
-    const order_no = _.get(orderDetail, 'order.pay.pay_no');
-    if (!order_no) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单号');
-    // 用工具函数,获取退货商品的实际支付价格(常规/团购)
+  async toReturnMoney(data, update, tran) {
+    const { order_detail, goods } = data;
+    const orderDetail = await this.orderDetailModel.findById(order_detail);
+    if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    // 取出订单详情的每种商品规格的价格明细
     const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
-    const goodsMoneyDetail = _.get(moneyDetail, goods_id, {});
-    const type = _.get(orderDetail, 'type', '0');
+    // 根据订单类型使用价格的key
     let priceKey;
-    if (type === '1') priceKey = 'ggrp';
+    if (_.get(orderDetail, 'type', '0') === '1') priceKey = 'ggrp';
     else priceKey = 'grp';
-    const money = _.get(goodsMoneyDetail, priceKey, 0);
-
-    // 取出商品输入的价格
-    const needRefund = _.get(data, 'money');
-    let refundMoney = 0;
-    if (money === needRefund) {
-      // 如果这俩价格相同,说明是正常退
-      refundMoney = money;
-    } else {
-      // 部分退,部分退是不退优惠券的
-      refundMoney = needRefund;
-    }
+    // 商品实际支付的金额
+    const goodsRealPay = _.get(moneyDetail, `${goods._id}.${priceKey}`);
+    // 需要退还的金额,如果传来的数据有金额,就使用传来的,没有的话就用原来的
+    const returnMoney = _.get(update, 'money', _.get(data, 'money'));
+    if (goodsRealPay < returnMoney) throw new BusinessError(ErrorCode.DATA_INVALID, '退款金额超出该商品支付的金额');
     // 组成退款单号
+    const { order: order_id } = orderDetail;
+    const order = await this.orderModel.findById(order_id);
+    if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
+    const order_no = _.get(order, 'pay.pay_no');
+    if (!order_no) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到微信支付订单号');
     const str = this.ctx.service.util.trade.createNonceStr();
     const out_refund_no = `${order_no}-r-${str}`;
-    refundInfo = { reason, money: refundMoney, order_no, out_refund_no };
-    if (data.status === '1') {
-      tran.update('AfterSale', afterSale_id, { status: '-1', end_time: moment().format('YYYY-MM-DD HH:mm:ss') });
+    const refundInfo = { reason: _.get(data, 'desc', '购物退款'), money: returnMoney, order_no, out_refund_no };
+    // 退积分
+    await this.ctx.service.user.point.refundOrderPoint(order_detail, tran);
+    return refundInfo;
+  }
+  /**
+   * 售后退优惠券
+   **回溯至支付订单,检查每张优惠券影响的商品 及 当前售后的商品 是否都 全额退款
+   **如果优惠券影响的商品全额退款,那就退还优惠券;
+   **如果有一个没有退款 或者 全额退款,那优惠券就不退
+   * @param {Object} data 售后修改前的数据
+   * @param {Object} update 要修改的数据
+   * @param {Transaction} tran 数据库事务
+   */
+  async toReturnCoupons(data, update, tran) {
+    const orderDetail = await this.orderDetailModel.findById(data.order_detail);
+    if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    const { order: order_id } = orderDetail;
+    const order = await this.orderModel.findById(order_id);
+    if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
+    const odList = await this.orderDetailModel.find({ order: order_id });
+    // 已退款记录
+    const goodsRefundList = [];
+    // 在下面处理已退款记录中,应该把当前的这个售后项,作为已处理售后
+    const tasq = { order_detail: data.order_detail, 'goods._id': _.get(data, 'goods._id'), status: [ '-1', '-2' ] };
+    const asdd = JSON.parse(JSON.stringify(data));
+    if (_.get(update, 'money')) asdd.money = _.get(update, 'money');
+    const tr = await this.checkIsAllRefund(data);
+    // 如果当前商品的售后都不满足全额退款条件.那就别往后查了
+    if (tr) goodsRefundList.push(tasq);
+    else return;
+    for (const od of odList) {
+      const { goods, _id: order_detail } = od;
+      // 组合成查售后的条件
+      // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券
+      const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: [ '-1', '-2' ] }));
+      for (const asq of afterSaleQuerys) {
+        const asd = await this.model.findOne(asq);
+        if (asd) {
+          const r = await this.checkIsAllRefund(asd);
+          if (r) goodsRefundList.push(asq);
+        }
+      }
+    }
+    // 获取支付单的优惠明细
+    const dd = _.get(order, 'total_detail.discount_detail', {});
+    for (const uc_id in dd) {
+      // uc_id 用户领取优惠券的id
+      // 该优惠券影响的商品id列表
+      const goodsIds = Object.keys(_.get(dd, uc_id, {}));
+      // 然后在已退款/退货记录中找,这个优惠券影响的商品是否都退款了.退全额
+      const r = goodsIds.every(i => goodsRefundList.find(f => i === f['goods._id']));
+      if (r) {
+        // 说明这个优惠券影响的商品都退了,这个优惠券也就能退了
+        tran.update('UserCoupon', uc_id, { status: '0' });
+      }
+    }
+  }
+
+  /**
+   * 售后退库存
+   * @param {Object} data 售后修改前的数据
+   * @param {Transaction} tran 数据库事务
+   */
+  async toReturnStock(data, tran) {}
+
+  /**
+   * 售后:修改拆分订单
+   * @param {Object} data 售后修改前的数据
+   * @param {Transaction} tran 数据库事务
+   */
+  async refundOrder(data, tran) {
+    const { order_detail } = data;
+    const orderDetail = await this.orderDetailModel.findById(order_detail);
+    if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    // 获取拆分订单的数据,并找到商品列表
+    const goodsList = _.get(orderDetail, 'goods');
+    // 依次找这些商品是否都售后完成,都售后完成,就改订单状态
+    const asList = await this.model.find({ order_detail, status: { $nin: [ '0', '!1', '!2', '!3', '!4', '!5' ] } });
+    let status;
+    let fl = [];
+    // 将当前数据添加进去
+    fl.push({ goods: _.get(data, 'goods._id'), status: 'finish' });
+    for (const gs of goodsList) {
+      const r = asList.find(f => ObjectId(_.get(f, 'goods._id')).equals(_.get(gs, '_id')));
+      if (r) {
+        const finishList = [ '-1', '-2', '-3', '-4', '-5' ];
+        if (finishList.includes(r.status)) fl.push({ goods: gs._id, status: 'finish' });
+      }
     }
-    // #region 团购部分
-    const status = _.get(orderDetail, 'status');
-    // 团购单,且未收货的单子,才需要走退团逻辑,否则不需要走退团逻辑
-    if (type === '1' && status !== '3') {
-      // 团购单,走团购退货逻辑补充
-      const { group, customer } = orderDetail;
-      await this.ctx.service.group.group.refund({ group: group._id, customer: customer._id }, tran);
+    fl = _.uniqBy(fl, 'goods');
+    // 说明所有的商品 都有 已处理的售后
+    if (fl.length === goodsList.length) status = '-4';
+    // 有状态码,则修改订单的状态
+    if (status) {
+      tran.update('OrderDetail', order_detail, { status });
     }
-    // #endregion
-    // 检查优惠券是否都退了
-    //  查看支付订单-优惠明细中,该优惠券影响的商品是否都有退款/退货成功的记录,且退款成功的记录为退全款的.退部分是不退优惠券的
-    // 如果有,就说明该优惠券可以退了,没有影响任何一单了
-    const payOrder = _.get(orderDetail, 'order');
-    await this.checkToReturnUserCoupon(payOrder, tran);
-    // 退积分,退多钱就退多少积分
-    const { _id } = orderDetail;
-    await this.ctx.service.user.point.refundOrderPoint(_id, tran);
-    return refundInfo;
   }
 
   /**
-   * 检查订单的优惠券并退优惠券, 必须是退全款,部分退款不退优惠券
-   * @param {Object} order 订单信息
-   * @param {Transaction} tran 事务的实例
+   * 售后 取消订单 & 拒收处理完成
+   * @param {Object} data 售后修改前的数据
+   * @param {Transaction} tran 数据库事务
    */
-  async checkToReturnUserCoupon(order, tran) {
-    // 该支付订单下所有拆分的子订单
-    const orderDetailList = await this.orderDetailModel.find({ order: order._id });
-    // 已退款记录
+  async returnOrder(data, tran) {
+    // 1.退钱
+    const { order_detail } = data;
+    const orderDetail = await this.orderDetailModel.findById(order_detail);
+    if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
+    const order_id = _.get(orderDetail, 'order');
+    const goodsList = _.get(orderDetail, 'goods');
+    const order = await this.orderModel.findById(order_id);
+    if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
+    this.tran.update('OrderDetail', order_detail, { status: '-1' });
+    // 实付的钱
+    const realPay = await this.ctx.service.util.orderDetail.computedRealPay(orderDetail);
+    // 组成退款单号
+    const str = this.ctx.service.util.trade.createNonceStr();
+    const order_no = _.get(order, 'pay.pay_no');
+    const out_refund_no = `${order_no}-r-${str}`;
+    const refundInfo = { reason: _.get(data, 'desc'), money: realPay, order_no, out_refund_no };
+    // 2.退卷
+    // 查找支付订单拆分的所有订单.不包含当前要退的单.默认当前要退的单是全都退了的,只检查其他拆分的就可以
+    const odList = await this.orderDetailModel.find({ order: order_id, _id: { $ne: ObjectId(order_detail) } });
     const goodsRefundList = [];
-    for (const od of orderDetailList) {
+    // 将这个单内的商品加入满足全额退款的商品列表中
+    for (const g of goodsList) {
+      const obj = { order_detail, 'goods._id': g._id };
+      goodsRefundList.push(obj);
+    }
+    for (const od of odList) {
       const { goods, _id: order_detail } = od;
       // 组合成查售后的条件
       // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券
-      const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: '-1' }));
+      const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: [ '-1', '-2' ] }));
       for (const asq of afterSaleQuerys) {
         const asd = await this.model.findOne(asq);
         if (asd) {
-          // 商品有退款审核通过的记录,查询每个商品是否退的是全款
-          // money: 实际退款的金额
-          const { money } = asd;
-          const od_id = _.get(asd, 'order_detail');
-          const goods_id = _.get(asd, 'goods._id');
-          const moneyDetail = await this.computedGoodsForRefund({ order_detail: od_id, goods_id });
-          if (moneyDetail) {
-            const { payTotal } = moneyDetail;
-            if (this.ctx.minus(payTotal, money) === 0) {
-              // 添加到已退款的列表中
-              goodsRefundList.push(asq);
-            }
-          }
+          const r = await this.checkIsAllRefund(asd);
+          if (r) goodsRefundList.push(asq);
         }
       }
     }
@@ -180,13 +341,45 @@ class AfterSaleService extends CrudService {
       // uc_id 用户领取优惠券的id
       // 该优惠券影响的商品id列表
       const goodsIds = Object.keys(_.get(dd, uc_id, {}));
-      // 然后在已退款记录中找,这个优惠券影响的商品是否都退款了.都退款
+      // 然后在已退款/退货记录中找,这个优惠券影响的商品是否都退款了.退全额
       const r = goodsIds.every(i => goodsRefundList.find(f => i === f['goods._id']));
       if (r) {
         // 说明这个优惠券影响的商品都退了,这个优惠券也就能退了
         tran.update('UserCoupon', uc_id, { status: '0' });
       }
     }
+    // 3.退库存
+    for (const g of goodsList) {
+      const { _id, buy_num } = g;
+      const goodsSpec = await this.goodsSpecModel.findById(_id);
+      if (!goodsSpec) continue;
+      const { num } = goodsSpec;
+      const newNum = this.ctx.plus(buy_num, num);
+      this.tran.update('GoodsSpec', goodsSpec._id, { num: newNum });
+    }
+
+    return refundInfo;
+  }
+
+  /**
+   * 判断是否全额退的商品
+   * @param {Object} data 售后数据
+   * @return {Boolean}
+   */
+  async checkIsAllRefund(data) {
+    // 商品有退款审核通过的记录,查询每个商品是否退的是全款
+    // money: 实际退款的金额
+    const { money } = data;
+    const od_id = _.get(data, 'order_detail');
+    const goods_id = _.get(data, 'goods._id');
+    const moneyDetail = await this.computedGoodsForRefund({ order_detail: od_id, goods_id });
+    if (moneyDetail) {
+      const { payTotal } = moneyDetail;
+      if (this.ctx.minus(payTotal, money) === 0) {
+        // 添加到已退款的列表中
+        return true;
+      }
+    }
   }
 
   /**
@@ -198,21 +391,36 @@ class AfterSaleService extends CrudService {
   async computedGoodsForRefund({ order_detail, goods_id }) {
     const orderDetail = await this.orderDetailModel.findById(order_detail);
     if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
-    const goods = orderDetail.goods.find(f => f._id === goods_id);
-    // 货物支付金额, 数量*购买数量+ 数量*运费 - 优惠
-    const goodsTotal = this.ctx.multiply(goods.sell_money, goods.buy_num);
-    const freightTotal = this.ctx.multiply(goods.freight, goods.buy_num);
-    let discountTotal = 0;
-    const { total_detail = {} } = orderDetail;
-    const { discount_detail = {} } = total_detail;
-    for (const dd in discount_detail) {
-      const dm = _.get(discount_detail, `${dd}.${goods_id}`, 0);
-      discountTotal = this.ctx.plus(discountTotal, dm);
+    const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
+    const gmd = _.get(moneyDetail, goods_id);
+    const obj = {};
+    if (_.get(orderDetail, 'type', '0') === '1') {
+      obj.payTotal = _.get(gmd, 'ggrp');
+      obj.goodsTotal = _.get(gmd, 'gst');
+    } else {
+      obj.payTotal = _.get(gmd, 'grp');
+      obj.goodsTotal = _.get(gmd, 'st');
     }
-    const payTotal = this.ctx.minus(this.ctx.plus(goodsTotal, freightTotal), discountTotal);
-    const obj = { payTotal, goodsTotal, freightTotal, discountTotal };
+    obj.freightTotal = _.get(gmd, 'ft');
+    obj.discountTotal = _.get(gmd, 'dt');
     return obj;
+    // const goodsTotal =
+    // const goods = orderDetail.goods.find(f => f._id === goods_id);
+    // // 货物支付金额, 数量*购买数量+ 数量*运费 - 优惠
+    // const goodsTotal = this.ctx.multiply(goods.sell_money, goods.buy_num);
+    // const freightTotal = this.ctx.multiply(goods.freight, goods.buy_num);
+    // let discountTotal = 0;
+    // const { total_detail = {} } = orderDetail;
+    // const { discount_detail = {} } = total_detail;
+    // for (const dd in discount_detail) {
+    //   const dm = _.get(discount_detail, `${dd}.${goods_id}`, 0);
+    //   discountTotal = this.ctx.plus(discountTotal, dm);
+    // }
+    // const payTotal = this.ctx.minus(this.ctx.plus(goodsTotal, freightTotal), discountTotal);
+    // const obj = { payTotal, goodsTotal, freightTotal, discountTotal };
+    // return obj;
   }
+
   /**
    * 退单
    * @param {Object} body 参数体
@@ -235,20 +443,6 @@ class AfterSaleService extends CrudService {
       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) {
@@ -261,6 +455,31 @@ class AfterSaleService extends CrudService {
     res = await this.afterFetch(filter, res);
     return res;
   }
+
+  /**
+   * 查询售后快递
+   * @param {Object} query 查询参数
+   * @param {String} query.id 售后id
+   */
+  async getTransportInfo({ id }) {
+    const data = await this.model.findById(id);
+    if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后数据');
+    const { transport } = data;
+    if (!transport) return;
+    const { customer_transport_no, customer_transport_type, shop_transport_no, shop_transport_type } = transport;
+    const result = {};
+    if (customer_transport_no && customer_transport_type) {
+      const q = { no: customer_transport_no, type: customer_transport_type };
+      const customer = await this.ctx.service.util.kd100.search(q);
+      result.customer = customer;
+    }
+    if (shop_transport_no && shop_transport_type) {
+      const q = { no: shop_transport_no, type: shop_transport_type };
+      const shop = await this.ctx.service.util.kd100.search(q);
+      result.shop = shop;
+    }
+    return result;
+  }
 }
 
 module.exports = AfterSaleService;

+ 1 - 1
app/service/trade/orderDetail.js

@@ -234,7 +234,7 @@ class OrderDetailService extends CrudService {
       // 评价与售后
       const rate = await this.goodsRateModel.findOne({ orderDetail: obj._id }, { _id: 1 });
       obj.rate = _.get(rate, '_id');
-      const asum = await this.afterSaleModel.count({ order_detail: obj._id, status: { $ne: '0' } });
+      const asum = await this.afterSaleModel.count({ order_detail: obj._id, status: { $nin: [ '0', '!1', '!2', '!3', '!4', '!5' ] } });
       obj.is_afterSale = asum > 0;
       list.push(obj);
     }

+ 0 - 1
app/service/util/kd100.js

@@ -30,7 +30,6 @@ class Kd100Service extends CrudService {
       if (!returnCode) {
         const list = this.resetData(data);
         const is_check = this.isCheck(ischeck);
-        console.log(is_check);
         return { list, is_check, no: nu, status };
       } else if (returnCode !== '200') throw new BusinessError(ErrorCode.SERVICE_FAUL, message);
     }

+ 1 - 0
app/z_router/trade/afterSale.js

@@ -7,6 +7,7 @@ const rkey = 'afterSale';
 const ckey = 'trade.afterSale';
 const keyZh = '售后';
 const routes = [
+  { method: 'get', path: `${rkey}/getTransportInfo/:id`, controller: `${ckey}.getTransportInfo`, name: `${ckey}getTransportInfo`, zh: `${keyZh}查询售后快递` },
   { method: 'post', path: `${rkey}/orderCancel`, controller: `${ckey}.orderCancel`, name: `${ckey}orderCancel`, zh: '取消订单' },
   { method: 'post', path: `${rkey}/cgfr`, controller: `${ckey}.cgfr`, name: `${ckey}cgfr`, zh: '查询退货商品的金额设置' },
   { method: 'get', path: `${rkey}`, controller: `${ckey}.index`, name: `${ckey}Query`, zh: `${keyZh}列表查询` },