|
@@ -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;
|