afterSale.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose-free/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const _ = require('lodash');
  5. const assert = require('assert');
  6. const { ObjectId } = require('mongoose').Types;
  7. const moment = require('moment');
  8. const Transaction = require('mongoose-transactions');
  9. //
  10. class AfterSaleService extends CrudService {
  11. constructor(ctx) {
  12. super(ctx, 'aftersale');
  13. this.model = this.ctx.model.Trade.AfterSale;
  14. this.orderDetailModel = this.ctx.model.Trade.OrderDetail;
  15. this.tran = new Transaction();
  16. }
  17. async create({ order_detail, goods_id, ...others }) {
  18. const orderDetail = await this.orderDetailModel.findById(order_detail);
  19. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  20. // 查看订单是否签收
  21. // 查看该商品是否已经申请售后
  22. const hasData = await this.model.count({ order_detail, 'goods._id': goods_id, type: [ '0', '1', '2', '3' ] });
  23. if (hasData > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '该商品已有正在处理中的售后申请.请勿重复申请');
  24. const { goods: goodsList } = orderDetail;
  25. const goods = goodsList.find(f => ObjectId(f._id).equals(goods_id));
  26. if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未在当前订单中搜索到要售后的商品');
  27. const { shop, customer } = orderDetail;
  28. const apply_time = moment().format('YYYY-MM-DD HH:mm:ss');
  29. const obj = { order_detail, customer, shop, goods, ...others, apply_time, status: '0' };
  30. console.log(obj);
  31. await this.model.create(obj);
  32. }
  33. async update(filter, update, { projection } = {}) {
  34. assert(filter);
  35. assert(update);
  36. const beforeUpdateResult = await this.beforeUpdate(filter, update);
  37. filter = beforeUpdateResult.filter;
  38. update = beforeUpdateResult.update;
  39. const { _id, id } = filter;
  40. if (_id || id) filter = { _id: ObjectId(_id || id) };
  41. // 检查数据是否存在
  42. const entity = await this.model.findOne(filter).exec();
  43. if (!entity) throw new BusinessError(ErrorCode.DATA_NOT_EXIST);
  44. // 修改数据
  45. try {
  46. this.tran.update('AfterSale', entity._id, update);
  47. await this.tran.run();
  48. const type = _.get(entity, 'type');
  49. const status = _.get(update, 'status');
  50. // 同意退款,则直接进行退款,然后再将状态修改为已退款
  51. if (type !== '2' && status === '1') {
  52. await this.toRefund({ afterSale_id: entity._id, goods_id: _.get(entity, 'goods._id') }, this.tran);
  53. }
  54. await this.tran.run();
  55. } catch (error) {
  56. console.error(error);
  57. await this.tran.rollback();
  58. throw new BusinessError(ErrorCode.SERVICE_FAULT, '售后:修改失败');
  59. } finally {
  60. this.tran.clean();
  61. }
  62. const reSearchData = await this.model.findOne(filter, projection).exec();
  63. return reSearchData;
  64. }
  65. /**
  66. * 退款
  67. * @param {Object} param 参数
  68. * @param param.afterSale_id 售后申请id
  69. * @param param.goods_id 商品规格id
  70. * @param {Transaction} tran 事务的实例
  71. */
  72. async toRefund({ afterSale_id, goods_id }, tran) {
  73. const data = await this.model.findById(afterSale_id);
  74. if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后信息');
  75. const { populate } = this.ctx.service.trade.orderDetail.getRefMods();
  76. const orderDetail = await this.orderDetailModel.findById(data.order_detail).populate(populate);
  77. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后信息的订单');
  78. // 计算商品原价,运费
  79. let money = this.ctx.multiply(_.get(data, 'goods.sell_money'), _.get(data, 'goods.buy_num'));
  80. const freight = this.ctx.multiply(_.get(data, 'goods.buy_num'), _.get(data, 'goods.freight'));
  81. money = this.ctx.plus(money, freight);
  82. const reason = _.get(data, 'desc');
  83. const order_no = _.get(orderDetail, 'order.pay.pay_no');
  84. if (!order_no) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单号');
  85. // 查查优惠中,是否有这个商品:将这个商品的每一个优惠券的优惠部分都加一起,然后用原价-优惠价=实付价
  86. const discount_detail = _.get(orderDetail, 'total_detail.discount_detail');
  87. if (discount_detail) {
  88. let discountMoney = 0;
  89. for (const uc_id in discount_detail) {
  90. const detail = discount_detail[uc_id];
  91. discountMoney = this.ctx.plus(discountMoney, detail[goods_id]);
  92. }
  93. money = this.ctx.minus(money, discountMoney);
  94. }
  95. // 取出商品输入的价格
  96. const needRefund = _.get(data, 'money');
  97. let refundMoney = 0;
  98. if (money === needRefund) {
  99. // 如果这俩价格相同,说明是正常退
  100. refundMoney = money;
  101. } else {
  102. // 部分退,部分退是不退优惠券的
  103. refundMoney = needRefund;
  104. }
  105. // 找下当前这个订单有多少次售后记录
  106. let num = await this.model.count({ order_detail: data.order_detail });
  107. num += 2;
  108. // 组成退款单号
  109. const out_refund_no = `${order_no}-r${num}`;
  110. const obj = { reason, money: refundMoney, order_no, out_refund_no };
  111. // 退款请求
  112. if (refundMoney > 0) {
  113. const res = await this.ctx.service.trade.pay.refund(obj);
  114. if (res.errcode && res.errcode !== 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, res.errmsg);
  115. }
  116. if (data.status === '1') tran.update('AfterSale', afterSale_id, { status: '-1' });
  117. await tran.run();
  118. // 检查优惠券是否都退了
  119. // 查看支付订单-优惠明细中,该优惠券影响的商品是否都有退款/退货成功的记录,且退款成功的记录为退全款的.退部分是不退优惠券的
  120. // 如果有,就说明该优惠券可以退了,没有影响任何一单了
  121. const payOrder = _.get(orderDetail, 'order');
  122. await this.checkToReturnUserCoupon(payOrder, tran);
  123. }
  124. /**
  125. * 检查订单的优惠券并退优惠券, 必须是退全款,部分退款不退优惠券
  126. * @param {Object} order 订单信息
  127. * @param {Transaction} tran 事务的实例
  128. */
  129. async checkToReturnUserCoupon(order, tran) {
  130. // 该支付订单下所有拆分的子订单
  131. const orderDetailList = await this.orderDetailModel.find({ order: order._id });
  132. // 已退款记录
  133. const goodsRefundList = [];
  134. for (const od of orderDetailList) {
  135. const { goods, _id: order_detail } = od;
  136. // 组合成查售后的条件
  137. // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券
  138. const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: '-1' }));
  139. for (const asq of afterSaleQuerys) {
  140. const asd = await this.model.findOne(asq);
  141. if (asd) {
  142. // 商品有退款审核通过的记录,查询每个商品是否退的是全款
  143. // money: 实际退款的金额
  144. const { money } = asd;
  145. const od_id = _.get(asd, 'order_detail');
  146. const goods_id = _.get(asd, 'goods._id');
  147. const moneyDetail = await this.computedGoodsForRefund({ order_detail: od_id, goods_id });
  148. if (moneyDetail) {
  149. const { payTotal } = moneyDetail;
  150. if (this.ctx.minus(payTotal, money) === 0) {
  151. // 添加到已退款的列表中
  152. goodsRefundList.push(asq);
  153. }
  154. }
  155. }
  156. }
  157. }
  158. // 获取支付单的优惠明细
  159. const dd = _.get(order, 'total_detail.discount_detail', {});
  160. for (const uc_id in dd) {
  161. // uc_id 用户领取优惠券的id
  162. // 该优惠券影响的商品id列表
  163. const goodsIds = Object.keys(_.get(dd, uc_id, {}));
  164. // 然后在已退款记录中找,这个优惠券影响的商品是否都退款了.都退款
  165. const r = goodsIds.every(i => goodsRefundList.find(f => i === f['goods._id']));
  166. if (r) {
  167. // 说明这个优惠券影响的商品都退了,这个优惠券也就能退了
  168. tran.update('UserCoupon', uc_id, { status: '0' });
  169. }
  170. }
  171. }
  172. /**
  173. * 计算商品退货的金额最大值
  174. * @param {Object} body 参数体
  175. * @param body.order_detail 订单详情id
  176. * @param body.goods_id 商品id
  177. */
  178. async computedGoodsForRefund({ order_detail, goods_id }) {
  179. const orderDetail = await this.orderDetailModel.findById(order_detail);
  180. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  181. const goods = orderDetail.goods.find(f => f._id === goods_id);
  182. // 货物支付金额, 数量*购买数量+ 数量*运费 - 优惠
  183. const goodsTotal = this.ctx.multiply(goods.sell_money, goods.buy_num);
  184. const freightTotal = this.ctx.multiply(goods.freight, goods.buy_num);
  185. let discountTotal = 0;
  186. const { total_detail = {} } = orderDetail;
  187. const { discount_detail = {} } = total_detail;
  188. for (const dd in discount_detail) {
  189. const dm = _.get(discount_detail, `${dd}.${goods_id}`, 0);
  190. discountTotal = this.ctx.plus(discountTotal, dm);
  191. }
  192. const payTotal = this.ctx.minus(this.ctx.plus(goodsTotal, freightTotal), discountTotal);
  193. const obj = { payTotal, goodsTotal, freightTotal, discountTotal };
  194. return obj;
  195. }
  196. /**
  197. * 退单
  198. * @param {Object} body 参数体
  199. * @param body.order_detail 订单详情id
  200. * @param body.desc 退单理由
  201. */
  202. async orderCancel({ order_detail, desc }) {
  203. // 查询要退的订单
  204. const orderDetail = await this.orderDetailModel.findById(order_detail);
  205. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  206. const { goods, customer } = orderDetail;
  207. const basic = { order_detail, customer, type: '1', desc };
  208. const discount_detail = _.get(orderDetail, 'total_detail.discount_detail', {});
  209. // 组织数据
  210. for (const g of goods) {
  211. let money = this.ctx.multiply(g.buy_num, g.sell_money);
  212. let dmt = 0;
  213. for (const dd in discount_detail) {
  214. const detail = _.get(discount_detail, dd, {});
  215. const dm = _.get(detail, g._id);
  216. dmt = this.ctx.plus(dmt, dm);
  217. }
  218. money = this.ctx.minus(money, dmt);
  219. if (money <= 0) money = 0;
  220. const obj = { ...basic, goods_id: g._id, money };
  221. await this.create(obj);
  222. }
  223. }
  224. async fetch(filter) {
  225. assert(filter);
  226. filter = await this.beforeFetch(filter);
  227. const { _id, id } = filter;
  228. if (_id || id) filter = { _id: ObjectId(_id || id) };
  229. const { populate } = this.getRefMods();
  230. let res = await this.model.findOne(filter).populate(populate).exec();
  231. res = await this.afterFetch(filter, res);
  232. return res;
  233. }
  234. }
  235. module.exports = AfterSaleService;