Save.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. const API = require('../../../lib/API.js')
  2. const db = require('../../../plugin/DataBase/db.js')
  3. const { BaseStdResponse } = require('../../../BaseStdResponse.js')
  4. const AccessControl = require('../../../lib/AccessControl.js')
  5. function parseUsernames(text) {
  6. if (!text) return []
  7. return [...new Set(
  8. String(text)
  9. .split(/[,,\n\r\s]+/)
  10. .map((s) => s.trim())
  11. .filter(Boolean)
  12. )]
  13. }
  14. class SaveCoupon extends API {
  15. constructor() {
  16. super()
  17. this.setPath('/Admin/Coupon/Save')
  18. this.setMethod('POST')
  19. }
  20. async onRequest(req, res) {
  21. const {
  22. uuid,
  23. session,
  24. id,
  25. code,
  26. name,
  27. discount_type,
  28. discount_value,
  29. user_scope,
  30. goods_scope,
  31. total_limit,
  32. per_user_limit,
  33. min_amount,
  34. start_time,
  35. end_time,
  36. state,
  37. allowed_usernames,
  38. allowed_goods_ids
  39. } = req.body
  40. if ([uuid, session, code, discount_type, discount_value, user_scope, goods_scope, state].some(
  41. (v) => v === '' || v == null
  42. )) {
  43. return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
  44. }
  45. if (!(await AccessControl.checkSession(uuid, session))) {
  46. return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED })
  47. }
  48. const permission = await AccessControl.getPermission(uuid)
  49. if (!permission.includes('admin') && !permission.includes('product')) {
  50. return res.json({ ...BaseStdResponse.PERMISSION_DENIED })
  51. }
  52. const normalizedCode = String(code).trim().toUpperCase()
  53. if (!/^[A-Z0-9_-]{3,32}$/.test(normalizedCode)) {
  54. return res.json({
  55. ...BaseStdResponse.ERR,
  56. msg: '优惠码仅支持 3-32 位字母、数字、下划线或横线'
  57. })
  58. }
  59. if (!['percent', 'fixed'].includes(discount_type)) {
  60. return res.json({ ...BaseStdResponse.ERR, msg: '折扣类型无效' })
  61. }
  62. const dValue = Number(discount_value)
  63. if (discount_type === 'percent' && (dValue <= 0 || dValue > 100)) {
  64. return res.json({ ...BaseStdResponse.ERR, msg: '折扣比例需在 1~100 之间' })
  65. }
  66. if (discount_type === 'fixed' && dValue <= 0) {
  67. return res.json({ ...BaseStdResponse.ERR, msg: '减免金额需大于 0' })
  68. }
  69. const uScope = Number(user_scope)
  70. const gScope = Number(goods_scope)
  71. const usernames = parseUsernames(allowed_usernames)
  72. const goodsIds = (Array.isArray(allowed_goods_ids) ? allowed_goods_ids : [])
  73. .map((g) => Number(g))
  74. .filter((g) => g > 0)
  75. if (uScope === 1 && usernames.length === 0) {
  76. return res.json({ ...BaseStdResponse.ERR, msg: '请指定可使用该优惠码的用户' })
  77. }
  78. if (gScope === 1 && goodsIds.length === 0) {
  79. return res.json({ ...BaseStdResponse.ERR, msg: '请指定可使用该优惠码的商品' })
  80. }
  81. const dup = await db.query(
  82. 'SELECT id FROM coupons WHERE code = ? AND id != ? LIMIT 1',
  83. [normalizedCode, id || 0]
  84. )
  85. if (dup && dup.length > 0) {
  86. return res.json({ ...BaseStdResponse.ERR, msg: '优惠码已存在' })
  87. }
  88. const time = Date.now()
  89. const conn = await db.connect()
  90. try {
  91. await conn.beginTransaction()
  92. let couponId = id ? Number(id) : null
  93. if (!couponId) {
  94. const [ins] = await conn.execute(
  95. `INSERT INTO coupons (
  96. code, name, discount_type, discount_value, user_scope, goods_scope,
  97. total_limit, per_user_limit, min_amount, start_time, end_time,
  98. state, create_user, create_time, update_time
  99. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
  100. [
  101. normalizedCode,
  102. name || '',
  103. discount_type,
  104. dValue,
  105. uScope,
  106. gScope,
  107. Number(total_limit || 0),
  108. Math.max(1, Number(per_user_limit || 1)),
  109. Number(min_amount || 0),
  110. start_time || null,
  111. end_time || null,
  112. Number(state),
  113. uuid,
  114. time,
  115. time
  116. ]
  117. )
  118. couponId = ins.insertId
  119. } else {
  120. await conn.execute(
  121. `UPDATE coupons SET
  122. code = ?, name = ?, discount_type = ?, discount_value = ?,
  123. user_scope = ?, goods_scope = ?, total_limit = ?, per_user_limit = ?,
  124. min_amount = ?, start_time = ?, end_time = ?, state = ?, update_time = ?
  125. WHERE id = ?`,
  126. [
  127. normalizedCode,
  128. name || '',
  129. discount_type,
  130. dValue,
  131. uScope,
  132. gScope,
  133. Number(total_limit || 0),
  134. Math.max(1, Number(per_user_limit || 1)),
  135. Number(min_amount || 0),
  136. start_time || null,
  137. end_time || null,
  138. Number(state),
  139. time,
  140. couponId
  141. ]
  142. )
  143. }
  144. await conn.execute('DELETE FROM coupon_users WHERE coupon_id = ?', [couponId])
  145. await conn.execute('DELETE FROM coupon_goods WHERE coupon_id = ?', [couponId])
  146. if (uScope === 1) {
  147. for (const username of usernames) {
  148. const [userRows] = await conn.execute(
  149. 'SELECT uuid FROM users WHERE username = ? LIMIT 1',
  150. [username]
  151. )
  152. if (!userRows || userRows.length !== 1) {
  153. await conn.rollback()
  154. return res.json({
  155. ...BaseStdResponse.ERR,
  156. msg: `用户不存在:${username}`
  157. })
  158. }
  159. await conn.execute(
  160. 'INSERT INTO coupon_users (coupon_id, user_uuid) VALUES (?, ?)',
  161. [couponId, userRows[0].uuid]
  162. )
  163. }
  164. }
  165. if (gScope === 1) {
  166. for (const goodsId of goodsIds) {
  167. await conn.execute(
  168. 'INSERT INTO coupon_goods (coupon_id, goods_id) VALUES (?, ?)',
  169. [couponId, goodsId]
  170. )
  171. }
  172. }
  173. await conn.commit()
  174. return res.json({ ...BaseStdResponse.OK, id: couponId })
  175. } catch (err) {
  176. await conn.rollback()
  177. this.logger.error(`保存优惠码失败: ${err.stack}`)
  178. return res.json({ ...BaseStdResponse.ERR, msg: '保存优惠码失败' })
  179. }
  180. }
  181. }
  182. module.exports.SaveCoupon = SaveCoupon