zrOrder.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose-free/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const _ = require('lodash');
  5. const assert = require('assert');
  6. const moment = require('moment');
  7. const Transaction = require('mongoose-transactions');
  8. const { ObjectId } = require('mongoose').Types;
  9. //
  10. class ZrOrderService extends CrudService {
  11. constructor(ctx) {
  12. super(ctx, 'zrorder');
  13. this.redis = this.app.redis;
  14. this.redisKey = this.app.config.redisKey;
  15. this.redisTimeout = this.app.config.redisTimeout;
  16. this.model = this.ctx.model.ZrOrder;
  17. this.goodsModel = this.ctx.model.ZrGoods;
  18. this.pointModel = this.ctx.model.Base.Point;
  19. this.shopModel = this.ctx.model.Base.Shop;
  20. this.addressModel = this.ctx.model.Base.Address;
  21. this.tran = new Transaction();
  22. }
  23. // 进入订单页前,通过函数判断,存储是否可以购买 指定商品 的 指定数量
  24. // 下单,走同样的函数判断.
  25. // 直接生成订单,扣除积分
  26. async searchOrderTransport({ id }) {
  27. const order = await this.model.findById(id);
  28. if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单信息');
  29. const { transport = {} } = order;
  30. const { shop_transport_no: no, shop_transport_type: type } = transport;
  31. if (!no || !type) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '缺少快递信息');
  32. const res = await this.ctx.service.util.kd100.search({ no, type });
  33. return res;
  34. }
  35. async fetch(filter, { sort, desc, projection } = {}) {
  36. assert(filter);
  37. filter = await this.beforeFetch(filter);
  38. const { _id, id } = filter;
  39. if (_id || id) filter = { _id: ObjectId(_id || id) };
  40. // 处理排序
  41. if (sort && _.isString(sort)) {
  42. sort = { [sort]: desc ? -1 : 1 };
  43. } else if (sort && _.isArray(sort)) {
  44. sort = sort.map(f => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {});
  45. }
  46. const { populate } = this.getRefMods();
  47. let res = await this.model.findOne(filter, projection).populate(populate).exec();
  48. res = await this.afterFetch(filter, res);
  49. return res;
  50. }
  51. async create(data) {
  52. const { shop, goods, buy_num, address, remarks } = data;
  53. const res = await this.checkCanBuy({ shop, goods, num: buy_num }, false);
  54. if (!res.result) throw new BusinessError(ErrorCode.DATA_INVALID, res.msg);
  55. // 可以购买
  56. const customer = _.get(this.ctx.user, '_id');
  57. let goodsInfo = await this.goodsModel.findById(goods);
  58. goodsInfo = JSON.parse(JSON.stringify(goodsInfo));
  59. const buy_time = moment().format('YYYY-MM-DD HH:mm:ss');
  60. const no = `${moment().format('YYYYMMDDHHmmss')}-${this.createNonceStr()}`;
  61. const addressInfo = await this.addressModel.findById(address);
  62. try {
  63. const orderData = {
  64. customer,
  65. shop,
  66. goods: goodsInfo,
  67. buy_num,
  68. buy_time,
  69. no,
  70. address: addressInfo,
  71. status: '1',
  72. remarks,
  73. };
  74. const order_id = this.tran.insert('ZrOrder', orderData);
  75. // #region 积分处理
  76. const costTotal = this.ctx.multiply(_.get(goodsInfo, 'cost'), buy_num);
  77. const pointData = { customer, point: costTotal, time: buy_time, source: '-2', source_id: order_id };
  78. this.tran.insert('Point', pointData);
  79. // #endregion
  80. // #region 库存处理
  81. const newNum = this.ctx.minus(_.get(goodsInfo, 'num'), buy_num);
  82. this.tran.update('ZrGoods', goods, { num: newNum });
  83. // #endregion
  84. await this.tran.run();
  85. return 'ok';
  86. } catch (error) {
  87. await this.tran.rollback();
  88. console.error(error);
  89. } finally {
  90. this.tran.clean();
  91. }
  92. }
  93. async toMakeOrder({ key }) {
  94. key = `${this.redisKey.orderKeyPrefix}${key}`;
  95. let data = await this.redis.get(key);
  96. if (!data) throw new BusinessError(ErrorCode.SERVICE_FAULT, '请求超时,请重新进入下单页');
  97. data = JSON.parse(data);
  98. const { shop, goods, num: buy_num } = data;
  99. // 店铺信息
  100. const shopInfo = await this.shopModel.findById(shop);
  101. // 商品信息
  102. const goodsInfo = await this.goodsModel.findById(goods);
  103. // 消耗积分
  104. const costTotal = this.ctx.multiply(_.get(goodsInfo, 'cost'), buy_num);
  105. // 积分信息
  106. const customer = _.get(this.ctx.user, '_id');
  107. const points = await this.pointComputedTotal({ customer });
  108. // 地址信息
  109. const address = await this.addressModel.findOne({ customer, is_default: '1' });
  110. return { buy_num, shop: shopInfo, goods: goodsInfo, points, address, costTotal };
  111. }
  112. /**
  113. * 检查是否可以购买商品,并生成缓存
  114. * @param {Object} body 参数体
  115. * @param body.shop 店铺id
  116. * @param body.goods 尊荣商品id
  117. * @param body.num 购买数量
  118. * @param {Boolean} makeCache 是否生成缓存
  119. */
  120. async checkCanBuy({ shop, goods, num }, makeCache = true) {
  121. const result = { result: true };
  122. const shopInfo = await this.shopModel.findById(shop);
  123. if (!shopInfo || _.get(shopInfo, '1')) {
  124. result.result = false;
  125. result.msg = '店铺当前不处于营业状态';
  126. return result;
  127. }
  128. const goodsInfo = await this.goodsModel.findById(goods);
  129. if (!goodsInfo) {
  130. result.result = false;
  131. result.msg = '未找到尊荣商品';
  132. return result;
  133. }
  134. const user = this.ctx.user;
  135. const customer = _.get(user, '_id');
  136. if (!customer) {
  137. result.result = false;
  138. result.msg = '未找到用户信息';
  139. }
  140. // #region 库存判断
  141. const knum = _.get(goodsInfo, 'num');
  142. const res = this.ctx.minus(knum, num);
  143. if (res < 0) {
  144. result.result = false;
  145. result.msg = '库存不足';
  146. return result;
  147. }
  148. // #endregion
  149. // #region 积分判断
  150. const pointTotal = await this.pointComputedTotal({ customer });
  151. // 计算购买所需积分
  152. const cost = _.get(goodsInfo, 'cost', 0);
  153. if (cost === 0) {
  154. result.result = false;
  155. result.msg = '商品设置的尊荣有误,无法进行购买';
  156. return result;
  157. }
  158. const costTotal = this.ctx.multiply(cost, num);
  159. const afterCost = this.ctx.minus(pointTotal, costTotal);
  160. if (afterCost < 0) {
  161. result.result = false;
  162. result.msg = '您的积分不足';
  163. return result;
  164. }
  165. // #endregion
  166. if (makeCache) {
  167. const key = await this.makeOrderKey({ shop, goods, num });
  168. result.key = key;
  169. }
  170. return result;
  171. }
  172. // 生成key
  173. async makeOrderKey(data) {
  174. const str = this.createNonceStr();
  175. const key = `${this.redisKey.orderKeyPrefix}${str}`;
  176. const value = JSON.stringify(data);
  177. await this.redis.set(key, value, 'EX', this.redisTimeout);
  178. return str;
  179. }
  180. /**
  181. * 计算用户的积分
  182. * @param {Object} data 数据对象
  183. * @param data.customer 用户数据id
  184. */
  185. async pointComputedTotal({ customer }) {
  186. assert(customer, '缺少用户信息');
  187. const res = await this.pointModel.find({ customer });
  188. const total = res.reduce((p, n) => {
  189. let point = n.point;
  190. if (!(n.source === '0' || n.source === '1')) point = -point;
  191. return this.ctx.plus(p, point);
  192. }, 0);
  193. return total;
  194. }
  195. // 随机字符串产生函数
  196. createNonceStr() {
  197. return Math.random().toString(36).substr(2, 15);
  198. }
  199. }
  200. module.exports = ZrOrderService;