'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'); const { ObjectId } = require('mongoose').Types; // class ZrOrderService extends CrudService { constructor(ctx) { super(ctx, 'zrorder'); this.redis = this.app.redis; this.redisKey = this.app.config.redisKey; this.redisTimeout = this.app.config.redisTimeout; this.model = this.ctx.model.ZrOrder; this.goodsModel = this.ctx.model.ZrGoods; this.pointModel = this.ctx.model.Base.Point; this.shopModel = this.ctx.model.Base.Shop; this.addressModel = this.ctx.model.Base.Address; this.tran = new Transaction(); } // 进入订单页前,通过函数判断,存储是否可以购买 指定商品 的 指定数量 // 下单,走同样的函数判断. // 直接生成订单,扣除积分 async searchOrderTransport({ id }) { const order = await this.model.findById(id); if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息'); const { transport = {} } = order; const { shop_transport_no: no, shop_transport_type: type } = transport; if (!no || !type) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '缺少快递信息'); const res = await this.ctx.service.util.kd100.search({ no, type }); return res; } async fetch(filter, { sort, desc, projection } = {}) { assert(filter); filter = await this.beforeFetch(filter); const { _id, id } = filter; if (_id || id) filter = { _id: ObjectId(_id || id) }; // 处理排序 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 }), {}); } const { populate } = this.getRefMods(); let res = await this.model.findOne(filter, projection).populate(populate).exec(); res = await this.afterFetch(filter, res); return res; } async create(data) { const { shop, goods, buy_num, address, remarks } = data; const res = await this.checkCanBuy({ shop, goods, num: buy_num }, false); if (!res.result) throw new BusinessError(ErrorCode.DATA_INVALID, res.msg); // 可以购买 const customer = _.get(this.ctx.user, '_id'); let goodsInfo = await this.goodsModel.findById(goods); goodsInfo = JSON.parse(JSON.stringify(goodsInfo)); const buy_time = moment().format('YYYY-MM-DD HH:mm:ss'); const no = `${moment().format('YYYYMMDDHHmmss')}-${this.createNonceStr()}`; const addressInfo = await this.addressModel.findById(address); try { const orderData = { customer, shop, goods: goodsInfo, buy_num, buy_time, no, address: addressInfo, status: '1', remarks, }; const order_id = this.tran.insert('ZrOrder', orderData); // #region 积分处理 const costTotal = this.ctx.multiply(_.get(goodsInfo, 'cost'), buy_num); const pointData = { customer, point: costTotal, time: buy_time, source: '-2', source_id: order_id }; this.tran.insert('Point', pointData); // #endregion // #region 库存处理 const newNum = this.ctx.minus(_.get(goodsInfo, 'num'), buy_num); this.tran.update('ZrGoods', goods, { num: newNum }); // #endregion await this.tran.run(); return 'ok'; } catch (error) { await this.tran.rollback(); console.error(error); } finally { this.tran.clean(); } } 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); const { shop, goods, num: buy_num } = data; // 店铺信息 const shopInfo = await this.shopModel.findById(shop); // 商品信息 const goodsInfo = await this.goodsModel.findById(goods); // 消耗积分 const costTotal = this.ctx.multiply(_.get(goodsInfo, 'cost'), buy_num); // 积分信息 const customer = _.get(this.ctx.user, '_id'); const points = await this.pointComputedTotal({ customer }); // 地址信息 const address = await this.addressModel.findOne({ customer, is_default: '1' }); return { buy_num, shop: shopInfo, goods: goodsInfo, points, address, costTotal }; } /** * 检查是否可以购买商品,并生成缓存 * @param {Object} body 参数体 * @param body.shop 店铺id * @param body.goods 尊荣商品id * @param body.num 购买数量 * @param {Boolean} makeCache 是否生成缓存 */ async checkCanBuy({ shop, goods, num }, makeCache = true) { const result = { result: true }; const shopInfo = await this.shopModel.findById(shop); if (!shopInfo || _.get(shopInfo, '1')) { result.result = false; result.msg = '店铺当前不处于营业状态'; return result; } const goodsInfo = await this.goodsModel.findById(goods); if (!goodsInfo) { result.result = false; result.msg = '未找到尊荣商品'; return result; } const user = this.ctx.user; const customer = _.get(user, '_id'); if (!customer) { result.result = false; result.msg = '未找到用户信息'; } // #region 库存判断 const knum = _.get(goodsInfo, 'num'); const res = this.ctx.minus(knum, num); if (res < 0) { result.result = false; result.msg = '库存不足'; return result; } // #endregion // #region 积分判断 const pointTotal = await this.pointComputedTotal({ customer }); // 计算购买所需积分 const cost = _.get(goodsInfo, 'cost', 0); if (cost === 0) { result.result = false; result.msg = '商品设置的尊荣有误,无法进行购买'; return result; } const costTotal = this.ctx.multiply(cost, num); const afterCost = this.ctx.minus(pointTotal, costTotal); if (afterCost < 0) { result.result = false; result.msg = '您的积分不足'; return result; } // #endregion if (makeCache) { const key = await this.makeOrderKey({ shop, goods, num }); result.key = key; } return result; } // 生成key async makeOrderKey(data) { const str = this.createNonceStr(); const key = `${this.redisKey.orderKeyPrefix}${str}`; const value = JSON.stringify(data); await this.redis.set(key, value, 'EX', this.redisTimeout); return str; } /** * 计算用户的积分 * @param {Object} data 数据对象 * @param data.customer 用户数据id */ async pointComputedTotal({ customer }) { assert(customer, '缺少用户信息'); const res = await this.pointModel.find({ customer }); const total = res.reduce((p, n) => { let point = n.point; if (!(n.source === '0' || n.source === '1')) point = -point; return this.ctx.plus(p, point); }, 0); return total; } // 随机字符串产生函数 createNonceStr() { return Math.random().toString(36).substr(2, 15); } } module.exports = ZrOrderService;