From 8d39c618636de7713954561eed1d82d1ffb00e1a Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Thu, 27 Aug 2026 16:54:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .runtime-tailleg/SchemaQuery.class | Bin 0 -> 3162 bytes deploy/sql/头程数据库统一增量-MySQL8.sql | 10 ++++ .../service/DeliveryTransferService.java | 4 +- .../DispatchPickOrderAttributionService.java | 1 + .../mapper/InventoryCommandMapper.java | 9 ++++ .../service/InventoryCommandService.java | 34 ++++++++++++- .../service/DeliveryTransferServiceTest.java | 4 +- ...spatchPickOrderAttributionServiceTest.java | 4 +- .../command/InventoryCommandServiceTest.java | 46 ++++++++++++++++++ scripts/db/incremental.sql | 14 ++++++ 10 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 .runtime-tailleg/SchemaQuery.class diff --git a/.runtime-tailleg/SchemaQuery.class b/.runtime-tailleg/SchemaQuery.class new file mode 100644 index 0000000000000000000000000000000000000000..1d07fe14d380f3d98b83bd564c59773a5d9a0453 GIT binary patch literal 3162 zcma)8T~iy^8Ga5Rt+XtL$bh|W63dQ*K^PR{#&v~>ivcGz!gef78oOz`NXM|S+LiYM zoAkqJlh8J4>hwG5v^Q;Ld|P*>#5gnYO()aIRXfwG{)7I4wwW~Ub0iR8)J|tW=j^-B zdEW2$>_?y8{Q$rSexaiY%^F$^#1I$gSg@9?tYf)z*>kfCvKR`)Pui{>J|)oH+c%>j zA#iXLH0d~U&T>Zmxu`7NaN>GVRzllz6VL_5qrjSzxpbj8FU!_*k@T0-C+EE&d@6mi zWQEqO6-aKQK>7pHUE;Q41;I7XFV!9|2UndJb!ae@b!`G&z4=Ymh0wR%x#7MU9qs7Q z@Q{HNb_=v_QO74=UX5&DmL}XKfu2SLJ2?~R5=ay#@)Kjz>7yw|YL9`vFa`FMtwlNJ zxkW2{-VW!v3qs2c1A!9_sCP2=?G)+w3?A0-h=E73U!c3{AwAEI`}UIbr!1F2^aa}H zWJrB3lfnQCO#NR9cj{X=L21~e8*aq`9MtfbfkSv)V0Yb$O^iT$(U(>z3n710x@m3i z7P`8eY=_aSq0c}D{Q{{vTOA^x$?LKhh4R0o^->Z87}St8@C1%-tIWc3K>aL8FQN%u zH5Y9!d!7XmvMi)ku3B)+z-RHKz%EAaVucmQ%tgv-R`O&D0-sY^J)sPkY?zoFhBcft z*e5K$T1d95p%)dU)Nz%^rwn{SHBqeSc!5+AJ*C8B2F5WVV9<;UGKie8ARi1}T~5QF z#xoiw4SW&LZVg=>!lEYAlnkwLHj%)iTXRrHZM-Mp6wYcmXW+aF#=d$kc0g;98uOf} z>{cU{zy$%3)RDuqfr}~%v8um{^LYbbQo&Qvt)0nRv(i!EODbe><;j-~yoj#|w7Bwm zE%29=_-h6%%nBrVc4N*wkc66eSVsv`!xaN_nBQIn)&6G&mMkZd=dL`^-RtKYGaMggX=1-hH zahfT^Lj#fWUbBPK)Uuf3u)U^gjI8}#lB zjbaZK7pJUB6|do2^rHb(K(9~ZK*P7Ud0T%&RKbhHj_cVN8`SQBJp=wQ4<7m4}-lELbhBIde2jv;77<&G)z}s8Hw+^h4 zK4ZBhM+S%Tp0^lPhIcx?MY8SS^m0WufNzaQgF;xR7_%IwV25%zfgcFGvQU~W=E}<) zjJa%9@f z5_n5sf7uQK>PoY)Y~OR$@J%mSzO70omlhl@le`)HF^3nZWV(MM@c53TG>Wx`pK*fx zZzyZJmKYr6Rolet5NPK3n*>pVzk^B9k| z67cKH2ikAY_CB;1QpxwwdM^_jx`SQ$Ok6EfeETkv8ExnmVyVuvndYHP%g|kP1GyHn zrT@qs?90W>*c#FTxwsi0SVK<}j%!`oFERLg95Lf-7!tUP-GARqe29!9vjuJ;ol7b~cU@rQ zXq>69!ZDNglv|l~w-i=Wg6_J&pj(=mP+0Arayzr`w!+4hpt~+G)L~Oo*!aEMNZ_W} zD-L5p42mbk2@b_P%A&w^MU1OeToRTj@p%l(;zm_oQL9)HZ;H40+*B=r6&zp*Bv}eN zI$&TA+W4Kbi+6iFFNzdT?t6AJ7Q|t*~JsU5q9zbvoctXQW8y{sPE}g8()5>3Vj8ya_!{vYgF|{_4m!{ S?@j!OZ*)q3jGyA?F#iLmXdVav literal 0 HcmV?d00001 diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index f6c0d9e3b..aa7b31e27 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -16629,6 +16629,16 @@ CREATE TABLE IF NOT EXISTS iam_customer_dimension_initialization_task ( KEY idx_customer_dimension_task_diagnosis (status,updated_at) ) ENGINE=InnoDB COMMENT='IAM客户默认尺寸可靠后台初始化任务及诊断状态'; +-- 兼容已存在的旧表:CREATE TABLE IF NOT EXISTS 不会为旧表补充新字段。 +SET @customer_dimension_lease_ddl = (SELECT IF( + EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='iam_customer_dimension_initialization_task' AND column_name='lease_until'), + 'SELECT 1', + 'ALTER TABLE iam_customer_dimension_initialization_task ADD COLUMN lease_until DATETIME(3) NULL COMMENT ''当前任务租约截止时间'' AFTER lease_owner')); +PREPARE customer_dimension_lease_stmt FROM @customer_dimension_lease_ddl; +EXECUTE customer_dimension_lease_stmt; +DEALLOCATE PREPARE customer_dimension_lease_stmt; + CALL baiyue_firstleg.assert_unified_final_schema(); DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema; SELECT 'PASS' AS migration_status, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java index 4394ab6bd..e54443cdc 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java @@ -290,7 +290,7 @@ public class DeliveryTransferService { if (delivery != null) { for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper().eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getOutboundOrderNo, outboundNo))) { String from = order.getCurrentStatus(); order.setCurrentStatus("RECEIVED"); order.setSubOrderStatus(null); order.setOutboundOrderNo(null); order.setDeliveryOrderNo(null); order.setUpdatedAt(now); tailOrders.updateById(order); - LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("RECEIVED"); history.setEventId("CANCEL_OUTBOUND:" + outboundNo); history.setRemark(request.reason().trim()); history.setCreatedAt(now); histories.insert(history); + LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("RECEIVED"); history.setEventId("CANCEL_OUTBOUND:" + outboundNo); history.setRemark(request.reason().trim()); history.setCreatedAt(now); histories.insert(history); } } } @@ -375,7 +375,7 @@ public class DeliveryTransferService { order.setDeliveryOrderNo(deliveryNo); order.setCurrentStatus("WAIT_OUTBOUND"); order.setUpdatedAt(now); order.setSubOrderStatus("OUTBOUND:WAIT_OUTBOUND;DELIVERY:WAIT_DELIVERY"); tailOrders.updateById(order); - LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId()); + LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("WAIT_OUTBOUND"); history.setEventId("TRANSFER_DELIVERY:" + deliveryNo); history.setRemark(StringUtils.hasText(remark) ? remark.trim() : "Transferred to outbound and delivery documents"); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java index 441d30d56..c435f098e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java @@ -84,6 +84,7 @@ public class DispatchPickOrderAttributionService { long shipped = value(before.getShippedPackageCount()) + entry.getValue(); String to = shipped == value(before.getPackageCount()) ? "COMPLETED" : "PARTIALLY_SHIPPED"; LastlegOrderStatusHistory row = new LastlegOrderStatusHistory(); + row.setCustomerId(customerId); row.setOrderId(entry.getKey()); row.setFromStatus(before.getCurrentStatus()); row.setToStatus(to); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java index 0fd568123..e292046d9 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java @@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import java.util.List; + @Mapper public interface InventoryCommandMapper { @Select("SELECT * FROM ll_inventory_position WHERE id=#{positionId} LIMIT 1") @@ -22,4 +24,11 @@ public interface InventoryCommandMapper { @Select("SELECT COALESCE(SUM(available_packages+hold_packages+allocated_packages+picked_packages+receiving_packages),0) " + "FROM ll_inventory_position WHERE customer_id=#{customerId} AND pallet_id=#{palletId}") long palletPackages(@Param("customerId") String customerId, @Param("palletId") Long palletId); + + @Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} " + + "AND inbound_order_id=#{inboundOrderId} AND inbound_code=#{inboundCode} " + + "AND in_transit_packages>0 ORDER BY id FOR UPDATE") + List lockInboundTransitBalances(@Param("customerId") String customerId, + @Param("inboundOrderId") Long inboundOrderId, + @Param("inboundCode") String inboundCode); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java index f42015986..9481a2c11 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java @@ -27,6 +27,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.util.HexFormat; +import java.util.List; @Service public class InventoryCommandService { @@ -60,6 +61,8 @@ public class InventoryCommandService { public InventoryCommandResponse stockIn(String customerId, String operatorId, InventoryCommandRequests.StockIn request) { return execute(customerId, operatorId, "STOCK_IN", request.idempotencyKey(), request, operation -> { + List transitBalances = commands.lockInboundTransitBalances(customerId, + request.inboundOrderId(), request.inboundCode()); InventoryBalance balance = balance(customerId, request); InventoryPosition position = position(customerId, balance, request); long before = value(position.getAvailablePackages()); @@ -69,8 +72,8 @@ public class InventoryCommandService { position.setUpdatedAt(LocalDateTime.now()); positions.updateById(position); - long transferred = Math.min(value(balance.getInTransitPackages()), request.packages()); - balance.setInTransitPackages(value(balance.getInTransitPackages()) - transferred); + long transferred = transferInTransit(customerId, operatorId, operation.getId(), balance, + transitBalances, request); balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages()); touch(balance); balances.updateById(balance); @@ -83,6 +86,33 @@ public class InventoryCommandService { }); } + private long transferInTransit(String customerId, String operatorId, Long operationId, + InventoryBalance target, List sources, + InventoryCommandRequests.StockIn request) { + long pending = request.packages(); + long transferred = 0; + for (InventoryBalance source : sources) { + if (pending == 0) break; + boolean targetDimension = source.getId().equals(target.getId()); + long sourceBefore = value(targetDimension ? target.getInTransitPackages() : source.getInTransitPackages()); + long moved = Math.min(sourceBefore, pending); + if (moved == 0) continue; + if (targetDimension) target.setInTransitPackages(sourceBefore - moved); + else source.setInTransitPackages(sourceBefore - moved); + transferred += moved; + pending -= moved; + if (targetDimension) continue; + touch(source); + balances.updateById(source); + ledger(customerId, operatorId, operationId, source, null, sku(request.skuCode()), + "STOCK_IN_TRANSIT_RELEASE", "IN_TRANSIT", "STOCK_IN", sourceBefore, -moved, + sourceBefore - moved, request.businessOrderNo(), request.inboundOrderNo(), null, + request.sourceEventId(), request.idempotencyKey() + ":TRANSIT:" + source.getId(), + "In-transit inventory transferred to overseas warehouse " + target.getOverseasWarehouseId()); + } + return transferred; + } + @Transactional public InventoryCommandResponse hold(String customerId, String operatorId, InventoryCommandRequests.Move request) { return move(customerId, operatorId, "HOLD", Bucket.AVAILABLE, Bucket.HOLD, request); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java index c23a505a6..91e56a48d 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java @@ -192,7 +192,9 @@ class DeliveryTransferServiceTest { assertNotNull(tailOrder.getOutboundOrderNo()); assertNotNull(tailOrder.getDeliveryOrderNo()); verify(tailOrders).updateById(tailOrder); - verify(histories).insert(any(LastlegOrderStatusHistory.class)); + ArgumentCaptor history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class); + verify(histories).insert(history.capture()); + assertEquals("TENANT-A", history.getValue().getCustomerId()); } @Test diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionServiceTest.java index ad9dcee13..dd7339d81 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionServiceTest.java @@ -75,8 +75,8 @@ class DispatchPickOrderAttributionServiceTest { "t1", "OUT1", "D1", List.of(pick), LocalDateTime.of(2026, 8, 17, 15, 0)); assertEquals(List.of(new DispatchPickOrderAttributionService.OrderPackages(101L, 4L)), result); - verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "PARTIALLY_SHIPPED".equals(value.getToStatus()) - && value.getRemark().contains("6/10"))); + verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "t1".equals(value.getCustomerId()) + && "PARTIALLY_SHIPPED".equals(value.getToStatus()) && value.getRemark().contains("6/10"))); } private DispatchPickOrderAttributionService service() { diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java index 5722fc1e3..d0659e077 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java @@ -24,6 +24,8 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -57,6 +59,7 @@ class InventoryCommandServiceTest { ReflectionTestUtils.setField(service, "warehouseGateway", warehouseGateway); ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper()); lenient().when(operations.findIdempotency(any(), any())).thenReturn(null); + lenient().when(commands.lockInboundTransitBalances(any(), any(), any())).thenReturn(List.of()); } @Test @@ -114,6 +117,7 @@ class InventoryCommandServiceTest { balance.setSkuCode("SKU-1"); balance.setInTransitPackages(12L); when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(balance); + when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(balance)); InventoryCommandResponse result = service.stockIn("100", "7", stockIn("IN-1", "stock-in-transit", 12, 24)); @@ -122,6 +126,48 @@ class InventoryCommandServiceTest { assertEquals(12L, balance.getAvailablePackages()); } + @Test + void stockInTransfersInTransitFromTheOriginalOverseasWarehouse() { + InventoryBalance target = balance(200L, 0); + target.setSkuCode("SKU-1"); + target.setInTransitPackages(0L); + InventoryBalance source = balance(201L, 0); + source.setOverseasWarehouseId("WH-OLD"); + source.setSkuCode("SKU-1"); + source.setInTransitPackages(12L); + when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target); + when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(source)); + + InventoryCommandResponse result = service.stockIn("100", "7", + stockIn("IN-1", "stock-in-cross-warehouse", 12, 24)); + + assertEquals(12, result.availablePackages()); + assertEquals(0L, source.getInTransitPackages()); + ArgumentCaptor written = ArgumentCaptor.forClass(InventoryLedger.class); + verify(ledgers, times(2)).insert(written.capture()); + assertEquals("STOCK_IN_TRANSIT_RELEASE", written.getAllValues().get(0).getBusinessType()); + assertEquals(-12L, written.getAllValues().get(0).getDeltaPackages()); + assertEquals("STOCK_IN", written.getAllValues().get(1).getBusinessType()); + assertEquals("IN_TRANSIT", written.getAllValues().get(1).getStatusFrom()); + } + + @Test + void stockInKeepsOutsideSourceWhenInTransitIsInsufficient() { + InventoryBalance target = balance(200L, 0); + target.setSkuCode("SKU-1"); + target.setInTransitPackages(2L); + when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target); + when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(target)); + + service.stockIn("100", "7", stockIn("IN-1", "stock-in-partial-transit", 5, 10)); + + ArgumentCaptor ledger = ArgumentCaptor.forClass(InventoryLedger.class); + verify(ledgers).insert(ledger.capture()); + assertEquals("OUTSIDE", ledger.getValue().getStatusFrom()); + assertEquals(0L, target.getInTransitPackages()); + assertEquals(5L, target.getAvailablePackages()); + } + @Test void differentSkusShareInboundBalanceAndKeepSkuInLedger() { InventoryBalance balance = balance(200L, 4); diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index 40ffbc876..7430afad5 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -3895,3 +3895,17 @@ CREATE TABLE IF NOT EXISTS ll_customer_visit ( KEY idx_ll_customer_visit_list(customer_id,status,delivery_completed_at), KEY idx_ll_customer_visit_delivery_no(customer_id,delivery_no) ) ENGINE=InnoDB COMMENT='派送单维度客户回访记录'; + +-- ============================================================================ +-- 2026-08-27 / local legacy inventory-ledger cleanup +-- ============================================================================ +USE baiyue_lastleg; + +-- These quantity columns are absent from the current ledger contract. Legacy +-- NOT NULL definitions without defaults block package-only ledger writes. +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='before_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN before_quantity', 'SELECT 1')); +PREPARE lastleg_ledger_legacy_001 FROM @ddl; EXECUTE lastleg_ledger_legacy_001; DEALLOCATE PREPARE lastleg_ledger_legacy_001; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='delta_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN delta_quantity', 'SELECT 1')); +PREPARE lastleg_ledger_legacy_002 FROM @ddl; EXECUTE lastleg_ledger_legacy_002; DEALLOCATE PREPARE lastleg_ledger_legacy_002; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='after_quantity'), 'ALTER TABLE ll_inventory_ledger DROP COLUMN after_quantity', 'SELECT 1')); +PREPARE lastleg_ledger_legacy_003 FROM @ddl; EXECUTE lastleg_ledger_legacy_003; DEALLOCATE PREPARE lastleg_ledger_legacy_003;