|
@@ -0,0 +1,202 @@
|
|
|
|
|
+const API = require('../../../lib/API.js')
|
|
|
|
|
+const db = require('../../../plugin/DataBase/db.js')
|
|
|
|
|
+const { BaseStdResponse } = require('../../../BaseStdResponse.js')
|
|
|
|
|
+const AccessControl = require('../../../lib/AccessControl.js')
|
|
|
|
|
+function parseUsernames(text) {
|
|
|
|
|
+ if (!text) return []
|
|
|
|
|
+ return [...new Set(
|
|
|
|
|
+ String(text)
|
|
|
|
|
+ .split(/[,,\n\r\s]+/)
|
|
|
|
|
+ .map((s) => s.trim())
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ )]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class SaveCoupon extends API {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ super()
|
|
|
|
|
+ this.setPath('/Admin/Coupon/Save')
|
|
|
|
|
+ this.setMethod('POST')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async onRequest(req, res) {
|
|
|
|
|
+ const {
|
|
|
|
|
+ uuid,
|
|
|
|
|
+ session,
|
|
|
|
|
+ id,
|
|
|
|
|
+ code,
|
|
|
|
|
+ name,
|
|
|
|
|
+ discount_type,
|
|
|
|
|
+ discount_value,
|
|
|
|
|
+ user_scope,
|
|
|
|
|
+ goods_scope,
|
|
|
|
|
+ total_limit,
|
|
|
|
|
+ per_user_limit,
|
|
|
|
|
+ min_amount,
|
|
|
|
|
+ start_time,
|
|
|
|
|
+ end_time,
|
|
|
|
|
+ state,
|
|
|
|
|
+ allowed_usernames,
|
|
|
|
|
+ allowed_goods_ids
|
|
|
|
|
+ } = req.body
|
|
|
|
|
+
|
|
|
|
|
+ if ([uuid, session, code, discount_type, discount_value, user_scope, goods_scope, state].some(
|
|
|
|
|
+ (v) => v === '' || v == null
|
|
|
|
|
+ )) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!(await AccessControl.checkSession(uuid, session))) {
|
|
|
|
|
+ return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const permission = await AccessControl.getPermission(uuid)
|
|
|
|
|
+ if (!permission.includes('admin') && !permission.includes('product')) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.PERMISSION_DENIED })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const normalizedCode = String(code).trim().toUpperCase()
|
|
|
|
|
+ if (!/^[A-Z0-9_-]{3,32}$/.test(normalizedCode)) {
|
|
|
|
|
+ return res.json({
|
|
|
|
|
+ ...BaseStdResponse.ERR,
|
|
|
|
|
+ msg: '优惠码仅支持 3-32 位字母、数字、下划线或横线'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!['percent', 'fixed'].includes(discount_type)) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '折扣类型无效' })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const dValue = Number(discount_value)
|
|
|
|
|
+ if (discount_type === 'percent' && (dValue <= 0 || dValue > 100)) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '折扣比例需在 1~100 之间' })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (discount_type === 'fixed' && dValue <= 0) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '减免金额需大于 0' })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const uScope = Number(user_scope)
|
|
|
|
|
+ const gScope = Number(goods_scope)
|
|
|
|
|
+ const usernames = parseUsernames(allowed_usernames)
|
|
|
|
|
+ const goodsIds = (Array.isArray(allowed_goods_ids) ? allowed_goods_ids : [])
|
|
|
|
|
+ .map((g) => Number(g))
|
|
|
|
|
+ .filter((g) => g > 0)
|
|
|
|
|
+
|
|
|
|
|
+ if (uScope === 1 && usernames.length === 0) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '请指定可使用该优惠码的用户' })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (gScope === 1 && goodsIds.length === 0) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '请指定可使用该优惠码的商品' })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const dup = await db.query(
|
|
|
|
|
+ 'SELECT id FROM coupons WHERE code = ? AND id != ? LIMIT 1',
|
|
|
|
|
+ [normalizedCode, id || 0]
|
|
|
|
|
+ )
|
|
|
|
|
+ if (dup && dup.length > 0) {
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '优惠码已存在' })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const time = Date.now()
|
|
|
|
|
+ const conn = await db.connect()
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await conn.beginTransaction()
|
|
|
|
|
+
|
|
|
|
|
+ let couponId = id ? Number(id) : null
|
|
|
|
|
+
|
|
|
|
|
+ if (!couponId) {
|
|
|
|
|
+ const [ins] = await conn.execute(
|
|
|
|
|
+ `INSERT INTO coupons (
|
|
|
|
|
+ code, name, discount_type, discount_value, user_scope, goods_scope,
|
|
|
|
|
+ total_limit, per_user_limit, min_amount, start_time, end_time,
|
|
|
|
|
+ state, create_user, create_time, update_time
|
|
|
|
|
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
+ [
|
|
|
|
|
+ normalizedCode,
|
|
|
|
|
+ name || '',
|
|
|
|
|
+ discount_type,
|
|
|
|
|
+ dValue,
|
|
|
|
|
+ uScope,
|
|
|
|
|
+ gScope,
|
|
|
|
|
+ Number(total_limit || 0),
|
|
|
|
|
+ Math.max(1, Number(per_user_limit || 1)),
|
|
|
|
|
+ Number(min_amount || 0),
|
|
|
|
|
+ start_time || null,
|
|
|
|
|
+ end_time || null,
|
|
|
|
|
+ Number(state),
|
|
|
|
|
+ uuid,
|
|
|
|
|
+ time,
|
|
|
|
|
+ time
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ couponId = ins.insertId
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ `UPDATE coupons SET
|
|
|
|
|
+ code = ?, name = ?, discount_type = ?, discount_value = ?,
|
|
|
|
|
+ user_scope = ?, goods_scope = ?, total_limit = ?, per_user_limit = ?,
|
|
|
|
|
+ min_amount = ?, start_time = ?, end_time = ?, state = ?, update_time = ?
|
|
|
|
|
+ WHERE id = ?`,
|
|
|
|
|
+ [
|
|
|
|
|
+ normalizedCode,
|
|
|
|
|
+ name || '',
|
|
|
|
|
+ discount_type,
|
|
|
|
|
+ dValue,
|
|
|
|
|
+ uScope,
|
|
|
|
|
+ gScope,
|
|
|
|
|
+ Number(total_limit || 0),
|
|
|
|
|
+ Math.max(1, Number(per_user_limit || 1)),
|
|
|
|
|
+ Number(min_amount || 0),
|
|
|
|
|
+ start_time || null,
|
|
|
|
|
+ end_time || null,
|
|
|
|
|
+ Number(state),
|
|
|
|
|
+ time,
|
|
|
|
|
+ couponId
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await conn.execute('DELETE FROM coupon_users WHERE coupon_id = ?', [couponId])
|
|
|
|
|
+ await conn.execute('DELETE FROM coupon_goods WHERE coupon_id = ?', [couponId])
|
|
|
|
|
+
|
|
|
|
|
+ if (uScope === 1) {
|
|
|
|
|
+ for (const username of usernames) {
|
|
|
|
|
+ const [userRows] = await conn.execute(
|
|
|
|
|
+ 'SELECT uuid FROM users WHERE username = ? LIMIT 1',
|
|
|
|
|
+ [username]
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!userRows || userRows.length !== 1) {
|
|
|
|
|
+ await conn.rollback()
|
|
|
|
|
+ return res.json({
|
|
|
|
|
+ ...BaseStdResponse.ERR,
|
|
|
|
|
+ msg: `用户不存在:${username}`
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ 'INSERT INTO coupon_users (coupon_id, user_uuid) VALUES (?, ?)',
|
|
|
|
|
+ [couponId, userRows[0].uuid]
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (gScope === 1) {
|
|
|
|
|
+ for (const goodsId of goodsIds) {
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ 'INSERT INTO coupon_goods (coupon_id, goods_id) VALUES (?, ?)',
|
|
|
|
|
+ [couponId, goodsId]
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await conn.commit()
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.OK, id: couponId })
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ await conn.rollback()
|
|
|
|
|
+ this.logger.error(`保存优惠码失败: ${err.stack}`)
|
|
|
|
|
+ return res.json({ ...BaseStdResponse.ERR, msg: '保存优惠码失败' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports.SaveCoupon = SaveCoupon
|