feat: add dropship fulfillment producer chain
This commit is contained in:
+6
-1
@@ -37,12 +37,17 @@ class UnifiedInboundOpenApiContractTest {
|
||||
assertThat(stringMap(inbound.get("properties"))).containsKeys(
|
||||
"id", "tenantId", "orderId", "inboundCode", "processStatus", "version",
|
||||
"customerId", "warehouseId", "deliveryWarehouseId", "totalPackageCount",
|
||||
"businessType", "channelCode", "pricingTaxMode", "containerMode", "destinationType",
|
||||
"businessType", "fulfillmentType", "channelCode", "pricingTaxMode", "containerMode", "destinationType",
|
||||
"holdFlag", "actualGrossWeightKg", "actualNetWeightKg", "actualVolumeCbm",
|
||||
"volumeWeightKg", "chargeableWeightKg", "chargeableVolumeCbm", "palletCount",
|
||||
"inboundedAt", "requestedPickupAt", "expressCompanyCode", "expressCompanyName", "containerNo",
|
||||
"cabinetNo", "estimatedSailingAt", "actualSailingAt", "estimatedPortArrivalAt",
|
||||
"actualPortArrivalAt", "createdByName", "createdAt", "updatedAt");
|
||||
|
||||
Map<String, Object> approval = stringMap(schemas.get("OrderApprovedInboundCommand"));
|
||||
assertThat(stringList(approval.get("required"))).contains("fulfillmentType");
|
||||
Map<String, Object> fulfillment = stringMap(stringMap(approval.get("properties")).get("fulfillmentType"));
|
||||
assertThat(stringList(fulfillment.get("enum"))).containsExactly("STANDARD", "ONE_CLICK_DROPSHIP");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FulfillmentTypeSqlContractTest {
|
||||
@Test
|
||||
void baselinesAndUnifiedMigrationPersistIndependentFulfillmentType() throws Exception {
|
||||
String order = Files.readString(resolve("order/src/main/resources/db/order.sql"), StandardCharsets.UTF_8);
|
||||
String inbound = Files.readString(resolve("firstleg/src/main/resources/db/phase2-inbound-schema.sql"), StandardCharsets.UTF_8);
|
||||
String unified = Files.readString(resolve("deploy/sql/头程数据库统一增量-MySQL8.sql"), StandardCharsets.UTF_8);
|
||||
|
||||
assertFulfillmentMigration(order, "baiyue_order.ord_order");
|
||||
assertFulfillmentMigration(inbound, "fl_inbound_order");
|
||||
assertFulfillmentMigration(unified, "baiyue_order.ord_order");
|
||||
assertFulfillmentMigration(unified, "fl_inbound_order");
|
||||
assertThat(unified)
|
||||
.contains("unified_assert_column('baiyue_order', 'ord_order', 'fulfillment_type')")
|
||||
.contains("unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'fulfillment_type')");
|
||||
}
|
||||
|
||||
private static void assertFulfillmentMigration(String sql, String table) {
|
||||
assertThat(sql)
|
||||
.contains("fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD'")
|
||||
.contains("ADD COLUMN fulfillment_type VARCHAR(32) NULL")
|
||||
.contains("SET fulfillment_type = 'STANDARD'")
|
||||
.contains("MODIFY COLUMN fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD'")
|
||||
.contains(table);
|
||||
}
|
||||
|
||||
private static Path resolve(String path) {
|
||||
Path direct = Path.of(path);
|
||||
return Files.exists(direct) ? direct : Path.of("..").resolve(path).normalize();
|
||||
}
|
||||
}
|
||||
+1
@@ -100,6 +100,7 @@ class PdaSplitSettlementSqlContractTest {
|
||||
assertThat(clone)
|
||||
.contains("#{childOrderId},#{childOrderNo}")
|
||||
.contains("CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'WAIT_SPLIT' END")
|
||||
.contains("business_type,fulfillment_type")
|
||||
.contains("actual_gross_weight_kg * #{packageCount}")
|
||||
.contains("actual_net_weight_kg * #{packageCount}");
|
||||
|
||||
|
||||
+24
@@ -72,11 +72,35 @@ class InboundOrderCreationServiceTest {
|
||||
assertThat(inboundCaptor.getValue().getCarrierId()).isNull();
|
||||
assertThat(inboundCaptor.getValue().getTaxMode()).isEqualTo("BUY_DECLARATION");
|
||||
assertThat(inboundCaptor.getValue().getPricingTaxMode()).isEqualTo("TAX_EXCLUDED");
|
||||
assertThat(inboundCaptor.getValue().getFulfillmentType()).isEqualTo("STANDARD");
|
||||
assertThat(inboundCaptor.getValue().getTotalPackageCount()).isEqualTo(4);
|
||||
assertThat(createdId).isEqualTo(inboundCaptor.getValue().getId());
|
||||
assertThat(replayedId).isEqualTo(900L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistsDropshipFulfillmentTypeWithoutChangingPricingBusinessType() {
|
||||
OrderApprovedInboundCommand base = approvedCommand("PCHK2608140001");
|
||||
OrderApprovedInboundCommand command = new OrderApprovedInboundCommand(
|
||||
base.tenantId(), base.orderId(), base.orderNo(), base.inboundCode(), base.customerId(),
|
||||
base.customerName(), base.warehouseId(), base.warehouseName(), base.deliveryWarehouseId(),
|
||||
base.deliveryWarehouseName(), base.expectedArrivalAt(), base.businessType(), base.channelCode(),
|
||||
base.channelName(), base.carrierId(), base.carrierName(), base.taxMode(), base.pricingTaxMode(),
|
||||
base.containerMode(), base.customsInspection(), base.destinationType(), base.destinationWarehouseId(),
|
||||
base.destinationCode(), base.destinationName(), base.publicRemark(), base.expressCompanyName(),
|
||||
base.expressTrackingNo(), base.totalPackageCount(), base.pickupMethod(), base.requestedPickupAt(),
|
||||
"ONE_CLICK_DROPSHIP");
|
||||
when(orders.selectOne(any(Wrapper.class))).thenReturn(null);
|
||||
when(fulfillmentTasks.provision(any(), any())).thenReturn(task(700L));
|
||||
|
||||
service.createFromApprovedOrder(command);
|
||||
|
||||
ArgumentCaptor<InboundOrderEntity> captor = ArgumentCaptor.forClass(InboundOrderEntity.class);
|
||||
verify(orders).insert(captor.capture());
|
||||
assertThat(captor.getValue().getBusinessType()).isEqualTo("FBA");
|
||||
assertThat(captor.getValue().getFulfillmentType()).isEqualTo("ONE_CLICK_DROPSHIP");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectReplayWithDifferentInboundCode() {
|
||||
InboundOrderEntity existing = inbound(900L, "RK-OLD", "UNPRICED", null, null);
|
||||
|
||||
+32
-2
@@ -146,9 +146,9 @@ class LoadingCompletedOutboxServiceTest {
|
||||
}
|
||||
|
||||
@Test void publishesDropshipPalletSkuAndCartonSnapshots() throws Exception {
|
||||
Row first = row(11, 100, 101); first.setBusinessType("ONE_CLICK_DROPSHIP");
|
||||
Row first = row(11, 100, 101); first.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
first.setPackageCount(2); first.setProductQuantity(20); first.setSkuCode("SKU-A");
|
||||
Row second = row(11, 100, 102); second.setBusinessType("ONE_CLICK_DROPSHIP");
|
||||
Row second = row(11, 100, 102); second.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
second.setPackageCount(1); second.setProductQuantity(5); second.setSkuCode("SKU-B");
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(first, second));
|
||||
when(snapshots.palletRows(90L)).thenReturn(List.of(
|
||||
@@ -168,6 +168,35 @@ class LoadingCompletedOutboxServiceTest {
|
||||
assertThat(event.payload().at("/orders/0/palletCount").asInt()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pricingBusinessTypeCannotTurnAStandardOrderIntoDropship() throws Exception {
|
||||
Row value = row(11, 100, 101);
|
||||
value.setBusinessType("ONE_CLICK_DROPSHIP");
|
||||
value.setFulfillmentType("STANDARD");
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(value));
|
||||
|
||||
service.publish(90L);
|
||||
|
||||
ArgumentCaptor<InboundOutboxEntity> captor = ArgumentCaptor.forClass(InboundOutboxEntity.class);
|
||||
verify(outbox).insert(captor.capture());
|
||||
EventEnvelope event = mapper.readValue(captor.getValue().getPayloadJson(), EventEnvelope.class);
|
||||
assertThat(event.payload().at("/orders/0/orderType").asText()).isEqualTo("FIRST_LEG");
|
||||
assertThat(event.payload().at("/orders/0/pallets")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMixedFulfillmentTypesWithinOneInbound() {
|
||||
Row first = row(11, 100, 101);
|
||||
Row second = row(11, 100, 102);
|
||||
second.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(first, second));
|
||||
|
||||
assertThatThrownBy(() -> service.publish(90L))
|
||||
.hasMessageContaining("履约类型必须唯一");
|
||||
verify(outbox, org.mockito.Mockito.never()).insert(
|
||||
org.mockito.ArgumentMatchers.any(InboundOutboxEntity.class));
|
||||
}
|
||||
|
||||
private Row row(long account,long inbound,long detail) {
|
||||
Row r=new Row(); r.setLoadingPlanId(90L); r.setPlanNo("P90"); r.setContainerBookingId(91L);
|
||||
r.setAggregateVersion(8); r.setContainerNo("CONT"); r.setCabinetNo("CAB"); r.setWarehouseId(92L);
|
||||
@@ -176,6 +205,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
r.setInboundCode("RK"+inbound); r.setLoadingPlanItemId(detail); r.setDestinationWarehouseId(93L); r.setDestinationWarehouseCode("ONT8");
|
||||
r.setDestinationWarehouseName("ONT8"); r.setSourceInboundDetailId(detail); r.setSkuCode("SKU"+detail);
|
||||
r.setDestinationType("OVERSEAS");
|
||||
r.setFulfillmentType("STANDARD");
|
||||
r.setPackageCount(1); r.setProductQuantity(10); r.setPalletCount(1); r.setGrossWeightKg(BigDecimal.ONE);
|
||||
r.setNetWeightKg(BigDecimal.ONE); r.setVolumeCbm(BigDecimal.ONE); return r;
|
||||
}
|
||||
|
||||
+9
-7
@@ -58,8 +58,8 @@ class TaillegRealProducerAcceptanceTest {
|
||||
BOOKING, MARKER + "-BOOK", MARKER + "-CNTR", MARKER + "-CAB", 92L, "真实验收海外仓", "40HQ");
|
||||
jdbc.update("INSERT IGNORE INTO fl_loading_plan(id,plan_no,container_booking_id,plan_status,created_by," +
|
||||
"updated_by,version) VALUES(?,?,?,'LOADING',1,1,0)", PLAN, MARKER + "-PLAN", BOOKING);
|
||||
seedInbound(FIRST_INBOUND, 990022221L, MARKER + "-FIRST-IN", "FBA_SEA", "LOADED", "ONT8");
|
||||
seedInbound(DROPSHIP_INBOUND, 990022222L, MARKER + "-DROP-IN", "ONE_CLICK_DROPSHIP", "LOADED", "LAX9");
|
||||
seedInbound(FIRST_INBOUND, 990022221L, MARKER + "-FIRST-IN", "FBA_SEA", "STANDARD", "LOADED", "ONT8");
|
||||
seedInbound(DROPSHIP_INBOUND, 990022222L, MARKER + "-DROP-IN", "FBA_SEA", "ONE_CLICK_DROPSHIP", "LOADED", "LAX9");
|
||||
seedItem(990022321L, FIRST_INBOUND, 990022421L, 990022221L, MARKER + "-FIRST-IN", "SKU-REAL-A", 3, "ONT8");
|
||||
seedItem(990022322L, DROPSHIP_INBOUND, 990022422L, 990022222L, MARKER + "-DROP-IN", "SKU-REAL-D", 2, "LAX9");
|
||||
for (int i = 1; i <= 3; i++) seedLabel(990022540L + i, FIRST_INBOUND, 990022221L, 990022421L,
|
||||
@@ -86,13 +86,15 @@ class TaillegRealProducerAcceptanceTest {
|
||||
id, 990022901L, planItemId, inboundId, detailId, packages, packages);
|
||||
}
|
||||
|
||||
private void seedInbound(long id, long orderId, String code, String type, String status, String destination) {
|
||||
private void seedInbound(long id, long orderId, String code, String businessType, String fulfillmentType,
|
||||
String status, String destination) {
|
||||
jdbc.update("INSERT IGNORE INTO fl_inbound_order(id,tenant_id,order_id,order_no,inbound_code,process_status," +
|
||||
"order_status_snapshot,customer_name,warehouse_id,warehouse_name,business_type,destination_type,destination_warehouse_id," +
|
||||
"order_status_snapshot,customer_name,warehouse_id,warehouse_name,business_type,fulfillment_type,destination_type,destination_warehouse_id," +
|
||||
"destination_code,destination_name,package_count,actual_volume_cbm,created_by,updated_by) " +
|
||||
"VALUES(?,?,?,?,?,?, 'IN_TRANSIT',?,92,'真实验收海外仓',?,'FBA',9001,?,?,?,0.4000,1,1)",
|
||||
id, CUSTOMER, orderId, MARKER + "-ORDER-" + id, code, status, "真实验收客户", type,
|
||||
destination, "FBA " + destination, type.equals("ONE_CLICK_DROPSHIP") ? 2 : 3);
|
||||
"VALUES(?,?,?,?,?,?, 'IN_TRANSIT',?,92,'真实验收海外仓',?,?,'FBA',9001,?,?,?,0.4000,1,1)",
|
||||
id, CUSTOMER, orderId, MARKER + "-ORDER-" + id, code, status, "真实验收客户", businessType,
|
||||
fulfillmentType, destination, "FBA " + destination,
|
||||
fulfillmentType.equals("ONE_CLICK_DROPSHIP") ? 2 : 3);
|
||||
}
|
||||
|
||||
private void seedItem(long id, long inboundId, long detailId, long orderId, String inboundCode,
|
||||
|
||||
Reference in New Issue
Block a user