123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- '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;
|