u
This commit is contained in:
162
src/utils/deliveryLedger.js
Normal file
162
src/utils/deliveryLedger.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import { delivery as defaultDeliveryApi, ledger as defaultLedgerApi } from '../api';
|
||||
import artikelAutomat from '../automats/artikelAutomat';
|
||||
|
||||
/**
|
||||
* @typedef {Object} DeliveryLineLedgerStatus
|
||||
* @property {number} id - Manifestarticle ID
|
||||
* @property {string} sku - Article SKU
|
||||
* @property {string} name - Article Name
|
||||
* @property {number} targetAmount - Expected delivery quantity
|
||||
* @property {number} bookedAmount - Quantity booked in ledger
|
||||
* @property {number} remainingAmount - Quantity remaining to be booked
|
||||
* @property {boolean} isBooked - Whether this position is fully booked
|
||||
* @property {Array<Object>} ledgerEntries - Ledger entries for this line
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DeliveryLedgerSummary
|
||||
* @property {number} deliveryId
|
||||
* @property {Object} delivery - Full delivery object with lines
|
||||
* @property {'empty' | 'booked' | 'partial' | 'unbooked'} status
|
||||
* @property {number} totalPositions
|
||||
* @property {number} bookedPositions
|
||||
* @property {number} totalQuantity
|
||||
* @property {number} bookedQuantity
|
||||
* @property {Array<DeliveryLineLedgerStatus>} lines
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches delivery lines and checks ledger entries for each line to compute the booking status.
|
||||
*
|
||||
* @param {number|string} deliveryId
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.deliveryApi]
|
||||
* @param {Object} [options.ledgerApi]
|
||||
* @returns {Promise<DeliveryLedgerSummary>}
|
||||
*/
|
||||
export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = defaultDeliveryApi, ledgerApi = defaultLedgerApi } = {}) {
|
||||
const res = await deliveryApi.get(deliveryId);
|
||||
const delivery = res?.delivery ?? res;
|
||||
const rawLines = Array.isArray(delivery?.lines) ? delivery.lines : [];
|
||||
|
||||
if (rawLines.length === 0) {
|
||||
return {
|
||||
deliveryId,
|
||||
delivery,
|
||||
status: 'empty',
|
||||
totalPositions: 0,
|
||||
bookedPositions: 0,
|
||||
totalQuantity: 0,
|
||||
bookedQuantity: 0,
|
||||
lines: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch ledger entries for each line in parallel
|
||||
const linesWithLedger = await Promise.all(
|
||||
rawLines.map(async (line) => {
|
||||
let ledgerEntries = [];
|
||||
try {
|
||||
const ledgerRes = await ledgerApi.listByManifestarticleId(line.id, 'DELIVERY');
|
||||
ledgerEntries = Array.isArray(ledgerRes?.ledger) ? ledgerRes.ledger : [];
|
||||
} catch (err) {
|
||||
console.error(`[deliveryLedger] Failed to fetch ledger for line ${line.id}:`, err);
|
||||
}
|
||||
|
||||
const deliveryEntries = ledgerEntries.filter(
|
||||
(e) => String(e.transaction_type).toUpperCase() === 'DELIVERY'
|
||||
);
|
||||
const bookedAmount = deliveryEntries.reduce((sum, e) => sum + Number(e.amount || 0), 0);
|
||||
const targetAmount = Number(line.amount || 0);
|
||||
const remainingAmount = Math.max(0, targetAmount - bookedAmount);
|
||||
const isBooked = targetAmount > 0 ? bookedAmount >= targetAmount : bookedAmount > 0;
|
||||
|
||||
return {
|
||||
...line,
|
||||
targetAmount,
|
||||
bookedAmount,
|
||||
remainingAmount,
|
||||
isBooked,
|
||||
ledgerEntries: deliveryEntries,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const totalPositions = linesWithLedger.length;
|
||||
const bookedPositions = linesWithLedger.filter((l) => l.isBooked).length;
|
||||
const totalQuantity = linesWithLedger.reduce((sum, l) => sum + l.targetAmount, 0);
|
||||
const bookedQuantity = linesWithLedger.reduce((sum, l) => sum + l.bookedAmount, 0);
|
||||
|
||||
let status = 'unbooked';
|
||||
if (totalPositions === 0) {
|
||||
status = 'empty';
|
||||
} else if (bookedPositions === totalPositions && bookedQuantity >= totalQuantity) {
|
||||
status = 'booked';
|
||||
} else if (bookedPositions > 0 || bookedQuantity > 0) {
|
||||
status = 'partial';
|
||||
} else {
|
||||
status = 'unbooked';
|
||||
}
|
||||
|
||||
return {
|
||||
deliveryId,
|
||||
delivery,
|
||||
status,
|
||||
totalPositions,
|
||||
bookedPositions,
|
||||
totalQuantity,
|
||||
bookedQuantity,
|
||||
lines: linesWithLedger,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Books specified delivery lines to the ledger as goods receipts (DELIVERY).
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {Object} params.delivery - The delivery object (used for transaction memo and fallback)
|
||||
* @param {Array<{ manifestarticleId?: number, id?: number, amount: number, sku?: string }>} params.linesToBook
|
||||
* @param {Object} [params.ledgerApi]
|
||||
* @param {string} [params.transactionText]
|
||||
* @returns {Promise<Array<Object>>}
|
||||
*/
|
||||
export async function bookDeliveryLines({
|
||||
delivery,
|
||||
linesToBook,
|
||||
ledgerApi = defaultLedgerApi,
|
||||
transactionText,
|
||||
}) {
|
||||
if (!Array.isArray(linesToBook) || linesToBook.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const memo = String(
|
||||
transactionText != null && transactionText !== ''
|
||||
? transactionText
|
||||
: `Lieferung ${delivery?.number || delivery?.id || ''}`
|
||||
).trim();
|
||||
|
||||
const results = [];
|
||||
for (const item of linesToBook) {
|
||||
const manifestarticleId = Number(item.manifestarticleId ?? item.id);
|
||||
const amount = Number(item.amount);
|
||||
|
||||
if (!manifestarticleId || !Number.isFinite(amount) || amount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const res = await ledgerApi.add({
|
||||
manifestarticle_id: manifestarticleId,
|
||||
transaction_type: 'DELIVERY',
|
||||
amount,
|
||||
transaction_text: memo,
|
||||
});
|
||||
results.push(res?.ledger ?? res);
|
||||
|
||||
if (item.sku) {
|
||||
void artikelAutomat.actions.reloadArticle(item.sku).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
Reference in New Issue
Block a user