|
|
@@ -12,7 +12,9 @@ import com.etotem.cfc.entity.OctopusEffectKeyPerson;
|
|
|
import com.etotem.cfc.entity.OctopusEffectRecord;
|
|
|
import com.etotem.cfc.entity.OctopusKeyPerson;
|
|
|
import com.etotem.cfc.entity.PpointConsumptionLog;
|
|
|
+import com.etotem.cfc.entity.OrderItem;
|
|
|
import com.etotem.cfc.entity.Product;
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
import com.etotem.cfc.entity.ReferralTree;
|
|
|
import com.etotem.cfc.entity.User;
|
|
|
import com.etotem.cfc.mapper.FamilyMemberMapper;
|
|
|
@@ -21,6 +23,8 @@ import com.etotem.cfc.mapper.OctopusEffectRecordMapper;
|
|
|
import com.etotem.cfc.mapper.OctopusKeyPersonMapper;
|
|
|
import com.etotem.cfc.mapper.PpointConsumptionLogMapper;
|
|
|
import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.mapper.OrderItemMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
import com.etotem.cfc.mapper.ReferralTreeMapper;
|
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
@@ -92,6 +96,10 @@ public class CfCommissionService {
|
|
|
@Resource
|
|
|
private ProductMapper productMapper;
|
|
|
@Resource
|
|
|
+ private OrderItemMapper orderItemMapper;
|
|
|
+ @Resource
|
|
|
+ private ProductOrderMapper productOrderMapper;
|
|
|
+ @Resource
|
|
|
private OctopusKeyPersonMapper octopusKeyPersonMapper;
|
|
|
@Resource
|
|
|
private OctopusEffectRecordMapper octopusEffectRecordMapper;
|
|
|
@@ -133,7 +141,8 @@ public class CfCommissionService {
|
|
|
// 当前消费人返 CF(F02 统一账本:1 元 = 100 CF,按实付金额)
|
|
|
earnBuyerCf(orderId, orderType, buyerUserId, paidAmountCent);
|
|
|
// 推荐人分润(两级销售奖)
|
|
|
- distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, productId);
|
|
|
+ distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId,
|
|
|
+ orderAmountCent, paidAmountCent, productId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -157,7 +166,8 @@ public class CfCommissionService {
|
|
|
public void settleReferrerOnly(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
|
|
|
Integer orderAmountCent, Integer paidAmountCent) {
|
|
|
earnBuyerCf(orderId, orderType, buyerUserId, paidAmountCent);
|
|
|
- distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId, orderAmountCent, null);
|
|
|
+ distributeToReferrers(orderId, orderType, buyerUserId, buyerFamilyId,
|
|
|
+ orderAmountCent, paidAmountCent, null);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -184,12 +194,12 @@ public class CfCommissionService {
|
|
|
* </ul>
|
|
|
*/
|
|
|
private void distributeToReferrers(Long orderId, String orderType, Long buyerUserId, Long buyerFamilyId,
|
|
|
- Integer orderAmountCent, Long productId) {
|
|
|
+ Integer orderAmountCent, Integer paidAmountCent, Long productId) {
|
|
|
if (orderAmountCent == null || orderAmountCent <= 0) return;
|
|
|
int amountYuan = orderAmountCent / 100;
|
|
|
|
|
|
// 本单消费产生的 P 点:F03a / F03b 的共同基数(§4.4)
|
|
|
- int basePpoints = calcOrderPpoints(orderType, amountYuan, productId);
|
|
|
+ int basePpoints = calcOrderPpoints(orderId, orderType, orderAmountCent, paidAmountCent, productId);
|
|
|
if (basePpoints <= 0) {
|
|
|
log.info("本单 P 点基数为 0,跳过销售奖: orderType={}, orderId={}", orderType, orderId);
|
|
|
return;
|
|
|
@@ -320,22 +330,66 @@ public class CfCommissionService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 本单消费产生的 P 点(销售奖基数)。
|
|
|
- * 商品:实付(元) × 有效 P 点 / 100;非商品:实付(元) × 服务费率(bps) / 10000。
|
|
|
+ * 本单消费产生的 P 点(销售奖基数,单位:分)。
|
|
|
+ *
|
|
|
+ * <p>口径:P 点 = 实际成交额 − 成本价({@code products.purchase_price},分)。</p>
|
|
|
+ * <ul>
|
|
|
+ * <li>单商品订单:实付 − 成本价 × 数量;</li>
|
|
|
+ * <li>多商品订单({@code order_items} 有明细):按各行 单价×数量 占比分摊实付,
|
|
|
+ * 再减各行成本价×数量,各行毛利(负数记 0)求和(方案A);</li>
|
|
|
+ * <li>亏本销售不发:P 点 = max(0, ...);</li>
|
|
|
+ * <li>非商品订单(会员/套餐/订阅/测评):沿用服务费率口径。</li>
|
|
|
+ * </ul>
|
|
|
*/
|
|
|
- private int calcOrderPpoints(String orderType, int amountYuan, Long productId) {
|
|
|
- if ("product".equals(orderType) && productId != null) {
|
|
|
+ private int calcOrderPpoints(Long orderId, String orderType, Integer amountCent, Integer paidAmountCent, Long productId) {
|
|
|
+ if ("product".equals(orderType)) {
|
|
|
try {
|
|
|
+ int paid = paidAmountCent != null ? paidAmountCent : (amountCent != null ? amountCent : 0);
|
|
|
+ if (paid <= 0) return 0;
|
|
|
+ ProductOrder order = productOrderMapper.selectById(orderId);
|
|
|
+ if (order == null) return 0;
|
|
|
+
|
|
|
+ // 多商品订单:order_items 有明细 → 按行分摊实付 - 行成本(方案A)
|
|
|
+ List<OrderItem> items = orderItemMapper.findByOrderId(orderId);
|
|
|
+ if (items != null && !items.isEmpty()) {
|
|
|
+ long totalBase = 0L;
|
|
|
+ for (OrderItem it : items) {
|
|
|
+ int qty = it.getQuantity() != null ? it.getQuantity() : 0;
|
|
|
+ long base = (long) Math.round((it.getPrice() != null ? it.getPrice().doubleValue() : 0d) * qty);
|
|
|
+ if (base > 0) totalBase += base;
|
|
|
+ }
|
|
|
+ if (totalBase > 0) {
|
|
|
+ int total = 0;
|
|
|
+ for (OrderItem it : items) {
|
|
|
+ int qty = it.getQuantity() != null ? it.getQuantity() : 0;
|
|
|
+ if (qty <= 0) continue;
|
|
|
+ long base = (long) Math.round((it.getPrice() != null ? it.getPrice().doubleValue() : 0d) * qty);
|
|
|
+ if (base <= 0) continue;
|
|
|
+ long allocPaid = (long) ((double) paid * base / totalBase);
|
|
|
+ Product p = productMapper.selectById(it.getProductId());
|
|
|
+ int costPerUnit = (p != null && p.getPurchasePrice() != null) ? p.getPurchasePrice() : 0;
|
|
|
+ long lineCost = (long) costPerUnit * qty;
|
|
|
+ long linePpoint = allocPaid - lineCost;
|
|
|
+ if (linePpoint > 0) total += (int) linePpoint;
|
|
|
+ }
|
|
|
+ return total;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 单商品订单:实付 − 成本 × 数量
|
|
|
+ if (productId == null) return 0;
|
|
|
Product p = productMapper.selectById(productId);
|
|
|
if (p == null) return 0;
|
|
|
- int effectivePpoint = ppointConfigService.getEffectivePpoint(productId, p.getCategoryId());
|
|
|
- if (effectivePpoint <= 0) return 0;
|
|
|
- return amountYuan * effectivePpoint / 100;
|
|
|
+ int costPerUnit = p.getPurchasePrice() != null ? p.getPurchasePrice() : 0;
|
|
|
+ int qty = order.getQuantity() != null ? order.getQuantity() : 1;
|
|
|
+ int ppoints = paid - costPerUnit * qty;
|
|
|
+ return ppoints > 0 ? ppoints : 0;
|
|
|
} catch (Exception e) {
|
|
|
- log.warn("计算本单 P 点失败: productId={}, err={}", productId, e.getMessage());
|
|
|
+ log.warn("计算本单 P 点失败: orderId={}, err={}", orderId, e.getMessage());
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
+ int amountYuan = (amountCent != null ? amountCent : 0) / 100;
|
|
|
int serviceRateBps = configService.getIntConfig(CfConfigKeys.COMMISSION_SERVICE_RATE, 1000);
|
|
|
if (serviceRateBps <= 0) return 0;
|
|
|
return amountYuan * serviceRateBps / 10000;
|