@rytass/wms-module-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/index.cjs.js +6479 -0
- package/index.d.ts +5 -0
- package/index.js +74 -0
- package/lib/constants/index.d.ts +1 -0
- package/lib/constants/stock-status.d.ts +30 -0
- package/lib/constants/stock-status.js +51 -0
- package/lib/core.module.d.ts +7 -0
- package/lib/core.module.js +102 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order-change.entity.js +40 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order-item.entity.d.ts +20 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order-item.entity.js +103 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order.entity.d.ts +8 -0
- package/lib/models/extensions/allocate-inventory-order/allocate-inventory-order.entity.js +33 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order-change.entity.js +40 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order-item.entity.d.ts +19 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order-item.entity.js +97 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order.entity.d.ts +10 -0
- package/lib/models/extensions/hold-inventory-order/hold-inventory-order.entity.js +43 -0
- package/lib/models/extensions/index.d.ts +37 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order-change.entity.js +40 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order-item.entity.d.ts +15 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order-item.entity.js +74 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order.entity.d.ts +8 -0
- package/lib/models/extensions/merge-batch-order/merge-batch-order.entity.js +33 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order-change.entity.d.ts +11 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order-change.entity.js +45 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order-item.entity.d.ts +18 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order-item.entity.js +93 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order.entity.d.ts +9 -0
- package/lib/models/extensions/quality-inspection-order/quality-inspection-order.entity.js +38 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order-change.entity.d.ts +12 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order-change.entity.js +46 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order-item.entity.d.ts +19 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order-item.entity.js +89 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order.entity.d.ts +13 -0
- package/lib/models/extensions/receive-inventory-order/receive-inventory-order.entity.js +57 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order-change.entity.js +40 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order-item.entity.d.ts +15 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order-item.entity.js +74 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order.entity.d.ts +8 -0
- package/lib/models/extensions/reclassify-inventory-order/reclassify-inventory-order.entity.js +33 -0
- package/lib/models/extensions/scrape-order/scrape-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/scrape-order/scrape-order-change.entity.js +40 -0
- package/lib/models/extensions/scrape-order/scrape-order-item.entity.d.ts +18 -0
- package/lib/models/extensions/scrape-order/scrape-order-item.entity.js +93 -0
- package/lib/models/extensions/scrape-order/scrape-order.entity.d.ts +8 -0
- package/lib/models/extensions/scrape-order/scrape-order.entity.js +33 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-change.entity.d.ts +12 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-change.entity.js +46 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-item.entity.d.ts +19 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-item.entity.js +90 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-reference.entity.d.ts +13 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order-reference.entity.js +65 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order.entity.d.ts +13 -0
- package/lib/models/extensions/ship-inventory-order/ship-inventory-order.entity.js +56 -0
- package/lib/models/extensions/split-batch-order/split-batch-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/split-batch-order/split-batch-order-change.entity.js +40 -0
- package/lib/models/extensions/split-batch-order/split-batch-order-item.entity.d.ts +15 -0
- package/lib/models/extensions/split-batch-order/split-batch-order-item.entity.js +74 -0
- package/lib/models/extensions/split-batch-order/split-batch-order.entity.d.ts +8 -0
- package/lib/models/extensions/split-batch-order/split-batch-order.entity.js +33 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order-change.entity.js +40 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order-item.entity.d.ts +19 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order-item.entity.js +98 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order.entity.d.ts +8 -0
- package/lib/models/extensions/transfer-customer-order/transfer-customer-order.entity.js +33 -0
- package/lib/models/extensions/transfer-order/transfer-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/transfer-order/transfer-order-change.entity.js +40 -0
- package/lib/models/extensions/transfer-order/transfer-order-item.entity.d.ts +21 -0
- package/lib/models/extensions/transfer-order/transfer-order-item.entity.js +108 -0
- package/lib/models/extensions/transfer-order/transfer-order.entity.d.ts +12 -0
- package/lib/models/extensions/transfer-order/transfer-order.entity.js +53 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order-change.entity.d.ts +10 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order-change.entity.js +40 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order-item.entity.d.ts +19 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order-item.entity.js +98 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order.entity.d.ts +8 -0
- package/lib/models/extensions/transfer-vendor-order/transfer-vendor-order.entity.js +33 -0
- package/lib/models/index.d.ts +2 -0
- package/lib/models/metadata/batch-group.entity.d.ts +8 -0
- package/lib/models/metadata/batch-group.entity.js +31 -0
- package/lib/models/metadata/batch.entity.d.ts +15 -0
- package/lib/models/metadata/batch.entity.js +73 -0
- package/lib/models/metadata/index.d.ts +11 -0
- package/lib/models/metadata/loader-type.entity.d.ts +9 -0
- package/lib/models/metadata/loader-type.entity.js +43 -0
- package/lib/models/metadata/loader-unit.entity.d.ts +5 -0
- package/lib/models/metadata/loader-unit.entity.js +21 -0
- package/lib/models/metadata/loader.entity.d.ts +21 -0
- package/lib/models/metadata/loader.entity.js +98 -0
- package/lib/models/metadata/location.entity.d.ts +8 -0
- package/lib/models/metadata/location.entity.js +28 -0
- package/lib/models/metadata/material-unit.entity.d.ts +11 -0
- package/lib/models/metadata/material-unit.entity.js +51 -0
- package/lib/models/metadata/material.entity.d.ts +7 -0
- package/lib/models/metadata/material.entity.js +28 -0
- package/lib/models/metadata/sale-order.entity.d.ts +5 -0
- package/lib/models/metadata/sale-order.entity.js +21 -0
- package/lib/models/metadata/stock.entity.d.ts +8 -0
- package/lib/models/metadata/stock.entity.js +29 -0
- package/lib/models/metadata/unit.entity.d.ts +5 -0
- package/lib/models/metadata/unit.entity.js +21 -0
- package/lib/models/models.module.d.ts +6 -0
- package/lib/models/models.module.js +122 -0
- package/lib/services/allocate-inventory-order.service.d.ts +29 -0
- package/lib/services/allocate-inventory-order.service.js +299 -0
- package/lib/services/base-order.service.d.ts +41 -0
- package/lib/services/batch.service.d.ts +30 -0
- package/lib/services/batch.service.js +218 -0
- package/lib/services/helper.service.d.ts +6 -0
- package/lib/services/helper.service.js +28 -0
- package/lib/services/hold-inventory-order.service.d.ts +29 -0
- package/lib/services/hold-inventory-order.service.js +285 -0
- package/lib/services/index.d.ts +22 -0
- package/lib/services/loader.service.d.ts +43 -0
- package/lib/services/loader.service.js +282 -0
- package/lib/services/location.service.d.ts +13 -0
- package/lib/services/location.service.js +130 -0
- package/lib/services/material.service.d.ts +27 -0
- package/lib/services/material.service.js +172 -0
- package/lib/services/merge-batch-order.service.d.ts +19 -0
- package/lib/services/merge-batch-order.service.js +195 -0
- package/lib/services/quality-inspection-order.service.d.ts +19 -0
- package/lib/services/quality-inspection-order.service.js +135 -0
- package/lib/services/receive-inventory-order.service.d.ts +41 -0
- package/lib/services/receive-inventory-order.service.js +407 -0
- package/lib/services/reclassify-inventory-order.service.d.ts +19 -0
- package/lib/services/reclassify-inventory-order.service.js +181 -0
- package/lib/services/sale-order.service.d.ts +7 -0
- package/lib/services/sale-order.service.js +20 -0
- package/lib/services/scrape.service.d.ts +18 -0
- package/lib/services/scrape.service.js +139 -0
- package/lib/services/ship-inventory-order.service.d.ts +60 -0
- package/lib/services/ship-inventory-order.service.js +599 -0
- package/lib/services/split-batch-order.service.d.ts +19 -0
- package/lib/services/split-batch-order.service.js +161 -0
- package/lib/services/stock.service.d.ts +38 -0
- package/lib/services/stock.service.js +477 -0
- package/lib/services/transfer-customer-order.service.d.ts +35 -0
- package/lib/services/transfer-customer-order.service.js +279 -0
- package/lib/services/transfer-order.service.d.ts +44 -0
- package/lib/services/transfer-order.service.js +632 -0
- package/lib/services/transfer-vendor-order.service.d.ts +35 -0
- package/lib/services/transfer-vendor-order.service.js +279 -0
- package/lib/services/unit.service.d.ts +7 -0
- package/lib/services/unit.service.js +67 -0
- package/lib/services/warehouse-map.service.d.ts +10 -0
- package/lib/services/warehouse-map.service.js +38 -0
- package/lib/typings/aggregated-stock.d.ts +22 -0
- package/lib/typings/cancel-ship-inventory-order.input.d.ts +4 -0
- package/lib/typings/cancel-transfer-order.input.d.ts +4 -0
- package/lib/typings/core-module-options.d.ts +8 -0
- package/lib/typings/core-module-options.js +3 -0
- package/lib/typings/create-allocate-inventory-order.input.d.ts +25 -0
- package/lib/typings/create-hold-inventory-order.input.d.ts +21 -0
- package/lib/typings/create-inspection-order.input.d.ts +14 -0
- package/lib/typings/create-loader-type.input.d.ts +9 -0
- package/lib/typings/create-loader.input.d.ts +15 -0
- package/lib/typings/create-location.input.d.ts +12 -0
- package/lib/typings/create-material-unit.input.d.ts +5 -0
- package/lib/typings/create-material.input.d.ts +11 -0
- package/lib/typings/create-receive-inventory-order.input.d.ts +26 -0
- package/lib/typings/create-ship-inventory-order.input.d.ts +46 -0
- package/lib/typings/create-transfer-customer-order.input.d.ts +25 -0
- package/lib/typings/create-transfer-order.input.d.ts +25 -0
- package/lib/typings/create-transfer-vendor-order.input.d.ts +25 -0
- package/lib/typings/deallocate-inventory-order.d.ts +8 -0
- package/lib/typings/find-allocate-inventory-order.input.d.ts +13 -0
- package/lib/typings/find-batch.input.d.ts +8 -0
- package/lib/typings/find-hold-inventory-order.input.d.ts +13 -0
- package/lib/typings/find-loader.input.d.ts +33 -0
- package/lib/typings/find-location.input.d.ts +10 -0
- package/lib/typings/find-material.input.d.ts +10 -0
- package/lib/typings/find-receive-inventory-order.input.d.ts +26 -0
- package/lib/typings/find-receive-inventory-order.input.js +9 -0
- package/lib/typings/find-ship-inventory-order.input.d.ts +26 -0
- package/lib/typings/find-ship-inventory-order.input.js +9 -0
- package/lib/typings/find-stock.input.d.ts +21 -0
- package/lib/typings/find-transfer-order.input.d.ts +21 -0
- package/lib/typings/get-batch.input.d.ts +14 -0
- package/lib/typings/index.d.ts +18 -0
- package/lib/typings/merge-batch.input.d.ts +11 -0
- package/lib/typings/paginations.d.ts +4 -0
- package/lib/typings/receive-inventory-order.input.d.ts +8 -0
- package/lib/typings/reclassify-inventory-order.input.d.ts +11 -0
- package/lib/typings/release-inventory-order.input.d.ts +8 -0
- package/lib/typings/scrape.input.d.ts +8 -0
- package/lib/typings/ship-inventory-order.input.d.ts +4 -0
- package/lib/typings/split-batch.input.d.ts +10 -0
- package/lib/typings/transfer-order.input.d.ts +4 -0
- package/package.json +52 -0
- package/src/lib/models/extensions/README.md +219 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { __decorate, __param, __metadata } from 'tslib';
|
|
2
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
3
|
+
import '../models/metadata/batch.entity.js';
|
|
4
|
+
import '../models/metadata/batch-group.entity.js';
|
|
5
|
+
import { LoadersEntityClass } from '../models/metadata/loader.entity.js';
|
|
6
|
+
import '../models/metadata/loader-type.entity.js';
|
|
7
|
+
import '../models/metadata/loader-unit.entity.js';
|
|
8
|
+
import '../models/metadata/location.entity.js';
|
|
9
|
+
import '../models/metadata/material.entity.js';
|
|
10
|
+
import '../models/metadata/material-unit.entity.js';
|
|
11
|
+
import '../models/metadata/sale-order.entity.js';
|
|
12
|
+
import { StockEntity } from '../models/metadata/stock.entity.js';
|
|
13
|
+
import '../models/metadata/unit.entity.js';
|
|
14
|
+
import '../models/extensions/receive-inventory-order/receive-inventory-order-item.entity.js';
|
|
15
|
+
import '../models/extensions/receive-inventory-order/receive-inventory-order.entity.js';
|
|
16
|
+
import '../models/extensions/receive-inventory-order/receive-inventory-order-change.entity.js';
|
|
17
|
+
import '../models/extensions/ship-inventory-order/ship-inventory-order-item.entity.js';
|
|
18
|
+
import '../models/extensions/ship-inventory-order/ship-inventory-order.entity.js';
|
|
19
|
+
import '../models/extensions/ship-inventory-order/ship-inventory-order-reference.entity.js';
|
|
20
|
+
import '../models/extensions/ship-inventory-order/ship-inventory-order-change.entity.js';
|
|
21
|
+
import { TransferOrderEntity } from '../models/extensions/transfer-order/transfer-order.entity.js';
|
|
22
|
+
import { TransferOrderItemEntity } from '../models/extensions/transfer-order/transfer-order-item.entity.js';
|
|
23
|
+
import { TransferOrderChangeEntityClass } from '../models/extensions/transfer-order/transfer-order-change.entity.js';
|
|
24
|
+
import '../models/extensions/hold-inventory-order/hold-inventory-order.entity.js';
|
|
25
|
+
import '../models/extensions/hold-inventory-order/hold-inventory-order-item.entity.js';
|
|
26
|
+
import '../models/extensions/hold-inventory-order/hold-inventory-order-change.entity.js';
|
|
27
|
+
import '../models/extensions/allocate-inventory-order/allocate-inventory-order.entity.js';
|
|
28
|
+
import '../models/extensions/allocate-inventory-order/allocate-inventory-order-item.entity.js';
|
|
29
|
+
import '../models/extensions/allocate-inventory-order/allocate-inventory-order-change.entity.js';
|
|
30
|
+
import '../models/extensions/split-batch-order/split-batch-order.entity.js';
|
|
31
|
+
import '../models/extensions/split-batch-order/split-batch-order-item.entity.js';
|
|
32
|
+
import '../models/extensions/split-batch-order/split-batch-order-change.entity.js';
|
|
33
|
+
import '../models/extensions/merge-batch-order/merge-batch-order.entity.js';
|
|
34
|
+
import '../models/extensions/merge-batch-order/merge-batch-order-item.entity.js';
|
|
35
|
+
import '../models/extensions/merge-batch-order/merge-batch-order-change.entity.js';
|
|
36
|
+
import '../models/extensions/reclassify-inventory-order/reclassify-inventory-order.entity.js';
|
|
37
|
+
import '../models/extensions/reclassify-inventory-order/reclassify-inventory-order-item.entity.js';
|
|
38
|
+
import '../models/extensions/reclassify-inventory-order/reclassify-inventory-order-change.entity.js';
|
|
39
|
+
import '../models/extensions/transfer-vendor-order/transfer-vendor-order.entity.js';
|
|
40
|
+
import '../models/extensions/transfer-vendor-order/transfer-vendor-order-item.entity.js';
|
|
41
|
+
import '../models/extensions/transfer-vendor-order/transfer-vendor-order-change.entity.js';
|
|
42
|
+
import '../models/extensions/quality-inspection-order/quality-inspection-order.entity.js';
|
|
43
|
+
import '../models/extensions/quality-inspection-order/quality-inspection-order-item.entity.js';
|
|
44
|
+
import '../models/extensions/quality-inspection-order/quality-inspection-order-change.entity.js';
|
|
45
|
+
import '../models/extensions/transfer-customer-order/transfer-customer-order.entity.js';
|
|
46
|
+
import '../models/extensions/transfer-customer-order/transfer-customer-order-item.entity.js';
|
|
47
|
+
import '../models/extensions/transfer-customer-order/transfer-customer-order-change.entity.js';
|
|
48
|
+
import '../models/extensions/scrape-order/scrape-order.entity.js';
|
|
49
|
+
import '../models/extensions/scrape-order/scrape-order-item.entity.js';
|
|
50
|
+
import '../models/extensions/scrape-order/scrape-order-change.entity.js';
|
|
51
|
+
import { DataSource, In } from 'typeorm';
|
|
52
|
+
import { StockStatus } from '../constants/stock-status.js';
|
|
53
|
+
import { OrderEntity } from '@rytass/wms-base-nestjs-module';
|
|
54
|
+
import { HelperService } from './helper.service.js';
|
|
55
|
+
import { MaterialService } from './material.service.js';
|
|
56
|
+
import { BatchService } from './batch.service.js';
|
|
57
|
+
import { StockService } from './stock.service.js';
|
|
58
|
+
|
|
59
|
+
let TransferOrderService = class TransferOrderService {
|
|
60
|
+
dataSource;
|
|
61
|
+
helperService;
|
|
62
|
+
materialService;
|
|
63
|
+
batchService;
|
|
64
|
+
stockService;
|
|
65
|
+
transferOrderEntity;
|
|
66
|
+
transferOrderItemEntity;
|
|
67
|
+
stockEntity;
|
|
68
|
+
seqPrefix = 'TO';
|
|
69
|
+
transferOrderRepository;
|
|
70
|
+
constructor(dataSource, helperService, materialService, batchService, stockService, transferOrderEntity, transferOrderItemEntity, stockEntity) {
|
|
71
|
+
this.dataSource = dataSource;
|
|
72
|
+
this.helperService = helperService;
|
|
73
|
+
this.materialService = materialService;
|
|
74
|
+
this.batchService = batchService;
|
|
75
|
+
this.stockService = stockService;
|
|
76
|
+
this.transferOrderEntity = transferOrderEntity;
|
|
77
|
+
this.transferOrderItemEntity = transferOrderItemEntity;
|
|
78
|
+
this.stockEntity = stockEntity;
|
|
79
|
+
this.transferOrderRepository = this.dataSource.getRepository(this.transferOrderEntity);
|
|
80
|
+
}
|
|
81
|
+
getRepositories(manager) {
|
|
82
|
+
return {
|
|
83
|
+
transferOrder: manager.getRepository(this.transferOrderEntity),
|
|
84
|
+
transferOrderItem: manager.getRepository(this.transferOrderItemEntity),
|
|
85
|
+
transferOrderChange: manager.getRepository(TransferOrderChangeEntityClass),
|
|
86
|
+
order: manager.getRepository(OrderEntity),
|
|
87
|
+
stock: manager.getRepository(this.stockEntity),
|
|
88
|
+
loader: manager.getRepository(LoadersEntityClass),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Process transfer order items (create transfer records and update stock)
|
|
93
|
+
*/
|
|
94
|
+
async processTransferOrderItems(items, repos, manager) {
|
|
95
|
+
await Promise.all(items.map(async (data) => {
|
|
96
|
+
// Ensure required fields are present
|
|
97
|
+
if (!data.batchId || !data.materialId)
|
|
98
|
+
return;
|
|
99
|
+
// Save item with ORIGINAL batch id to maintain source state
|
|
100
|
+
const item = await repos.transferOrderItem.save(repos.transferOrderItem.create(data));
|
|
101
|
+
// Split batch first with inTransfer flag to track transfer state
|
|
102
|
+
const splittedBatch = await this.batchService.splitBatch({
|
|
103
|
+
id: data.batchId,
|
|
104
|
+
transferId: item.orderId,
|
|
105
|
+
manager,
|
|
106
|
+
});
|
|
107
|
+
const base = await repos.order.save({});
|
|
108
|
+
const conversionFactor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
109
|
+
await repos.transferOrderChange.save(repos.transferOrderChange.create({
|
|
110
|
+
itemId: item.id,
|
|
111
|
+
id: base.id,
|
|
112
|
+
quantityChanged: item.targetTransferredQuantity * conversionFactor,
|
|
113
|
+
}));
|
|
114
|
+
// Stock movements track the transfer through splitted batch
|
|
115
|
+
await this.stockService.validateStockAvailability({
|
|
116
|
+
materialId: item.materialId,
|
|
117
|
+
batchId: item.batchId,
|
|
118
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
119
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
120
|
+
quantity: item.targetTransferredQuantity * conversionFactor,
|
|
121
|
+
}, manager);
|
|
122
|
+
await repos.stock.save(repos.stock.create([
|
|
123
|
+
// deduct from original batch source
|
|
124
|
+
{
|
|
125
|
+
orderId: base.id,
|
|
126
|
+
materialId: item.materialId,
|
|
127
|
+
batchId: item.batchId, // original batch
|
|
128
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
129
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
130
|
+
quantity: -1 * item.targetTransferredQuantity * conversionFactor,
|
|
131
|
+
},
|
|
132
|
+
// add to splitted batch in transfer state
|
|
133
|
+
{
|
|
134
|
+
orderId: base.id,
|
|
135
|
+
materialId: item.materialId,
|
|
136
|
+
batchId: splittedBatch.id, // splitted batch marked inTransfer
|
|
137
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
138
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
139
|
+
quantity: item.targetTransferredQuantity * conversionFactor,
|
|
140
|
+
},
|
|
141
|
+
]));
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
async cancelTransfer(options) {
|
|
145
|
+
if (!Array.isArray(options))
|
|
146
|
+
options = [options];
|
|
147
|
+
return this.dataSource.transaction(async (transactionManager) => {
|
|
148
|
+
await this.cancelTransferInternal(options, transactionManager);
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Internal cancel method that accepts a manager for transaction support
|
|
154
|
+
*/
|
|
155
|
+
async cancelTransferInternal(options, manager) {
|
|
156
|
+
const repos = this.getRepositories(manager);
|
|
157
|
+
const items = await repos.transferOrderItem.find({
|
|
158
|
+
where: { id: In(options.map((o) => o.itemId)) },
|
|
159
|
+
relations: ['changes'],
|
|
160
|
+
});
|
|
161
|
+
const itemMap = new Map(items.map((item) => [item.id, item]));
|
|
162
|
+
// Whole-loader transfer cancel validation (Phase 2d-sync).
|
|
163
|
+
// For each whole-loader item in the REQUEST, the cancel qty must equal the
|
|
164
|
+
// item's remaining cancellable (= sumPos - sumNeg). Partial cancel of a
|
|
165
|
+
// whole-loader item is forbidden — they cancel atomically or not at all.
|
|
166
|
+
// Decant items in the same order can be partially cancelled independently.
|
|
167
|
+
for (const option of options) {
|
|
168
|
+
const item = itemMap.get(option.itemId);
|
|
169
|
+
if (!item || !item.loaderId || !item.targetLoaderId)
|
|
170
|
+
continue;
|
|
171
|
+
if (item.loaderId !== item.targetLoaderId)
|
|
172
|
+
continue; // not whole-loader
|
|
173
|
+
const factor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
174
|
+
const totalChanged = item.changes.reduce((sum, c) => sum + Number(c.quantityChanged), 0);
|
|
175
|
+
const remainingBase = item.targetTransferredQuantity * factor - totalChanged;
|
|
176
|
+
const cancelQtyBase = option.quantity != null
|
|
177
|
+
? option.quantity * factor
|
|
178
|
+
: remainingBase;
|
|
179
|
+
if (cancelQtyBase !== remainingBase) {
|
|
180
|
+
throw new Error(`Whole-loader item must be cancelled atomically (item=${item.id}, thisCall=${cancelQtyBase}, remaining=${remainingBase})`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await Promise.all(options.map(async (option) => {
|
|
184
|
+
const item = itemMap.get(option.itemId);
|
|
185
|
+
if (!item)
|
|
186
|
+
return;
|
|
187
|
+
const conversionFactor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
188
|
+
const totalChanged = item.changes.reduce((sum, change) => sum + Number(change.quantityChanged), 0);
|
|
189
|
+
const remainingQuantity = conversionFactor * item.targetTransferredQuantity - totalChanged;
|
|
190
|
+
// If quantity not provided, cancel all remaining cancellable quantity
|
|
191
|
+
const cancelQuantity = option.quantity != null
|
|
192
|
+
? option.quantity * conversionFactor
|
|
193
|
+
: remainingQuantity;
|
|
194
|
+
if (remainingQuantity - cancelQuantity < 0) {
|
|
195
|
+
throw new Error(`Cancel quantity exceeds pending transfer for item ${option.itemId}`);
|
|
196
|
+
}
|
|
197
|
+
if (cancelQuantity <= 0)
|
|
198
|
+
return;
|
|
199
|
+
// Generate new splitted batch for cancellation reversal with inTransfer flag
|
|
200
|
+
const reversalBatch = await this.batchService.splitBatch({
|
|
201
|
+
id: item.batchId,
|
|
202
|
+
transferId: item.orderId,
|
|
203
|
+
manager,
|
|
204
|
+
});
|
|
205
|
+
const reverseOrder = await repos.order.save(repos.order.create());
|
|
206
|
+
await this.stockService.validateStockAvailability({
|
|
207
|
+
materialId: item.materialId,
|
|
208
|
+
batchId: reversalBatch.id,
|
|
209
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
210
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
211
|
+
quantity: cancelQuantity,
|
|
212
|
+
}, manager);
|
|
213
|
+
// Reverse the stock changes via new splitted batch
|
|
214
|
+
await Promise.all([
|
|
215
|
+
repos.transferOrderChange.save(repos.transferOrderChange.create({
|
|
216
|
+
itemId: option.itemId,
|
|
217
|
+
id: reverseOrder.id,
|
|
218
|
+
quantityChanged: -1 * cancelQuantity,
|
|
219
|
+
})),
|
|
220
|
+
// Stock ledger will reconcile +/- quantities by batch metrics grouping
|
|
221
|
+
repos.stock.save(repos.stock.create([
|
|
222
|
+
// re-add to original batch
|
|
223
|
+
{
|
|
224
|
+
orderId: reverseOrder.id,
|
|
225
|
+
materialId: item.materialId,
|
|
226
|
+
batchId: item.batchId, // original batch
|
|
227
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
228
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
229
|
+
quantity: cancelQuantity,
|
|
230
|
+
},
|
|
231
|
+
// remove from new reversal splitted batch (marked inTransfer)
|
|
232
|
+
{
|
|
233
|
+
orderId: reverseOrder.id,
|
|
234
|
+
materialId: item.materialId,
|
|
235
|
+
batchId: reversalBatch.id, // new splitted batch for cancel reversal
|
|
236
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
237
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
238
|
+
quantity: -1 * cancelQuantity,
|
|
239
|
+
},
|
|
240
|
+
])),
|
|
241
|
+
]);
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Remove a transfer order
|
|
246
|
+
* Automatically cancels all remaining pending transfers to release stock
|
|
247
|
+
*/
|
|
248
|
+
async removeTransferOrder(id) {
|
|
249
|
+
return this.dataSource.transaction(async (manager) => {
|
|
250
|
+
const repos = this.getRepositories(manager);
|
|
251
|
+
// Find order
|
|
252
|
+
const order = await repos.transferOrder.findOne({ where: { id } });
|
|
253
|
+
if (!order || order.removedAt)
|
|
254
|
+
return false;
|
|
255
|
+
// Find all items with changes to calculate cancellable quantities
|
|
256
|
+
const items = await repos.transferOrderItem.find({
|
|
257
|
+
where: { orderId: id },
|
|
258
|
+
relations: ['changes'],
|
|
259
|
+
});
|
|
260
|
+
// Build cancel inputs for items with remaining pending quantity
|
|
261
|
+
const cancelInputs = [];
|
|
262
|
+
for (const item of items) {
|
|
263
|
+
const conversionFactor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
264
|
+
const totalChanged = item.changes.reduce((sum, change) => sum + Number(change.quantityChanged), 0);
|
|
265
|
+
const remainingQuantity = conversionFactor * item.targetTransferredQuantity - totalChanged;
|
|
266
|
+
if (remainingQuantity > 0) {
|
|
267
|
+
// Use base quantity (without conversion) since cancelTransferInternal will apply conversion
|
|
268
|
+
cancelInputs.push({
|
|
269
|
+
itemId: item.id,
|
|
270
|
+
quantity: remainingQuantity / conversionFactor,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Cancel all remaining pending transfers
|
|
275
|
+
if (cancelInputs.length > 0) {
|
|
276
|
+
await this.cancelTransferInternal(cancelInputs, manager);
|
|
277
|
+
}
|
|
278
|
+
// Mark order as removed
|
|
279
|
+
await repos.transferOrder.update({ id }, { removedAt: new Date() });
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async createTransferOrder(options, manager = this.dataSource.manager) {
|
|
284
|
+
const repos = this.getRepositories(manager);
|
|
285
|
+
const { items, ...orderOptions } = options;
|
|
286
|
+
// Whole-loader transfer validation (Phase 2d-sync).
|
|
287
|
+
// If any item's source loader matches the header's targetLoaderId, treat the
|
|
288
|
+
// items sharing that source loader as a whole-loader group:
|
|
289
|
+
// Gate 1: the loader's stocks must be 100% FREE (no orphan-prone HELD/etc.)
|
|
290
|
+
// Gate 2: those items collectively must cover the loader's full qty
|
|
291
|
+
if (options.loaderId && items?.length) {
|
|
292
|
+
const wholeLoaderSourceIds = new Set();
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
if (item.loaderId && item.loaderId === options.loaderId) {
|
|
295
|
+
wholeLoaderSourceIds.add(item.loaderId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const srcLoaderId of wholeLoaderSourceIds) {
|
|
299
|
+
const [aggRows] = await this.stockService.findAggregatedStocks({
|
|
300
|
+
loaderId: srcLoaderId,
|
|
301
|
+
aggregated: true,
|
|
302
|
+
});
|
|
303
|
+
// Gate 1
|
|
304
|
+
const nonFree = aggRows.filter((r) => r.status !== StockStatus.FREE);
|
|
305
|
+
if (nonFree.length > 0) {
|
|
306
|
+
const statuses = [...new Set(nonFree.map((r) => r.status))].join(', ');
|
|
307
|
+
throw new Error(`Cannot whole-loader transfer: loader ${srcLoaderId} has non-FREE stocks (statuses: ${statuses})`);
|
|
308
|
+
}
|
|
309
|
+
// Gate 2
|
|
310
|
+
const loaderTotalQty = aggRows.reduce((s, r) => s + Number(r.quantity), 0);
|
|
311
|
+
let subTotalQty = 0;
|
|
312
|
+
for (const item of items) {
|
|
313
|
+
if (item.loaderId === srcLoaderId) {
|
|
314
|
+
// Runtime callers augment items with materialId/batchId/unitId beyond
|
|
315
|
+
// the public CreateTransferOrderItemInput shape (see processTransferOrderItems
|
|
316
|
+
// which expects Partial<TransferOrderItemEntityClass>); access via cast.
|
|
317
|
+
const itemFull = item;
|
|
318
|
+
if (!itemFull.materialId)
|
|
319
|
+
continue;
|
|
320
|
+
const factor = await this.materialService.getConversionFactor(itemFull.materialId, itemFull.unitId || undefined);
|
|
321
|
+
subTotalQty +=
|
|
322
|
+
Number(item.targetTransferredQuantity) * factor;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (subTotalQty !== loaderTotalQty) {
|
|
326
|
+
throw new Error(`Whole-loader transfer must reserve entire loader qty for loader ${srcLoaderId} (requested=${subTotalQty}, loaderTotal=${loaderTotalQty})`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const key = await this.helperService.getSeq(this.seqPrefix, manager);
|
|
331
|
+
const order = await repos.transferOrder.save(repos.transferOrder.create({ key, ...orderOptions }));
|
|
332
|
+
if (items?.length > 0) {
|
|
333
|
+
const processedItems = items.map((item) => ({
|
|
334
|
+
...item,
|
|
335
|
+
orderId: order.id,
|
|
336
|
+
targetLoaderId: options.loaderId,
|
|
337
|
+
targetLocationId: options.locationId,
|
|
338
|
+
}));
|
|
339
|
+
// TODO: check stock availability before creating transfer order
|
|
340
|
+
await this.processTransferOrderItems(processedItems, repos, manager);
|
|
341
|
+
}
|
|
342
|
+
return order;
|
|
343
|
+
}
|
|
344
|
+
async updateTransferOrder({ id, manager = this.dataSource.manager, ...options }) {
|
|
345
|
+
const repos = this.getRepositories(manager);
|
|
346
|
+
const { items, ...orderOptions } = options;
|
|
347
|
+
const order = await repos.transferOrder.findOneByOrFail({ id });
|
|
348
|
+
const updated = await repos.transferOrder.save(repos.transferOrder.create({ ...order, ...orderOptions }));
|
|
349
|
+
if (items?.length) {
|
|
350
|
+
const existingItemIds = new Set((await repos.transferOrderItem.find({ where: { orderId: id } })).map((i) => i.id));
|
|
351
|
+
const newItems = [];
|
|
352
|
+
const existingItems = [];
|
|
353
|
+
items.forEach((item) => {
|
|
354
|
+
if (item.removedAt)
|
|
355
|
+
return;
|
|
356
|
+
if (!item.id) {
|
|
357
|
+
newItems.push({ ...item, orderId: order.id });
|
|
358
|
+
}
|
|
359
|
+
else if (existingItemIds.has(item.id)) {
|
|
360
|
+
existingItems.push({ ...item, orderId: order.id });
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
// Process new items with stock updates
|
|
364
|
+
if (newItems.length) {
|
|
365
|
+
// TODO: check stock availability before creating transfer order
|
|
366
|
+
await this.processTransferOrderItems(newItems, repos, manager);
|
|
367
|
+
}
|
|
368
|
+
// Update existing items
|
|
369
|
+
if (existingItems.length) {
|
|
370
|
+
await repos.transferOrderItem.save(await Promise.all(existingItems.map((item) => repos.transferOrderItem.create(item))));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return updated;
|
|
374
|
+
}
|
|
375
|
+
findMany(options) {
|
|
376
|
+
const queryBuilder = this.dataSource
|
|
377
|
+
.getRepository(this.transferOrderEntity)
|
|
378
|
+
.createQueryBuilder('to');
|
|
379
|
+
if (options.key)
|
|
380
|
+
queryBuilder.andWhere('to.key ILIKE :key', { key: `%${options.key}%` });
|
|
381
|
+
if (options.receiver)
|
|
382
|
+
queryBuilder.andWhere('to.receiver ILIKE :receiver', {
|
|
383
|
+
receiver: `%${options.receiver}%`,
|
|
384
|
+
});
|
|
385
|
+
if (options.description)
|
|
386
|
+
queryBuilder.andWhere('to.description ILIKE :description', {
|
|
387
|
+
description: `%${options.description}%`,
|
|
388
|
+
});
|
|
389
|
+
if (options.dateRange)
|
|
390
|
+
queryBuilder.andWhere('to.createdAt BETWEEN :start AND :end', {
|
|
391
|
+
start: options.dateRange.from,
|
|
392
|
+
end: options.dateRange.to,
|
|
393
|
+
});
|
|
394
|
+
if (options.materialId)
|
|
395
|
+
queryBuilder.innerJoin('to.items', 'm_items', 'm_items.materialId = :materialId', {
|
|
396
|
+
materialId: options.materialId,
|
|
397
|
+
});
|
|
398
|
+
if (options.batchId)
|
|
399
|
+
queryBuilder.innerJoin('to.items', 'b_items', 'b_items.batchId = :batchId', {
|
|
400
|
+
batchId: options.batchId,
|
|
401
|
+
});
|
|
402
|
+
if (options.locationId)
|
|
403
|
+
queryBuilder.innerJoin('to.items', 'l_items', 'l_items.locationId = :locationId', {
|
|
404
|
+
locationId: options.locationId,
|
|
405
|
+
});
|
|
406
|
+
if (options.loaderId)
|
|
407
|
+
queryBuilder.innerJoin('to.items', 'ld_items', 'ld_items.loaderId = :loaderId', {
|
|
408
|
+
loaderId: options.loaderId,
|
|
409
|
+
});
|
|
410
|
+
if (options.materialKey)
|
|
411
|
+
queryBuilder.andWhere(`EXISTS (
|
|
412
|
+
SELECT 1 FROM transfer_order_items to_mat_items
|
|
413
|
+
INNER JOIN materials to_mat ON to_mat.id = to_mat_items."materialId"
|
|
414
|
+
WHERE to_mat_items."orderId" = to.id
|
|
415
|
+
AND to_mat.key ILIKE :materialKey
|
|
416
|
+
)`, { materialKey: `%${options.materialKey}%` });
|
|
417
|
+
if (options.batchKey)
|
|
418
|
+
queryBuilder.andWhere(`EXISTS (
|
|
419
|
+
SELECT 1 FROM transfer_order_items to_batch_items
|
|
420
|
+
INNER JOIN batches to_batch ON to_batch.id = to_batch_items."batchId"
|
|
421
|
+
WHERE to_batch_items."orderId" = to.id
|
|
422
|
+
AND to_batch.key ILIKE :batchKey
|
|
423
|
+
)`, { batchKey: `%${options.batchKey}%` });
|
|
424
|
+
if (options.locationKey)
|
|
425
|
+
queryBuilder.andWhere(`EXISTS (
|
|
426
|
+
SELECT 1 FROM transfer_order_items to_loc_items
|
|
427
|
+
INNER JOIN locations to_loc ON to_loc.id = to_loc_items."locationId"
|
|
428
|
+
WHERE to_loc_items."orderId" = to.id
|
|
429
|
+
AND to_loc.key ILIKE :locationKey
|
|
430
|
+
)`, { locationKey: `%${options.locationKey}%` });
|
|
431
|
+
if (options.loaderKey)
|
|
432
|
+
queryBuilder.andWhere(`EXISTS (
|
|
433
|
+
SELECT 1 FROM transfer_order_items to_ldr_items
|
|
434
|
+
INNER JOIN loaders to_ldr ON to_ldr.id = to_ldr_items."loaderId"
|
|
435
|
+
WHERE to_ldr_items."orderId" = to.id
|
|
436
|
+
AND to_ldr."serialId" ILIKE :loaderKey
|
|
437
|
+
)`, { loaderKey: `%${options.loaderKey}%` });
|
|
438
|
+
if (options.targetLocationKey)
|
|
439
|
+
queryBuilder.andWhere(`EXISTS (
|
|
440
|
+
SELECT 1 FROM transfer_order_items to_tgt_loc_items
|
|
441
|
+
INNER JOIN locations to_tgt_loc ON to_tgt_loc.id = to_tgt_loc_items."targetLocationId"
|
|
442
|
+
WHERE to_tgt_loc_items."orderId" = to.id
|
|
443
|
+
AND to_tgt_loc.key ILIKE :targetLocationKey
|
|
444
|
+
)`, { targetLocationKey: `%${options.targetLocationKey}%` });
|
|
445
|
+
if (options.targetLoaderKey)
|
|
446
|
+
queryBuilder.andWhere(`EXISTS (
|
|
447
|
+
SELECT 1 FROM transfer_order_items to_tgt_ldr_items
|
|
448
|
+
INNER JOIN loaders to_tgt_ldr ON to_tgt_ldr.id = to_tgt_ldr_items."targetLoaderId"
|
|
449
|
+
WHERE to_tgt_ldr_items."orderId" = to.id
|
|
450
|
+
AND to_tgt_ldr."serialId" ILIKE :targetLoaderKey
|
|
451
|
+
)`, { targetLoaderKey: `%${options.targetLoaderKey}%` });
|
|
452
|
+
if (options.offset !== undefined && options.offset >= 0)
|
|
453
|
+
queryBuilder.skip(options.offset);
|
|
454
|
+
if (options.limit !== undefined && options.limit > 0)
|
|
455
|
+
queryBuilder.take(options.limit);
|
|
456
|
+
return queryBuilder.getManyAndCount();
|
|
457
|
+
}
|
|
458
|
+
transfer(options) {
|
|
459
|
+
if (!Array.isArray(options))
|
|
460
|
+
options = [options];
|
|
461
|
+
return this.dataSource.transaction(async (manager) => {
|
|
462
|
+
const changeRepository = manager.getRepository(TransferOrderChangeEntityClass);
|
|
463
|
+
const itemRepository = manager.getRepository(this.transferOrderItemEntity);
|
|
464
|
+
const stockRepository = manager.getRepository(this.stockEntity);
|
|
465
|
+
const items = await itemRepository.find({
|
|
466
|
+
where: { id: In(options.map((o) => o.itemId)) },
|
|
467
|
+
relations: ['changes'],
|
|
468
|
+
});
|
|
469
|
+
const itemMap = new Map(items.map((item) => [item.id, item]));
|
|
470
|
+
const inputMap = new Map(options.map((o) => [o.itemId, o]));
|
|
471
|
+
// Whole-loader transfer commit validation (Phase 2d-sync).
|
|
472
|
+
//
|
|
473
|
+
// Priority rule: for each whole-loader item in the ORDER, it must be either
|
|
474
|
+
// (a) already fully transferred (alreadyTransferred >= target), or
|
|
475
|
+
// (b) being fully transferred in this request (alreadyTransferred + input.qty == target)
|
|
476
|
+
// Otherwise reject — whole-loader items must be 100% committed before any other
|
|
477
|
+
// items in the same order can commit. Prevents the unconditional loader UPDATE
|
|
478
|
+
// from moving the target loader prematurely (which would orphan stocks).
|
|
479
|
+
//
|
|
480
|
+
// Plus the per-loader sum check: for whole-loader-mode inputs in this request,
|
|
481
|
+
// sum.input.quantity * factor must equal loaderTotalQty (catches the
|
|
482
|
+
// receive-onto-moving-loader scenario where loaderTotalQty drifts > sum).
|
|
483
|
+
const orderIds = new Set();
|
|
484
|
+
for (const item of items)
|
|
485
|
+
orderIds.add(item.orderId);
|
|
486
|
+
if (orderIds.size > 1) {
|
|
487
|
+
throw new Error('Transfer request spans multiple orders, which is not supported');
|
|
488
|
+
}
|
|
489
|
+
const orderId = orderIds.size === 1 ? [...orderIds][0] : null;
|
|
490
|
+
if (orderId) {
|
|
491
|
+
const allOrderItems = await itemRepository.find({
|
|
492
|
+
where: { orderId },
|
|
493
|
+
relations: ['changes'],
|
|
494
|
+
});
|
|
495
|
+
for (const orderItem of allOrderItems) {
|
|
496
|
+
if (orderItem.removedAt)
|
|
497
|
+
continue;
|
|
498
|
+
if (!orderItem.loaderId || !orderItem.targetLoaderId)
|
|
499
|
+
continue;
|
|
500
|
+
if (orderItem.loaderId !== orderItem.targetLoaderId)
|
|
501
|
+
continue;
|
|
502
|
+
// Whole-loader item — apply priority rule.
|
|
503
|
+
const factor = await this.materialService.getConversionFactor(orderItem.materialId, orderItem.unitId || undefined);
|
|
504
|
+
const targetBase = orderItem.targetTransferredQuantity * factor;
|
|
505
|
+
const alreadyTransferred = orderItem.changes.reduce((sum, c) => c.quantityChanged < 0 ? sum + Math.abs(Number(c.quantityChanged)) : sum, 0);
|
|
506
|
+
if (alreadyTransferred >= targetBase)
|
|
507
|
+
continue; // already fully transferred (committed or cancelled)
|
|
508
|
+
const inputOpt = inputMap.get(orderItem.id);
|
|
509
|
+
if (inputOpt) {
|
|
510
|
+
const inputBase = inputOpt.quantity * factor;
|
|
511
|
+
if (alreadyTransferred + inputBase === targetBase)
|
|
512
|
+
continue; // this request completes it
|
|
513
|
+
}
|
|
514
|
+
throw new Error(`Whole-loader item must be fully transferred first (item=${orderItem.id}, target=${targetBase}, alreadyTransferred=${alreadyTransferred})`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Per-loader sum check (catches receive-onto-moving-loader orphan W08).
|
|
518
|
+
const wholeLoaderGroupSums = new Map();
|
|
519
|
+
for (const option of options) {
|
|
520
|
+
const item = itemMap.get(option.itemId);
|
|
521
|
+
if (!item || !item.loaderId)
|
|
522
|
+
continue;
|
|
523
|
+
if (item.loaderId === item.targetLoaderId) {
|
|
524
|
+
const factor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
525
|
+
wholeLoaderGroupSums.set(item.loaderId, (wholeLoaderGroupSums.get(item.loaderId) ?? 0) +
|
|
526
|
+
option.quantity * factor);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
for (const [srcLoaderId, subTotalQty] of wholeLoaderGroupSums) {
|
|
530
|
+
const [aggRows] = await this.stockService.findAggregatedStocks({
|
|
531
|
+
loaderId: srcLoaderId,
|
|
532
|
+
aggregated: true,
|
|
533
|
+
});
|
|
534
|
+
const loaderTotalQty = aggRows.reduce((s, r) => s + Number(r.quantity), 0);
|
|
535
|
+
if (subTotalQty !== loaderTotalQty) {
|
|
536
|
+
throw new Error(`Whole-loader transfer must execute 100% qty (loader=${srcLoaderId}, thisRequest=${subTotalQty}, loaderTotal=${loaderTotalQty})`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
await Promise.all(options.map(async (option) => {
|
|
540
|
+
const item = itemMap.get(option.itemId);
|
|
541
|
+
if (!item)
|
|
542
|
+
return;
|
|
543
|
+
const conversionFactor = await this.materialService.getConversionFactor(item.materialId, item.unitId || undefined);
|
|
544
|
+
const totalTransferred = item.changes.reduce((sum, change) => {
|
|
545
|
+
if (change.quantityChanged < 0)
|
|
546
|
+
sum += Number(change.quantityChanged);
|
|
547
|
+
return sum;
|
|
548
|
+
}, 0);
|
|
549
|
+
const remainingQuantity = item.targetTransferredQuantity * conversionFactor +
|
|
550
|
+
totalTransferred;
|
|
551
|
+
if (remainingQuantity - option.quantity * conversionFactor < 0)
|
|
552
|
+
throw new Error(`Transfer quantity exceeds remaining quantity for item ${item.id}`);
|
|
553
|
+
// Locate the existing IN_TRANSFER split (created at reservation time) via
|
|
554
|
+
// idempotent forward sibling-match: same call shape processTransferOrderItems
|
|
555
|
+
// used to create it. No new batch row is written — sibling-match returns the
|
|
556
|
+
// existing IN_TRANSFER batch B for this order. The original batch (item.batchId,
|
|
557
|
+
// transferId=null) is the anchor for the FREE-restore at target loc/loader.
|
|
558
|
+
const inTransferBatch = await this.batchService.splitBatch({
|
|
559
|
+
id: item.batchId,
|
|
560
|
+
transferId: item.orderId,
|
|
561
|
+
manager,
|
|
562
|
+
});
|
|
563
|
+
// Record the transfer change
|
|
564
|
+
const base = await manager.getRepository(OrderEntity).save({});
|
|
565
|
+
// Availability check at the IN_TRANSFER coord — must have ≥ qty to drain
|
|
566
|
+
await this.stockService.validateStockAvailability({
|
|
567
|
+
materialId: item.materialId,
|
|
568
|
+
batchId: inTransferBatch.id,
|
|
569
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
570
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
571
|
+
quantity: option.quantity * conversionFactor,
|
|
572
|
+
}, manager);
|
|
573
|
+
await Promise.all([
|
|
574
|
+
changeRepository.save(changeRepository.create({
|
|
575
|
+
itemId: item.id,
|
|
576
|
+
id: base.id,
|
|
577
|
+
quantityChanged: -1 * option.quantity * conversionFactor,
|
|
578
|
+
})),
|
|
579
|
+
// Stock pair: +qty FREE @ original batch at TARGET loc/loader
|
|
580
|
+
// -qty IN_TRANSFER @ split batch at SOURCE loc/loader (drains pool)
|
|
581
|
+
// The original-batch FREE at SOURCE loc/loader is left untouched (its
|
|
582
|
+
// reservation-time deduction stays as the durable "moved out of source" record).
|
|
583
|
+
stockRepository.save(stockRepository.create([
|
|
584
|
+
// add to destination using original batch (transferId=null → derived FREE)
|
|
585
|
+
{
|
|
586
|
+
orderId: base.id,
|
|
587
|
+
materialId: item.materialId,
|
|
588
|
+
batchId: item.batchId,
|
|
589
|
+
locationId: item.targetLocationId || undefined,
|
|
590
|
+
loaderId: this.stockService.resolveLoaderId(item.targetLoaderId),
|
|
591
|
+
quantity: option.quantity * conversionFactor,
|
|
592
|
+
},
|
|
593
|
+
// drain from the IN_TRANSFER split batch
|
|
594
|
+
{
|
|
595
|
+
orderId: base.id,
|
|
596
|
+
materialId: item.materialId,
|
|
597
|
+
batchId: inTransferBatch.id,
|
|
598
|
+
locationId: this.stockService.resolveLocationId(item.locationId),
|
|
599
|
+
loaderId: this.stockService.resolveLoaderId(item.loaderId),
|
|
600
|
+
quantity: -1 * option.quantity * conversionFactor,
|
|
601
|
+
},
|
|
602
|
+
])),
|
|
603
|
+
]);
|
|
604
|
+
// Whole-loader transfer (Phase 2d-sync): unconditionally pin the target
|
|
605
|
+
// loader to the target location on commit. Idempotent in regular-decant
|
|
606
|
+
// case (loader already at target location); performs the actual
|
|
607
|
+
// relocation in whole-loader-mode case. No-op on cancel-transfer.
|
|
608
|
+
if (item.targetLoaderId && item.targetLocationId) {
|
|
609
|
+
await manager
|
|
610
|
+
.getRepository(LoadersEntityClass)
|
|
611
|
+
.update({ id: item.targetLoaderId }, {
|
|
612
|
+
locationId: item.targetLocationId,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}));
|
|
616
|
+
return true;
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
TransferOrderService = __decorate([
|
|
621
|
+
Injectable(),
|
|
622
|
+
__param(5, Inject(TransferOrderEntity)),
|
|
623
|
+
__param(6, Inject(TransferOrderItemEntity)),
|
|
624
|
+
__param(7, Inject(StockEntity)),
|
|
625
|
+
__metadata("design:paramtypes", [DataSource,
|
|
626
|
+
HelperService,
|
|
627
|
+
MaterialService,
|
|
628
|
+
BatchService,
|
|
629
|
+
StockService, Object, Object, Object])
|
|
630
|
+
], TransferOrderService);
|
|
631
|
+
|
|
632
|
+
export { TransferOrderService };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TransferVendorOrderEntityClass, TransferVendorOrderItemEntityClass, StockEntityClass } from '../models';
|
|
2
|
+
import { DataSource, EntityManager, EntityTarget } from 'typeorm';
|
|
3
|
+
import { CreateTransferVendorOrderInput, UpdateTransferVendorOrderInput } from '../typings/create-transfer-vendor-order.input';
|
|
4
|
+
import { HelperService } from './helper.service';
|
|
5
|
+
import { MaterialService } from './material.service';
|
|
6
|
+
import { BatchService } from './batch.service';
|
|
7
|
+
import { StockService } from './stock.service';
|
|
8
|
+
export type TransferVendorOrderItemInput = {
|
|
9
|
+
materialId: string;
|
|
10
|
+
batchId: string;
|
|
11
|
+
locationId?: string;
|
|
12
|
+
loaderId?: string;
|
|
13
|
+
quantity: number;
|
|
14
|
+
locationKey: string;
|
|
15
|
+
loaderKey?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare class TransferVendorOrderService<T extends TransferVendorOrderEntityClass = TransferVendorOrderEntityClass, K extends TransferVendorOrderItemEntityClass = TransferVendorOrderItemEntityClass> {
|
|
18
|
+
private readonly dataSource;
|
|
19
|
+
private readonly helperService;
|
|
20
|
+
private readonly materialService;
|
|
21
|
+
private readonly batchService;
|
|
22
|
+
private readonly stockService;
|
|
23
|
+
readonly transferVendorOrderEntity: EntityTarget<T>;
|
|
24
|
+
readonly transferVendorOrderItemEntity: EntityTarget<K>;
|
|
25
|
+
readonly stockEntity: EntityTarget<StockEntityClass>;
|
|
26
|
+
private readonly seqPrefix;
|
|
27
|
+
constructor(dataSource: DataSource, helperService: HelperService, materialService: MaterialService, batchService: BatchService, stockService: StockService, transferVendorOrderEntity: EntityTarget<T>, transferVendorOrderItemEntity: EntityTarget<K>, stockEntity: EntityTarget<StockEntityClass>);
|
|
28
|
+
private getRepositories;
|
|
29
|
+
private processTransferVendorOrderItems;
|
|
30
|
+
createTransferVendorOrder(options: CreateTransferVendorOrderInput, manager?: EntityManager): Promise<T>;
|
|
31
|
+
updateTransferVendorOrder({ id, manager, ...options }: UpdateTransferVendorOrderInput & {
|
|
32
|
+
manager?: EntityManager;
|
|
33
|
+
}): Promise<T>;
|
|
34
|
+
returnTransferVendorOrder(options: TransferVendorOrderItemInput | TransferVendorOrderItemInput[], manager?: EntityManager): Promise<T>;
|
|
35
|
+
}
|