'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 Transaction = require('mongoose-transactions'); // class OrderDetailService extends CrudService { constructor(ctx) { super(ctx, 'orderdetail'); this.model = this.ctx.model.Trade.OrderDetail; this.orderModel = this.ctx.model.Trade.Order; this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec; this.userCouponModel = this.ctx.model.User.UserCoupon; this.goodsRateModel = this.ctx.model.Shop.GoodsRate; this.afterSaleModel = this.ctx.model.Trade.AfterSale; this.platformActModel = this.ctx.model.System.PlatformAct; this.tran = new Transaction(); } async searchOrderTransport({ id, goods_id }) { const orderDetail = await this.model.findById(id); if (!id) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息'); const { transport = [] } = orderDetail; let toSearch = []; if (goods_id) { // 传来指定商品,查有该商品的订单,但是如果没有,就查每一项中是否有 shop_transport_no 和 shop_transport_type // 如果有这俩属性,说明也有单子,也查出来 toSearch = transport.filter(f => _.isArray(f.goods) && f.goods.find(fg => fg.goods_id === goods_id)); if (toSearch.length <= 0) { toSearch = transport.filter(f => f.shop_transport_no && f.shop_transport_type); } } else { toSearch = transport; } const result = []; for (const t of toSearch) { const { shop_transport_no: no, shop_transport_type: type, goods } = t; if (!no || !type) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '缺少快递信息'); const res = await this.ctx.service.util.kd100.search({ no, type }); result.push({ ...res, goods }); } return result; } /** * 创建:用户支付完成后的订单 * @param {Object} body 请求参数体 * @param body.order_id 下单的id * @param {Transaction} tran 数据库事务实例 */ async create({ order_id }, tran) { assert(order_id, '缺少支付订单信息'); const order = await this.orderModel.findById(order_id); if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付订单的数据'); // 返现部分 const { customer, address, goods: shopGoods, no, status, buy_time, pay, total_detail: otd, inviter } = order; if (status === '0') throw new BusinessError(ErrorCode.DATA_INVALID, '订单未支付'); const orderDetailData = { customer, address, order: order_id, buy_time, pay_time: _.get(pay, 'pay_time'), status, inviter }; const shopMoneyDetail = this.ctx.service.util.order.shopMoneyDetail(order); // 分订单计数器 let noTimes = 1; // const list = []; const ids = []; const groupShop = _.groupBy(shopGoods, 'shop'); for (const shop in groupShop) { const list = groupShop[shop]; const detailNo = `${no}-${noTimes}`; const total_detail = shopMoneyDetail[shop]; const newGoodsList = []; for (const s of list) { const { is_set = '1', remarks } = s; if (is_set === '1') { let goodsList = _.get(s, 'goods', []); goodsList = goodsList.map(i => ({ ...i, remarks })); // 优惠部分分割 if (_.get(otd, 'discount_detail')) { // 如果有优惠部分,那就得找,优惠里面有没有对应的商品规格 const discount_detail = this.getGoodsListDiscountDetail(goodsList, _.get(otd, 'discount_detail')); total_detail.discount_detail = discount_detail; } noTimes++; newGoodsList.push(...goodsList); } else { const g = _.pick(s, [ 'name', 'buy_num', 'freight', 'is_set', 'is_use', 'meta', 'sell_money', 'set_id', 'goods', 'remarks' ]); g._id = g.set_id; newGoodsList.push(g); } } const obj = { ...orderDetailData, shop, no: detailNo, total_detail, goods: newGoodsList }; const od_id = tran.insert('OrderDetail', obj); ids.push(od_id); } // for (const s of shopGoods) { // const { is_set = '1' } = s; // if (is_set === '1') { // const shop = _.get(s, 'shop'); // const remarks = _.get(s, 'remarks'); // const goodsList = _.get(s, 'goods', []); // const detailNo = `${no}-${noTimes}`; // const total_detail = shopMoneyDetail[shop]; // // 优惠部分分割 // if (_.get(otd, 'discount_detail')) { // // 如果有优惠部分,那就得找,优惠里面有没有对应的商品规格 // const discount_detail = this.getGoodsListDiscountDetail(goodsList, _.get(otd, 'discount_detail')); // total_detail.discount_detail = discount_detail; // } // noTimes++; // const obj = { ...orderDetailData, shop, goods: goodsList, no: detailNo, total_detail, remarks }; // obj.status = '1'; // // list.push(obj); // const od_id = tran.insert('OrderDetail', obj); // ids.push(od_id); // } else { // const shop = _.get(s, 'shop'); // const detailNo = `${no}-${noTimes}`; // const total_detail = shopMoneyDetail[shop]; // const remarks = _.get(s, 'remarks'); // const g = _.pick(s, [ 'name', 'buy_num', 'freight', 'is_set', 'is_use', 'meta', 'sell_money', 'set_id', 'goods' ]); // g._id = g.set_id; // const obj = { ...orderDetailData, shop, goods: g, no: detailNo, total_detail, remarks, status: '1' }; // const od_id = tran.insert('OrderDetail', obj); // ids.push(od_id); // } // } return ids; } /** * 将商品规格列表中,优惠的部分提取出来,分单用 * @param {Array} goodsList 某店的商品列表 * @param {Object} odd discount_detail 支付订单的优惠券设置 */ getGoodsListDiscountDetail(goodsList, odd) { const result = {}; for (const uc_id in odd) { const detail = odd[uc_id]; const obj = {}; for (const g of goodsList) { const { _id } = g; const gdd = detail[_id]; if (gdd) obj[_id] = gdd; } result[uc_id] = obj; } return result; } /** * 计算某店铺的金额总计 * @param {Array} goodsList 商品规格列表 */ getTotalDetail(goodsList) { const goods_total = goodsList.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(n.sell_money, n.buy_num)), 0); const freight_total = goodsList.reduce((p, n) => this.ctx.plus(p, this.ctx.multiply(n.freight, n.buy_num)), 0); return { goods_total, freight_total }; } 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).lean(); res = JSON.parse(JSON.stringify(res)); if (!res) throw new BusinessError(ErrorCode.DATA_NOT_EXIST); // 找售后和评论 const afterSale = await this.afterSaleModel.find({ order_detail: res._id }); const rate = await this.goodsRateModel.find({ orderDetail: res._id }); const goods = _.get(res, 'goods', []); for (const g of goods) { const { is_set = '1' } = g; if (is_set === '1') { const r = afterSale.find(f => ObjectId(_.get(f, 'goods._id')).equals(g._id)); if (r) g.is_afterSale = true; else g.is_afterSale = false; const r2 = rate.find(f => ObjectId(_.get(f, 'goodsSpec')).equals(g._id)); if (r2) { g.is_rate = true; g.rate = r2._id; } else g.is_rate = false; } else { const { set_id, goods } = g; for (const i of goods) { const { spec } = i; const r = afterSale.find(f => f.set_id === set_id && _.get(f, 'goods._id') === _.get(spec, '_id')); if (r) i.is_afterSale = true; else i.is_afterSale = false; const r2 = rate.find(f => f.set_id === set_id && f.goodsSpec === _.get(spec, '_id')); if (r2) { i.is_rate = true; i.rate = r2._id; } else i.is_rate = false; } } } res.goods = goods; // 整理total_detail为列表:让前端显示更方便 const total_detail = _.get(res, 'total_detail', {}); res.total_detail = await this.fetchResetTotalDetail(total_detail); return res; } async fetchResetTotalDetail(total_detail) { const tarr = []; if (_.isNumber(_.get(total_detail, 'goods_total', 0))) tarr.push({ key: 'goods_total', zh: '商品总价', money: _.get(total_detail, 'goods_total', 0) }); if (_.isNumber(_.get(total_detail, 'freight_total', 0))) tarr.push({ key: 'freight_total', zh: '运费总价', money: _.get(total_detail, 'freight_total', 0) }); const act = _.get(total_detail, 'act', []); for (const a of act) { const { platform_act, platform_act_type, discount } = a; const text = `满${platform_act_type === '6' ? '折' : '减'}活动`; const actData = await this.platformActModel.findById(platform_act); const title = _.get(actData, 'act_time.title', text); const obj = { key: platform_act, zh: title, money: this.ctx.minus(0, discount) }; tarr.push(obj); } const discount_detail = _.get(total_detail, 'discount_detail', {}); const userCoupons = Object.keys(discount_detail); if (userCoupons.length > 0) { let dm = 0; for (const uc_id of userCoupons) { const detail = _.get(discount_detail, uc_id, {}); const values = Object.values(detail); dm = values.reduce((p, n) => this.ctx.minus(p, n), dm); } tarr.push({ key: 'coupons', zh: '优惠券优惠', money: dm }); } return tarr; } async query(filter, { skip = 0, limit, sort, desc, projection } = {}) { // 处理排序 if (sort && _.isString(sort)) { sort = { [sort]: desc ? -1 : 1 }; } else if (sort && _.isArray(sort)) { sort = sort.map(f => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {}); } let condition = _.cloneDeep(filter); condition = await this.beforeQuery(condition); condition = this.dealFilter(condition); // 过滤出ref字段 const pipline = [{ $sort: { 'meta.createdAt': -1 } }]; pipline.push({ $match: condition }); // 整理字段 // 店铺需要的字段 pipline.push({ $addFields: { shop_id: { $toObjectId: '$shop' } } }); pipline.push({ $lookup: { from: 'shop', localField: 'shop_id', foreignField: '_id', as: 'shopInfo', }, }); pipline.push({ $addFields: { customer_id: { $toObjectId: '$customer' } } }); pipline.push({ $lookup: { from: 'user', localField: 'customer_id', foreignField: '_id', as: 'customerInfo', }, }); pipline.push({ $unwind: '$shopInfo' }); pipline.push({ $unwind: '$customerInfo' }); const lastProject = { $project: { _id: 1, type: 1, order: 1, buy_time: 1, pay_time: 1, status: 1, no: 1, address: 1, goods: 1, shop: { _id: '$shopInfo._id', name: '$shopInfo.name', }, customer: { _id: '$customerInfo._id', name: '$customerInfo.name', }, total_detail: 1, }, }; pipline.push(lastProject); const qPipline = _.cloneDeep(pipline); if (parseInt(skip) >= 0) qPipline.push({ $skip: parseInt(skip) }); if (parseInt(limit)) qPipline.push({ $limit: parseInt(limit) }); const rs = await this.model.aggregate(qPipline); const tPipline = _.cloneDeep(pipline); tPipline.push({ $addFields: { id: { $toString: '$_id' } } }); tPipline.push({ $count: 'id' }); const t = await this.model.aggregate(tPipline); const total = _.get(_.head(t), 'id'); const list = []; for (const i of rs) { const { goods, _id: orderDetail } = i; const obj = _.cloneDeep(i); const real_pay = this.ctx.service.util.orderDetail.computedRealPay(obj); obj.real_pay = real_pay; obj.buy_num_total = goods.reduce((p, n) => this.ctx.plus(p, n.buy_num), 0); for (const og of obj.goods) { const { file = [], _id: goodsSpec } = og; const gfile = _.get(og, 'goods.file', []); const nf = [ ...file, ...gfile ]; const url = _.get(_.head(nf), 'url'); og.url = url; delete og.file; delete og.goods.file; // 评价 const q = { orderDetail, goodsSpec }; const rate = await this.goodsRateModel.findOne(q, { _id: 1 }); obj.rate = _.get(rate, '_id'); obj.price = _.get(obj, 'price', obj.sell_money); } // 售后 const asum = await this.afterSaleModel.count({ order_detail: obj._id, status: { $nin: [ '-1', '!1', '-2', '!2', '-3', '!3', '-4', '!4', '-5', '!5' ] } }); obj.is_afterSale = asum > 0; list.push(obj); } return { data: list, total }; } async update(filter, update, { projection } = {}) { // 需要注意不能让order和orderDetail修改total_detail assert(filter); assert(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('OrderDetail', filter._id, update); if (_.get(update, 'status') === '3') { // 积分部分 await this.ctx.service.user.point.addPoints(filter._id, this.tran); // 返现部分 await this.ctx.service.user.cashBack.create(filter._id, this.tran); // 店铺流水部分 await this.ctx.service.shop.shopInBill.createByOrder(entity, this.tran); } await this.tran.run(); const e = await this.model.findOne(filter, projection).exec(); return e; } catch (error) { await this.tran.rollback(); } finally { this.tran.clean(); } } } module.exports = OrderDetailService;