新增打单产品渠道查询与替换功能

This commit is contained in:
wuweihua
2026-11-11 14:50:34 +08:00
parent 41a80f9794
commit 7e1041391a
20 changed files with 603 additions and 109 deletions
@@ -208,6 +208,39 @@ public class LabelPackageController extends BaseController
return success(payProductService.selectPayProductDetail(productId));
}
/** 查询打单产品已绑定渠道(默认应收 AR) */
@PreAuthorize("@ss.hasPermi('price:label:query')")
@GetMapping("/product/{productId}/channels")
public AjaxResult productChannels(@PathVariable Long productId,
@RequestParam(value = "payType", required = false) String payType)
{
String type = (payType == null || payType.trim().isEmpty()) ? "AR" : payType.trim();
return success(payProductService.listProductChannelBinds(productId, type));
}
/** 覆盖保存打单产品渠道绑定,写入 srv_channel_product_map */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单产品绑定渠道", businessType = BusinessType.UPDATE)
@PutMapping("/product/{productId}/channels")
public AjaxResult replaceProductChannels(@PathVariable Long productId, @RequestBody Map<String, Object> body)
{
String payType = body == null || body.get("payType") == null ? "AR" : String.valueOf(body.get("payType")).trim();
List<String> channelIds = new ArrayList<String>();
Object rawIds = body == null ? null : body.get("channelIds");
if (rawIds instanceof List<?>)
{
for (Object item : (List<?>) rawIds)
{
if (item != null && !String.valueOf(item).trim().isEmpty())
{
channelIds.add(String.valueOf(item).trim());
}
}
}
String msg = payProductService.replaceProductChannelBinds(productId, payType, channelIds);
return AjaxResult.success(msg);
}
@PreAuthorize("@ss.hasPermi('price:label:remove')")
@Log(title = "打单套餐移除产品", businessType = BusinessType.DELETE)
@DeleteMapping("/{priceSetId}/products/{productIds}")
@@ -120,6 +120,12 @@ public class PayProduct extends BaseEntity
@Excel(name = "附加费数", width = 12)
private Integer surchargeCount;
/** 已绑定渠道展示文案(非表字段,列表回显) */
private String boundChannelText;
/** 已绑定渠道数量(非表字段) */
private Integer boundChannelCount;
@Excel(name = "其他配置数", width = 12)
private Integer extraCount;
@@ -84,9 +84,10 @@ public class PrcOrderBill extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate;
private Long tmsId;
private String matchSource;
private String matchMessage;
/** 渠道编码(上游 TAB_ORDER.PRODUCT_FLOOR_CODE) */
private String productFloorCode;
/** 渠道名称(上游 TAB_ORDER.PRODUCT_FLOOR) */
private String productFloor;
private Long priceSetId;
private String priceSetCode;
private String priceSetName;
@@ -43,8 +43,10 @@ public class PrcBillingBillListExportRow implements Serializable
private BigDecimal incomeTotal;
@Excel(name = "应付总额", scale = 2, width = 14)
private BigDecimal payTotal;
@Excel(name = "计价来源", width = 16)
private String matchSourceLabel;
@Excel(name = "渠道编码", width = 16)
private String productFloorCode;
@Excel(name = "渠道名称", width = 20)
private String productFloor;
@Excel(name = "订单状态", width = 12)
private String orderStatusLabel;
@Excel(name = "计费状态", width = 14)
@@ -34,6 +34,10 @@ public class TmsPriceQuoteResult implements Serializable
private String productCode;
private String productName;
/** 渠道编码 */
private String productFloorCode;
/** 渠道名称 */
private String productFloor;
private String countryCode;
private String countryName;
private Long zoneId;
@@ -10,6 +10,11 @@ public interface ChannelProductMapMapper
{
ChannelProductMap selectByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
java.util.List<ChannelProductMap> selectByProductIdAndType(@Param("productId") Long productId,
@Param("payType") String payType);
java.util.List<ChannelProductMap> selectByProductIdsAndType(@Param("productIds") java.util.List<Long> productIds,
@Param("payType") String payType);
int upsert(ChannelProductMap row);
@@ -5,6 +5,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.PrcOrderBillExt;
/**
@@ -14,6 +15,18 @@ public final class OracleOrderBillExtFiller
{
private OracleOrderBillExtFiller() {}
/** 从上游 TAB_ORDER 写入 prc_order_bill 渠道编码/名称。 */
public static void fillBillChannelFromRow(PrcOrderBill bill, Map<String, Object> row)
{
if (bill == null || row == null || row.isEmpty())
{
return;
}
bill.setProductFloorCode(str(row, "PRODUCT_FLOOR_CODE"));
bill.setProductFloor(str(row, "PRODUCT_FLOOR"));
}
public static void fillFromRow(PrcOrderBillExt ext, Map<String, Object> row)
{
if (ext == null || row == null || row.isEmpty())
@@ -61,6 +61,16 @@ public interface IPayProductService
/** 批量绑定渠道应收/应付产品;未传的产品不改原绑定 */
String batchBindChannel(java.util.List<String> channelIds, Long apProductId, Long arProductId);
/** 查询产品已绑定的渠道(按收付类型) */
java.util.List<com.bms.price.domain.ChannelProductMap> listProductChannelBinds(Long productId, String payType);
/**
* 覆盖保存产品的渠道绑定:勾选渠道写入 srv_channel_product_map,
* 取消勾选且当前仍绑该产品的渠道会解绑。
*/
String replaceProductChannelBinds(Long productId, String payType, java.util.List<String> channelIds);
/**
* 打单套餐预览:解析复用应付解析器,但「已存在」只按本套餐 price_set_id 判断,
* 不与应付公共模板互相挡导入。
@@ -1101,9 +1101,104 @@ public class PayProductServiceImpl implements IPayProductService
product.setSurchargeCount(term.getSurchargeCount() == null ? product.getSurchargeCount() : term.getSurchargeCount());
list.add(product);
}
fillLabelBoundChannels(list);
return list;
}
/** 打单产品列表回填已绑定渠道(收款 AR) */
private void fillLabelBoundChannels(List<PayProduct> list)
{
if (list == null || list.isEmpty())
{
return;
}
java.util.LinkedHashSet<Long> ids = new java.util.LinkedHashSet<Long>();
for (PayProduct product : list)
{
if (product != null && product.getProductId() != null)
{
ids.add(product.getProductId());
}
}
if (ids.isEmpty())
{
return;
}
List<ChannelProductMap> binds = channelProductMapMapper.selectByProductIdsAndType(
new ArrayList<Long>(ids), PAY_TYPE_AR);
java.util.Map<Long, java.util.List<ChannelProductMap>> byProduct =
new java.util.HashMap<Long, java.util.List<ChannelProductMap>>();
if (binds != null)
{
for (ChannelProductMap row : binds)
{
if (row == null || row.getProductId() == null)
{
continue;
}
java.util.List<ChannelProductMap> bucket = byProduct.get(row.getProductId());
if (bucket == null)
{
bucket = new ArrayList<ChannelProductMap>();
byProduct.put(row.getProductId(), bucket);
}
bucket.add(row);
}
}
for (PayProduct product : list)
{
if (product == null || product.getProductId() == null)
{
continue;
}
java.util.List<ChannelProductMap> bucket = byProduct.get(product.getProductId());
if (bucket == null || bucket.isEmpty())
{
product.setBoundChannelCount(Integer.valueOf(0));
product.setBoundChannelText(null);
continue;
}
product.setBoundChannelCount(Integer.valueOf(bucket.size()));
StringBuilder sb = new StringBuilder();
for (ChannelProductMap row : bucket)
{
if (row == null)
{
continue;
}
String code = row.getChannelCode();
String name = row.getChannelName();
String label;
if (!isBlank(code) && !isBlank(name) && !code.equals(name))
{
label = code + "/" + name;
}
else if (!isBlank(code))
{
label = code;
}
else if (!isBlank(name))
{
label = name;
}
else
{
label = row.getChannelId();
}
if (isBlank(label))
{
continue;
}
if (sb.length() > 0)
{
sb.append("、");
}
sb.append(label);
}
product.setBoundChannelText(sb.length() == 0 ? null : sb.toString());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public int removeLabelProducts(Long priceSetId, Long[] productIds)
@@ -1701,6 +1796,54 @@ public class PayProductServiceImpl implements IPayProductService
return "已为 " + n + " 个渠道绑定产品";
}
@Override
public List<ChannelProductMap> listProductChannelBinds(Long productId, String payType)
{
Long root = resolveRoot(productId);
String type = payTypeOf(payType);
List<ChannelProductMap> list = channelProductMapMapper.selectByProductIdAndType(root, type);
return list == null ? new ArrayList<ChannelProductMap>() : list;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String replaceProductChannelBinds(Long productId, String payType, List<String> channelIds)
{
Long root = resolveRoot(productId);
String type = payTypeOf(payType);
java.util.LinkedHashSet<String> selected = new java.util.LinkedHashSet<String>();
if (channelIds != null)
{
for (String channelId : channelIds)
{
if (!isBlank(channelId))
{
selected.add(channelId.trim());
}
}
}
List<ChannelProductMap> existing = channelProductMapMapper.selectByProductIdAndType(root, type);
if (existing != null)
{
for (ChannelProductMap row : existing)
{
if (row == null || isBlank(row.getChannelId()))
{
continue;
}
if (!selected.contains(row.getChannelId().trim()))
{
channelProductMapMapper.deleteByChannelAndType(row.getChannelId().trim(), type);
}
}
}
for (String channelId : selected)
{
bindChannel(channelId, root, type);
}
return "已绑定 " + selected.size() + " 个渠道";
}
@Override
public void unbindChannel(String channelId, String payType)
{
@@ -2061,9 +2061,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setWeight(weight);
bill.setBizDate(bizDate);
bill.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId()));
// 不在订单账单上持久化 match_source / match_message。
bill.setMatchSource(null);
bill.setMatchMessage(null);
bill.setPriceSetId(result.getPriceSetId());
bill.setPriceSetCode(result.getPriceSetCode());
bill.setPriceSetName(result.getPriceSetName());
@@ -2614,8 +2611,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setCustomerCode(bill.getCustomerCode());
fillResultOrder(r, bill);
r.setFromCache(Boolean.TRUE);
r.setMatchSource(bill.getMatchSource());
r.setMatchMessage(bill.getMatchMessage());
r.setCustomerId(bill.getCustomerId());
r.setPriceSetId(bill.getPriceSetId());
r.setPriceSetCode(bill.getPriceSetCode());
@@ -2627,6 +2622,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setPsdId(bill.getPsdId());
r.setProductCode(bill.getProductCode());
r.setProductName(bill.getProductName());
r.setProductFloorCode(bill.getProductFloorCode());
r.setProductFloor(bill.getProductFloor());
r.setCountryCode(bill.getCountryCode());
r.setCountryName(bill.getCountryName());
r.setWeightMin(bill.getWeightMin());
@@ -3589,6 +3586,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setProductName(request.getProductName());
bill.setCountryCode(request.getCountryCode());
bill.setCountryName(request.getCountryName());
if (request.getUpstreamRow() != null && !request.getUpstreamRow().isEmpty())
{
OracleOrderBillExtFiller.fillBillChannelFromRow(bill, request.getUpstreamRow());
}
}
/**
@@ -3656,7 +3657,5 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
failText = failText.substring(0, 1000);
}
bill.setErrorMessage(failText);
bill.setMatchMessage(null);
bill.setMatchSource(null);
}
}
@@ -192,7 +192,8 @@ public final class OrderBillExportSupport
row.setReceiveWeight(bill.getReceiveWeight());
row.setIncomeTotal(bill.getIncomeTotal());
row.setPayTotal(bill.getPayTotal());
row.setMatchSourceLabel(matchSourceLabel(bill.getMatchSource()));
row.setProductFloorCode(bill.getProductFloorCode());
row.setProductFloor(bill.getProductFloor());
row.setOrderStatusLabel(orderStatusLabel(bill.getOrderStatus()));
row.setBillingFailStatusLabel(billingFailStatusLabel(bill.getBillingFailStatus()));
row.setCustomerActiveStatusLabel(customerActiveLabel(bill.getCustomerActiveStatus()));
@@ -270,30 +271,6 @@ public final class OrderBillExportSupport
}
}
public static String matchSourceLabel(String source)
{
if (StringUtils.isEmpty(source))
{
return "";
}
if ("PACKAGE_FIXED".equals(source) || "FIXED".equals(source))
{
return "客户固定价套餐";
}
if ("PACKAGE_DISCOUNT".equals(source) || "DISCOUNT".equals(source))
{
return "客户折扣套餐";
}
if ("PRODUCT_DEFAULT".equals(source))
{
return "产品默认价";
}
if ("BILLING_FAIL".equals(source))
{
return "计费失败";
}
return source;
}
public static String customerActiveLabel(String status)
{
@@ -13,6 +13,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
limit 1
</select>
<select id="selectByProductIdAndType" resultType="com.bms.price.domain.ChannelProductMap">
select m.id,
m.channel_id as channelId,
m.channel_code as channelCode,
m.product_id as productId,
c.channel_name as channelName,
m.pay_type as payType,
m.create_by as createBy,
m.create_time as createTime,
m.update_by as updateBy,
m.update_time as updateTime
from srv_channel_product_map m
left join srv_service_channel c on c.id = m.channel_id
where m.product_id = #{productId}
and m.pay_type = #{payType}
order by m.channel_code asc, m.channel_id asc
</select>
<select id="selectByProductIdsAndType" resultType="com.bms.price.domain.ChannelProductMap">
select m.id,
m.channel_id as channelId,
m.channel_code as channelCode,
m.product_id as productId,
c.channel_name as channelName,
m.pay_type as payType,
m.create_by as createBy,
m.create_time as createTime,
m.update_by as updateBy,
m.update_time as updateTime
from srv_channel_product_map m
left join srv_service_channel c on c.id = m.channel_id
where m.pay_type = #{payType}
and m.product_id in
<foreach collection="productIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
order by m.product_id asc, m.channel_code asc, m.channel_id asc
</select>
<insert id="upsert">
insert into srv_channel_product_map (
@@ -42,8 +42,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="weight" column="weight" />
<result property="bizDate" column="biz_date" />
<result property="tmsId" column="tms_id" />
<result property="matchSource" column="match_source" />
<result property="matchMessage" column="match_message" />
<result property="productFloorCode" column="product_floor_code" />
<result property="productFloor" column="product_floor" />
<result property="priceSetId" column="price_set_id" />
<result property="priceSetCode" column="price_set_code" />
<result property="priceSetName" column="price_set_name" />
@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height,
shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_remark,
product_code, product_name, country_code, country_name, weight, biz_date, tms_id,
match_source, match_message, price_set_id, price_set_code, price_set_name,
product_floor_code, product_floor, price_set_id, price_set_code, price_set_name,
set_type, bind_priority, customer_priceset_id, psv_id, psd_id,
weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee,
@@ -112,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
p.pieces, p.actual_weight, p.receive_weight, p.volume, p.volume_weight, p.pkg_length, p.pkg_width, p.pkg_height,
p.shipper_name, p.consignee_name, p.dest_postcode, p.warehouse_code, p.order_time, p.order_status, p.bl_pickup, p.pickup_date, p.order_remark,
p.product_code, p.product_name, p.country_code, p.country_name, p.weight, p.biz_date, p.tms_id,
p.match_source, p.match_message, p.price_set_id, p.price_set_code, p.price_set_name,
p.product_floor_code, p.product_floor, p.price_set_id, p.price_set_code, p.price_set_name,
p.set_type, p.bind_priority, p.customer_priceset_id, p.psv_id, p.psd_id,
p.weight_min, p.weight_max, p.weight_label, p.effective_date, p.charge_weight,
p.freight_rate, p.base_freight_rate, p.discount_percent, p.registration_fee,
@@ -542,8 +542,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="countryCode != null and countryCode != ''">
AND p.country_code = #{countryCode}
</if>
<if test="matchSource != null and matchSource != ''">
AND p.match_source = #{matchSource}
<if test="productFloorCode != null and productFloorCode != ''">
AND p.product_floor_code like concat('%', #{productFloorCode}, '%')
</if>
<if test="bizDate != null">
AND p.biz_date = #{bizDate}
@@ -844,7 +844,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height,
shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_status, bl_pickup, pickup_date, order_remark,
product_code, product_name, country_code, country_name, weight, biz_date, tms_id,
match_source, match_message, price_set_id, price_set_code, price_set_name,
product_floor_code, product_floor, price_set_id, price_set_code, price_set_name,
set_type, bind_priority, customer_priceset_id, psv_id, psd_id,
weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee,
@@ -858,7 +858,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{pieces}, #{actualWeight}, #{receiveWeight}, #{volume}, #{volumeWeight}, #{pkgLength}, #{pkgWidth}, #{pkgHeight},
#{shipperName}, #{consigneeName}, #{destPostcode}, #{warehouseCode}, #{orderTime}, #{orderStatus}, #{blPickup}, #{pickupDate}, #{orderRemark},
#{productCode}, #{productName}, #{countryCode}, #{countryName}, #{weight}, #{bizDate}, #{tmsId},
#{matchSource}, #{matchMessage}, #{priceSetId}, #{priceSetCode}, #{priceSetName},
#{productFloorCode}, #{productFloor}, #{priceSetId}, #{priceSetCode}, #{priceSetName},
#{setType}, #{bindPriority}, #{customerPricesetId}, #{psvId}, #{psdId},
#{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight},
#{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee},
@@ -908,8 +908,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
weight = #{weight},
biz_date = #{bizDate},
tms_id = #{tmsId},
match_source = #{matchSource},
match_message = #{matchMessage},
product_floor_code = #{productFloorCode},
product_floor = #{productFloor},
price_set_id = #{priceSetId},
price_set_code = #{priceSetCode},
price_set_name = #{priceSetName},
+2 -2
View File
@@ -924,8 +924,8 @@ CREATE TABLE `prc_order_bill` (
`weight` decimal(10,3) DEFAULT NULL COMMENT '计费重量KG',
`biz_date` datetime NOT NULL COMMENT '计费时间',
`tms_id` bigint(20) DEFAULT NULL COMMENT '多租户系统ID',
`match_source` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中来源',
`match_message` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中说明',
`product_floor_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道编码',
`product_floor` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道名称',
`price_set_id` bigint(20) DEFAULT NULL COMMENT '价格套餐ID',
`price_set_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐编码',
`price_set_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐名称',
@@ -0,0 +1,30 @@
-- prc_order_bill: drop match_source / match_message.
-- Idempotent.
SET NAMES utf8mb4;
SET @sql_drop_match_source = (
SELECT IF(
COUNT(*) > 0,
'ALTER TABLE prc_order_bill DROP COLUMN match_source',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'match_source'
);
PREPARE stmt FROM @sql_drop_match_source;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql_drop_match_message = (
SELECT IF(
COUNT(*) > 0,
'ALTER TABLE prc_order_bill DROP COLUMN match_message',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'match_message'
);
PREPARE stmt FROM @sql_drop_match_message;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -0,0 +1,30 @@
-- prc_order_bill: add product_floor_code / product_floor from upstream.
-- Idempotent.
SET NAMES utf8mb4;
SET @sql_add_floor_code = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE prc_order_bill ADD COLUMN product_floor_code varchar(64) DEFAULT NULL COMMENT ''渠道编码'' AFTER tms_id',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'product_floor_code'
);
PREPARE stmt FROM @sql_add_floor_code;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql_add_floor_name = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE prc_order_bill ADD COLUMN product_floor varchar(200) DEFAULT NULL COMMENT ''渠道名称'' AFTER product_floor_code',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'product_floor'
);
PREPARE stmt FROM @sql_add_floor_name;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;