'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 { ObjectId } = require('mongoose').Types; const moment = require('moment'); const Transaction = require('mongoose-transactions'); // class AfterSaleService extends CrudService { constructor(ctx) { super(ctx, 'aftersale'); this.model = this.ctx.model.Trade.AfterSale; this.orderDetailModel = this.ctx.model.Trade.OrderDetail; this.tran = new Transaction(); } 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, '该商品已有正在处理中的售后申请.请勿重复申请'); const { goods: goodsList } = orderDetail; const goods = goodsList.find(f => ObjectId(f._id).equals(goods_id)); if (!goods) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未在当前订单中搜索到要售后的商品'); 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' }; 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 { 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')) { await this.toRefund({ afterSale_id: entity._id, goods_id: _.get(entity, 'goods._id') }, this.tran); } // 2022-10-17 需求8:标记处理售后的人 if (entity.status === '0' && 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 }); } await this.tran.run(); } catch (error) { console.error(error); await this.tran.rollback(); throw new BusinessError(ErrorCode.SERVICE_FAULT, '售后:修改失败'); } 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 事务的实例 */ async toRefund({ afterSale_id, goods_id }, tran) { 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, '未找到支付订单号'); // 用工具函数,获取退货商品的实际支付价格(常规/团购) 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'); let refundMoney = 0; if (money === needRefund) { // 如果这俩价格相同,说明是正常退 refundMoney = money; } else { // 部分退,部分退是不退优惠券的 refundMoney = needRefund; } // 组成退款单号 const str = this.ctx.service.util.trade.createNonceStr(); const out_refund_no = `${order_no}-r-${str}`; const obj = { reason, money: refundMoney, order_no, out_refund_no }; // 退款请求 if (refundMoney > 0) { const res = await this.ctx.service.trade.pay.refund(obj); 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', end_time: moment().format('YYYY-MM-DD HH:mm:ss') }); } // #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); } // #endregion // 检查优惠券是否都退了 // 查看支付订单-优惠明细中,该优惠券影响的商品是否都有退款/退货成功的记录,且退款成功的记录为退全款的.退部分是不退优惠券的 // 如果有,就说明该优惠券可以退了,没有影响任何一单了 const payOrder = _.get(orderDetail, 'order'); await this.checkToReturnUserCoupon(payOrder, tran); } /** * 检查订单的优惠券并退优惠券, 必须是退全款,部分退款不退优惠券 * @param {Object} order 订单信息 * @param {Transaction} tran 事务的实例 */ async checkToReturnUserCoupon(order, tran) { // 该支付订单下所有拆分的子订单 const orderDetailList = await this.orderDetailModel.find({ order: order._id }); // 已退款记录 const goodsRefundList = []; for (const od of orderDetailList) { const { goods, _id: order_detail } = od; // 组合成查售后的条件 // 然后查这些商品有没有退款审核成功的记录, 且只有退全款的商品才能退券 const afterSaleQuerys = goods.map(i => ({ order_detail, 'goods._id': i._id, status: '-1' })); 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 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} body 参数体 * @param body.order_detail 订单详情id * @param body.goods_id 商品id */ 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 payTotal = this.ctx.minus(this.ctx.plus(goodsTotal, freightTotal), discountTotal); const obj = { payTotal, goodsTotal, freightTotal, discountTotal }; return obj; } /** * 退单 * @param {Object} body 参数体 * @param body.order_detail 订单详情id * @param body.desc 退单理由 */ async orderCancel({ order_detail, desc }) { // 查询要退的订单 const orderDetail = await this.orderDetailModel.findById(order_detail); if (!orderDetail) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息'); const { customer } = orderDetail; const basic = { order_detail, customer, type: '1', desc }; 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) { assert(filter); filter = await this.beforeFetch(filter); const { _id, id } = filter; if (_id || id) filter = { _id: ObjectId(_id || id) }; const { populate } = this.getRefMods(); let res = await this.model.findOne(filter).populate(populate).exec(); res = await this.afterFetch(filter, res); return res; } } module.exports = AfterSaleService;