u
This commit is contained in:
@@ -351,6 +351,7 @@ const SCHEMA_TABLES = [
|
|||||||
['companysetting', 'id PK, company_id->company, key:s(255), value:txt, frontend:b=0, writeonly:b=0, UQ(company_id, key)'],
|
['companysetting', 'id PK, company_id->company, key:s(255), value:txt, frontend:b=0, writeonly:b=0, UQ(company_id, key)'],
|
||||||
['companyuser', 'id PK, google_auth_id->google_auth, company_id->company, UQ(google_auth_id, company_id)'],
|
['companyuser', 'id PK, google_auth_id->google_auth, company_id->company, UQ(google_auth_id, company_id)'],
|
||||||
['customergroup', 'id PK, name:s(255), sort:i=0, company_id?->company'],
|
['customergroup', 'id PK, name:s(255), sort:i=0, company_id?->company'],
|
||||||
|
['articlewarehousesummary', 'id PK, article_id->article, warehouse_id->warehouse, stock:f=0, UQ(article_id, warehouse_id)'],
|
||||||
['deliverysummary', 'id PK, company_id->company, manifest_id->manifest UQ, delivery_number:s(100), external_number?:s(100), delivery_date:dt, created_at:dt, supplier_id?->entity, supplier_name?:s(255), positions:i=0, gross_total:f=0, net_total:f=0, total_quantity:f=0, stock_positions:i=0, booked_positions:i=0, unbooked_positions:i=0, stock_quantity:f=0, booked_quantity:f=0, unbooked_quantity:f=0, is_booked:b=0'],
|
['deliverysummary', 'id PK, company_id->company, manifest_id->manifest UQ, delivery_number:s(100), external_number?:s(100), delivery_date:dt, created_at:dt, supplier_id?->entity, supplier_name?:s(255), positions:i=0, gross_total:f=0, net_total:f=0, total_quantity:f=0, stock_positions:i=0, booked_positions:i=0, unbooked_positions:i=0, stock_quantity:f=0, booked_quantity:f=0, unbooked_quantity:f=0, is_booked:b=0'],
|
||||||
['entity', 'id PK, customergroup_id->customergroup, first_name?:s(255), name:s(255), number?:s(100), created_at:dt, updated_at:dt, UQ(company_id, number)'],
|
['entity', 'id PK, customergroup_id->customergroup, first_name?:s(255), name:s(255), number?:s(100), created_at:dt, updated_at:dt, UQ(company_id, number)'],
|
||||||
['entityaddress', 'id PK, entity_id->entity, first_name?:s(255), name?:s(255), street?:s(255), streetnumber?:s(50), zip?:s(50), city?:s(255), county?:s(255), addresstype:s(50), sort:i=0'],
|
['entityaddress', 'id PK, entity_id->entity, first_name?:s(255), name?:s(255), street?:s(255), streetnumber?:s(50), zip?:s(50), city?:s(255), county?:s(255), addresstype:s(50), sort:i=0'],
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ const CLUSTERS = [
|
|||||||
'manifest',
|
'manifest',
|
||||||
'manifestarticle',
|
'manifestarticle',
|
||||||
'manifestattachment',
|
'manifestattachment',
|
||||||
|
'articlewarehousesummary',
|
||||||
'deliverysummary',
|
'deliverysummary',
|
||||||
'ordersummary',
|
'ordersummary',
|
||||||
'quotesummary',
|
'quotesummary',
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ import labeltemplateDelete from './store/labeltemplate/delete.js';
|
|||||||
import stockAdjust from './store/stock/adjust.js';
|
import stockAdjust from './store/stock/adjust.js';
|
||||||
import stockAccept from './store/stock/accept.js';
|
import stockAccept from './store/stock/accept.js';
|
||||||
import stockShip from './store/stock/ship.js';
|
import stockShip from './store/stock/ship.js';
|
||||||
|
import stockRebuildSummaries from './store/stock/rebuildSummaries.js';
|
||||||
import propertynameList from './store/propertyname/list.js';
|
import propertynameList from './store/propertyname/list.js';
|
||||||
import propertynameCreate from './store/propertyname/create.js';
|
import propertynameCreate from './store/propertyname/create.js';
|
||||||
import propertynameUpdate from './store/propertyname/update.js';
|
import propertynameUpdate from './store/propertyname/update.js';
|
||||||
@@ -624,6 +625,7 @@ export default class LibDashStore {
|
|||||||
adjust: stockAdjust(provider),
|
adjust: stockAdjust(provider),
|
||||||
accept: stockAccept(provider),
|
accept: stockAccept(provider),
|
||||||
ship: stockShip(provider),
|
ship: stockShip(provider),
|
||||||
|
rebuildSummaries: stockRebuildSummaries(provider),
|
||||||
};
|
};
|
||||||
this.adjustment = this.stock;
|
this.adjustment = this.stock;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/** Drop order = reverse of schema create dependency order. */
|
/** Drop order = reverse of schema create dependency order. */
|
||||||
export const TABLES = [
|
export const TABLES = [
|
||||||
|
|
||||||
|
'articlewarehousesummary',
|
||||||
'deliverysummary',
|
'deliverysummary',
|
||||||
'shipmentsummary',
|
'shipmentsummary',
|
||||||
'quotesummary',
|
'quotesummary',
|
||||||
|
|||||||
@@ -161,3 +161,14 @@ WHERE e.id = @id
|
|||||||
AND cg.company_id = @companyId
|
AND cg.company_id = @companyId
|
||||||
AND cg.name = @groupName
|
AND cg.name = @groupName
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const CHECK_SUPPLIER_IN_USE = `
|
||||||
|
SELECT TOP 1 1 AS inUse
|
||||||
|
FROM manifest m
|
||||||
|
WHERE m.entity_id = @id AND m.company_id = @companyId
|
||||||
|
UNION ALL
|
||||||
|
SELECT TOP 1 1 AS inUse
|
||||||
|
FROM articlesupplier asup
|
||||||
|
INNER JOIN article a ON a.id = asup.article_id
|
||||||
|
WHERE asup.entity_id = @id AND a.company_id = @companyId;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ BEGIN
|
|||||||
pu FLOAT NOT NULL CONSTRAINT DF_articlesupplier_pu DEFAULT 1,
|
pu FLOAT NOT NULL CONSTRAINT DF_articlesupplier_pu DEFAULT 1,
|
||||||
sort INT NOT NULL CONSTRAINT DF_articlesupplier_sort DEFAULT 0,
|
sort INT NOT NULL CONSTRAINT DF_articlesupplier_sort DEFAULT 0,
|
||||||
CONSTRAINT FK_articlesupplier_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
CONSTRAINT FK_articlesupplier_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||||
CONSTRAINT FK_articlesupplier_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
|
CONSTRAINT FK_articlesupplier_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE NO ACTION,
|
||||||
CONSTRAINT FK_articlesupplier_tax FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE NO ACTION,
|
CONSTRAINT FK_articlesupplier_tax FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE NO ACTION,
|
||||||
CONSTRAINT UQ_articlesupplier_article_entity UNIQUE (article_id, entity_id),
|
CONSTRAINT UQ_articlesupplier_article_entity UNIQUE (article_id, entity_id),
|
||||||
CONSTRAINT UQ_articlesupplier_article_sort UNIQUE (article_id, sort)
|
CONSTRAINT UQ_articlesupplier_article_sort UNIQUE (article_id, sort)
|
||||||
@@ -643,7 +643,7 @@ BEGIN
|
|||||||
[date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date DEFAULT SYSUTCDATETIME(),
|
[date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date DEFAULT SYSUTCDATETIME(),
|
||||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_manifest_created_at DEFAULT SYSUTCDATETIME(),
|
created_at DATETIME2 NOT NULL CONSTRAINT DF_manifest_created_at DEFAULT SYSUTCDATETIME(),
|
||||||
CONSTRAINT FK_manifest_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
CONSTRAINT FK_manifest_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||||
CONSTRAINT FK_manifest_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
CONSTRAINT FK_manifest_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE NO ACTION
|
||||||
);
|
);
|
||||||
END
|
END
|
||||||
`;
|
`;
|
||||||
@@ -1338,6 +1338,24 @@ BEGIN
|
|||||||
END
|
END
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const INIT_ARTICLEWAREHOUSESUMMARY = `
|
||||||
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlewarehousesummary')
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE articlewarehousesummary (
|
||||||
|
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||||
|
article_id INT NOT NULL,
|
||||||
|
warehouse_id INT NOT NULL,
|
||||||
|
stock FLOAT NOT NULL CONSTRAINT DF_articlewarehousesummary_stock DEFAULT 0,
|
||||||
|
CONSTRAINT FK_articlewarehousesummary_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT FK_articlewarehousesummary_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouse(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT UQ_articlewarehousesummary_article_warehouse UNIQUE (article_id, warehouse_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IX_articlewarehousesummary_warehouse
|
||||||
|
ON articlewarehousesummary (warehouse_id);
|
||||||
|
END
|
||||||
|
`;
|
||||||
|
|
||||||
export const INIT_MCP_CLIENT = `
|
export const INIT_MCP_CLIENT = `
|
||||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'mcp_client')
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'mcp_client')
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -1485,6 +1503,7 @@ export const SCHEMA_SCRIPTS = [
|
|||||||
INIT_QUOTESUMMARY,
|
INIT_QUOTESUMMARY,
|
||||||
INIT_SHIPMENTSUMMARY,
|
INIT_SHIPMENTSUMMARY,
|
||||||
INIT_DELIVERYSUMMARY,
|
INIT_DELIVERYSUMMARY,
|
||||||
|
INIT_ARTICLEWAREHOUSESUMMARY,
|
||||||
INIT_MCP_CLIENT,
|
INIT_MCP_CLIENT,
|
||||||
INIT_MCP_AUTH_CODE,
|
INIT_MCP_AUTH_CODE,
|
||||||
INIT_MCP_TOKEN,
|
INIT_MCP_TOKEN,
|
||||||
|
|||||||
@@ -23,4 +23,16 @@ WHERE id = @id
|
|||||||
AND customergroup_id IN (
|
AND customergroup_id IN (
|
||||||
SELECT id FROM customergroup WHERE company_id = @companyId AND name = @groupName
|
SELECT id FROM customergroup WHERE company_id = @companyId AND name = @groupName
|
||||||
)
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CHECK_SUPPLIER_IN_USE = `
|
||||||
|
SELECT 1 AS inUse
|
||||||
|
FROM manifest m
|
||||||
|
WHERE m.entity_id = @id AND m.company_id = @companyId
|
||||||
|
UNION ALL
|
||||||
|
SELECT 1 AS inUse
|
||||||
|
FROM articlesupplier asup
|
||||||
|
INNER JOIN article a ON a.id = asup.article_id
|
||||||
|
WHERE asup.entity_id = @id AND a.company_id = @companyId
|
||||||
|
LIMIT 1;
|
||||||
`;
|
`;
|
||||||
@@ -412,7 +412,7 @@ CREATE TABLE IF NOT EXISTS articlesupplier (
|
|||||||
UNIQUE (article_id, entity_id),
|
UNIQUE (article_id, entity_id),
|
||||||
UNIQUE (article_id, sort),
|
UNIQUE (article_id, sort),
|
||||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
|
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE NO ACTION,
|
||||||
FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE NO ACTION
|
FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE NO ACTION
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS IX_articlesupplier_article_entity
|
CREATE INDEX IF NOT EXISTS IX_articlesupplier_article_entity
|
||||||
@@ -430,7 +430,7 @@ CREATE TABLE IF NOT EXISTS manifest (
|
|||||||
"date" TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
"date" TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE NO ACTION
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -981,6 +981,20 @@ CREATE INDEX IF NOT EXISTS IX_deliverysummary_company_supplier
|
|||||||
ON deliverysummary (company_id, supplier_id);
|
ON deliverysummary (company_id, supplier_id);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const INIT_ARTICLEWAREHOUSESUMMARY = `
|
||||||
|
CREATE TABLE IF NOT EXISTS articlewarehousesummary (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
article_id INTEGER NOT NULL,
|
||||||
|
warehouse_id INTEGER NOT NULL,
|
||||||
|
stock REAL NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE (article_id, warehouse_id),
|
||||||
|
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (warehouse_id) REFERENCES warehouse(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS IX_articlewarehousesummary_warehouse
|
||||||
|
ON articlewarehousesummary (warehouse_id);
|
||||||
|
`;
|
||||||
|
|
||||||
export const INIT_MCP_CLIENT = `
|
export const INIT_MCP_CLIENT = `
|
||||||
CREATE TABLE IF NOT EXISTS mcp_client (
|
CREATE TABLE IF NOT EXISTS mcp_client (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -1111,6 +1125,7 @@ export const SCHEMA_SCRIPTS = [
|
|||||||
INIT_QUOTESUMMARY,
|
INIT_QUOTESUMMARY,
|
||||||
INIT_SHIPMENTSUMMARY,
|
INIT_SHIPMENTSUMMARY,
|
||||||
INIT_DELIVERYSUMMARY,
|
INIT_DELIVERYSUMMARY,
|
||||||
|
INIT_ARTICLEWAREHOUSESUMMARY,
|
||||||
INIT_MCP_CLIENT,
|
INIT_MCP_CLIENT,
|
||||||
INIT_MCP_AUTH_CODE,
|
INIT_MCP_AUTH_CODE,
|
||||||
INIT_MCP_TOKEN,
|
INIT_MCP_TOKEN,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
recordChildAssignedToParentHistory,
|
recordChildAssignedToParentHistory,
|
||||||
} from '../articlehistory/record.js';
|
} from '../articlehistory/record.js';
|
||||||
import attachChild from './attachChild.js';
|
import attachChild from './attachChild.js';
|
||||||
|
import { initArticleWarehouseSummary } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
export default function addChild(provider) {
|
export default function addChild(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
@@ -266,6 +267,10 @@ export default function addChild(provider) {
|
|||||||
childSku,
|
childSku,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (finalTrackStock === 1) {
|
||||||
|
await initArticleWarehouseSummary(runner, { companyId, articleId: childId });
|
||||||
|
}
|
||||||
|
|
||||||
return load(googleAuthId, childSku, runner);
|
return load(googleAuthId, childSku, runner);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import nextSku from '../numbers/nextSku.js';
|
|||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
import resolveUniqueSlug from './resolveUniqueSlug.js';
|
import resolveUniqueSlug from './resolveUniqueSlug.js';
|
||||||
import { recordArticleCreatedHistory } from '../articlehistory/record.js';
|
import { recordArticleCreatedHistory } from '../articlehistory/record.js';
|
||||||
|
import { initArticleWarehouseSummary } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
export default function createUnderParent(provider) {
|
export default function createUnderParent(provider) {
|
||||||
const access = resolveCategoryAccess(provider);
|
const access = resolveCategoryAccess(provider);
|
||||||
@@ -113,6 +114,10 @@ export default function createUnderParent(provider) {
|
|||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (finalTrackStock === 1) {
|
||||||
|
await initArticleWarehouseSummary(runner, { companyId, articleId: id });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
parentId,
|
parentId,
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import resolveArticleBySku from './resolveArticleBySku.js';
|
|||||||
import { recordTrackStockChangeHistory } from '../articlehistory/record.js';
|
import { recordTrackStockChangeHistory } from '../articlehistory/record.js';
|
||||||
import stockAdjust from '../stock/adjust.js';
|
import stockAdjust from '../stock/adjust.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
import {
|
||||||
|
initArticleWarehouseSummary,
|
||||||
|
removeArticleWarehouseSummary,
|
||||||
|
} from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
export default function setTrackStockBySku(provider) {
|
export default function setTrackStockBySku(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
@@ -94,6 +98,12 @@ export default function setTrackStockBySku(provider) {
|
|||||||
|
|
||||||
// Write history if track_stock changed
|
// Write history if track_stock changed
|
||||||
if (oldTrack !== newTrack) {
|
if (oldTrack !== newTrack) {
|
||||||
|
if (!newTrack) {
|
||||||
|
await removeArticleWarehouseSummary(runner, { articleId });
|
||||||
|
} else {
|
||||||
|
await initArticleWarehouseSummary(runner, { companyId, articleId });
|
||||||
|
}
|
||||||
|
|
||||||
await recordTrackStockChangeHistory(runner, {
|
await recordTrackStockChangeHistory(runner, {
|
||||||
articleId,
|
articleId,
|
||||||
companyuserId,
|
companyuserId,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
recordParentDeactivatedHistory,
|
recordParentDeactivatedHistory,
|
||||||
} from '../articlehistory/record.js';
|
} from '../articlehistory/record.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
import { removeArticleWarehouseSummary } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
export default function toggleParent(provider) {
|
export default function toggleParent(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
@@ -96,6 +97,7 @@ export default function toggleParent(provider) {
|
|||||||
|
|
||||||
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
|
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
|
||||||
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
|
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
|
||||||
|
await removeArticleWarehouseSummary(runner, { articleId });
|
||||||
|
|
||||||
// If article is a child, verify attribute is not used by parent or any parent up the chain
|
// If article is a child, verify attribute is not used by parent or any parent up the chain
|
||||||
let currentParentId = currentArt?.parentarticle_id;
|
let currentParentId = currentArt?.parentarticle_id;
|
||||||
|
|||||||
266
src/store/articlewarehousesummary/syncSummary.js
Normal file
266
src/store/articlewarehousesummary/syncSummary.js
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
import { DELETE_BY_COMPANY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/entity.js';
|
import {
|
||||||
import { DELETE_BY_COMPANY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/entity.js';
|
DELETE_BY_COMPANY_AND_ID as MSSQL_DELETE,
|
||||||
|
CHECK_SUPPLIER_IN_USE as MSSQL_CHECK_IN_USE,
|
||||||
|
GET_BY_COMPANY_AND_ID as MSSQL_GET_BY_ID,
|
||||||
|
} from '../../providers/mssql/queries/entity.js';
|
||||||
|
import {
|
||||||
|
DELETE_BY_COMPANY_AND_ID as SQLITE_DELETE,
|
||||||
|
CHECK_SUPPLIER_IN_USE as SQLITE_CHECK_IN_USE,
|
||||||
|
GET_BY_COMPANY_AND_ID as SQLITE_GET_BY_ID,
|
||||||
|
} from '../../providers/sqlite3/queries/entity.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
export default function del(provider) {
|
export default function del(provider) {
|
||||||
const access = resolveSelectedCompanyAccess(provider);
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
const sql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
||||||
|
const checkInUseSql = isSqlite ? SQLITE_CHECK_IN_USE : MSSQL_CHECK_IN_USE;
|
||||||
|
const getByIdSql = isSqlite ? SQLITE_GET_BY_ID : MSSQL_GET_BY_ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an entity by its id within the user's selected company.
|
* Deletes an entity by its id within the user's selected company.
|
||||||
@@ -14,20 +25,38 @@ export default function del(provider) {
|
|||||||
* @param {number} id - The id of the entity to delete.
|
* @param {number} id - The id of the entity to delete.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @throws {Error} If the entity is not found.
|
* @throws {Error} If the entity is not found or is in use.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* await store.entity.delete(42, 5);
|
* await store.entity.delete(42, 5);
|
||||||
*/
|
*/
|
||||||
return async (googleAuthId, id, tx) => {
|
return async (googleAuthId, id, tx) => {
|
||||||
if (!Number.isFinite(id)) {
|
const numericId = Number(id);
|
||||||
|
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||||
throw new Error('Entity not found');
|
throw new Error('Entity not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { companyId } = await access(googleAuthId, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
|
const { companyId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
const result = await makeRunner(provider, tx).query(sql, {
|
const existing = await runner.query(getByIdSql, {
|
||||||
id,
|
id: numericId,
|
||||||
|
companyId,
|
||||||
|
});
|
||||||
|
if (!existing.rows || existing.rows.length === 0) {
|
||||||
|
throw new Error('Entity not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = await runner.query(checkInUseSql, {
|
||||||
|
id: numericId,
|
||||||
|
companyId,
|
||||||
|
});
|
||||||
|
if (usage.rows && usage.rows.length > 0) {
|
||||||
|
throw new Error('Entity in use');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await runner.query(sql, {
|
||||||
|
id: numericId,
|
||||||
companyId,
|
companyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,3 +65,4 @@ export default function del(provider) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { syncDeliverySummary } from '../delivery/syncSummary.js';
|
import { syncDeliverySummary } from '../delivery/syncSummary.js';
|
||||||
import { syncShipmentSummary } from '../shipment/syncSummary.js';
|
import { syncShipmentSummary } from '../shipment/syncSummary.js';
|
||||||
|
import { syncArticleWarehouseSummaryForLedger } from '../articlewarehousesummary/syncSummary.js';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import * as mssqlQueries from '../../providers/mssql/queries/ledger.js';
|
import * as mssqlQueries from '../../providers/mssql/queries/ledger.js';
|
||||||
import * as sqliteQueries from '../../providers/sqlite3/queries/ledger.js';
|
import * as sqliteQueries from '../../providers/sqlite3/queries/ledger.js';
|
||||||
@@ -239,6 +240,12 @@ export default function add(provider) {
|
|||||||
memo: text,
|
memo: text,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await syncArticleWarehouseSummaryForLedger(runner, {
|
||||||
|
companyId,
|
||||||
|
manifestarticleId,
|
||||||
|
warehouseId: effectiveWhId,
|
||||||
|
});
|
||||||
|
|
||||||
for (const match of articleMatches) {
|
for (const match of articleMatches) {
|
||||||
await recordWarehouseHistory(runner, {
|
await recordWarehouseHistory(runner, {
|
||||||
articleId: match.id,
|
articleId: match.id,
|
||||||
@@ -315,6 +322,12 @@ export default function add(provider) {
|
|||||||
memo: text,
|
memo: text,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await syncArticleWarehouseSummaryForLedger(runner, {
|
||||||
|
companyId,
|
||||||
|
manifestarticleId,
|
||||||
|
warehouseId: effectiveWhId,
|
||||||
|
});
|
||||||
|
|
||||||
for (const match of articleMatches) {
|
for (const match of articleMatches) {
|
||||||
await recordWarehouseHistory(runner, {
|
await recordWarehouseHistory(runner, {
|
||||||
articleId: match.id,
|
articleId: match.id,
|
||||||
@@ -357,6 +370,12 @@ export default function add(provider) {
|
|||||||
memo: transactionText,
|
memo: transactionText,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await syncArticleWarehouseSummaryForLedger(runner, {
|
||||||
|
companyId,
|
||||||
|
manifestarticleId,
|
||||||
|
warehouseId,
|
||||||
|
});
|
||||||
|
|
||||||
return shape(row);
|
return shape(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import nextNumber from '../numbers/nextNumber.js';
|
|||||||
import ledgerAdd from '../ledger/add.js';
|
import ledgerAdd from '../ledger/add.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
import { parseDate } from '../manifestLines.js';
|
import { parseDate } from '../manifestLines.js';
|
||||||
|
import { syncArticleWarehouseSummaryForLedger } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
const ADJUSTMENT_TYPE = 'adjustment';
|
const ADJUSTMENT_TYPE = 'adjustment';
|
||||||
|
|
||||||
@@ -140,6 +141,12 @@ export default function adjust(provider) {
|
|||||||
});
|
});
|
||||||
const ledgerRow = inserted.rows[0];
|
const ledgerRow = inserted.rows[0];
|
||||||
|
|
||||||
|
await syncArticleWarehouseSummaryForLedger(runner, {
|
||||||
|
companyId,
|
||||||
|
manifestarticleId,
|
||||||
|
warehouseId,
|
||||||
|
});
|
||||||
|
|
||||||
// Write warehouse history for tracked article
|
// Write warehouse history for tracked article
|
||||||
if (manifestArticle.manifest_type === 'delivery') {
|
if (manifestArticle.manifest_type === 'delivery') {
|
||||||
await recordWarehouseHistory(runner, {
|
await recordWarehouseHistory(runner, {
|
||||||
|
|||||||
19
src/store/stock/rebuildSummaries.js
Normal file
19
src/store/stock/rebuildSummaries.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
import { rebuildArticleWarehouseSummaries } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds the `articlewarehousesummary` 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 rebuildArticleWarehouseSummaries(runner, companyId);
|
||||||
|
return { ok: true, companyId };
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,14 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
DELETE_BY_ID as MSSQL_DELETE_BY_ID,
|
DELETE_BY_ID as MSSQL_DELETE_BY_ID,
|
||||||
|
CHECK_SUPPLIER_IN_USE as MSSQL_CHECK_IN_USE,
|
||||||
|
GET_SUPPLIER_BY_ID as MSSQL_GET_SUPPLIER,
|
||||||
SUPPLIER_GROUP_NAME,
|
SUPPLIER_GROUP_NAME,
|
||||||
} from '../../providers/mssql/queries/entity.js';
|
} from '../../providers/mssql/queries/entity.js';
|
||||||
import { DELETE_BY_ID as SQLITE_DELETE_BY_ID } from '../../providers/sqlite3/queries/entity.js';
|
import {
|
||||||
|
DELETE_BY_ID as SQLITE_DELETE_BY_ID,
|
||||||
|
CHECK_SUPPLIER_IN_USE as SQLITE_CHECK_IN_USE,
|
||||||
|
GET_SUPPLIER_BY_ID as SQLITE_GET_SUPPLIER,
|
||||||
|
} from '../../providers/sqlite3/queries/entity.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
export default function del(provider) {
|
export default function del(provider) {
|
||||||
const access = resolveSelectedCompanyAccess(provider);
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE_BY_ID : MSSQL_DELETE_BY_ID;
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
const sql = isSqlite ? SQLITE_DELETE_BY_ID : MSSQL_DELETE_BY_ID;
|
||||||
|
const checkInUseSql = isSqlite ? SQLITE_CHECK_IN_USE : MSSQL_CHECK_IN_USE;
|
||||||
|
const getSupplierSql = isSqlite ? SQLITE_GET_SUPPLIER : MSSQL_GET_SUPPLIER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a supplier by its id within the user's selected company.
|
* Deletes a supplier by its id within the user's selected company.
|
||||||
@@ -17,21 +26,39 @@ export default function del(provider) {
|
|||||||
* @param {number} id - The id of the supplier to delete.
|
* @param {number} id - The id of the supplier to delete.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @throws {Error} If the supplier is not found.
|
* @throws {Error} If the supplier is not found or is in use.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* await store.supplier.delete(42, 5);
|
* await store.supplier.delete(42, 5);
|
||||||
*/
|
*/
|
||||||
return async (googleAuthId, id, tx) => {
|
return async (googleAuthId, id, tx) => {
|
||||||
if (!Number.isFinite(id)) {
|
const numericId = Number(id);
|
||||||
|
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||||
throw new Error('Supplier not found');
|
throw new Error('Supplier not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const runner = makeRunner(provider, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
const { companyId } = await access(googleAuthId, tx);
|
const { companyId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
|
const existing = await runner.query(getSupplierSql, {
|
||||||
|
id: numericId,
|
||||||
|
companyId,
|
||||||
|
groupName: SUPPLIER_GROUP_NAME,
|
||||||
|
});
|
||||||
|
if (!existing.rows || existing.rows.length === 0) {
|
||||||
|
throw new Error('Supplier not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = await runner.query(checkInUseSql, {
|
||||||
|
id: numericId,
|
||||||
|
companyId,
|
||||||
|
});
|
||||||
|
if (usage.rows && usage.rows.length > 0) {
|
||||||
|
throw new Error('Supplier in use');
|
||||||
|
}
|
||||||
|
|
||||||
const result = await runner.query(sql, {
|
const result = await runner.query(sql, {
|
||||||
id,
|
id: numericId,
|
||||||
companyId,
|
companyId,
|
||||||
groupName: SUPPLIER_GROUP_NAME,
|
groupName: SUPPLIER_GROUP_NAME,
|
||||||
});
|
});
|
||||||
@@ -41,3 +68,4 @@ export default function del(provider) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { isUniqueConstraintError } from '../../providers/provider.js';
|
import { isUniqueConstraintError } from '../../providers/provider.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
import { initWarehouseSummaryForNewWarehouse } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
|
||||||
function shape(row) {
|
function shape(row) {
|
||||||
return { id: row.id, name: row.name, sort: row.sort };
|
return { id: row.id, name: row.name, sort: row.sort };
|
||||||
@@ -65,6 +66,9 @@ export default function create(provider) {
|
|||||||
name: cleaned,
|
name: cleaned,
|
||||||
sort,
|
sort,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await initWarehouseSummaryForNewWarehouse(runner, { companyId, warehouseId: row.id });
|
||||||
|
|
||||||
return shape(row);
|
return shape(row);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
Reference in New Issue
Block a user