zrOrder.js 5.7 KB

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