afterSale.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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.orderModel = this.ctx.model.Trade.Order;
  16. this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
  17. this.configModel = this.ctx.model.System.Config;
  18. this.orderDetailService = this.ctx.service.util.orderDetail;
  19. this.tran = new Transaction();
  20. }
  21. /**
  22. * 售后申请
  23. ** 售后申请有几种:取消订单; 拒收; 退款; 退货; 换货
  24. ** 取消订单:发货之前,付款之后: 买家申请, 卖家同意之后,直接处理退款即可(退回库存,券影响的所有商品全额退款,则退券)
  25. ** 拒收: 发货之后,收货之前;买家申请,卖家同意. 直到卖家 确认结束 售后,钱才退回给买家;(退回库存,券影响的所有商品全额退款,则退券)
  26. ** 退款:收货之后;买家申请,卖家同意.当即退钱,自动结束(退钱,券影响的所有商品全额退款,则退券)
  27. ** 退货:收货之后;买家申请,卖家同意.直到卖家 确认结束 售后,钱才退回给买家(退钱,退库存,券影响的所有商品全额退款,则退券)
  28. ** 换货:收货之后;买家申请,卖家同意.双方填写各自的单号. 买卖双方确认收货 后 售后结束(没什么变化?库存-x?)
  29. * @param {Object} body 售后信息
  30. */
  31. async create(body) {
  32. const { order_detail, goods: goods_id, set_id, type, ...others } = body;
  33. if (!order_detail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  34. const orderDetail = await this.orderDetailModel.findById(order_detail);
  35. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  36. // 查看该商品是否已经申请售后
  37. let goods;
  38. const hasData = await this.model.count({ order_detail, set_id, 'goods._id': goods_id, status: { $nin: [ '!1', '!2', '!3', '!4', '!5' ] } });
  39. if (hasData > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '该商品已有正在处理中的售后申请.请勿重复申请');
  40. if (type !== '4' && type !== '5') {
  41. const { goods: goodsList } = orderDetail;
  42. goods = goodsList.find(f => {
  43. const { is_set = '1' } = f;
  44. if (!set_id && is_set === '1') return ObjectId(f._id).equals(goods_id);
  45. return set_id === f.set_id;
  46. });
  47. // 套装的数据处理
  48. if (set_id && _.get(goods, 'set_id') === set_id) {
  49. const { buy_num } = goods;
  50. const setData = goods.goods.find(f => _.get(f, 'spec._id') === goods_id);
  51. if (setData) {
  52. const { spec, goods: sg } = setData;
  53. const newGoods = { ...spec, buy_num, goods: sg };
  54. goods = newGoods;
  55. }
  56. }
  57. if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未在当前订单中搜索到要售后的商品');
  58. } else {
  59. const realPay = await this.ctx.service.util.orderDetail.computedRealPay(orderDetail);
  60. others.money = realPay;
  61. others.desc = type === '4' ? '取消订单' : '拒收商品';
  62. }
  63. const { shop, customer } = orderDetail;
  64. const apply_time = moment().format('YYYY-MM-DD HH:mm:ss');
  65. this.orderDetailService.turn$numberDecimalToNumber(goods);
  66. const obj = { order_detail, customer, shop, goods, type, ...others, apply_time, status: '0', set_id };
  67. const res = await this.model.create(obj);
  68. const msgData = { source_id: res._id, type: '0' };
  69. try {
  70. await this.ctx.service.shop.shopNotice.remindToAfterSale(msgData);
  71. } catch (error) {
  72. const errobj = error;
  73. await this.ctx.service.util.email.errorEmail(errobj);
  74. }
  75. }
  76. async update(filter, update) {
  77. assert(filter);
  78. assert(update);
  79. const { _id, id } = filter;
  80. if (_id || id) filter = { _id: ObjectId(_id || id) };
  81. // 检查数据是否存在
  82. const entity = await this.model.findOne(filter).exec();
  83. if (!entity) throw new BusinessError(ErrorCode.DATA_NOT_EXIST);
  84. try {
  85. // 先处理数据,数据处理对了,需要退钱再退钱
  86. const { type } = entity;
  87. const { status } = update;
  88. // 没有修改状态,那就直接走修改返回
  89. if (!status) {
  90. entity.set(update);
  91. await entity.save();
  92. return;
  93. }
  94. // 退款信息,有内容就是要退款,没有内容(undefined)就是不需要退款
  95. let refundInfo;
  96. // 要修改成的状态
  97. const uStatus = _.get(update, 'status');
  98. // 根据类型不同,处理的函数不一样.
  99. if (type === '1') {
  100. // 仅退款,退优惠券,退款
  101. // 如果不是处理中,则不进行退款
  102. if (_.get(update, 'status') === '1') {
  103. // 1.检验并组织退款信息
  104. refundInfo = await this.toReturnMoney(entity, update, this.tran);
  105. // 2.检查是否退优惠券,该退就退
  106. await this.toReturnCoupons(entity, update, this.tran);
  107. // 3.修改数据, 直接修改成退完款的状态.如果后面退款失败了.直接回滚了
  108. update.status = '-1';
  109. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  110. } else if (_.get(update, 'status') === '!1') {
  111. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  112. }
  113. // 4.修改订单状态
  114. await this.refundOrder(entity, this.tran);
  115. } else if (type === '2') {
  116. // 退货(退库存),退款,退优惠券
  117. // 做记录,但是不是结束状态,都不退
  118. if (uStatus === '-2') {
  119. // 1.检验并组织退款信息
  120. refundInfo = await this.toReturnMoney(entity, update, this.tran);
  121. // 2.检查是否退优惠券
  122. await this.toReturnCoupons(entity, update, this.tran);
  123. // 3.修改订单状态
  124. await this.refundOrder(entity, this.tran);
  125. // 结束时间
  126. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  127. } else if (uStatus === '!2') {
  128. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  129. }
  130. } else if (type === '3') {
  131. // 换货,不需要退款
  132. // 但需要检查买卖双方的快递是否签收,都签收的话,需要将状态改为结束
  133. const uto = _.get(update, 'transport', {});
  134. const eto = _.get(entity, 'transport', {});
  135. // 有关快递的字段整合,将传来的和以前的放在一起.然后找是否签收
  136. const to = { ...eto, ...uto };
  137. const cr = _.get(to, 'customer_receive');
  138. const sr = _.get(to, 'shop_receive');
  139. if (cr && sr) {
  140. update.status = '-3';
  141. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  142. } else if (uStatus === '!3') {
  143. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  144. }
  145. } else if (type === '4') {
  146. // 取消订单: 退钱,退货(退库存)----不过不需要有快递信息,没发货,退优惠券
  147. // 没有商品,只有拆分的订单号,用这个去退
  148. if (uStatus === '4') {
  149. refundInfo = await this.returnOrder(entity, this.tran);
  150. update.status = '-4';
  151. }
  152. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  153. } else if (type === '5') {
  154. // 拒收: 退钱, 退货
  155. // 如果状态不是完成,那就不退,只是数据修改
  156. if (uStatus === '-5') {
  157. refundInfo = await this.returnOrder(entity, this.tran);
  158. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  159. } else if (uStatus === '!5') {
  160. update.end_time = moment().format('YYYY-MM-DD HH:mm:ss');
  161. }
  162. } else throw new BusinessError(ErrorCode.DATA_INVALID, '未知的售后类型,无法处理');
  163. // 售后处理人的添加
  164. if (update.status !== '0') {
  165. const admin = this.ctx.admin;
  166. update.deal_person = admin._id;
  167. }
  168. // 修改数据
  169. this.tran.update('AfterSale', entity._id, update);
  170. await this.tran.run();
  171. // 退钱
  172. if (!refundInfo) return;
  173. // console.log(refundInfo);
  174. const res = await this.ctx.service.trade.pay.refund(refundInfo);
  175. if (res.errcode && res.errcode !== 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, res.errmsg);
  176. } catch (error) {
  177. console.error(error);
  178. await this.tran.rollback();
  179. throw new BusinessError(ErrorCode.SERVICE_FAULT, '售后:修改失败');
  180. } finally {
  181. this.tran.clean();
  182. }
  183. }
  184. /**
  185. * 售后退钱
  186. **组织退钱的请求参数即可,没啥需要改的
  187. * @param {Object} data 售后修改前的数据
  188. * @param {Object} update 要修改的数据
  189. * @param {Transaction} tran 数据库事务
  190. */
  191. async toReturnMoney(data, update, tran) {
  192. const { order_detail, goods, set_id } = data;
  193. const orderDetail = await this.orderDetailModel.findById(order_detail);
  194. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  195. // 取出订单详情的每种商品规格的价格明细
  196. const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
  197. // 根据订单类型使用价格的key
  198. const priceKey = 'grp';
  199. let returnMoney = 0;
  200. let goodsRealPay = 0;
  201. if (!set_id) {
  202. // 商品实际支付的金额
  203. goodsRealPay = _.get(moneyDetail, `${goods._id}.${priceKey}`);
  204. } else {
  205. // 商品实际支付的金额
  206. goodsRealPay = _.get(moneyDetail, `${set_id}.${priceKey}`);
  207. }
  208. // 需要退还的金额,如果传来的数据有金额,就使用传来的,没有的话就用原来的
  209. returnMoney = _.get(update, 'money', _.get(data, 'money'));
  210. if (goodsRealPay < returnMoney) throw new BusinessError(ErrorCode.DATA_INVALID, '退款金额超出该商品支付的金额');
  211. // 组成退款单号
  212. const { order: order_id } = orderDetail;
  213. const order = await this.orderModel.findById(order_id);
  214. if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
  215. const order_no = _.get(order, 'pay.pay_no');
  216. if (!order_no) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到微信支付订单号');
  217. const str = this.ctx.service.util.trade.createNonceStr();
  218. const out_refund_no = `${order_no}-r-${str}`;
  219. const refundInfo = { reason: _.get(data, 'desc', '购物退款'), money: returnMoney, order_no, out_refund_no };
  220. // 退积分
  221. await this.ctx.service.user.point.refundOrderPoint(order_detail, tran);
  222. // 退商铺的入账流水金额
  223. await this.ctx.service.shop.shopInBill.createByAfterSale(data, returnMoney, tran);
  224. await this.ctx.service.user.cashBack.refund(data, tran);
  225. return refundInfo;
  226. }
  227. /**
  228. * 售后退优惠券
  229. **回溯至支付订单,检查每张优惠券影响的商品 及 当前售后的商品 是否都 全额退款
  230. **如果优惠券影响的商品全额退款,那就退还优惠券;
  231. **如果有一个没有退款 或者 全额退款,那优惠券就不退
  232. * @param {Object} data 售后修改前的数据
  233. * @param {Object} update 要修改的数据
  234. * @param {Transaction} tran 数据库事务
  235. */
  236. async toReturnCoupons(data, update, tran) {
  237. const orderDetail = await this.orderDetailModel.findById(data.order_detail);
  238. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  239. const { order: order_id } = orderDetail;
  240. const order = await this.orderModel.findById(order_id);
  241. if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
  242. const odList = await this.orderDetailModel.find({ order: order_id });
  243. // 已退款记录
  244. const goodsRefundList = [];
  245. // 在下面处理已退款记录中,应该把当前的这个售后项,作为已处理售后
  246. const tasq = { order_detail: data.order_detail, 'goods._id': _.get(data, 'goods._id'), status: [ '-1', '-2' ] };
  247. const asdd = JSON.parse(JSON.stringify(data));
  248. if (_.get(update, 'money')) asdd.money = _.get(update, 'money');
  249. const tr = await this.checkIsAllRefund(data);
  250. // 如果当前商品的售后都不满足全额退款条件.那就别往后查了
  251. if (tr) goodsRefundList.push(tasq);
  252. else return;
  253. for (const od of odList) {
  254. const { goods, _id: order_detail } = od;
  255. // 组合成查售后的条件
  256. // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券
  257. const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: [ '-1', '-2' ] }));
  258. for (const asq of afterSaleQuerys) {
  259. const asd = await this.model.findOne(asq);
  260. if (asd) {
  261. const r = await this.checkIsAllRefund(asd);
  262. if (r) goodsRefundList.push(asq);
  263. }
  264. }
  265. }
  266. // 获取支付单的优惠明细
  267. const dd = _.get(order, 'total_detail.discount_detail', {});
  268. for (const uc_id in dd) {
  269. // uc_id 用户领取优惠券的id
  270. // 该优惠券影响的商品id列表
  271. const goodsIds = Object.keys(_.get(dd, uc_id, {}));
  272. // 然后在已退款/退货记录中找,这个优惠券影响的商品是否都退款了.退全额
  273. const r = goodsIds.every(i => goodsRefundList.find(f => i === f['goods._id']));
  274. if (r) {
  275. // 说明这个优惠券影响的商品都退了,这个优惠券也就能退了
  276. tran.update('UserCoupon', uc_id, { status: '0' });
  277. }
  278. }
  279. }
  280. /**
  281. * 售后:修改拆分订单
  282. * @param {Object} data 售后修改前的数据
  283. * @param {Transaction} tran 数据库事务
  284. */
  285. async refundOrder(data, tran) {
  286. const { order_detail } = data;
  287. const orderDetail = await this.orderDetailModel.findById(order_detail);
  288. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  289. // 获取拆分订单的数据,并找到商品列表
  290. const goodsList = _.get(orderDetail, 'goods');
  291. // 依次找这些商品是否都售后完成,都售后完成,就改订单状态
  292. const asList = await this.model.find({ order_detail, status: { $nin: [ '0', '!1', '!2', '!3', '!4', '!5' ] } });
  293. let status;
  294. let fl = [];
  295. // 将当前数据添加进去
  296. fl.push({ goods: _.get(data, 'goods._id'), status: 'finish' });
  297. for (const gs of goodsList) {
  298. const { set_id } = gs;
  299. if (!set_id) {
  300. const r = asList.find(f => ObjectId(_.get(f, 'goods._id')).equals(_.get(gs, '_id')));
  301. if (r) {
  302. const finishList = [ '-1', '-2', '-3', '-4', '-5' ];
  303. if (finishList.includes(r.status)) fl.push({ goods: gs._id, status: 'finish' });
  304. }
  305. } else {
  306. const { goods } = gs;
  307. for (const i of goods) {
  308. const { spec } = i;
  309. const goods_id = _.get(spec, '_id');
  310. const r = asList.find(f => f.set_id === set_id && _.get(f, 'goods._id') === goods_id);
  311. if (r) {
  312. const finishList = [ '-1', '-2', '-3', '-4', '-5' ];
  313. if (finishList.includes(r.status)) fl.push({ set_id, goods: goods_id, status: 'finish' });
  314. }
  315. }
  316. }
  317. }
  318. fl = _.uniqBy(fl, 'goods');
  319. // 说明所有的商品 都有 已处理的售后
  320. if (fl.length === goodsList.length) status = '-4';
  321. // 有状态码,则修改订单的状态
  322. if (status) {
  323. tran.update('OrderDetail', order_detail, { status });
  324. }
  325. }
  326. /**
  327. * 售后 取消订单 & 拒收处理完成
  328. * @param {Object} data 售后修改前的数据
  329. * @param {Transaction} tran 数据库事务
  330. */
  331. async returnOrder(data, tran) {
  332. // 1.退钱
  333. const { order_detail } = data;
  334. const orderDetail = await this.orderDetailModel.findById(order_detail);
  335. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  336. const order_id = _.get(orderDetail, 'order');
  337. const goodsList = _.get(orderDetail, 'goods');
  338. const order = await this.orderModel.findById(order_id);
  339. if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单信息');
  340. this.tran.update('OrderDetail', order_detail, { status: '-1' });
  341. // 实付的钱
  342. const realPay = await this.ctx.service.util.orderDetail.computedRealPay(orderDetail);
  343. // 组成退款单号
  344. const str = this.ctx.service.util.trade.createNonceStr();
  345. const order_no = _.get(order, 'pay.pay_no');
  346. const out_refund_no = `${order_no}-r-${str}`;
  347. const refundInfo = { reason: _.get(data, 'desc'), money: realPay, order_no, out_refund_no };
  348. // 2.退卷
  349. // 查找支付订单拆分的所有订单.不包含当前要退的单.默认当前要退的单是全都退了的,只检查其他拆分的就可以
  350. const odList = await this.orderDetailModel.find({ order: order_id, _id: { $ne: ObjectId(order_detail) } });
  351. const goodsRefundList = [];
  352. // 将这个单内的商品加入满足全额退款的商品列表中
  353. for (const g of goodsList) {
  354. const obj = { order_detail, 'goods._id': g._id };
  355. goodsRefundList.push(obj);
  356. }
  357. for (const od of odList) {
  358. const { goods, _id: order_detail } = od;
  359. // 组合成查售后的条件
  360. // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券
  361. const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: [ '-1', '-2' ] }));
  362. for (const asq of afterSaleQuerys) {
  363. const asd = await this.model.findOne(asq);
  364. if (asd) {
  365. const r = await this.checkIsAllRefund(asd);
  366. if (r) goodsRefundList.push(asq);
  367. }
  368. }
  369. }
  370. // 获取支付单的优惠明细
  371. const dd = _.get(order, 'total_detail.discount_detail', {});
  372. for (const uc_id in dd) {
  373. // uc_id 用户领取优惠券的id
  374. // 该优惠券影响的商品id列表
  375. const goodsIds = Object.keys(_.get(dd, uc_id, {}));
  376. // 然后在已退款/退货记录中找,这个优惠券影响的商品是否都退款了.退全额
  377. const r = goodsIds.every(i => goodsRefundList.find(f => i === f['goods._id']));
  378. if (r) {
  379. // 说明这个优惠券影响的商品都退了,这个优惠券也就能退了
  380. tran.update('UserCoupon', uc_id, { status: '0' });
  381. }
  382. }
  383. // 3.退库存
  384. for (const g of goodsList) {
  385. const { _id, buy_num } = g;
  386. const goodsSpec = await this.goodsSpecModel.findById(_id);
  387. if (!goodsSpec) continue;
  388. const { num } = goodsSpec;
  389. const newNum = this.ctx.plus(buy_num, num);
  390. this.tran.update('GoodsSpec', goodsSpec._id, { num: newNum });
  391. }
  392. return refundInfo;
  393. }
  394. /**
  395. * 判断是否全额退的商品
  396. * @param {Object} data 售后数据
  397. * @return {Boolean}
  398. */
  399. async checkIsAllRefund(data) {
  400. // 商品有退款审核通过的记录,查询每个商品是否退的是全款
  401. // money: 实际退款的金额
  402. const { money } = data;
  403. const od_id = _.get(data, 'order_detail');
  404. const goods_id = _.get(data, 'goods._id');
  405. const moneyDetail = await this.computedGoodsForRefund({ order_detail: od_id, goods_id });
  406. if (moneyDetail) {
  407. const { payTotal } = moneyDetail;
  408. if (this.ctx.minus(payTotal, money) === 0) {
  409. // 添加到已退款的列表中
  410. return true;
  411. }
  412. }
  413. }
  414. /**
  415. * 计算商品退货的金额最大值
  416. * @param {Object} body 参数体
  417. * @param body.order_detail 订单详情id
  418. * @param body.goods_id 商品id/套装id
  419. */
  420. async computedGoodsForRefund({ order_detail, goods_id }) {
  421. const orderDetail = await this.orderDetailModel.findById(order_detail);
  422. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  423. const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
  424. const gmd = _.get(moneyDetail, goods_id);
  425. const obj = {};
  426. obj.payTotal = _.get(gmd, 'grp');
  427. obj.goodsTotal = _.get(gmd, 'st');
  428. obj.freightTotal = _.get(gmd, 'ft');
  429. obj.discountTotal = _.get(gmd, 'dt');
  430. return obj;
  431. // const goodsTotal =
  432. // const goods = orderDetail.goods.find(f => f._id === goods_id);
  433. // // 货物支付金额, 数量*购买数量+ 数量*运费 - 优惠
  434. // const goodsTotal = this.ctx.multiply(goods.sell_money, goods.buy_num);
  435. // const freightTotal = this.ctx.multiply(goods.freight, goods.buy_num);
  436. // let discountTotal = 0;
  437. // const { total_detail = {} } = orderDetail;
  438. // const { discount_detail = {} } = total_detail;
  439. // for (const dd in discount_detail) {
  440. // const dm = _.get(discount_detail, `${dd}.${goods_id}`, 0);
  441. // discountTotal = this.ctx.plus(discountTotal, dm);
  442. // }
  443. // const payTotal = this.ctx.minus(this.ctx.plus(goodsTotal, freightTotal), discountTotal);
  444. // const obj = { payTotal, goodsTotal, freightTotal, discountTotal };
  445. // return obj;
  446. }
  447. /**
  448. * 退单
  449. * @param {Object} body 参数体
  450. * @param body.order_detail 订单详情id
  451. * @param body.desc 退单理由
  452. */
  453. async orderCancel({ order_detail, desc }) {
  454. // 查询要退的订单
  455. const orderDetail = await this.orderDetailModel.findById(order_detail);
  456. if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  457. const { customer } = orderDetail;
  458. const basic = { order_detail, customer, type: '1', desc };
  459. const moneyDetail = this.ctx.service.util.orderDetail.moneyDetail(orderDetail);
  460. let priceKey;
  461. if (_.get(orderDetail, 'type') === '1') priceKey = 'ggrp';
  462. else priceKey = 'grp';
  463. for (const goods_id in moneyDetail) {
  464. const d = _.get(moneyDetail, goods_id, {});
  465. const money = _.get(d, priceKey, 0);
  466. const obj = { ...basic, goods_id, money };
  467. await this.create(obj);
  468. }
  469. }
  470. async fetch(filter) {
  471. assert(filter);
  472. filter = await this.beforeFetch(filter);
  473. const { _id, id } = filter;
  474. if (_id || id) filter = { _id: ObjectId(_id || id) };
  475. const { populate } = this.getRefMods();
  476. let res = await this.model.findOne(filter).populate(populate).exec();
  477. res = await this.afterFetch(filter, res);
  478. return res;
  479. }
  480. /**
  481. * 查询售后快递
  482. * @param {Object} query 查询参数
  483. * @param {String} query.id 售后id
  484. */
  485. async getTransportInfo({ id }) {
  486. const data = await this.model.findById(id);
  487. if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到售后数据');
  488. const { transport } = data;
  489. if (!transport) return;
  490. const { customer_transport_no, customer_transport_type, shop_transport_no, shop_transport_type } = transport;
  491. const result = {};
  492. if (customer_transport_no && customer_transport_type) {
  493. const q = { no: customer_transport_no, type: customer_transport_type };
  494. const customer = await this.ctx.service.util.kd100.search(q);
  495. result.customer = customer;
  496. }
  497. if (shop_transport_no && shop_transport_type) {
  498. const q = { no: shop_transport_no, type: shop_transport_type };
  499. const shop = await this.ctx.service.util.kd100.search(q);
  500. result.shop = shop;
  501. }
  502. return result;
  503. }
  504. async canRefund({ id }) {
  505. const od = await this.orderDetailModel.findById(id, { buy_time: 1 }).lean();
  506. if (!od) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单数据');
  507. const config = await this.configModel.findOne({}, { reward_day: 1 }).lean();
  508. const rd = _.get(config, 'reward_day', 14);
  509. const min = this.ctx.multiply(rd, this.ctx.multiply(24, 60));
  510. const buy_time = _.get(od, 'buy_time');
  511. const m = moment().diff(buy_time, 'm');
  512. const r = m < min;
  513. return r;
  514. }
  515. }
  516. module.exports = AfterSaleService;