u
This commit is contained in:
48
src/store/delivery/book.js
Normal file
48
src/store/delivery/book.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import stockAccept from '../stock/accept.js';
|
||||
import { loadDelivery } from './helpers.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
/**
|
||||
* Books all positions of an incoming delivery manifest to the ledger
|
||||
* by delegating to store.stock.accept.
|
||||
*
|
||||
* @param {object} provider - Storage provider instance.
|
||||
* @returns {Function} `(googleAuthId, input, tx) => Promise<object>`
|
||||
*/
|
||||
export default function book(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const accept = stockAccept(provider);
|
||||
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const rawId = typeof input === 'number' ? input : (input?.id ?? input?.deliveryId);
|
||||
const id = Number(rawId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
|
||||
const warehouseId = input?.warehouseId ?? input?.warehouse_id ?? null;
|
||||
|
||||
const doBook = async (runner, activeTx) => {
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
const delivery = await loadDelivery(provider, activeTx, companyId, id);
|
||||
if (!delivery) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
if (!delivery.lines || delivery.lines.length === 0) {
|
||||
throw new Error('Delivery has no lines');
|
||||
}
|
||||
|
||||
await accept(googleAuthId, { deliveryId: id, warehouseId }, activeTx);
|
||||
return loadDelivery(provider, activeTx, companyId, id);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return doBook(makeRunner(provider, tx), tx);
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(doBook);
|
||||
}
|
||||
return doBook(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { syncDeliverySummary } from './syncSummary.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
@@ -73,6 +74,7 @@ export default function create(provider) {
|
||||
body.files,
|
||||
);
|
||||
|
||||
await syncDeliverySummary(runner, { companyId, manifestId });
|
||||
return loadDelivery(provider, tx, companyId, manifestId);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function del(provider) {
|
||||
const runner = makeRunner(provider);
|
||||
|
||||
try {
|
||||
await runner.query('DELETE FROM deliverysummary WHERE manifest_id = @id AND company_id = @companyId', { id, companyId });
|
||||
const result = await runner(queries.DELETE_MANIFEST, {
|
||||
id,
|
||||
companyId,
|
||||
|
||||
@@ -17,12 +17,20 @@ export function shapeSummary(row) {
|
||||
createdAt: row.createdAt ?? row.created_at ?? null,
|
||||
};
|
||||
|
||||
if (row.totalPositions !== undefined || row.completelyOnLedgerPositions !== undefined) {
|
||||
summary.ledger = {
|
||||
totalPositions: Number(row.totalPositions ?? 0),
|
||||
completelyOnLedgerPositions: Number(row.completelyOnLedgerPositions ?? 0),
|
||||
};
|
||||
}
|
||||
const totalPositions = Number(row.stockPositions ?? row.totalPositions ?? row.positions ?? 0);
|
||||
const bookedPositions = Number(row.completelyOnLedgerPositions ?? row.bookedPositions ?? 0);
|
||||
const unbookedPositions = Number(row.unbookedPositions ?? Math.max(0, totalPositions - bookedPositions));
|
||||
|
||||
summary.ledger = {
|
||||
totalPositions,
|
||||
completelyOnLedgerPositions: bookedPositions,
|
||||
bookedPositions,
|
||||
unbookedPositions,
|
||||
totalQuantity: Number(row.stockQuantity ?? row.totalQuantity ?? 0),
|
||||
bookedQuantity: Number(row.bookedQuantity ?? 0),
|
||||
unbookedQuantity: Number(row.unbookedQuantity ?? 0),
|
||||
isBooked: row.isBooked !== undefined ? Boolean(row.isBooked) : (totalPositions > 0 && bookedPositions >= totalPositions),
|
||||
};
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
@@ -10,30 +10,317 @@ export default function list(provider) {
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
return async (googleAuthId) => {
|
||||
const { companyId } = await access(googleAuthId);
|
||||
const runner = makeRunner(provider);
|
||||
const sqlCountManifests = isSqlite
|
||||
? 'SELECT COUNT(*) AS total FROM manifest WHERE company_id = @companyId AND "type" = @type'
|
||||
: 'SELECT COUNT(*) AS total FROM manifest WHERE company_id = @companyId AND [type] = @type';
|
||||
|
||||
const result = await runner(queries.LIST_DELIVERIES, {
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
/**
|
||||
* Lists the deliveries of the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [opts] - Optional pagination and filter options.
|
||||
* @param {number} [opts.offset=0] - Number of deliveries to skip.
|
||||
* @param {number} [opts.limit=50] - Number of deliveries to return.
|
||||
* @param {string} [opts.search] - Free-text search matching delivery number, external number, or supplier name.
|
||||
* @param {boolean} [opts.onlyUnbooked=false] - When true, returns only deliveries with unbooked stock items.
|
||||
* @param {number} [opts.supplierId] - Filter by supplier entity id.
|
||||
* @param {number} [opts.seekMonths] - Negative integer for cutoff months ago (e.g. -1).
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{deliveries: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>}
|
||||
*/
|
||||
return async (googleAuthId, opts, tx) => {
|
||||
let offset = null;
|
||||
let limit = null;
|
||||
let isPaged = false;
|
||||
let resolvedTx = tx;
|
||||
|
||||
const filesResult = await runner(queries.LIST_DELIVERY_FILES, {
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
const hasFilter = opts != null && typeof opts === 'object' && !opts._isTx && (
|
||||
opts.offset !== undefined ||
|
||||
opts.limit !== undefined ||
|
||||
(typeof opts.search === 'string' && opts.search.trim() !== '') ||
|
||||
opts.onlyUnbooked === true ||
|
||||
opts.supplierId !== undefined ||
|
||||
opts.seekMonths !== undefined
|
||||
);
|
||||
|
||||
const filesByManifest = new Map();
|
||||
for (const row of filesResult.rows) {
|
||||
const list = filesByManifest.get(row.manifestId) ?? [];
|
||||
list.push(shapeAttachmentMeta(row));
|
||||
filesByManifest.set(row.manifestId, list);
|
||||
if (opts != null && typeof opts === 'object' && !opts._isTx) {
|
||||
if (hasFilter || opts.offset !== undefined || opts.limit !== undefined) {
|
||||
isPaged = true;
|
||||
const parsedOffset = Number(opts.offset ?? 0);
|
||||
const parsedLimit = Number(opts.limit ?? 50);
|
||||
if (!Number.isInteger(parsedOffset) || parsedOffset < 0) {
|
||||
throw new Error('Invalid offset');
|
||||
}
|
||||
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
||||
throw new Error('Invalid limit');
|
||||
}
|
||||
offset = parsedOffset;
|
||||
limit = parsedLimit;
|
||||
}
|
||||
} else if (opts != null) {
|
||||
resolvedTx = opts;
|
||||
}
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
const runner = makeRunner(provider, resolvedTx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const conditions = ['m.company_id = @companyId', `m.${isSqlite ? '"type"' : '[type]'} = @type`];
|
||||
const params = {
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
};
|
||||
|
||||
// Date cutoff via seekMonths (strictly integer)
|
||||
if (opts?.seekMonths !== undefined) {
|
||||
if (typeof opts.seekMonths !== 'number' || !Number.isInteger(opts.seekMonths)) {
|
||||
throw new Error('Invalid seekMonths');
|
||||
}
|
||||
const d = new Date();
|
||||
d.setMonth(d.getMonth() - Math.abs(opts.seekMonths));
|
||||
params.dateTo = d.toISOString();
|
||||
conditions.push(`m.${isSqlite ? '"date"' : '[date]'} <= @dateTo`);
|
||||
}
|
||||
|
||||
// Filter by supplierId
|
||||
if (opts?.supplierId !== undefined && opts.supplierId !== null) {
|
||||
const supplierId = Number(opts.supplierId);
|
||||
if (!Number.isInteger(supplierId) || supplierId <= 0) {
|
||||
throw new Error('Invalid supplierId');
|
||||
}
|
||||
params.supplierId = supplierId;
|
||||
conditions.push('m.entity_id = @supplierId');
|
||||
}
|
||||
|
||||
// Free-text search
|
||||
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
|
||||
params.searchPattern = `%${opts.search.trim()}%`;
|
||||
if (isSqlite) {
|
||||
conditions.push(`(
|
||||
m."number" LIKE @searchPattern
|
||||
OR m.external_number LIKE @searchPattern
|
||||
OR e.name LIKE @searchPattern
|
||||
OR e.first_name LIKE @searchPattern
|
||||
OR (IFNULL(e.first_name, '') || ' ' || IFNULL(e.name, '')) LIKE @searchPattern
|
||||
OR (IFNULL(e.name, '') || ' ' || IFNULL(e.first_name, '')) LIKE @searchPattern
|
||||
OR ds.delivery_number LIKE @searchPattern
|
||||
OR ds.supplier_name LIKE @searchPattern
|
||||
)`);
|
||||
} else {
|
||||
conditions.push(`(
|
||||
m.[number] LIKE @searchPattern
|
||||
OR m.external_number LIKE @searchPattern
|
||||
OR e.name LIKE @searchPattern
|
||||
OR e.first_name LIKE @searchPattern
|
||||
OR (ISNULL(e.first_name, '') + ' ' + ISNULL(e.name, '')) LIKE @searchPattern
|
||||
OR (ISNULL(e.name, '') + ' ' + ISNULL(e.first_name, '')) LIKE @searchPattern
|
||||
OR ds.delivery_number LIKE @searchPattern
|
||||
OR ds.supplier_name LIKE @searchPattern
|
||||
)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Only unbooked deliveries (has stock positions that are not yet booked)
|
||||
if (opts?.onlyUnbooked === true) {
|
||||
conditions.push('(COALESCE(ds.unbooked_positions, 0) > 0 OR (COALESCE(ds.is_booked, 0) = 0 AND COALESCE(ds.stock_positions, 0) > 0))');
|
||||
}
|
||||
|
||||
const whereClause = conditions.join('\n AND ');
|
||||
const sqlCount = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM manifest m
|
||||
LEFT JOIN deliverysummary ds ON ds.manifest_id = m.id
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
WHERE ${whereClause}
|
||||
`;
|
||||
|
||||
let sqlList;
|
||||
if (isSqlite) {
|
||||
sqlList = `
|
||||
SELECT
|
||||
m.id,
|
||||
m."number",
|
||||
m."type",
|
||||
m.entity_id AS supplierId,
|
||||
m.external_number AS externalNumber,
|
||||
m."date",
|
||||
m.created_at AS createdAt,
|
||||
ds.positions,
|
||||
ds.gross_total AS grossTotal,
|
||||
ds.net_total AS netTotal,
|
||||
ds.total_quantity AS totalQuantity,
|
||||
ds.stock_positions AS stockPositions,
|
||||
ds.booked_positions AS bookedPositions,
|
||||
ds.unbooked_positions AS unbookedPositions,
|
||||
ds.stock_quantity AS stockQuantity,
|
||||
ds.booked_quantity AS bookedQuantity,
|
||||
ds.unbooked_quantity AS unbookedQuantity,
|
||||
ds.is_booked AS isBooked,
|
||||
ds.supplier_name AS supplierName
|
||||
FROM manifest m
|
||||
LEFT JOIN deliverysummary ds ON ds.manifest_id = m.id
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY m."date" DESC, m.id DESC
|
||||
${isPaged ? 'LIMIT @limit OFFSET @offset' : ''}
|
||||
`;
|
||||
} else {
|
||||
sqlList = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.[number],
|
||||
m.[type],
|
||||
m.entity_id AS supplierId,
|
||||
m.external_number AS externalNumber,
|
||||
m.[date],
|
||||
m.created_at AS createdAt,
|
||||
ds.positions,
|
||||
ds.gross_total AS grossTotal,
|
||||
ds.net_total AS netTotal,
|
||||
ds.total_quantity AS totalQuantity,
|
||||
ds.stock_positions AS stockPositions,
|
||||
ds.booked_positions AS bookedPositions,
|
||||
ds.unbooked_positions AS unbookedPositions,
|
||||
ds.stock_quantity AS stockQuantity,
|
||||
ds.booked_quantity AS bookedQuantity,
|
||||
ds.unbooked_quantity AS unbookedQuantity,
|
||||
ds.is_booked AS isBooked,
|
||||
ds.supplier_name AS supplierName
|
||||
FROM manifest m
|
||||
LEFT JOIN deliverysummary ds ON ds.manifest_id = m.id
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY m.[date] DESC, m.id DESC
|
||||
${isPaged ? 'OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY' : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
const hasUserFilters = (
|
||||
opts?.seekMonths !== undefined ||
|
||||
(typeof opts?.search === 'string' && opts.search.trim() !== '') ||
|
||||
opts?.onlyUnbooked === true ||
|
||||
opts?.supplierId !== undefined
|
||||
);
|
||||
|
||||
let manifestRows;
|
||||
let total = 0;
|
||||
let unfilteredTotal = 0;
|
||||
|
||||
if (isPaged) {
|
||||
params.offset = offset;
|
||||
params.limit = limit;
|
||||
const [countResult, listResult, unfilteredCountResult] = await Promise.all([
|
||||
runner.query(sqlCount, params),
|
||||
runner.query(sqlList, params),
|
||||
hasUserFilters ? runner.query(sqlCountManifests, { companyId, type: queries.DELIVERY_TYPE }) : null,
|
||||
]);
|
||||
total = Number(countResult.rows[0]?.total ?? 0);
|
||||
unfilteredTotal = hasUserFilters
|
||||
? Number(unfilteredCountResult?.rows[0]?.total ?? 0)
|
||||
: total;
|
||||
manifestRows = listResult.rows;
|
||||
} else {
|
||||
const result = await runner.query(sqlList, params);
|
||||
manifestRows = result.rows;
|
||||
total = manifestRows.length;
|
||||
if (hasUserFilters) {
|
||||
const countRes = await runner.query(sqlCountManifests, { companyId, type: queries.DELIVERY_TYPE });
|
||||
unfilteredTotal = Number(countRes.rows[0]?.total ?? 0);
|
||||
} else {
|
||||
unfilteredTotal = total;
|
||||
}
|
||||
}
|
||||
|
||||
const manifestIds = manifestRows.map((r) => r.id);
|
||||
const filesByManifest = new Map();
|
||||
|
||||
if (manifestIds.length > 0) {
|
||||
const sqlFiles = isSqlite
|
||||
? `SELECT
|
||||
ma.id,
|
||||
ma.manifest_id AS manifestId,
|
||||
ma.file_id AS fileId,
|
||||
ma.sort,
|
||||
f.hash,
|
||||
f.mimetype,
|
||||
f.name,
|
||||
f.size,
|
||||
f.created,
|
||||
f.modified,
|
||||
f.thumbx,
|
||||
f.thumby,
|
||||
f.meta
|
||||
FROM manifestattachment ma
|
||||
INNER JOIN "file" f ON f.id = ma.file_id
|
||||
WHERE ma.manifest_id IN (SELECT value FROM json_each(@manifestIdsJson))
|
||||
ORDER BY ma.manifest_id, ma.sort ASC, ma.id ASC`
|
||||
: `SELECT
|
||||
ma.id,
|
||||
ma.manifest_id AS manifestId,
|
||||
ma.file_id AS fileId,
|
||||
ma.sort,
|
||||
f.hash,
|
||||
f.mimetype,
|
||||
f.name,
|
||||
f.size,
|
||||
f.created,
|
||||
f.modified,
|
||||
f.thumbavif,
|
||||
f.thumbx,
|
||||
f.thumby,
|
||||
f.meta
|
||||
FROM manifestattachment ma
|
||||
INNER JOIN [file] f ON f.id = ma.file_id
|
||||
WHERE ma.manifest_id IN (SELECT CAST(value AS INT) FROM OPENJSON(@manifestIdsJson))
|
||||
ORDER BY ma.manifest_id, ma.sort ASC, ma.id ASC`;
|
||||
|
||||
const filesResult = await runner.query(sqlFiles, {
|
||||
manifestIdsJson: JSON.stringify(manifestIds),
|
||||
});
|
||||
|
||||
for (const row of filesResult.rows) {
|
||||
const list = filesByManifest.get(row.manifestId) ?? [];
|
||||
list.push(shapeAttachmentMeta(row));
|
||||
filesByManifest.set(row.manifestId, list);
|
||||
}
|
||||
}
|
||||
|
||||
const deliveries = manifestRows.map((row) => ({
|
||||
...shapeSummary(row),
|
||||
positions: row.positions !== undefined && row.positions !== null ? Number(row.positions) : 0,
|
||||
grossTotal: row.grossTotal !== undefined && row.grossTotal !== null ? Number(row.grossTotal) : 0,
|
||||
netTotal: row.netTotal !== undefined && row.netTotal !== null ? Number(row.netTotal) : 0,
|
||||
totalQuantity: row.totalQuantity !== undefined && row.totalQuantity !== null ? Number(row.totalQuantity) : 0,
|
||||
stockPositions: row.stockPositions !== undefined && row.stockPositions !== null ? Number(row.stockPositions) : 0,
|
||||
bookedPositions: row.bookedPositions !== undefined && row.bookedPositions !== null ? Number(row.bookedPositions) : 0,
|
||||
unbookedPositions: row.unbookedPositions !== undefined && row.unbookedPositions !== null ? Number(row.unbookedPositions) : 0,
|
||||
stockQuantity: row.stockQuantity !== undefined && row.stockQuantity !== null ? Number(row.stockQuantity) : 0,
|
||||
bookedQuantity: row.bookedQuantity !== undefined && row.bookedQuantity !== null ? Number(row.bookedQuantity) : 0,
|
||||
unbookedQuantity: row.unbookedQuantity !== undefined && row.unbookedQuantity !== null ? Number(row.unbookedQuantity) : 0,
|
||||
isBooked: row.isBooked !== undefined && row.isBooked !== null ? Boolean(row.isBooked) : false,
|
||||
supplierName: row.supplierName ?? undefined,
|
||||
ledger: {
|
||||
totalPositions: Number(row.stockPositions ?? row.totalPositions ?? 0),
|
||||
completelyOnLedgerPositions: Number(row.bookedPositions ?? row.completelyOnLedgerPositions ?? 0),
|
||||
bookedPositions: Number(row.bookedPositions ?? 0),
|
||||
unbookedPositions: Number(row.unbookedPositions ?? 0),
|
||||
totalQuantity: Number(row.stockQuantity ?? 0),
|
||||
bookedQuantity: Number(row.bookedQuantity ?? 0),
|
||||
unbookedQuantity: Number(row.unbookedQuantity ?? 0),
|
||||
isBooked: Boolean(row.isBooked),
|
||||
},
|
||||
files: filesByManifest.get(row.id) ?? [],
|
||||
}));
|
||||
|
||||
if (isPaged) {
|
||||
return {
|
||||
deliveries,
|
||||
total,
|
||||
unfilteredTotal,
|
||||
offset,
|
||||
limit,
|
||||
};
|
||||
}
|
||||
|
||||
return deliveries;
|
||||
};
|
||||
}
|
||||
|
||||
19
src/store/delivery/rebuildSummaries.js
Normal file
19
src/store/delivery/rebuildSummaries.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { rebuildDeliverySummaries } from './syncSummary.js';
|
||||
|
||||
/**
|
||||
* Rebuilds the `deliverysummary` table for the user's selected company.
|
||||
*
|
||||
* @param {import('../../providers/provider.js').DatabaseProvider} provider
|
||||
*/
|
||||
export default function rebuildSummaries(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
|
||||
return async (googleAuthId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
await rebuildDeliverySummaries(runner, companyId);
|
||||
return { ok: true, companyId };
|
||||
};
|
||||
}
|
||||
445
src/store/delivery/syncSummary.js
Normal file
445
src/store/delivery/syncSummary.js
Normal file
@@ -0,0 +1,445 @@
|
||||
/**
|
||||
* Centralized sync and rebuild logic for the materialized `deliverysummary` table.
|
||||
*
|
||||
* Keeps `deliverysummary` in sync with manifest (delivery), manifestarticle,
|
||||
* article (track_stock), and ledger (DELIVERY transactions).
|
||||
*/
|
||||
|
||||
const SQL_GET_DELIVERY_BY_ID_SQLITE = `
|
||||
SELECT m.id, m.company_id, m."number", m.external_number, m."date", m.created_at, m.entity_id
|
||||
FROM manifest m
|
||||
WHERE m.id = @manifestId AND m.company_id = @companyId AND m."type" = 'delivery'
|
||||
`;
|
||||
|
||||
const SQL_GET_DELIVERY_BY_ID_MSSQL = `
|
||||
SELECT m.id, m.company_id, m.[number], m.external_number, m.[date], m.created_at, m.entity_id
|
||||
FROM manifest m
|
||||
WHERE m.id = @manifestId AND m.company_id = @companyId AND m.[type] = 'delivery'
|
||||
`;
|
||||
|
||||
const SQL_GET_DELIVERY_BY_NUMBER_SQLITE = `
|
||||
SELECT m.id, m.company_id, m."number", m.external_number, m."date", m.created_at, m.entity_id
|
||||
FROM manifest m
|
||||
WHERE m."number" = @number AND m.company_id = @companyId AND m."type" = 'delivery'
|
||||
`;
|
||||
|
||||
const SQL_GET_DELIVERY_BY_NUMBER_MSSQL = `
|
||||
SELECT m.id, m.company_id, m.[number], m.external_number, m.[date], m.created_at, m.entity_id
|
||||
FROM manifest m
|
||||
WHERE m.[number] = @number AND m.company_id = @companyId AND m.[type] = 'delivery'
|
||||
`;
|
||||
|
||||
const SQL_SUPPLIER_NAME_SQLITE = `
|
||||
SELECT TRIM(COALESCE(first_name || ' ', '') || COALESCE(name, '')) AS supplier_name
|
||||
FROM entity
|
||||
WHERE id = @entityId
|
||||
`;
|
||||
|
||||
const SQL_SUPPLIER_NAME_MSSQL = `
|
||||
SELECT LTRIM(RTRIM(ISNULL(first_name + ' ', '') + ISNULL(name, ''))) AS supplier_name
|
||||
FROM entity
|
||||
WHERE id = @entityId
|
||||
`;
|
||||
|
||||
const SQL_DELIVERY_TOTALS_SQLITE = `
|
||||
WITH line_ledger AS (
|
||||
SELECT
|
||||
manifestarticle_id,
|
||||
SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
WHERE warehouse_id IS NULL
|
||||
AND UPPER(transaction_type) IN ('ACCEPTANCE', 'DELIVERY', 'GOODS_RECEIPT')
|
||||
GROUP BY manifestarticle_id
|
||||
),
|
||||
line_data AS (
|
||||
SELECT
|
||||
ma.id,
|
||||
ma.amount,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) * (1.0 + ma.taxrate / 100.0) AS line_gross,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) AS line_net,
|
||||
CASE WHEN COALESCE(a.track_stock, 1) = 0 THEN 0 ELSE 1 END AS track_stock,
|
||||
COALESCE(ll.booked_amount, 0) AS booked_amount
|
||||
FROM manifestarticle ma
|
||||
LEFT JOIN article a ON a.company_id = @companyId AND (a.sku = ma.sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = ma.sku))
|
||||
LEFT JOIN line_ledger ll ON ll.manifestarticle_id = ma.id
|
||||
WHERE ma.manifest_id = @manifestId
|
||||
)
|
||||
SELECT
|
||||
COUNT(*) AS positions,
|
||||
COALESCE(SUM(line_gross), 0) AS gross_total,
|
||||
COALESCE(SUM(line_net), 0) AS net_total,
|
||||
COALESCE(SUM(amount), 0) AS total_quantity,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN 1 ELSE 0 END), 0) AS stock_positions,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 AND (CASE WHEN amount > 0 THEN booked_amount >= amount ELSE booked_amount > 0 END) THEN 1 ELSE 0 END), 0) AS booked_positions,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN amount ELSE 0 END), 0) AS stock_quantity,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN booked_amount ELSE 0 END), 0) AS booked_quantity
|
||||
FROM line_data
|
||||
`;
|
||||
|
||||
const SQL_DELIVERY_TOTALS_MSSQL = `
|
||||
WITH line_ledger AS (
|
||||
SELECT
|
||||
manifestarticle_id,
|
||||
SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
WHERE warehouse_id IS NULL
|
||||
AND UPPER(transaction_type) IN ('ACCEPTANCE', 'DELIVERY', 'GOODS_RECEIPT')
|
||||
GROUP BY manifestarticle_id
|
||||
),
|
||||
line_data AS (
|
||||
SELECT
|
||||
ma.id,
|
||||
ma.amount,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) * (1.0 + ma.taxrate / 100.0) AS line_gross,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) AS line_net,
|
||||
CASE WHEN COALESCE(a.track_stock, 1) = 0 THEN 0 ELSE 1 END AS track_stock,
|
||||
COALESCE(ll.booked_amount, 0) AS booked_amount
|
||||
FROM manifestarticle ma
|
||||
LEFT JOIN article a ON a.company_id = @companyId AND (a.sku = ma.sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = ma.sku))
|
||||
LEFT JOIN line_ledger ll ON ll.manifestarticle_id = ma.id
|
||||
WHERE ma.manifest_id = @manifestId
|
||||
)
|
||||
SELECT
|
||||
COUNT(*) AS positions,
|
||||
COALESCE(SUM(line_gross), 0) AS gross_total,
|
||||
COALESCE(SUM(line_net), 0) AS net_total,
|
||||
COALESCE(SUM(amount), 0) AS total_quantity,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN 1 ELSE 0 END), 0) AS stock_positions,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 AND (CASE WHEN amount > 0 THEN booked_amount >= amount ELSE booked_amount > 0 END) THEN 1 ELSE 0 END), 0) AS booked_positions,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN amount ELSE 0 END), 0) AS stock_quantity,
|
||||
COALESCE(SUM(CASE WHEN track_stock = 1 THEN booked_amount ELSE 0 END), 0) AS booked_quantity
|
||||
FROM line_data
|
||||
`;
|
||||
|
||||
const SQL_UPSERT_SUMMARY_SQLITE = `
|
||||
INSERT INTO deliverysummary (
|
||||
company_id, manifest_id, delivery_number, external_number, delivery_date, created_at,
|
||||
supplier_id, supplier_name, positions, gross_total, net_total, total_quantity,
|
||||
stock_positions, booked_positions, unbooked_positions,
|
||||
stock_quantity, booked_quantity, unbooked_quantity, is_booked
|
||||
) VALUES (
|
||||
@companyId, @manifestId, @deliveryNumber, @externalNumber, @deliveryDate, @createdAt,
|
||||
@supplierId, @supplierName, @positions, @grossTotal, @netTotal, @totalQuantity,
|
||||
@stockPositions, @bookedPositions, @unbookedPositions,
|
||||
@stockQuantity, @bookedQuantity, @unbookedQuantity, @isBooked
|
||||
)
|
||||
ON CONFLICT(manifest_id) DO UPDATE SET
|
||||
delivery_number = excluded.delivery_number,
|
||||
external_number = excluded.external_number,
|
||||
delivery_date = excluded.delivery_date,
|
||||
created_at = excluded.created_at,
|
||||
supplier_id = excluded.supplier_id,
|
||||
supplier_name = excluded.supplier_name,
|
||||
positions = excluded.positions,
|
||||
gross_total = excluded.gross_total,
|
||||
net_total = excluded.net_total,
|
||||
total_quantity = excluded.total_quantity,
|
||||
stock_positions = excluded.stock_positions,
|
||||
booked_positions = excluded.booked_positions,
|
||||
unbooked_positions = excluded.unbooked_positions,
|
||||
stock_quantity = excluded.stock_quantity,
|
||||
booked_quantity = excluded.booked_quantity,
|
||||
unbooked_quantity = excluded.unbooked_quantity,
|
||||
is_booked = excluded.is_booked
|
||||
`;
|
||||
|
||||
const SQL_UPSERT_SUMMARY_MSSQL = `
|
||||
MERGE INTO deliverysummary AS target
|
||||
USING (SELECT @manifestId AS manifest_id) AS source
|
||||
ON (target.manifest_id = source.manifest_id)
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET
|
||||
delivery_number = @deliveryNumber,
|
||||
external_number = @externalNumber,
|
||||
delivery_date = @deliveryDate,
|
||||
created_at = @createdAt,
|
||||
supplier_id = @supplierId,
|
||||
supplier_name = @supplierName,
|
||||
positions = @positions,
|
||||
gross_total = @grossTotal,
|
||||
net_total = @netTotal,
|
||||
total_quantity = @totalQuantity,
|
||||
stock_positions = @stockPositions,
|
||||
booked_positions = @bookedPositions,
|
||||
unbooked_positions = @unbookedPositions,
|
||||
stock_quantity = @stockQuantity,
|
||||
booked_quantity = @bookedQuantity,
|
||||
unbooked_quantity = @unbookedQuantity,
|
||||
is_booked = @isBooked
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (
|
||||
company_id, manifest_id, delivery_number, external_number, delivery_date, created_at,
|
||||
supplier_id, supplier_name, positions, gross_total, net_total, total_quantity,
|
||||
stock_positions, booked_positions, unbooked_positions,
|
||||
stock_quantity, booked_quantity, unbooked_quantity, is_booked
|
||||
) VALUES (
|
||||
@companyId, @manifestId, @deliveryNumber, @externalNumber, @deliveryDate, @createdAt,
|
||||
@supplierId, @supplierName, @positions, @grossTotal, @netTotal, @totalQuantity,
|
||||
@stockPositions, @bookedPositions, @unbookedPositions,
|
||||
@stockQuantity, @bookedQuantity, @unbookedQuantity, @isBooked
|
||||
);
|
||||
`;
|
||||
|
||||
/**
|
||||
* Recalculates and upserts a single delivery summary into `deliverysummary`.
|
||||
*
|
||||
* @param {object} runner - Database runner.
|
||||
* @param {object} opts
|
||||
* @param {number} opts.companyId
|
||||
* @param {number} [opts.manifestId] - Delivery manifest id.
|
||||
* @param {string} [opts.deliveryNumber] - Delivery number (if manifestId not given).
|
||||
*/
|
||||
export async function syncDeliverySummary(runner, { companyId, manifestId, deliveryNumber }) {
|
||||
const isSqlite = runner.dialect === 'sqlite3';
|
||||
|
||||
let deliveryRow;
|
||||
if (manifestId) {
|
||||
const res = await runner.query(
|
||||
isSqlite ? SQL_GET_DELIVERY_BY_ID_SQLITE : SQL_GET_DELIVERY_BY_ID_MSSQL,
|
||||
{ manifestId, companyId },
|
||||
);
|
||||
deliveryRow = res.rows[0];
|
||||
} else if (deliveryNumber) {
|
||||
const res = await runner.query(
|
||||
isSqlite ? SQL_GET_DELIVERY_BY_NUMBER_SQLITE : SQL_GET_DELIVERY_BY_NUMBER_MSSQL,
|
||||
{ number: deliveryNumber, companyId },
|
||||
);
|
||||
deliveryRow = res.rows[0];
|
||||
}
|
||||
|
||||
if (!deliveryRow) {
|
||||
if (manifestId) {
|
||||
await runner.query('DELETE FROM deliverysummary WHERE manifest_id = @manifestId', { manifestId });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const deliveryId = deliveryRow.id;
|
||||
const num = deliveryRow.number;
|
||||
|
||||
const totalsRes = await runner.query(
|
||||
isSqlite ? SQL_DELIVERY_TOTALS_SQLITE : SQL_DELIVERY_TOTALS_MSSQL,
|
||||
{ manifestId: deliveryId, companyId },
|
||||
);
|
||||
const totals = totalsRes.rows[0] || {};
|
||||
|
||||
let supplierName = null;
|
||||
if (deliveryRow.entity_id) {
|
||||
const supRes = await runner.query(
|
||||
isSqlite ? SQL_SUPPLIER_NAME_SQLITE : SQL_SUPPLIER_NAME_MSSQL,
|
||||
{ entityId: deliveryRow.entity_id },
|
||||
);
|
||||
supplierName = supRes.rows[0]?.supplier_name || null;
|
||||
}
|
||||
|
||||
const positions = Number(totals.positions || 0);
|
||||
const grossTotal = Math.round(Number(totals.gross_total || 0) * 100) / 100;
|
||||
const netTotal = Math.round(Number(totals.net_total || 0) * 100) / 100;
|
||||
const totalQuantity = Math.round(Number(totals.total_quantity || 0) * 1000) / 1000;
|
||||
const stockPositions = Number(totals.stock_positions || 0);
|
||||
const bookedPositions = Number(totals.booked_positions || 0);
|
||||
const unbookedPositions = Math.max(0, stockPositions - bookedPositions);
|
||||
const stockQuantity = Math.round(Number(totals.stock_quantity || 0) * 1000) / 1000;
|
||||
const bookedQuantity = Math.round(Number(totals.booked_quantity || 0) * 1000) / 1000;
|
||||
const unbookedQuantity = Math.round(Math.max(0, stockQuantity - bookedQuantity) * 1000) / 1000;
|
||||
const isBooked = stockPositions > 0 && bookedPositions >= stockPositions ? 1 : 0;
|
||||
|
||||
await runner.query(isSqlite ? SQL_UPSERT_SUMMARY_SQLITE : SQL_UPSERT_SUMMARY_MSSQL, {
|
||||
companyId,
|
||||
manifestId: deliveryId,
|
||||
deliveryNumber: num,
|
||||
externalNumber: deliveryRow.external_number ?? null,
|
||||
deliveryDate: deliveryRow.date,
|
||||
createdAt: deliveryRow.created_at,
|
||||
supplierId: deliveryRow.entity_id ?? null,
|
||||
supplierName,
|
||||
positions,
|
||||
grossTotal,
|
||||
netTotal,
|
||||
totalQuantity,
|
||||
stockPositions,
|
||||
bookedPositions,
|
||||
unbookedPositions,
|
||||
stockQuantity,
|
||||
bookedQuantity,
|
||||
unbookedQuantity,
|
||||
isBooked,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Full rebuild of all deliveries in `deliverysummary` for a given company.
|
||||
*
|
||||
* @param {object} runner
|
||||
* @param {number} companyId
|
||||
*/
|
||||
export async function rebuildDeliverySummaries(runner, companyId) {
|
||||
const isSqlite = runner.dialect === 'sqlite3';
|
||||
|
||||
if (isSqlite) {
|
||||
const sqlRebuildSqlite = `
|
||||
WITH
|
||||
line_ledger AS (
|
||||
SELECT
|
||||
manifestarticle_id,
|
||||
SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
WHERE warehouse_id IS NULL
|
||||
AND UPPER(transaction_type) IN ('ACCEPTANCE', 'DELIVERY', 'GOODS_RECEIPT')
|
||||
GROUP BY manifestarticle_id
|
||||
),
|
||||
line_data AS (
|
||||
SELECT
|
||||
ma.manifest_id,
|
||||
ma.id AS line_id,
|
||||
ma.amount,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) * (1.0 + ma.taxrate / 100.0) AS line_gross,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) AS line_net,
|
||||
CASE WHEN COALESCE(a.track_stock, 1) = 0 THEN 0 ELSE 1 END AS track_stock,
|
||||
COALESCE(ll.booked_amount, 0) AS booked_amount
|
||||
FROM manifestarticle ma
|
||||
JOIN manifest m ON m.id = ma.manifest_id
|
||||
LEFT JOIN article a ON a.company_id = m.company_id AND (a.sku = ma.sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = ma.sku))
|
||||
LEFT JOIN line_ledger ll ON ll.manifestarticle_id = ma.id
|
||||
WHERE m.company_id = @companyId AND m."type" = 'delivery'
|
||||
),
|
||||
delivery_agg AS (
|
||||
SELECT
|
||||
manifest_id,
|
||||
COUNT(*) AS positions,
|
||||
SUM(line_gross) AS gross_total,
|
||||
SUM(line_net) AS net_total,
|
||||
SUM(amount) AS total_quantity,
|
||||
SUM(CASE WHEN track_stock = 1 THEN 1 ELSE 0 END) AS stock_positions,
|
||||
SUM(CASE WHEN track_stock = 1 AND (CASE WHEN amount > 0 THEN booked_amount >= amount ELSE booked_amount > 0 END) THEN 1 ELSE 0 END) AS booked_positions,
|
||||
SUM(CASE WHEN track_stock = 1 THEN amount ELSE 0 END) AS stock_quantity,
|
||||
SUM(CASE WHEN track_stock = 1 THEN booked_amount ELSE 0 END) AS booked_quantity
|
||||
FROM line_data
|
||||
GROUP BY manifest_id
|
||||
)
|
||||
INSERT INTO deliverysummary (
|
||||
company_id, manifest_id, delivery_number, external_number, delivery_date, created_at,
|
||||
supplier_id, supplier_name, positions, gross_total, net_total, total_quantity,
|
||||
stock_positions, booked_positions, unbooked_positions,
|
||||
stock_quantity, booked_quantity, unbooked_quantity, is_booked
|
||||
)
|
||||
SELECT
|
||||
m.company_id,
|
||||
m.id AS manifest_id,
|
||||
m."number" AS delivery_number,
|
||||
m.external_number,
|
||||
m."date" AS delivery_date,
|
||||
m.created_at,
|
||||
m.entity_id AS supplier_id,
|
||||
TRIM(COALESCE(e.first_name || ' ', '') || COALESCE(e.name, '')) AS supplier_name,
|
||||
COALESCE(da.positions, 0) AS positions,
|
||||
ROUND(COALESCE(da.gross_total, 0), 2) AS gross_total,
|
||||
ROUND(COALESCE(da.net_total, 0), 2) AS net_total,
|
||||
ROUND(COALESCE(da.total_quantity, 0), 3) AS total_quantity,
|
||||
COALESCE(da.stock_positions, 0) AS stock_positions,
|
||||
COALESCE(da.booked_positions, 0) AS booked_positions,
|
||||
MAX(0, COALESCE(da.stock_positions, 0) - COALESCE(da.booked_positions, 0)) AS unbooked_positions,
|
||||
ROUND(COALESCE(da.stock_quantity, 0), 3) AS stock_quantity,
|
||||
ROUND(COALESCE(da.booked_quantity, 0), 3) AS booked_quantity,
|
||||
ROUND(MAX(0, COALESCE(da.stock_quantity, 0) - COALESCE(da.booked_quantity, 0)), 3) AS unbooked_quantity,
|
||||
CASE WHEN COALESCE(da.stock_positions, 0) > 0 AND COALESCE(da.booked_positions, 0) >= COALESCE(da.stock_positions, 0) THEN 1 ELSE 0 END AS is_booked
|
||||
FROM manifest m
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
LEFT JOIN delivery_agg da ON da.manifest_id = m.id
|
||||
WHERE m.company_id = @companyId AND m."type" = 'delivery'
|
||||
ON CONFLICT(manifest_id) DO UPDATE SET
|
||||
delivery_number = excluded.delivery_number,
|
||||
external_number = excluded.external_number,
|
||||
delivery_date = excluded.delivery_date,
|
||||
created_at = excluded.created_at,
|
||||
supplier_id = excluded.supplier_id,
|
||||
supplier_name = excluded.supplier_name,
|
||||
positions = excluded.positions,
|
||||
gross_total = excluded.gross_total,
|
||||
net_total = excluded.net_total,
|
||||
total_quantity = excluded.total_quantity,
|
||||
stock_positions = excluded.stock_positions,
|
||||
booked_positions = excluded.booked_positions,
|
||||
unbooked_positions = excluded.unbooked_positions,
|
||||
stock_quantity = excluded.stock_quantity,
|
||||
booked_quantity = excluded.booked_quantity,
|
||||
unbooked_quantity = excluded.unbooked_quantity,
|
||||
is_booked = excluded.is_booked;
|
||||
`;
|
||||
await runner.query(sqlRebuildSqlite, { companyId });
|
||||
} else {
|
||||
await runner.query('DELETE FROM deliverysummary WHERE company_id = @companyId', { companyId });
|
||||
const sqlRebuildMssql = `
|
||||
WITH
|
||||
line_ledger AS (
|
||||
SELECT
|
||||
manifestarticle_id,
|
||||
SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
WHERE warehouse_id IS NULL
|
||||
AND UPPER(transaction_type) IN ('ACCEPTANCE', 'DELIVERY', 'GOODS_RECEIPT')
|
||||
GROUP BY manifestarticle_id
|
||||
),
|
||||
line_data AS (
|
||||
SELECT
|
||||
ma.manifest_id,
|
||||
ma.id AS line_id,
|
||||
ma.amount,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) * (1.0 + ma.taxrate / 100.0) AS line_gross,
|
||||
ma.amount * (ma.net * (1.0 - ma.rebate / 100.0)) AS line_net,
|
||||
CASE WHEN COALESCE(a.track_stock, 1) = 0 THEN 0 ELSE 1 END AS track_stock,
|
||||
COALESCE(ll.booked_amount, 0) AS booked_amount
|
||||
FROM manifestarticle ma
|
||||
JOIN manifest m ON m.id = ma.manifest_id
|
||||
LEFT JOIN article a ON a.company_id = m.company_id AND (a.sku = ma.sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = ma.sku))
|
||||
LEFT JOIN line_ledger ll ON ll.manifestarticle_id = ma.id
|
||||
WHERE m.company_id = @companyId AND m.[type] = 'delivery'
|
||||
),
|
||||
delivery_agg AS (
|
||||
SELECT
|
||||
manifest_id,
|
||||
COUNT(*) AS positions,
|
||||
SUM(line_gross) AS gross_total,
|
||||
SUM(line_net) AS net_total,
|
||||
SUM(amount) AS total_quantity,
|
||||
SUM(CASE WHEN track_stock = 1 THEN 1 ELSE 0 END) AS stock_positions,
|
||||
SUM(CASE WHEN track_stock = 1 AND (CASE WHEN amount > 0 THEN booked_amount >= amount ELSE booked_amount > 0 END) THEN 1 ELSE 0 END) AS booked_positions,
|
||||
SUM(CASE WHEN track_stock = 1 THEN amount ELSE 0 END) AS stock_quantity,
|
||||
SUM(CASE WHEN track_stock = 1 THEN booked_amount ELSE 0 END) AS booked_quantity
|
||||
FROM line_data
|
||||
GROUP BY manifest_id
|
||||
)
|
||||
INSERT INTO deliverysummary (
|
||||
company_id, manifest_id, delivery_number, external_number, delivery_date, created_at,
|
||||
supplier_id, supplier_name, positions, gross_total, net_total, total_quantity,
|
||||
stock_positions, booked_positions, unbooked_positions,
|
||||
stock_quantity, booked_quantity, unbooked_quantity, is_booked
|
||||
)
|
||||
SELECT
|
||||
m.company_id,
|
||||
m.id AS manifest_id,
|
||||
m.[number] AS delivery_number,
|
||||
m.external_number,
|
||||
m.[date] AS delivery_date,
|
||||
m.created_at,
|
||||
m.entity_id AS supplier_id,
|
||||
LTRIM(RTRIM(ISNULL(e.first_name + ' ', '') + ISNULL(e.name, ''))) AS supplier_name,
|
||||
COALESCE(da.positions, 0) AS positions,
|
||||
ROUND(COALESCE(da.gross_total, 0), 2) AS gross_total,
|
||||
ROUND(COALESCE(da.net_total, 0), 2) AS net_total,
|
||||
ROUND(COALESCE(da.total_quantity, 0), 3) AS total_quantity,
|
||||
COALESCE(da.stock_positions, 0) AS stock_positions,
|
||||
COALESCE(da.booked_positions, 0) AS booked_positions,
|
||||
CASE WHEN COALESCE(da.stock_positions, 0) > COALESCE(da.booked_positions, 0) THEN COALESCE(da.stock_positions, 0) - COALESCE(da.booked_positions, 0) ELSE 0 END AS unbooked_positions,
|
||||
ROUND(COALESCE(da.stock_quantity, 0), 3) AS stock_quantity,
|
||||
ROUND(COALESCE(da.booked_quantity, 0), 3) AS booked_quantity,
|
||||
ROUND(CASE WHEN COALESCE(da.stock_quantity, 0) > COALESCE(da.booked_quantity, 0) THEN COALESCE(da.stock_quantity, 0) - COALESCE(da.booked_quantity, 0) ELSE 0 END, 3) AS unbooked_quantity,
|
||||
CASE WHEN COALESCE(da.stock_positions, 0) > 0 AND COALESCE(da.booked_positions, 0) >= COALESCE(da.stock_positions, 0) THEN 1 ELSE 0 END AS is_booked
|
||||
FROM manifest m
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
LEFT JOIN delivery_agg da ON da.manifest_id = m.id
|
||||
WHERE m.company_id = @companyId AND m.[type] = 'delivery';
|
||||
`;
|
||||
await runner.query(sqlRebuildMssql, { companyId });
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { syncDeliverySummary } from './syncSummary.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import { isForeignKeyConstraintError } from '../../providers/provider.js';
|
||||
@@ -141,6 +142,7 @@ export default function update(provider) {
|
||||
);
|
||||
}
|
||||
|
||||
await syncDeliverySummary(runner, { companyId, manifestId: id });
|
||||
return loadDelivery(provider, tx, companyId, id);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user