123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- '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 moment = require('moment');
- const Transaction = require('mongoose-transactions');
- //
- class OrderService extends CrudService {
- constructor(ctx) {
- super(ctx, 'order');
- this.redis = this.app.redis;
- this.redisKey = this.app.config.redisKey;
- this.model = this.ctx.model.Trade.Order;
- this.goodsModel = this.ctx.model.Shop.Goods;
- this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
- this.addressModel = this.ctx.model.User.Address;
- this.cartModel = this.ctx.model.Trade.Cart;
- this.userCouponModel = this.ctx.model.User.UserCoupon;
- this.tran = new Transaction();
- }
- /**
- * 创建订单
- * 1.检测商品是否可以购买
- * 2.数据做快照处理
- * @param {Object} body
- */
- async create(body) {
- // 声明事务
- try {
- const user = this.ctx.user;
- const customer = _.get(user, '_id');
- if (!customer) throw new BusinessError(ErrorCode.NOT_LOGIN, '未找到用户信息');
- const { address, goods, total_detail, coupon = [], type = '0', group } = body;
- if (coupon.length > 1) throw new BusinessError(ErrorCode.DATA_INVALID, '目前只允许使用1张优惠券');
- // 检测商品是否可以下单
- for (const i of goods) {
- const { shop } = i;
- for (const g of i.goods) {
- const { goods_id: goods, goodsSpec_id: goodsSpec, num } = g;
- const { result, msg } = await this.ctx.service.util.trade.checkCanBuy({ shop, goods, goodsSpec, num }, false);
- if (!result) throw new BusinessError(ErrorCode.DATA_INVALID, msg);
- }
- }
- const orderData = {};
- const goodsSpecs = [];
- // 数据做快照处理
- // 1.地址快照
- const addressData = await this.addressModel.findById(address._id);
- if (!addressData) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到邮寄地址数据');
- // 2.商品快照
- const goodsData = [];
- // 商店不做快照,但是商品和商品对应的规格做快照
- const { populate } = this.ctx.service.shop.goodsSpec.getRefMods();
- for (const i of goods) {
- const { goods: goodsList, ...others } = i;
- const qp = [];
- for (const g of goodsList) {
- const { goodsSpec_id, num, cart_id } = g;
- let goodsSpec = await this.goodsSpecModel.findById(goodsSpec_id).populate(populate);
- if (!goodsSpec) continue;
- goodsSpec = JSON.parse(JSON.stringify(goodsSpec));
- // 将商店内容剔除
- const { goods: gd, ...dOthers } = goodsSpec;
- const gdOthers = _.omit(gd, [ 'shop' ]);
- const obj = { ...dOthers, goods: gdOthers, buy_num: num, tags: _.get(gdOthers, 'tags') };
- if (cart_id) obj.cart_id = cart_id;
- qp.push(obj);
- goodsSpecs.push({ id: goodsSpec_id, buy_num: num, sell_money: obj.sell_money, group_sell_money: _.get(obj, 'group_config.money'), type });
- }
- goodsData.push({ ...others, goods: qp });
- }
- // 3.商品总计明细.
- // 计算优惠券的明细
- const discountDetail = await this.ctx.service.user.userCoupon.computedOrderCouponDiscount(coupon, goodsSpecs);
- const totalDetailData = { ...total_detail, discount_detail: JSON.parse(JSON.stringify(discountDetail)) };
- // 接下来组织订单数据
- orderData.address = addressData;
- orderData.goods = goodsData;
- orderData.total_detail = totalDetailData;
- // 1.用户数据
- orderData.customer = customer;
- // 2.下单时间
- orderData.buy_time = moment().format('YYYY-MM-DD HH:mm:ss');
- // 3.订单号
- const str = this.ctx.service.util.trade.createNonceStr();
- orderData.no = `${moment().format('YYYYMMDDHHmmss')}-${str}`;
- // 4.状态
- orderData.status = '0';
- // #region 是否是团购
- orderData.type = type;
- if (type === '1' || group) {
- orderData.group = group;
- }
- // #endregion
- // 生成数据
- const order_id = this.tran.insert('Order', orderData);
- // 处理库存,删除购物车
- await this.dealGoodsNum(goodsData);
- // 处理优惠券,改为使用过
- if (coupon.length > 1) await this.ctx.service.user.userCoupon.useCoupon(coupon, this.tran);
- await this.tran.run();
- return order_id;
- } catch (error) {
- await this.tran.rollback();
- console.error(error);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '订单发生错误,下单失败');
- } finally {
- // 清空事务
- this.tran.clean();
- }
- }
- /**
- * 取消订单(支付前)
- * @param {Object} body 参数体
- * @param {String} body.order_id 订单id
- */
- async cancel({ order_id }) {
- try {
- const order = await this.model.findById(order_id);
- if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
- // 退单分为 2 部分, 涉及价格不需要管.因为这是交钱之前的的操作,不涉及退款
- // 1.货物的库存
- const { goods: shopGoods, total_detail } = order;
- // 需要归还库存的商品规格列表:{_id:商品规格id,buy_num:购买的数量}
- for (const sg of shopGoods) {
- const { goods: goodsList } = sg;
- const list = goodsList.map(i => _.pick(i, [ '_id', 'buy_num' ]));
- for (const i of list) {
- const goodsSpec = await this.goodsSpecModel.findById(i._id, { num: 1 });
- if (!goodsSpec) continue;
- const newNum = this.ctx.plus(goodsSpec.num, i.buy_num);
- this.tran.update('GoodsSpec', i._id, { num: newNum });
- }
- }
- // 2.优惠券
- const { discount_detail } = total_detail;
- if (discount_detail) {
- // 第一层key值全都是使用的优惠券id.拿去改了就好了
- const couponIds = Object.keys(discount_detail);
- for (const uc_id of couponIds) {
- this.tran.update('UserCoupon', uc_id, { status: '0' });
- }
- }
- // 3.订单修改为关闭
- this.tran.update('Order', order_id, { status: '-1' });
- await this.tran.run();
- } catch (error) {
- await this.tran.rollback();
- console.error(error);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '订单取消失败');
- } finally {
- this.tran.clean();
- }
- }
- /**
- * 减库存,删除购物车
- * @param {Array} list 商品
- */
- async dealGoodsNum(list) {
- for (const i of list) {
- for (const g of i.goods) {
- const { _id, buy_num, cart_id } = g;
- const goodsSpec = await this.goodsSpecModel.findById(_id);
- const newNum = this.ctx.minus(goodsSpec.num, buy_num);
- this.tran.update('GoodsSpec', _id, { num: newNum });
- if (cart_id) this.tran.remove('Cart', cart_id);
- }
- }
- }
- /**
- * 进入下单页面
- * @param {Object} body 请求参数
- * @param body.key 缓存key
- */
- async toMakeOrder({ key }) {
- key = `${this.redisKey.orderKeyPrefix}${key}`;
- let data = await this.redis.get(key);
- if (!data) throw new BusinessError(ErrorCode.SERVICE_FAULT, '请求超时,请重新进入下单页');
- data = JSON.parse(data);
- let specsData = [];
- if (_.isArray(data)) {
- // 购物车来的: 1.循环校验 规格商品; 2.按店铺分组
- const { populate } = this.ctx.service.trade.cart.getRefMods();
- const carts = await this.cartModel.find({ _id: data }).populate(populate);
- for (const cart of carts) {
- const { result, msg } = await this.ctx.service.util.trade.checkCanBuy(cart, false);
- if (!result) throw new BusinessError(ErrorCode.DATA_INVALID, msg);
- }
- specsData = this.setCartsGoodsToPageData(carts);
- } else if (_.isObject(data)) {
- // 商品页单独买: 1.校验规格商品; 2:按店铺分组
- const { result, msg } = await this.ctx.service.util.trade.checkCanBuy(data, false);
- if (!result) throw new BusinessError(ErrorCode.DATA_INVALID, msg);
- const { populate } = this.ctx.service.shop.goodsSpec.getRefMods();
- const list = await this.goodsSpecModel.find({ _id: data.goodsSpec }).populate(populate);
- specsData = this.setGoodsToPageData(list, data.num);
- } else throw new BusinessError(ErrorCode.DATA_INVALID, '数据不正确,请重新下单');
- // 组装页面的数据
- const user = this.ctx.user;
- const customer = _.get(user, '_id');
- if (!customer) throw new BusinessError(ErrorCode.NOT_LOGIN, '未找到用户信息');
- const pageData = {};
- // 找到默认地址
- const address = await this.addressModel.findOne({ customer, is_default: '1' });
- pageData.address = address;
- // #region 团购部分
- const { type, group } = data;
- pageData.type = type; // 新添字段,订单类型
- pageData.group = group; // 新添字段,团购的团id
- // #endregion
- // 商品总价,各店铺的价格明细
- specsData = this.ctx.service.util.order.makeOrder_computedShopTotal(specsData, type);
- const shopTotalDetail = this.ctx.service.util.order.makerOrder_computedOrderTotal(specsData);
- pageData.goodsData = specsData;
- pageData.orderTotal = shopTotalDetail;
- // 优惠券列表
- const couponList = await this.ctx.service.user.userCoupon.toMakeOrder_getList(specsData);
- pageData.couponList = couponList;
- // 查询优惠券列表
- return pageData;
- }
- /**
- * 单商品整理数据,剩下的可以简略
- * @param {Array} list 规格数组
- * @param num 购买数量
- */
- setGoodsToPageData(list, num) {
- // 按店铺分组,精简字段
- const arr = [];
- for (const i of list) {
- const obj = {};
- obj.shop_name = _.get(i.goods, 'shop.name');
- obj.shop = _.get(i.goods, 'shop._id');
- const goods = {};
- goods.goods_id = _.get(i.goods, '_id');
- goods.goods_name = _.get(i.goods, 'name');
- goods.goodsSpec_id = _.get(i, '_id');
- goods.goodsSpec_name = _.get(i, 'name');
- goods.freight = _.get(i, 'freight');
- goods.money = _.get(i, 'sell_money');
- goods.num = num;
- goods.file = _.get(i.goods, 'file');
- goods.tags = _.get(i.goods, 'tags');
- // #region 团购部分
- // 团购价
- goods.group_sell_money = _.get(i, 'group_config.money');
- // #endregion
- obj.goods = [ goods ];
- arr.push(obj);
- }
- return arr;
- }
- /**
- *购物车数据整理
- * @param {Array} list 规格数组
- */
- setCartsGoodsToPageData(list) {
- const arr = [];
- list = _.groupBy(list, 'shop._id');
- for (const key in list) {
- const data = list[key];
- const shopData = _.get(_.head(data), 'shop');
- const obj = {};
- obj.shop = _.get(shopData, '_id');
- obj.shop_name = _.get(shopData, 'name');
- const goodsList = [];
- for (const i of data) {
- const goods = {};
- goods.cart_id = _.get(i, '_id');
- goods.goods_id = _.get(i.goods, '_id');
- goods.goods_name = _.get(i.goods, 'name');
- goods.goodsSpec_id = _.get(i.goodsSpec, '_id');
- goods.goodsSpec_name = _.get(i.goodsSpec, 'name');
- goods.freight = _.get(i.goodsSpec, 'freight');
- goods.money = _.get(i.goodsSpec, 'sell_money');
- goods.num = _.get(i, 'num');
- goods.file = _.get(i.goods, 'file', []);
- goods.tags = _.get(i.goods, 'tags');
- // #region 团购部分
- // 团购价
- goods.group_sell_money = _.get(i.goodsSpec, 'group_config.money');
- // #endregion
- goodsList.push(goods);
- }
- obj.goods = goodsList;
- arr.push(obj);
- }
- return arr;
- }
- async afterQuery(filter, data) {
- data = JSON.parse(JSON.stringify(data));
- for (const i of data) {
- const { goods } = i;
- const buy_num_total = goods.reduce(
- (p, n) =>
- this.ctx.plus(
- p,
- n.goods.reduce((np, ng) => this.ctx.plus(np, ng.buy_num), 0)
- ),
- 0
- );
- i.buy_num_total = buy_num_total;
- i.real_pay = _.get(i, 'pay.pay_money');
- }
- return data;
- }
- }
- module.exports = OrderService;
|