|
|
@@ -20,6 +20,7 @@ import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import com.etotem.cfc.util.SortUtil;
|
|
|
@@ -192,7 +193,10 @@ public class CouponService {
|
|
|
return null;
|
|
|
}
|
|
|
// 新增:会员专属券(applicableTo=MEMBERSHIP)仅允许会员使用
|
|
|
- if ("MEMBERSHIP".equals(coupon.getApplicableTo()) && !isMember(userId)) {
|
|
|
+ // 例外:手动发放的券(grantType=MANUAL)允许非会员使用,用于赠送升级场景
|
|
|
+ if ("MEMBERSHIP".equals(coupon.getApplicableTo())
|
|
|
+ && !Objects.equals(coupon.getGrantType(), "MANUAL")
|
|
|
+ && !isMember(userId)) {
|
|
|
return null;
|
|
|
}
|
|
|
// 无门槛券(CASH)忽略起用门槛;满减/折扣券校验
|
|
|
@@ -258,6 +262,87 @@ public class CouponService {
|
|
|
familyCouponMapper.insert(fc);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 管理员定向发放「会员升级优惠券」给指定用户。
|
|
|
+ * 流程:校验用户 → 取该用户所属家庭 → 自动创建会员专属券模板(type=CASH 无门槛、
|
|
|
+ * applicableTo=MEMBERSHIP 仅限会员订单)→ 发放到该家庭 → 落发券流水
|
|
|
+ * (grantType=MANUAL, source=userId:{id})。
|
|
|
+ *
|
|
|
+ * @param userId 目标用户ID(users.id)
|
|
|
+ * @param amountFen 面额(分)
|
|
|
+ * @param validUntil 有效期止(null 则默认 +30 天)
|
|
|
+ * @param remark 备注(写入券名,便于区分)
|
|
|
+ * @return {couponId, familyId, couponName, amountFen};用户不存在/未加入家庭时返回含 error 的 Map
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public Map<String, Object> grantMemberUpgradeCoupon(Long userId, Integer amountFen, Date validUntil, String remark) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ User user = userMapper.selectById(userId);
|
|
|
+ if (user == null) {
|
|
|
+ result.put("error", "用户不存在");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ Long familyId = user.getFamilyId();
|
|
|
+ if (familyId == null) {
|
|
|
+ result.put("error", "该用户未加入家庭,无法发放家庭券");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ Date now = new Date();
|
|
|
+ if (validUntil == null) {
|
|
|
+ validUntil = new Date(now.getTime() + 30L * 24 * 3600 * 1000);
|
|
|
+ }
|
|
|
+ // 创建会员专属券模板(每次发放一张独立模板,便于追踪发放记录)
|
|
|
+ Coupon coupon = new Coupon();
|
|
|
+ coupon.setName(buildManualCouponName(amountFen, remark));
|
|
|
+ coupon.setType("CASH");
|
|
|
+ coupon.setValue(amountFen);
|
|
|
+ coupon.setMinSpend(0);
|
|
|
+ coupon.setApplicableTo("MEMBERSHIP");
|
|
|
+ coupon.setValidFrom(now);
|
|
|
+ coupon.setValidUntil(validUntil);
|
|
|
+ coupon.setTotalCount(1);
|
|
|
+ coupon.setUsedCount(0);
|
|
|
+ coupon.setStatus("ACTIVE");
|
|
|
+ coupon.setCreatedAt(now);
|
|
|
+ couponMapper.insert(coupon);
|
|
|
+
|
|
|
+ // 发放到用户所在家庭
|
|
|
+ issueToFamily(coupon.getId(), familyId);
|
|
|
+
|
|
|
+ // 落发券流水
|
|
|
+ FamilyCouponGrantLog log = new FamilyCouponGrantLog();
|
|
|
+ log.setFamilyId(familyId);
|
|
|
+ log.setCouponId(coupon.getId());
|
|
|
+ log.setGrantType("MANUAL");
|
|
|
+ log.setQuantity(1);
|
|
|
+ log.setSource("userId:" + userId);
|
|
|
+ log.setCreatedAt(now);
|
|
|
+ familyCouponGrantLogMapper.insert(log);
|
|
|
+
|
|
|
+ logger.info("管理员定向发放会员升级券: userId={}, familyId={}, couponId={}, amountFen={}, remark={}",
|
|
|
+ userId, familyId, coupon.getId(), amountFen, remark);
|
|
|
+ result.put("couponId", coupon.getId());
|
|
|
+ result.put("familyId", familyId);
|
|
|
+ result.put("couponName", coupon.getName());
|
|
|
+ result.put("amountFen", amountFen);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 生成手动发放券名称:会员升级券{金额}元[-备注] */
|
|
|
+ private String buildManualCouponName(Integer amountFen, String remark) {
|
|
|
+ String yuan;
|
|
|
+ if (amountFen % 100 == 0) {
|
|
|
+ yuan = String.valueOf(amountFen / 100);
|
|
|
+ } else {
|
|
|
+ yuan = String.format("%.2f", amountFen / 100.0);
|
|
|
+ }
|
|
|
+ String base = "会员升级券" + yuan + "元";
|
|
|
+ if (remark != null && !remark.trim().isEmpty()) {
|
|
|
+ return base + "-" + remark.trim();
|
|
|
+ }
|
|
|
+ return base;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 发放优惠券并落流水(幂等,家庭维度)。
|
|
|
* period 非空时先查流水:已存在则跳过(PERIODIC 防重用);唯一键冲突由事务回滚保证一致性。
|