281 lines
9.5 KiB
JavaScript
281 lines
9.5 KiB
JavaScript
import {
|
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
|
|
} from '../../providers/mssql/queries/article.js';
|
|
import {
|
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
|
|
} from '../../providers/sqlite3/queries/article.js';
|
|
|
|
/**
|
|
* Centralized sync and rebuild logic for the materialized `articlewarehousesummary` table.
|
|
*
|
|
* Keeps `articlewarehousesummary` in sync with ledger movements, track_stock status,
|
|
* and warehouses.
|
|
*/
|
|
|
|
const SQL_FIND_ARTICLES_BY_MANIFESTARTICLE = `
|
|
SELECT a.id AS articleId, a.company_id AS companyId, a.track_stock AS trackStock
|
|
FROM manifestarticle ma
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
INNER 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)
|
|
)
|
|
WHERE ma.id = @manifestarticleId AND m.company_id = @companyId;
|
|
`;
|
|
|
|
const SQL_GET_STOCK_FROM_LEDGER = `
|
|
SELECT COALESCE(SUM(l.amount), 0) AS stock
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
WHERE l.warehouse_id = @warehouseId
|
|
AND m.company_id = @companyId
|
|
AND (
|
|
ma.sku = (SELECT sku FROM article WHERE id = @articleId)
|
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = @articleId)
|
|
);
|
|
`;
|
|
|
|
const SQL_UPSERT_SUMMARY_SQLITE = `
|
|
INSERT INTO articlewarehousesummary (article_id, warehouse_id, stock)
|
|
VALUES (@articleId, @warehouseId, @stock)
|
|
ON CONFLICT (article_id, warehouse_id) DO UPDATE SET stock = excluded.stock;
|
|
`;
|
|
|
|
const SQL_UPSERT_SUMMARY_MSSQL = `
|
|
MERGE INTO articlewarehousesummary AS target
|
|
USING (SELECT @articleId AS article_id, @warehouseId AS warehouse_id) AS source
|
|
ON (target.article_id = source.article_id AND target.warehouse_id = source.warehouse_id)
|
|
WHEN MATCHED THEN
|
|
UPDATE SET stock = @stock
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (article_id, warehouse_id, stock)
|
|
VALUES (@articleId, @warehouseId, @stock);
|
|
`;
|
|
|
|
const SQL_INIT_FOR_ARTICLE_SQLITE = `
|
|
INSERT INTO articlewarehousesummary (article_id, warehouse_id, stock)
|
|
SELECT @articleId, w.id, 0
|
|
FROM warehouse w
|
|
WHERE w.company_id = @companyId
|
|
ON CONFLICT (article_id, warehouse_id) DO UPDATE SET stock = 0;
|
|
`;
|
|
|
|
const SQL_INIT_FOR_ARTICLE_MSSQL = `
|
|
MERGE INTO articlewarehousesummary AS target
|
|
USING (
|
|
SELECT @articleId AS article_id, w.id AS warehouse_id
|
|
FROM warehouse w
|
|
WHERE w.company_id = @companyId
|
|
) AS source
|
|
ON (target.article_id = source.article_id AND target.warehouse_id = source.warehouse_id)
|
|
WHEN MATCHED THEN
|
|
UPDATE SET stock = 0
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (article_id, warehouse_id, stock)
|
|
VALUES (source.article_id, source.warehouse_id, 0);
|
|
`;
|
|
|
|
const SQL_INIT_FOR_NEW_WAREHOUSE_SQLITE = `
|
|
INSERT INTO articlewarehousesummary (article_id, warehouse_id, stock)
|
|
SELECT a.id, @warehouseId, 0
|
|
FROM article a
|
|
WHERE a.company_id = @companyId AND a.track_stock = 1
|
|
ON CONFLICT (article_id, warehouse_id) DO UPDATE SET stock = 0;
|
|
`;
|
|
|
|
const SQL_INIT_FOR_NEW_WAREHOUSE_MSSQL = `
|
|
MERGE INTO articlewarehousesummary AS target
|
|
USING (
|
|
SELECT a.id AS article_id, @warehouseId AS warehouse_id
|
|
FROM article a
|
|
WHERE a.company_id = @companyId AND a.track_stock = 1
|
|
) AS source
|
|
ON (target.article_id = source.article_id AND target.warehouse_id = source.warehouse_id)
|
|
WHEN MATCHED THEN
|
|
UPDATE SET stock = 0
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (article_id, warehouse_id, stock)
|
|
VALUES (source.article_id, source.warehouse_id, 0);
|
|
`;
|
|
|
|
const SQL_DELETE_BY_ARTICLE = `
|
|
DELETE FROM articlewarehousesummary WHERE article_id = @articleId;
|
|
`;
|
|
|
|
/**
|
|
* Updates `articlewarehousesummary` after a ledger entry is appended with a warehouseId.
|
|
*
|
|
* @param {object} runner - Database runner.
|
|
* @param {object} opts
|
|
* @param {number} opts.companyId
|
|
* @param {number} opts.manifestarticleId
|
|
* @param {number|null} opts.warehouseId
|
|
*/
|
|
export async function syncArticleWarehouseSummaryForLedger(runner, { companyId, manifestarticleId, warehouseId }) {
|
|
if (warehouseId == null) {
|
|
return;
|
|
}
|
|
|
|
const isSqlite = runner.dialect === 'sqlite3';
|
|
const upsertSql = isSqlite ? SQL_UPSERT_SUMMARY_SQLITE : SQL_UPSERT_SUMMARY_MSSQL;
|
|
|
|
const res = await runner.query(SQL_FIND_ARTICLES_BY_MANIFESTARTICLE, {
|
|
manifestarticleId,
|
|
companyId,
|
|
});
|
|
|
|
const rows = res.rows ?? [];
|
|
for (const row of rows) {
|
|
const articleId = row.articleId;
|
|
const isTracked = Boolean(row.trackStock ?? 0);
|
|
|
|
if (!isTracked) {
|
|
await runner.query(SQL_DELETE_BY_ARTICLE, { articleId });
|
|
continue;
|
|
}
|
|
|
|
const stockRes = await runner.query(SQL_GET_STOCK_FROM_LEDGER, {
|
|
articleId,
|
|
warehouseId,
|
|
companyId,
|
|
});
|
|
const rawStock = stockRes.rows[0]?.stock ?? 0;
|
|
const stock = Math.round(Number(rawStock) * 1000) / 1000;
|
|
|
|
await runner.query(upsertSql, {
|
|
articleId,
|
|
warehouseId,
|
|
stock,
|
|
});
|
|
|
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
|
await runner.query(touchSql, { articleId });
|
|
await runner.query(touchStuecklisteSql, { articleId });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes `articlewarehousesummary` with stock = 0 for all warehouses of the company
|
|
* when track_stock is enabled for an article.
|
|
*
|
|
* @param {object} runner - Database runner.
|
|
* @param {object} opts
|
|
* @param {number} opts.companyId
|
|
* @param {number} opts.articleId
|
|
*/
|
|
export async function initArticleWarehouseSummary(runner, { companyId, articleId }) {
|
|
const isSqlite = runner.dialect === 'sqlite3';
|
|
const sql = isSqlite ? SQL_INIT_FOR_ARTICLE_SQLITE : SQL_INIT_FOR_ARTICLE_MSSQL;
|
|
await runner.query(sql, { companyId, articleId });
|
|
}
|
|
|
|
/**
|
|
* Removes all rows for an article from `articlewarehousesummary` when track_stock is disabled.
|
|
*
|
|
* @param {object} runner - Database runner.
|
|
* @param {object} opts
|
|
* @param {number} opts.articleId
|
|
*/
|
|
export async function removeArticleWarehouseSummary(runner, { articleId }) {
|
|
await runner.query(SQL_DELETE_BY_ARTICLE, { articleId });
|
|
}
|
|
|
|
/**
|
|
* Initializes entries in `articlewarehousesummary` for a newly created warehouse
|
|
* for all articles with stock tracking enabled.
|
|
*
|
|
* @param {object} runner - Database runner.
|
|
* @param {object} opts
|
|
* @param {number} opts.companyId
|
|
* @param {number} opts.warehouseId
|
|
*/
|
|
export async function initWarehouseSummaryForNewWarehouse(runner, { companyId, warehouseId }) {
|
|
const isSqlite = runner.dialect === 'sqlite3';
|
|
const sql = isSqlite ? SQL_INIT_FOR_NEW_WAREHOUSE_SQLITE : SQL_INIT_FOR_NEW_WAREHOUSE_MSSQL;
|
|
await runner.query(sql, { companyId, warehouseId });
|
|
}
|
|
|
|
/**
|
|
* Rebuilds the entire `articlewarehousesummary` for a given company.
|
|
*
|
|
* @param {object} runner - Database runner.
|
|
* @param {number} companyId - The company id.
|
|
*/
|
|
export async function rebuildArticleWarehouseSummaries(runner, companyId) {
|
|
const isSqlite = runner.dialect === 'sqlite3';
|
|
|
|
if (isSqlite) {
|
|
const sqlRebuildSqlite = `
|
|
DELETE FROM articlewarehousesummary
|
|
WHERE article_id IN (SELECT id FROM article WHERE company_id = @companyId);
|
|
|
|
INSERT INTO articlewarehousesummary (article_id, warehouse_id, stock)
|
|
SELECT
|
|
a.id AS article_id,
|
|
w.id AS warehouse_id,
|
|
COALESCE(s.stock, 0) AS stock
|
|
FROM article a
|
|
CROSS JOIN warehouse w ON w.company_id = a.company_id
|
|
LEFT JOIN (
|
|
SELECT
|
|
ca.id AS article_id,
|
|
l.warehouse_id,
|
|
SUM(l.amount) AS stock
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
INNER JOIN article ca ON ca.company_id = m.company_id
|
|
AND (
|
|
ca.sku = ma.sku
|
|
OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = ca.id AND asup.sku = ma.sku)
|
|
)
|
|
WHERE m.company_id = @companyId
|
|
AND l.warehouse_id IS NOT NULL
|
|
GROUP BY ca.id, l.warehouse_id
|
|
) s ON s.article_id = a.id AND s.warehouse_id = w.id
|
|
WHERE a.company_id = @companyId
|
|
AND a.track_stock = 1;
|
|
`;
|
|
await runner.query(sqlRebuildSqlite, { companyId });
|
|
} else {
|
|
const sqlRebuildMssql = `
|
|
DELETE FROM articlewarehousesummary
|
|
WHERE article_id IN (SELECT id FROM article WHERE company_id = @companyId);
|
|
|
|
INSERT INTO articlewarehousesummary (article_id, warehouse_id, stock)
|
|
SELECT
|
|
a.id AS article_id,
|
|
w.id AS warehouse_id,
|
|
COALESCE(s.stock, 0) AS stock
|
|
FROM article a
|
|
CROSS JOIN warehouse w
|
|
LEFT JOIN (
|
|
SELECT
|
|
ca.id AS article_id,
|
|
l.warehouse_id,
|
|
SUM(l.amount) AS stock
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
INNER JOIN article ca ON ca.company_id = m.company_id
|
|
AND (
|
|
ca.sku = ma.sku
|
|
OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = ca.id AND asup.sku = ma.sku)
|
|
)
|
|
WHERE m.company_id = @companyId
|
|
AND l.warehouse_id IS NOT NULL
|
|
GROUP BY ca.id, l.warehouse_id
|
|
) s ON s.article_id = a.id AND s.warehouse_id = w.id
|
|
WHERE a.company_id = @companyId
|
|
AND w.company_id = @companyId
|
|
AND a.track_stock = 1;
|
|
`;
|
|
await runner.query(sqlRebuildMssql, { companyId });
|
|
}
|
|
}
|