提交
This commit is contained in:
+10
-49
@@ -4,62 +4,23 @@ import com.baiyue.lastleg.dto.CustomerOrderProgress;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
|
||||
/** Batch read of every actual source, never the representative MIN(order_id) on a shared pallet. */
|
||||
/** 按客户及不可变来源明细读取进度,不使用混托的代表订单。 */
|
||||
@Mapper
|
||||
public interface CustomerOrderProgressMapper {
|
||||
/** 本客户明细决定归属,真实主阶段和实际发车时间决定待排及在途数量。 */
|
||||
/** 已排车只统计有效待出库单的来源数量,与平台出库单节点一致;在途仍要求实际发车。 */
|
||||
@Select("""
|
||||
<script>
|
||||
WITH scheduled AS (
|
||||
SELECT line.order_id,SUM(a.planned_package_count) AS packages
|
||||
FROM ll_dispatch_order d
|
||||
JOIN ll_dispatch_item i ON i.dispatch_order_id=d.id
|
||||
JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=i.plan_pallet_id
|
||||
JOIN ll_inbound_plan_line line ON line.id=a.line_id AND line.customer_id=i.customer_id
|
||||
SELECT i.source_order_id AS order_id,SUM(i.package_count) AS packages
|
||||
FROM ll_outbound_order b
|
||||
JOIN ll_outbound_pallet_item i ON i.outbound_order_id=b.id AND i.customer_id=b.customer_id
|
||||
<if test="orderIds != null">
|
||||
JOIN ll_order planned_source ON planned_source.id=line.order_id AND planned_source.customer_id=#{customerId}
|
||||
AND planned_source.source_order_id IN <foreach collection="orderIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
JOIN ll_order outbound_source ON outbound_source.id=i.source_order_id AND outbound_source.customer_id=#{customerId}
|
||||
AND outbound_source.source_order_id IN <foreach collection="orderIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</if>
|
||||
WHERE i.customer_id=#{customerId} AND d.status='ACTIVE'
|
||||
AND d.stage IN ('BEFORE_LOADING','LOADING','AFTER_LOADING')
|
||||
AND i.warehouse_pallet_id IS NULL
|
||||
GROUP BY line.order_id
|
||||
UNION ALL
|
||||
SELECT b.inbound_order_id AS order_id,SUM(p.available_packages+COALESCE(pk.packages,0)) AS packages
|
||||
FROM ll_dispatch_order d
|
||||
JOIN ll_dispatch_item i ON i.dispatch_order_id=d.id
|
||||
JOIN ll_inventory_position p ON p.customer_id=i.customer_id AND p.pallet_id=i.warehouse_pallet_id
|
||||
AND p.overseas_warehouse_id=i.overseas_warehouse_id
|
||||
JOIN ll_inventory_balance b ON b.id=p.balance_id AND b.customer_id=i.customer_id
|
||||
<if test="orderIds != null">
|
||||
JOIN ll_order stock_source ON stock_source.id=b.inbound_order_id AND stock_source.customer_id=#{customerId}
|
||||
AND stock_source.source_order_id IN <foreach collection="orderIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</if>
|
||||
LEFT JOIN (SELECT pick.dispatch_item_id,pick.inventory_position_id,SUM(pick.packages) AS packages
|
||||
FROM ll_dispatch_pda_pick pick
|
||||
<if test="orderIds != null">
|
||||
JOIN ll_inventory_position scoped_position ON scoped_position.id=pick.inventory_position_id
|
||||
AND scoped_position.customer_id=pick.customer_id
|
||||
JOIN ll_inventory_balance scoped_balance ON scoped_balance.id=scoped_position.balance_id
|
||||
AND scoped_balance.customer_id=pick.customer_id
|
||||
JOIN ll_order scoped_order ON scoped_order.id=scoped_balance.inbound_order_id
|
||||
AND scoped_order.customer_id=pick.customer_id
|
||||
AND scoped_order.source_order_id IN <foreach collection="orderIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</if>
|
||||
WHERE pick.customer_id=#{customerId}
|
||||
GROUP BY pick.dispatch_item_id,pick.inventory_position_id) pk
|
||||
ON pk.dispatch_item_id=i.id AND pk.inventory_position_id=p.id
|
||||
WHERE i.customer_id=#{customerId} AND d.status='ACTIVE'
|
||||
AND d.stage IN ('BEFORE_LOADING','LOADING','AFTER_LOADING')
|
||||
AND (i.plan_pallet_id IS NULL OR EXISTS (
|
||||
SELECT 1 FROM ll_inbound_plan_pallet_allocation a
|
||||
JOIN ll_inbound_plan_line line ON line.id=a.line_id AND line.customer_id=i.customer_id
|
||||
<if test="orderIds != null">
|
||||
JOIN ll_order planned_source ON planned_source.id=line.order_id AND planned_source.customer_id=#{customerId}
|
||||
AND planned_source.source_order_id IN <foreach collection="orderIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</if>
|
||||
WHERE a.pallet_id=i.plan_pallet_id AND line.order_id=b.inbound_order_id))
|
||||
GROUP BY b.inbound_order_id
|
||||
WHERE i.customer_id=#{customerId}
|
||||
AND b.status NOT IN ('CANCELLED','CANCELED','OUTBOUNDED')
|
||||
GROUP BY i.source_order_id
|
||||
), departed AS (
|
||||
SELECT a.order_id,SUM(a.packages) AS packages
|
||||
FROM ll_dispatch_pda_pick_order a
|
||||
|
||||
@@ -8,9 +8,10 @@ paths:
|
||||
summary: Order按认证客户读取排车和实际派送进度
|
||||
description: >-
|
||||
只读内部接口,无公网路由。customerId由Order认证上下文提供,所有业务表在Lastleg自身Schema内按客户读取。
|
||||
正式排车单PENDING_DEPARTURE/PICKING/READY_TO_DEPART按计划或库存来源计算,预排草稿和取消单不计入。
|
||||
派送必须已实际DEPARTED、出库OUTBOUNDED且对应目的地派送单仍WAIT_DELIVERY/DELIVERING。
|
||||
混托使用所有库存及不可变拣货来源,不使用代表orderId;数量覆盖不完整或异常时标记partial,不签发派送完成事实。
|
||||
已排车scheduled按有效待出库单的不可变来源明细及箱数计算,与平台出库单节点一致;仅生成排车单号、审核中、审核通过但未生成出库单均不计入。
|
||||
取消的出库单及已实际出库的单据不计入scheduled;排车审核、真实拣货核平、装车确认及安排派送仍按原流程执行。
|
||||
派送必须排车单ACTIVE且处于DELIVERING阶段、具有实际发车时间、出库OUTBOUNDED且对应目的地派送单仍WAIT_DELIVERY/DELIVERING。
|
||||
混托使用全部不可变出库及拣货来源,不使用代表orderId;数量覆盖不完整或异常时标记partial,不签发派送完成事实。
|
||||
security: [{internalServiceToken: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
|
||||
+57
-9
@@ -13,16 +13,19 @@ import static org.assertj.core.api.Assertions.*;
|
||||
class CustomerOrderProgressMapperTest {
|
||||
private JdbcTemplate jdbc;
|
||||
private CustomerOrderProgressMapper mapper;
|
||||
/** 两客户共用一单一任务,计划和在途均按各自真实明细归属统计。 */
|
||||
/** 两客户共用一单一任务,出库和在途均按各自真实明细归属统计。 */
|
||||
@Test void sharedDispatchAndTaskKeepBothCustomerProgressSeparate() {
|
||||
jdbc.update("INSERT INTO ll_dispatch_item(id,dispatch_order_id,customer_id,plan_pallet_id,overseas_warehouse_id,destination_warehouse_code) VALUES(101,10,'22',501,'W1','FBA2')");
|
||||
jdbc.update("INSERT INTO ll_inbound_plan_pallet_allocation VALUES(501,1002,30)");
|
||||
pendingOutbound(10000,"11",1,10);
|
||||
pendingOutbound(10000,"11",2,20);
|
||||
pendingOutbound(10002,"22",3,30);
|
||||
assertThat(mapper.sources("11",null)).extracting(s->s.scheduledPackages()).containsExactly(10L,20L);
|
||||
assertThat(mapper.sources("22",null)).singleElement().satisfies(s->assertThat(s.scheduledPackages()).isEqualTo(30));
|
||||
jdbc.update("INSERT INTO ll_dispatch_pda_task VALUES(700,10)");
|
||||
jdbc.update("INSERT INTO ll_dispatch_pda_pick VALUES(600,'11',100,400,4,700),(601,'22',101,401,7,700)");
|
||||
jdbc.update("INSERT INTO ll_dispatch_pda_pick_order VALUES(900,600,'11',1,4),(901,601,'22',3,7)");
|
||||
jdbc.update("INSERT INTO ll_outbound_order VALUES(10000,'11','OUTBOUNDED'),(10002,'22','OUTBOUNDED')");
|
||||
jdbc.update("UPDATE ll_outbound_order SET status='OUTBOUNDED'");
|
||||
jdbc.update("INSERT INTO ll_delivery_order VALUES(10001,'11',10,10000,'DELIVERING','CODE:FBA1'),(10003,'22',10,10002,'DELIVERING','CODE:FBA2')");
|
||||
jdbc.update("UPDATE ll_dispatch_order SET stage='DELIVERING',departed_at=CURRENT_TIMESTAMP");
|
||||
assertThat(mapper.sources("11",null)).extracting(s->s.deliveringPackages()).containsExactly(4L,0L);
|
||||
@@ -45,6 +48,7 @@ class CustomerOrderProgressMapperTest {
|
||||
jdbc.execute("CREATE TABLE ll_dispatch_pda_task(id BIGINT,dispatch_order_id BIGINT UNIQUE)");
|
||||
jdbc.execute("CREATE TABLE ll_delivery_order(id BIGINT,customer_id VARCHAR,dispatch_order_id BIGINT,outbound_order_id BIGINT,status VARCHAR,delivery_group_key VARCHAR)");
|
||||
jdbc.execute("CREATE TABLE ll_outbound_order(id BIGINT,customer_id VARCHAR,status VARCHAR)");
|
||||
jdbc.execute("CREATE TABLE ll_outbound_pallet_item(outbound_order_id BIGINT,customer_id VARCHAR,source_order_id BIGINT,package_count INT)");
|
||||
jdbc.update("INSERT INTO ll_order VALUES(1,'11','101','201','FIRSTLEG','FBA',10),(2,'11','102','202','FIRSTLEG','FBA',20),(3,'22','103','203','FIRSTLEG','FBA',30)");
|
||||
jdbc.update("INSERT INTO ll_dispatch_order VALUES(10,'ACTIVE','BEFORE_LOADING',NULL)");
|
||||
jdbc.update("INSERT INTO ll_dispatch_item(id,dispatch_order_id,customer_id,plan_pallet_id,overseas_warehouse_id,destination_warehouse_code) VALUES(100,10,'11',500,'W1','FBA1')");
|
||||
@@ -54,24 +58,40 @@ class CustomerOrderProgressMapperTest {
|
||||
var factory=new MybatisSqlSessionFactoryBean();factory.setDataSource(ds);factory.setConfiguration(config);
|
||||
mapper=new SqlSessionTemplate(factory.getObject()).getMapper(CustomerOrderProgressMapper.class);
|
||||
}
|
||||
/** 计划托盘上的所有本客户来源均计入,取消后不再计入。 */
|
||||
@Test void plannedSharedPalletIncludesEveryOwnSourceAndExcludesCancellation() {
|
||||
/** 排车号及审核阶段不代表有效出库,生成出库后才计入,取消后自动移出。 */
|
||||
@Test void onlyEffectiveOutboundSourcesCountAsScheduled() {
|
||||
jdbc.execute("ALTER TABLE ll_dispatch_order ADD review_state VARCHAR");
|
||||
for (String review : java.util.List.of("DRAFT","DISPATCH_PENDING","DISPATCH_REJECTED","LOADING_PENDING","LOADING_REJECTED","APPROVED")) {
|
||||
jdbc.update("UPDATE ll_dispatch_order SET review_state=?",review);
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
assertThat(new com.baiyue.lastleg.service.CustomerOrderProgressService(mapper).read(11,null).progress()).isEmpty();
|
||||
}
|
||||
jdbc.update("UPDATE ll_dispatch_order SET stage='AFTER_LOADING'");
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
pendingOutbound(10000,"11",1,10);
|
||||
pendingOutbound(10000,"11",2,20);
|
||||
var sources=mapper.sources("11", null);assertThat(sources).hasSize(2);
|
||||
assertThat(sources.get(0).scheduledPackages()).isEqualTo(10);
|
||||
assertThat(sources.get(1).scheduledPackages()).isEqualTo(20);
|
||||
assertThat(mapper.sources("11",java.util.List.of("101"))).containsExactly(sources.get(0));
|
||||
assertThat(sources).allMatch(s->s.deliveringPackages()==0);
|
||||
jdbc.update("UPDATE ll_dispatch_order SET status='CANCELLED'");
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
for (String status : java.util.List.of("CANCELLED","CANCELED","OUTBOUNDED")) {
|
||||
jdbc.update("UPDATE ll_outbound_order SET status=?",status);
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
}
|
||||
assertThat(mapper.sources("22", null)).hasSize(1).allMatch(s->s.scheduledPackages()==0);
|
||||
}
|
||||
/** 已收货使用实际库存和扫码,不与计划量重复累计。 */
|
||||
@Test void receivedPalletUsesInventoryAndActualPicksWithoutCountingBothPlanAndStock() {
|
||||
/** 已收货和扫码不能提前推进,出库来源快照不随剩余库存变化。 */
|
||||
@Test void scheduledUsesOutboundSnapshotInsteadOfPlannedOrRemainingInventory() {
|
||||
jdbc.update("UPDATE ll_dispatch_item SET warehouse_pallet_id=800");
|
||||
jdbc.update("INSERT INTO ll_inventory_balance VALUES(300,'11',1),(301,'11',2)");
|
||||
jdbc.update("INSERT INTO ll_inventory_position VALUES(400,'11',800,'W1',300,6),(401,'11',800,'W1',301,20)");
|
||||
jdbc.update("INSERT INTO ll_dispatch_pda_pick VALUES(600,'11',100,400,4,700)");
|
||||
var sources=mapper.sources("11", null);assertThat(sources.get(0).scheduledPackages()).isEqualTo(10);
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
pendingOutbound(10000,"11",1,4);
|
||||
pendingOutbound(10000,"11",2,20);
|
||||
jdbc.update("UPDATE ll_inventory_position SET available_packages=0");
|
||||
var sources=mapper.sources("11", null);assertThat(sources.get(0).scheduledPackages()).isEqualTo(4);
|
||||
assertThat(sources.get(1).scheduledPackages()).isEqualTo(20);
|
||||
assertThat(mapper.sources("11",java.util.List.of("101"))).containsExactly(sources.get(0));
|
||||
}
|
||||
@@ -95,8 +115,10 @@ class CustomerOrderProgressMapperTest {
|
||||
assertThat(mapper.sources("11", null)).allMatch(s->s.deliveringPackages()==0);
|
||||
}
|
||||
|
||||
/** 同源多入仓片段完整返回,过滤不得扩大到其他订单或客户。 */
|
||||
@Test void exactSourceOrderScopePreservesAllOwnInboundFragmentsAndExcludesForeignOrders() {
|
||||
jdbc.update("INSERT INTO ll_order VALUES(4,'11','101','204','FIRSTLEG','FBA',5)");
|
||||
pendingOutbound(10000,"11",1,10);
|
||||
var own=mapper.sources("11",java.util.List.of("101","103"));
|
||||
assertThat(own).extracting(s->s.orderId()).containsExactly("101","101");
|
||||
assertThat(own).extracting(s->s.scheduledPackages()).containsExactly(10L,0L);
|
||||
@@ -104,4 +126,30 @@ class CustomerOrderProgressMapperTest {
|
||||
assertThat(mapper.sources("11",java.util.List.of("999"))).isEmpty();
|
||||
}
|
||||
|
||||
/** 多张出库只汇总本客户的有效来源数量,不借用其他来源或客户单据。 */
|
||||
@Test void splitOutboundQuantitiesExcludeCancelledAndForeignDocuments() {
|
||||
pendingOutbound(10000,"11",1,4);
|
||||
pendingOutbound(10001,"11",1,6);
|
||||
pendingOutbound(10002,"11",2,20);
|
||||
jdbc.update("UPDATE ll_outbound_order SET status='CANCELLED' WHERE id=10001");
|
||||
jdbc.update("INSERT INTO ll_outbound_order VALUES(10003,'22','WAIT_OUTBOUND')");
|
||||
jdbc.update("INSERT INTO ll_outbound_pallet_item VALUES(10003,'11',1,100),(10000,'22',3,100),(10003,'22',1,100)");
|
||||
assertThat(mapper.sources("11", null)).extracting(s->s.scheduledPackages()).containsExactly(4L,20L);
|
||||
assertThat(new com.baiyue.lastleg.service.CustomerOrderProgressService(mapper).read(11,java.util.List.of(101L)).progress())
|
||||
.singleElement().satisfies(p->{assertThat(p.stage()).isEqualTo("scheduled");assertThat(p.partial()).isTrue();});
|
||||
assertThat(mapper.sources("22", null)).allMatch(s->s.scheduledPackages()==0);
|
||||
jdbc.update("UPDATE ll_outbound_order SET status='WAIT_OUTBOUND' WHERE id=10001");
|
||||
assertThat(mapper.sources("11", null)).extracting(s->s.scheduledPackages()).containsExactly(10L,20L);
|
||||
assertThat(new com.baiyue.lastleg.service.CustomerOrderProgressService(mapper).read(11,java.util.List.of(101L)).progress())
|
||||
.singleElement().satisfies(p->{assertThat(p.stage()).isEqualTo("scheduled");assertThat(p.partial()).isFalse();});
|
||||
}
|
||||
|
||||
/** 创建同客户的待出库单及精确来源明细,允许一单包含多个来源。 */
|
||||
private void pendingOutbound(long outboundId,String customerId,long sourceOrderId,int packages) {
|
||||
if (jdbc.queryForObject("SELECT COUNT(*) FROM ll_outbound_order WHERE id=?",Integer.class,outboundId)==0) {
|
||||
jdbc.update("INSERT INTO ll_outbound_order VALUES(?,?,'WAIT_OUTBOUND')",outboundId,customerId);
|
||||
}
|
||||
jdbc.update("INSERT INTO ll_outbound_pallet_item VALUES(?,?,?,?)",outboundId,customerId,sourceOrderId,packages);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user