diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/JDOrderListController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/JDOrderListController.java index b711e1e..05a4d05 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/JDOrderListController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/JDOrderListController.java @@ -24,6 +24,7 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.domain.dto.JDOrderSimpleDTO; +import com.ruoyi.jarvis.service.IJDOrderProfitService; import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IInstructionService; import com.ruoyi.common.utils.poi.ExcelUtil; @@ -40,16 +41,19 @@ public class JDOrderListController extends BaseController { private final IJDOrderService jdOrderService; + private final IJDOrderProfitService jdOrderProfitService; private final IOrderRowsService orderRowsService; private final IInstructionService instructionService; private final GroupRebateExcelImportService groupRebateExcelImportService; private final IGroupRebateExcelUploadService groupRebateExcelUploadService; - public JDOrderListController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService, + public JDOrderListController(IJDOrderService jdOrderService, IJDOrderProfitService jdOrderProfitService, + IOrderRowsService orderRowsService, IInstructionService instructionService, GroupRebateExcelImportService groupRebateExcelImportService, IGroupRebateExcelUploadService groupRebateExcelUploadService) { this.jdOrderService = jdOrderService; + this.jdOrderProfitService = jdOrderProfitService; this.orderRowsService = orderRowsService; this.instructionService = instructionService; this.groupRebateExcelImportService = groupRebateExcelImportService; @@ -279,6 +283,8 @@ public class JDOrderListController extends BaseController @PutMapping public AjaxResult edit(@RequestBody JDOrder jdOrder) { + jdOrderProfitService.recalculate(jdOrder); + jdOrder.getParams().put("applyProfitFields", Boolean.TRUE); return toAjax(jdOrderService.updateJDOrder(jdOrder)); } @@ -373,6 +379,43 @@ public class JDOrderListController extends BaseController * 一次性批量更新历史订单:将赔付金额大于0的订单标记为后返到账 * 此方法只应执行一次,用于处理历史数据 */ + /** + * 按 ID 批量重算售价(自动从型号配置回填)与利润(清除手动锁定后按规则计算) + */ + @Log(title = "JD订单批量重算利润", businessType = BusinessType.UPDATE) + @PostMapping("/tools/recalc-profit") + @SuppressWarnings("unchecked") + public AjaxResult recalcProfitBatch(@RequestBody(required = false) Map body) { + if (body == null || !body.containsKey("ids")) { + return AjaxResult.error("请传入 ids 数组"); + } + Object raw = body.get("ids"); + if (!(raw instanceof List)) { + return AjaxResult.error("ids 须为数组"); + } + List idList = (List) raw; + if (idList.isEmpty()) { + return AjaxResult.error("ids 不能为空"); + } + int affected = 0; + for (Object o : idList) { + if (o == null) { + continue; + } + long id = ((Number) o).longValue(); + JDOrder order = jdOrderService.selectJDOrderById(id); + if (order == null) { + continue; + } + order.setProfitManual(0); + order.setSellingPriceManual(0); + jdOrderProfitService.recalculate(order); + order.getParams().put("applyProfitFields", Boolean.TRUE); + affected += jdOrderService.updateJDOrder(order); + } + return AjaxResult.success("已更新 " + affected + " 条订单的售价/利润字段"); + } + @Log(title = "批量标记后返到账", businessType = BusinessType.UPDATE) @RequestMapping(value = "/tools/batch-mark-rebate-received", method = {RequestMethod.POST, RequestMethod.GET}) public AjaxResult batchMarkRebateReceivedForCompensation() { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/JDOrder.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/JDOrder.java index 1f35111..704e449 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/JDOrder.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/JDOrder.java @@ -154,6 +154,21 @@ public class JDOrder extends BaseEntity { /** 后返备注中是否存在异常项(1是 0否),便于列表筛选 */ private Integer rebateRemarkHasAbnormal; + /** 售价渠道:direct 直款,xianyu 闲鱼(仅 F 单使用) */ + private String sellingPriceType; + + /** 售价(对客成交价,可手动改) */ + private Double sellingPrice; + + /** 利润(可手动改;非 H-TF/F 一般为空) */ + private Double profit; + + /** 售价是否手动锁定(1 是:不再按型号配置自动回填) */ + private Integer sellingPriceManual; + + /** 利润是否手动锁定(1 是:保存时不再自动重算) */ + private Integer profitManual; + } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/ProductJdConfig.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/ProductJdConfig.java index 354d36f..7dd3237 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/ProductJdConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/ProductJdConfig.java @@ -27,6 +27,12 @@ public class ProductJdConfig extends BaseEntity /** 佣金(支付) - 支付给下单人的佣金 */ private BigDecimal commissionPay; + /** 参考售价(直款渠道) */ + private BigDecimal sellingPriceDirect; + + /** 参考售价(闲鱼渠道,订单侧仍会 ×0.984 计算实收) */ + private BigDecimal sellingPriceXianyu; + public ProductJdConfig() { } @@ -76,6 +82,22 @@ public class ProductJdConfig extends BaseEntity this.commissionPay = commissionPay; } + public BigDecimal getSellingPriceDirect() { + return sellingPriceDirect; + } + + public void setSellingPriceDirect(BigDecimal sellingPriceDirect) { + this.sellingPriceDirect = sellingPriceDirect; + } + + public BigDecimal getSellingPriceXianyu() { + return sellingPriceXianyu; + } + + public void setSellingPriceXianyu(BigDecimal sellingPriceXianyu) { + this.sellingPriceXianyu = sellingPriceXianyu; + } + @Override public String toString() { return "ProductJdConfig{" + @@ -84,6 +106,8 @@ public class ProductJdConfig extends BaseEntity ", commission=" + commission + ", commissionReceive=" + commissionReceive + ", commissionPay=" + commissionPay + + ", sellingPriceDirect=" + sellingPriceDirect + + ", sellingPriceXianyu=" + sellingPriceXianyu + '}'; } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderProfitService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderProfitService.java new file mode 100644 index 0000000..1203f68 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderProfitService.java @@ -0,0 +1,15 @@ +package com.ruoyi.jarvis.service; + +import com.ruoyi.jarvis.domain.JDOrder; + +/** + * 订单利润/售价:按分销标识规则计算并写回订单对象(由列表保存前调用)。 + */ +public interface IJDOrderProfitService { + + /** + * 根据分销标识、型号配置、手动标记等,填充售价(自动时)并计算利润。 + * 会修改传入的 {@code order}。 + */ + void recalculate(JDOrder order); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderProfitServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderProfitServiceImpl.java new file mode 100644 index 0000000..b57005e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderProfitServiceImpl.java @@ -0,0 +1,104 @@ +package com.ruoyi.jarvis.service.impl; + +import com.ruoyi.jarvis.domain.JDOrder; +import com.ruoyi.jarvis.domain.ProductJdConfig; +import com.ruoyi.jarvis.service.IJDOrderProfitService; +import com.ruoyi.jarvis.service.IProductJdConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +@Service +public class JDOrderProfitServiceImpl implements IJDOrderProfitService { + + private static final double XIANYU_NET_FACTOR = 0.984; + + @Autowired + private IProductJdConfigService productJdConfigService; + + @Override + public void recalculate(JDOrder order) { + if (order == null) { + return; + } + String mark = order.getDistributionMark() == null ? "" : order.getDistributionMark().trim(); + boolean profitLocked = order.getProfitManual() != null && order.getProfitManual() == 1; + boolean sellingLocked = order.getSellingPriceManual() != null && order.getSellingPriceManual() == 1; + + if ("H-TF".equals(mark)) { + order.setSellingPriceType(null); + order.setSellingPrice(null); + if (!profitLocked) { + String buyer = order.getBuyer(); + boolean fan = buyer != null && buyer.trim().startsWith("凡-"); + order.setProfit(fan ? 65.0 : 15.0); + } + return; + } + + if ("F".equals(mark)) { + if (!sellingLocked) { + fillSellingPriceFromConfig(order); + } + if (!profitLocked) { + computeProfitForF(order); + } + return; + } + + if (!profitLocked) { + order.setProfit(null); + } + } + + private void fillSellingPriceFromConfig(JDOrder order) { + String type = order.getSellingPriceType(); + if (type == null || type.isEmpty()) { + return; + } + String model = order.getModelNumber(); + if (model == null || model.trim().isEmpty()) { + return; + } + ProductJdConfig cfg = productJdConfigService.selectProductJdConfigByModel(model.trim()); + if (cfg == null) { + return; + } + if ("direct".equals(type)) { + BigDecimal p = cfg.getSellingPriceDirect(); + if (p != null) { + order.setSellingPrice(p.doubleValue()); + } + } else if ("xianyu".equals(type)) { + BigDecimal p = cfg.getSellingPriceXianyu(); + if (p != null) { + order.setSellingPrice(p.doubleValue()); + } + } + } + + private void computeProfitForF(JDOrder order) { + String type = order.getSellingPriceType(); + Double sp = order.getSellingPrice(); + if (type == null || type.isEmpty() || sp == null) { + order.setProfit(null); + return; + } + double pay = order.getPaymentAmount() != null ? order.getPaymentAmount() : 0.0; + double rebate = order.getRebateAmount() != null ? order.getRebateAmount() : 0.0; + double netReceipt; + if ("xianyu".equals(type)) { + netReceipt = BigDecimal.valueOf(sp).multiply(BigDecimal.valueOf(XIANYU_NET_FACTOR)) + .setScale(2, RoundingMode.HALF_UP).doubleValue(); + } else if ("direct".equals(type)) { + netReceipt = sp; + } else { + order.setProfit(null); + return; + } + order.setProfit(BigDecimal.valueOf(netReceipt - pay - rebate) + .setScale(2, RoundingMode.HALF_UP).doubleValue()); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java index a9640ad..1f9756b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java @@ -2,6 +2,7 @@ package com.ruoyi.jarvis.service.impl; import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.mapper.JDOrderMapper; +import com.ruoyi.jarvis.service.IJDOrderProfitService; import com.ruoyi.jarvis.service.IJDOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -14,6 +15,9 @@ public class JDOrderServiceImpl implements IJDOrderService { @Autowired private JDOrderMapper jdOrderMapper; + @Autowired + private IJDOrderProfitService jdOrderProfitService; + @Override public List selectJDOrderList(JDOrder jdOrder) { return jdOrderMapper.selectJDOrderList(jdOrder); @@ -31,6 +35,7 @@ public class JDOrderServiceImpl implements IJDOrderService { @Override public int insertJDOrder(JDOrder jdOrder) { + jdOrderProfitService.recalculate(jdOrder); return jdOrderMapper.insertJDOrder(jdOrder); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ProductJdConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ProductJdConfigServiceImpl.java index 8d1f236..dad97f7 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ProductJdConfigServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ProductJdConfigServiceImpl.java @@ -117,7 +117,16 @@ public class ProductJdConfigServiceImpl implements IProductJdConfigService config.setCommissionPay(new BigDecimal(commissionPayObj.toString())); } } - + + Object spd = map.get("sellingPriceDirect"); + if (spd != null) { + config.setSellingPriceDirect(spd instanceof BigDecimal ? (BigDecimal) spd : new BigDecimal(spd.toString())); + } + Object spx = map.get("sellingPriceXianyu"); + if (spx != null) { + config.setSellingPriceXianyu(spx instanceof BigDecimal ? (BigDecimal) spx : new BigDecimal(spx.toString())); + } + return config; } return null; @@ -147,13 +156,28 @@ public class ProductJdConfigServiceImpl implements IProductJdConfigService if (StringUtils.isEmpty(productJdConfig.getProductModel())) { return 0; } + String redisKey = PRODUCT_JD_CONFIG_KEY + productJdConfig.getProductModel(); Map map = new java.util.HashMap<>(); + Map old = redisCache.getCacheMap(redisKey); + if (old != null && !old.isEmpty()) { + map.putAll(old); + } map.put("productModel", productJdConfig.getProductModel()); map.put("jdUrl", productJdConfig.getJdUrl()); map.put("commission", productJdConfig.getCommission() != null ? productJdConfig.getCommission() : BigDecimal.ZERO); map.put("commissionReceive", productJdConfig.getCommissionReceive() != null ? productJdConfig.getCommissionReceive() : BigDecimal.ZERO); map.put("commissionPay", productJdConfig.getCommissionPay() != null ? productJdConfig.getCommissionPay() : BigDecimal.ZERO); - redisCache.setCacheMap(PRODUCT_JD_CONFIG_KEY + productJdConfig.getProductModel(), map); + if (productJdConfig.getSellingPriceDirect() != null) { + map.put("sellingPriceDirect", productJdConfig.getSellingPriceDirect()); + } else { + map.remove("sellingPriceDirect"); + } + if (productJdConfig.getSellingPriceXianyu() != null) { + map.put("sellingPriceXianyu", productJdConfig.getSellingPriceXianyu()); + } else { + map.remove("sellingPriceXianyu"); + } + redisCache.setCacheMap(redisKey, map); return 1; } diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml index 1f79715..06795cc 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml @@ -37,6 +37,11 @@ + + + + + @@ -44,7 +49,8 @@ address, logistics_link, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price, is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date, is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date, - rebate_remark_json, rebate_remark_has_abnormal + rebate_remark_json, rebate_remark_has_abnormal, + selling_price_type, selling_price, profit, selling_price_manual, profit_manual from jd_order @@ -151,14 +157,16 @@ tencent_doc_pushed, tencent_doc_push_time, order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price, is_refunded, refund_date, is_refund_received, refund_received_date, is_rebate_received, rebate_received_date, - is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date + is_price_protected, price_protected_date, is_invoice_opened, invoice_opened_date, is_review_posted, review_posted_date, + selling_price_type, selling_price, profit, selling_price_manual, profit_manual ) values ( #{remark}, #{distributionMark}, #{modelNumber}, #{link}, #{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink}, 0, null, #{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice}, #{isRefunded}, #{refundDate}, #{isRefundReceived}, #{refundReceivedDate}, #{isRebateReceived}, #{rebateReceivedDate}, - #{isPriceProtected}, #{priceProtectedDate}, #{isInvoiceOpened}, #{invoiceOpenedDate}, #{isReviewPosted}, #{reviewPostedDate} + #{isPriceProtected}, #{priceProtectedDate}, #{isInvoiceOpened}, #{invoiceOpenedDate}, #{isReviewPosted}, #{reviewPostedDate}, + #{sellingPriceType}, #{sellingPrice}, #{profit}, #{sellingPriceManual}, #{profitManual} ) @@ -196,6 +204,13 @@ review_posted_date = #{reviewPostedDate}, rebate_remark_json = #{rebateRemarkJson}, rebate_remark_has_abnormal = #{rebateRemarkHasAbnormal}, + + selling_price_type = #{sellingPriceType,jdbcType=VARCHAR}, + selling_price = #{sellingPrice,jdbcType=DOUBLE}, + profit = #{profit,jdbcType=DOUBLE}, + selling_price_manual = #{sellingPriceManual,jdbcType=INTEGER}, + profit_manual = #{profitManual,jdbcType=INTEGER}, + update_time = now() where id = #{id} diff --git a/sql/jd_order_profit_20260405.sql b/sql/jd_order_profit_20260405.sql new file mode 100644 index 0000000..ea269fb --- /dev/null +++ b/sql/jd_order_profit_20260405.sql @@ -0,0 +1,6 @@ +-- JD 订单:售价渠道、售价、利润及手动锁定标记(执行前请备份) +ALTER TABLE jd_order ADD COLUMN selling_price_type VARCHAR(16) NULL COMMENT '售价渠道 direct/xianyu,F单用' AFTER rebate_remark_has_abnormal; +ALTER TABLE jd_order ADD COLUMN selling_price DOUBLE NULL COMMENT '售价' AFTER selling_price_type; +ALTER TABLE jd_order ADD COLUMN profit DOUBLE NULL COMMENT '利润' AFTER selling_price; +ALTER TABLE jd_order ADD COLUMN selling_price_manual TINYINT NULL DEFAULT 0 COMMENT '1售价手动锁定' AFTER profit; +ALTER TABLE jd_order ADD COLUMN profit_manual TINYINT NULL DEFAULT 0 COMMENT '1利润手动锁定' AFTER selling_price_manual;