trade.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. //
  7. class TradeService extends CrudService {
  8. constructor(ctx) {
  9. super(ctx, 'trade');
  10. this.redis = this.app.redis;
  11. this.redisKey = this.app.config.redisKey;
  12. this.redisTimeout = this.app.config.redisTimeout;
  13. this.shopModel = this.ctx.model.Shop.Shop;
  14. this.goodsModel = this.ctx.model.Shop.Goods;
  15. this.goodsSpecModel = this.ctx.model.Shop.GoodsSpec;
  16. this.cartModel = this.ctx.model.Trade.Cart;
  17. this.setModel = this.ctx.model.Shop.GoodsSet;
  18. }
  19. /**
  20. * 检查选中的购物车是否符合购买的条件
  21. * @param {Object} body 请求体
  22. * @param body.cartIds 选中的购物车id列表
  23. * @param {Boolean} makeCache 生成缓存
  24. */
  25. async checkCartBuy({ cartIds }, makeCache = true) {
  26. const list = await this.cartModel.find({ _id: cartIds });
  27. let result = { result: true };
  28. for (const i of list) {
  29. const res = await this.checkCanBuy(i, false);
  30. if (res.result !== true) {
  31. result = res;
  32. break;
  33. }
  34. }
  35. if (makeCache) {
  36. const key = await this.makeOrderKey(cartIds);
  37. result.key = key;
  38. }
  39. return result;
  40. }
  41. /**
  42. * 检测是否可以购买该物品
  43. * @param {Object} data 查询条件
  44. * @param param.shop 商店id
  45. * @param param.goods 商品id
  46. * @param param.goodsSpec 商品规格id
  47. * @param param.num 购买数量
  48. * @param param.type 订单类型: 0:常规单 1:团购单,需要按团购价计算
  49. * @param param.group 团id
  50. * @param param.inviter 邀请用户id 返现部分
  51. * @param param.is_set 是否是套装,默认 1 不是套装
  52. * @param params.set_id 套装id
  53. * @param makeCache 生成缓存
  54. */
  55. async checkCanBuy(data, makeCache = true) {
  56. if (!_.isArray(data)) data = [ data ];
  57. let result = { result: true };
  58. for (const i of data) {
  59. const { is_set = '1' } = i;
  60. if (is_set === '1') {
  61. // 非套装,使用正常的判断
  62. result = await this.checkGoodsCanBuy(i);
  63. } else {
  64. // 套装,使用套装判断
  65. result = await this.checkSetCanBuy(i);
  66. }
  67. if (result.result !== true) break;
  68. }
  69. // #endregion
  70. if (result.result && makeCache) {
  71. const key = await this.makeOrderKey(data);
  72. result.key = key;
  73. }
  74. return result;
  75. }
  76. /**
  77. * 套装判断是否可以购买
  78. * @param {Object} data 购物车/直接购买 数据对象
  79. */
  80. async checkSetCanBuy(data) {
  81. const { set_id, num } = data;
  82. const setData = await this.setModel.findById(set_id).lean();
  83. let result = { result: true };
  84. const { set, single_stock = '1', stock, is_use } = setData;
  85. if (is_use === '1') return { result: false, msg: '套装已下架' };
  86. for (const sd of set) {
  87. const { shop, goods, spec, set_num } = sd;
  88. if (!shop) {
  89. result.result = false;
  90. result.msg = '缺少店铺信息';
  91. break;
  92. }
  93. if (!goods) {
  94. result.result = false;
  95. result.msg = '缺少商品信息';
  96. break;
  97. }
  98. if (!spec) {
  99. result.result = false;
  100. result.msg = '缺少商品规格信息';
  101. break;
  102. }
  103. if (!num) {
  104. result.result = false;
  105. result.msg = '缺少购买数量';
  106. break;
  107. }
  108. // 1.检查商店是否正常运行
  109. const shopData = await this.shopModel.findById(shop);
  110. const shopRes = this.checkShop(shopData);
  111. if (shopRes.result !== true) {
  112. result = shopRes;
  113. break;
  114. }
  115. // 2.检查商品是否可以购买
  116. const goodsData = await this.goodsModel.findById(goods);
  117. const goodsRes = this.checkGoods(goodsData);
  118. if (goodsRes.result !== true) {
  119. result = goodsRes;
  120. break;
  121. }
  122. // 3.根据套装库存设置判断是走各个商品库存还是走套装库存
  123. if (single_stock === '1') {
  124. // 走各个商品库存
  125. const goodsSpecData = await this.goodsSpecModel.findById(spec);
  126. // 单独计算下商品数量 = 购买数量 * 组成套装的数量
  127. const setGoodsNum = this.ctx.multiply(num, set_num);
  128. const gsRes = this.checkGoodsSpec(goodsSpecData, setGoodsNum, '0');
  129. if (gsRes.result !== true) {
  130. result = gsRes;
  131. break;
  132. }
  133. }
  134. }
  135. if (single_stock !== '1') {
  136. // 走单独的库存
  137. if (this.ctx.minus(stock, num) < 0) result = { result: false, msg: '套装库存不足' };
  138. }
  139. return result;
  140. }
  141. /**
  142. * 常规商品判断是否可以购买
  143. * @param {Object} data 购物车/直接购买 数据对象
  144. */
  145. async checkGoodsCanBuy(data) {
  146. let result = { result: true };
  147. const { shop, goods, goodsSpec, num } = data;
  148. if (!shop) {
  149. result.result = false;
  150. result.msg = '缺少店铺信息';
  151. return result;
  152. }
  153. if (!goods) {
  154. result.result = false;
  155. result.msg = '缺少商品信息';
  156. return result;
  157. }
  158. if (!goodsSpec) {
  159. result.result = false;
  160. result.msg = '缺少商品规格信息';
  161. return result;
  162. }
  163. if (!num) {
  164. result.result = false;
  165. result.msg = '缺少购买数量';
  166. return result;
  167. }
  168. // 1.检查商店是否正常运行
  169. const shopData = await this.shopModel.findById(shop);
  170. const shopRes = this.checkShop(shopData);
  171. if (shopRes.result !== true) {
  172. result = shopRes;
  173. return result;
  174. }
  175. // 2.检查商品是否可以购买
  176. const goodsData = await this.goodsModel.findById(goods);
  177. const goodsRes = this.checkGoods(goodsData);
  178. if (goodsRes.result !== true) {
  179. result = goodsRes;
  180. return result;
  181. }
  182. // 3.检验该规格是否可以购买
  183. const goodsSpecData = await this.goodsSpecModel.findById(goodsSpec);
  184. const gsRes = this.checkGoodsSpec(goodsSpecData, num);
  185. if (gsRes.result !== true) {
  186. result = gsRes;
  187. return result;
  188. }
  189. return result;
  190. }
  191. /**
  192. * 检查商店是否满足条件
  193. * @param {Object} shopData 店铺数据
  194. */
  195. checkShop(shopData) {
  196. const result = { result: true };
  197. if (!shopData) {
  198. result.msg = '未找到店铺';
  199. result.result = false;
  200. return result;
  201. }
  202. if (shopData.status !== '1') {
  203. result.msg = '店铺不处于营业状态';
  204. result.result = false;
  205. return result;
  206. }
  207. return result;
  208. }
  209. /**
  210. * 检查商品是否满足条件
  211. * @param {Object} goodsData 商品数据
  212. */
  213. checkGoods(goodsData) {
  214. const result = { result: true };
  215. if (!goodsData) {
  216. result.msg = '未找到商品';
  217. result.result = false;
  218. return result;
  219. }
  220. if (goodsData.status === '0') {
  221. result.msg = '该商品已下架';
  222. result.result = false;
  223. return result;
  224. }
  225. return result;
  226. }
  227. /**
  228. * 检查商品的规格是否满足购买条件
  229. * @param {Object} goodsSpecData 商品规格的数据
  230. * @param {Number} num 购买数量
  231. * @param {String} is_set 是否是套装;套装不需要检查购买限制
  232. */
  233. checkGoodsSpec(goodsSpecData, num, is_set = '1') {
  234. const result = { result: true };
  235. if (!goodsSpecData) {
  236. result.msg = '未找到商品的指定规格';
  237. result.result = false;
  238. return result;
  239. }
  240. if (goodsSpecData.status !== '0') {
  241. result.msg = '该规格的商品已下架';
  242. result.result = false;
  243. return result;
  244. }
  245. if (goodsSpecData.num < num) {
  246. result.msg = '库存量不足';
  247. result.result = false;
  248. return result;
  249. }
  250. if (is_set !== '1') return result;
  251. // 判断是否有购买限制
  252. if (_.get(goodsSpecData, 'buy_limit', '0') !== '0') {
  253. // 有购买限制,需要检测购买限制
  254. const buy_limit = _.get(goodsSpecData, 'buy_limit');
  255. const limit_num = _.get(goodsSpecData, 'limit_num', 0);
  256. if (buy_limit === '1') {
  257. if (num < limit_num) {
  258. result.msg = `该规格至少购买 ${limit_num} 份, 购买数量不足`;
  259. result.result = false;
  260. return result;
  261. }
  262. } else if (buy_limit === '2') {
  263. if (num > limit_num) {
  264. result.msg = `该规格至多购买 ${limit_num} 份, 购买数量超过上限`;
  265. result.result = false;
  266. return result;
  267. }
  268. } else if (buy_limit === '3') {
  269. if (num !== limit_num) {
  270. result.msg = `该规格只能购买 ${limit_num} 份`;
  271. result.result = false;
  272. return result;
  273. }
  274. }
  275. }
  276. return result;
  277. }
  278. // 随机字符串产生函数
  279. createNonceStr() {
  280. return Math.random().toString(36).substr(2, 15);
  281. }
  282. // 生成key
  283. async makeOrderKey(data) {
  284. const str = this.createNonceStr();
  285. const key = `${this.redisKey.orderKeyPrefix}${str}`;
  286. const value = JSON.stringify(data);
  287. await this.redis.set(key, value, 'EX', this.redisTimeout);
  288. return str;
  289. }
  290. }
  291. module.exports = TradeService;