diff --git a/src/providers/mssql/queries/article.js b/src/providers/mssql/queries/article.js index 5a91d8e..8f94271 100644 --- a/src/providers/mssql/queries/article.js +++ b/src/providers/mssql/queries/article.js @@ -56,14 +56,14 @@ export const GET_CATEGORY_ID_BY_SKU = ` SELECT TOP 1 ca.category_id AS categoryId, a.id AS articleId FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id -WHERE a.sku = @sku +WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; /** Resolve an article id by sku (used to wire father/child compound links). */ export const GET_ID_BY_SKU = ` SELECT TOP 1 id AS articleId FROM article -WHERE sku = @sku +WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId) `; /** True if an EAN is already assigned to any article (duplicate guard). */ @@ -78,7 +78,7 @@ export const UPDATE_FATHER_BY_SKU = ` UPDATE article SET fatherarticle_id = @fatherArticleId, fathersort = COALESCE(@fathersort, 0) -WHERE sku = @sku +WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId) `; export const SET_IS_FATHER = ` @@ -95,41 +95,22 @@ WHERE id = @articleId AND fatherarticle_id = @fatherArticleId /** SKU → article/category + company auth in one round-trip. */ export const RESOLVE_ARTICLE_ACCESS_BY_SKU = ` -WITH article_cat AS ( - SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId - FROM article a - INNER JOIN categoryarticle ca ON ca.article_id = a.id - WHERE a.sku = @sku -), -ancestors AS ( - SELECT c.id, c.parent_id - FROM category c - INNER JOIN article_cat ac ON c.id = ac.categoryId - - UNION ALL - - SELECT c.id, c.parent_id - FROM category c - INNER JOIN ancestors a ON c.id = a.parent_id -), -root AS ( - SELECT TOP 1 id AS rootCategoryId - FROM ancestors - WHERE parent_id IS NULL -) -SELECT - ac.articleId, - ac.categoryId, - r.rootCategoryId, +SELECT TOP 1 + a.id AS articleId, + ca.category_id AS categoryId, + co.root_category_id AS rootCategoryId, co.id AS companyId, co.default_language_id AS languageId, co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId -FROM article_cat ac -LEFT JOIN root r ON 1 = 1 -LEFT JOIN company co ON co.root_category_id = r.rootCategoryId -LEFT JOIN companyuser cu - ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId +FROM article a +INNER JOIN company co ON co.id = a.company_id +INNER JOIN google_auth ga ON ga.id = @googleAuthId +INNER JOIN companyuser cu + ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId +LEFT JOIN categoryarticle ca ON ca.article_id = a.id +WHERE a.sku = @sku + AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) `; export const UPDATE_WEIGHT = ` @@ -163,9 +144,9 @@ WHERE id = @articleId `; export const CREATE = ` -INSERT INTO article (sku, unit_id, unitamount, weight) +INSERT INTO article (company_id, sku, unit_id, unitamount, weight) OUTPUT INSERTED.id -VALUES (@sku, @unitId, @unitamount, @weight) +VALUES (@companyId, @sku, @unitId, @unitamount, @weight) `; export const DELETE_EANS_BY_ARTICLE = ` @@ -342,42 +323,23 @@ ORDER BY a.id * Returns one row with articleJson, or empty if not found/unauthorized. */ export const GET_ARTICLE_BY_SKU_FULL = ` -;WITH article_cat AS ( - SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId - FROM article a - INNER JOIN categoryarticle ca ON ca.article_id = a.id - WHERE a.sku = @sku -), -ancestors AS ( - SELECT c.id, c.parent_id - FROM category c - INNER JOIN article_cat ac ON c.id = ac.categoryId - - UNION ALL - - SELECT c.id, c.parent_id - FROM category c - INNER JOIN ancestors a ON c.id = a.parent_id -), -root AS ( - SELECT TOP 1 id AS rootCategoryId - FROM ancestors - WHERE parent_id IS NULL -), -auth AS ( - SELECT - ac.articleId, - ac.categoryId, - r.rootCategoryId, +;WITH auth AS ( + SELECT TOP 1 + a.id AS articleId, + ca.category_id AS categoryId, + co.root_category_id AS rootCategoryId, co.id AS companyId, co.default_language_id AS languageId, co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId - FROM article_cat ac - LEFT JOIN root r ON 1 = 1 - LEFT JOIN company co ON co.root_category_id = r.rootCategoryId - LEFT JOIN companyuser cu - ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId + FROM article a + INNER JOIN company co ON co.id = a.company_id + INNER JOIN google_auth ga ON ga.id = @googleAuthId + INNER JOIN companyuser cu + ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId + LEFT JOIN categoryarticle ca ON ca.article_id = a.id + WHERE a.sku = @sku + AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) ) SELECT auth.articleId, diff --git a/src/providers/mssql/queries/articleDetail.js b/src/providers/mssql/queries/articleDetail.js index a4a3255..73a7c49 100644 --- a/src/providers/mssql/queries/articleDetail.js +++ b/src/providers/mssql/queries/articleDetail.js @@ -57,7 +57,7 @@ OUTER APPLY ( ) first_brand_pic LEFT JOIN pic brand_pic ON brand_pic.id = first_brand_pic.pic_id LEFT JOIN article father ON father.id = a.fatherarticle_id -WHERE a.sku = @sku +WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; export const GET_DETAIL_BY_ARTICLE_ID = ` diff --git a/src/providers/mssql/schema.js b/src/providers/mssql/schema.js index 84e945f..b53c334 100644 --- a/src/providers/mssql/schema.js +++ b/src/providers/mssql/schema.js @@ -312,6 +312,7 @@ IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'article') BEGIN CREATE TABLE article ( id INT IDENTITY(1,1) PRIMARY KEY, + company_id INT NOT NULL, sku NVARCHAR(100) NULL, unit_id INT NOT NULL, unitamount FLOAT NOT NULL, @@ -324,12 +325,13 @@ BEGIN fathersort INT NOT NULL CONSTRAINT DF_article_fathersort DEFAULT 0, created_at DATETIME2 NOT NULL CONSTRAINT DF_article_created_at DEFAULT SYSUTCDATETIME(), updated_at DATETIME2 NOT NULL CONSTRAINT DF_article_updated_at DEFAULT SYSUTCDATETIME(), + CONSTRAINT FK_article_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION, CONSTRAINT FK_article_unit FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE NO ACTION, CONSTRAINT FK_article_brand FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL, CONSTRAINT FK_article_articlelist FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL, CONSTRAINT FK_article_optionslist FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE SET NULL, CONSTRAINT FK_article_fatherarticle FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION, - CONSTRAINT UQ_article_sku UNIQUE (sku) + CONSTRAINT UQ_article_company_sku UNIQUE (company_id, sku) ); -- Only enforce sibling sort uniqueness among children of the same father. diff --git a/src/providers/sqlite3/queries/article.js b/src/providers/sqlite3/queries/article.js index 78a1550..926b575 100644 --- a/src/providers/sqlite3/queries/article.js +++ b/src/providers/sqlite3/queries/article.js @@ -28,27 +28,23 @@ LEFT JOIN article father ON father.id = a.fatherarticle_id `; export const RESOLVE_ARTICLE_ACCESS_BY_SKU = ` -WITH RECURSIVE article_cat AS ( - SELECT a.id AS articleId, ca.category_id AS categoryId - FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id - WHERE a.sku = @sku LIMIT 1 -), ancestors AS ( - SELECT c.id, c.parent_id FROM category c - INNER JOIN article_cat ac ON c.id = ac.categoryId - UNION ALL - SELECT c.id, c.parent_id FROM category c - INNER JOIN ancestors a ON c.id = a.parent_id -), root AS ( - SELECT id AS rootCategoryId FROM ancestors WHERE parent_id IS NULL LIMIT 1 -) -SELECT ac.articleId, ac.categoryId, r.rootCategoryId, - co.id AS companyId, co.default_language_id AS languageId, - co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId -FROM article_cat ac -LEFT JOIN root r ON 1 = 1 -LEFT JOIN company co ON co.root_category_id = r.rootCategoryId -LEFT JOIN companyuser cu - ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId +SELECT + a.id AS articleId, + ca.category_id AS categoryId, + co.root_category_id AS rootCategoryId, + co.id AS companyId, + co.default_language_id AS languageId, + co.poscustomergroup_id AS posCustomerGroupId, + cu.id AS companyuserId +FROM article a +INNER JOIN company co ON co.id = a.company_id +INNER JOIN google_auth ga ON ga.id = @googleAuthId +INNER JOIN companyuser cu + ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId +LEFT JOIN categoryarticle ca ON ca.article_id = a.id +WHERE a.sku = @sku + AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) +LIMIT 1 `; const JSON_EANS = `(SELECT COALESCE(json_group_array(json_object('ean', ean)), '[]') @@ -103,27 +99,19 @@ ORDER BY a.id `; export const GET_ARTICLE_BY_SKU_FULL = ` -WITH RECURSIVE article_cat AS ( - SELECT a.id AS articleId, ca.category_id AS categoryId - FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id - WHERE a.sku = @sku LIMIT 1 -), ancestors AS ( - SELECT c.id, c.parent_id FROM category c - INNER JOIN article_cat ac ON c.id = ac.categoryId - UNION ALL - SELECT c.id, c.parent_id FROM category c - INNER JOIN ancestors a ON c.id = a.parent_id -), root AS ( - SELECT id AS rootCategoryId FROM ancestors WHERE parent_id IS NULL LIMIT 1 -), auth AS ( - SELECT ac.articleId, ac.categoryId, r.rootCategoryId, +WITH auth AS ( + SELECT a.id AS articleId, ca.category_id AS categoryId, co.root_category_id AS rootCategoryId, co.id AS companyId, co.default_language_id AS languageId, co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId - FROM article_cat ac - LEFT JOIN root r ON 1 = 1 - LEFT JOIN company co ON co.root_category_id = r.rootCategoryId - LEFT JOIN companyuser cu - ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId + FROM article a + INNER JOIN company co ON co.id = a.company_id + INNER JOIN google_auth ga ON ga.id = @googleAuthId + INNER JOIN companyuser cu + ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId + LEFT JOIN categoryarticle ca ON ca.article_id = a.id + WHERE a.sku = @sku + AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) + LIMIT 1 ) SELECT auth.*, json_object( diff --git a/src/providers/sqlite3/queries/articleDetail.js b/src/providers/sqlite3/queries/articleDetail.js index dec75d0..4f01b7c 100644 --- a/src/providers/sqlite3/queries/articleDetail.js +++ b/src/providers/sqlite3/queries/articleDetail.js @@ -26,7 +26,7 @@ export const GET_DETAIL_BY_SKU = ` SELECT ${ARTICLE_DETAIL_COLUMNS} FROM article a ${DETAIL_JOINS} -WHERE a.sku = @sku +WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; export const GET_DETAIL_BY_ARTICLE_ID = ` diff --git a/src/providers/sqlite3/schema.js b/src/providers/sqlite3/schema.js index 438013e..9301dd5 100644 --- a/src/providers/sqlite3/schema.js +++ b/src/providers/sqlite3/schema.js @@ -211,7 +211,8 @@ CREATE TABLE IF NOT EXISTS brand ( export const INIT_ARTICLE = ` CREATE TABLE IF NOT EXISTS article ( id INTEGER PRIMARY KEY, - sku TEXT COLLATE NOCASE UNIQUE, + company_id INTEGER NOT NULL, + sku TEXT COLLATE NOCASE, unit_id INTEGER NOT NULL, unitamount REAL NOT NULL, weight REAL NOT NULL, @@ -223,6 +224,8 @@ CREATE TABLE IF NOT EXISTS article ( fathersort INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (${UTC_NOW}), updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}), + UNIQUE (company_id, sku), + FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE, FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE NO ACTION, FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL, FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL, diff --git a/src/store/article/createUnderParent.js b/src/store/article/createUnderParent.js index 0dd5640..89d0857 100644 --- a/src/store/article/createUnderParent.js +++ b/src/store/article/createUnderParent.js @@ -54,6 +54,7 @@ export default function createUnderParent(provider) { let article; try { article = await runner.query(createArticleSql, { + companyId, sku, unitId, unitamount: 1, diff --git a/src/store/article/importRows.js b/src/store/article/importRows.js index 8975f58..03ed802 100644 --- a/src/store/article/importRows.js +++ b/src/store/article/importRows.js @@ -157,7 +157,7 @@ export default function importRows(provider) { continue; } - const existing = await baseRunner.query(articleQueries.GET_ID_BY_SKU, { sku }); + const existing = await baseRunner.query(articleQueries.GET_ID_BY_SKU, { sku, companyId }); if (existing.rows[0]) { skipped.push({ sku, name, reason: 'duplicate sku' }); continue; @@ -169,6 +169,7 @@ export default function importRows(provider) { const slug = slugify(name); const article = await runner.query(articleQueries.CREATE, { + companyId, sku, unitId, unitamount: 1, @@ -277,10 +278,10 @@ export default function importRows(provider) { try { await runInTx(async (runner) => { - const child = await runner.query(articleQueries.GET_ID_BY_SKU, { sku }); + const child = await runner.query(articleQueries.GET_ID_BY_SKU, { sku, companyId }); const childId = child.rows[0]?.articleId; if (childId) { - const father = await runner.query(articleQueries.GET_ID_BY_SKU, { sku: fatherSku }); + const father = await runner.query(articleQueries.GET_ID_BY_SKU, { sku: fatherSku, companyId }); const fatherId = father.rows[0]?.articleId; if (fatherId && fatherId !== childId) { const childCount = await runner.query(getChildCountSql, { @@ -289,6 +290,7 @@ export default function importRows(provider) { const count = childCount.rows[0].count; await runner.query(articleQueries.UPDATE_FATHER_BY_SKU, { sku, + companyId, fatherArticleId: fatherId, fathersort: count, }); diff --git a/src/store/article/setFatherBySku.js b/src/store/article/setFatherBySku.js index 3bc5a48..0a706f5 100644 --- a/src/store/article/setFatherBySku.js +++ b/src/store/article/setFatherBySku.js @@ -30,11 +30,12 @@ export default function setFatherBySku(provider) { */ return async (googleAuthId, sku, fatherSku, tx) => { const runSet = async (runner) => { - const { articleId, languageId } = await resolve(googleAuthId, sku, runner); + const { articleId, languageId, companyId } = await resolve(googleAuthId, sku, runner); if (fatherSku == null || fatherSku === '') { await runner.query(queries.UPDATE_FATHER_BY_SKU, { sku, + companyId, fatherArticleId: null, }); const unlinked = await load(articleId, languageId, runner); @@ -44,7 +45,7 @@ export default function setFatherBySku(provider) { return unlinked; } - const father = await runner.query(queries.GET_ID_BY_SKU, { sku: fatherSku }); + const father = await runner.query(queries.GET_ID_BY_SKU, { sku: fatherSku, companyId }); const fatherId = father.rows[0]?.articleId; if (!fatherId) { throw new Error('Father not found'); @@ -60,6 +61,7 @@ export default function setFatherBySku(provider) { await runner.query(queries.UPDATE_FATHER_BY_SKU, { sku, + companyId, fatherArticleId: fatherId, fathersort: count, }); diff --git a/src/store/numbers/list.js b/src/store/numbers/list.js index 67ac33c..7109f33 100644 --- a/src/store/numbers/list.js +++ b/src/store/numbers/list.js @@ -1,8 +1,16 @@ -import { LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY } from '../../providers/mssql/queries/numbers.js'; -import { LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY } from '../../providers/sqlite3/queries/numbers.js'; +import { + ENSURE_TYPE as MSSQL_ENSURE_TYPE, + LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY, +} from '../../providers/mssql/queries/numbers.js'; +import { + ENSURE_TYPE as SQLITE_ENSURE_TYPE, + LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY, +} from '../../providers/sqlite3/queries/numbers.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import makeRunner from '../runner.js'; +const DEFAULT_TYPES = ['sku', 'entity', 'delivery']; + function shape(row) { return { id: row.id, @@ -15,13 +23,14 @@ function shape(row) { export default function list(provider) { const access = resolveSelectedCompanyAccess(provider); + const isSqlite = provider?.dialect === 'sqlite3'; + const ensureSql = isSqlite ? SQLITE_ENSURE_TYPE : MSSQL_ENSURE_TYPE; const sql = - provider?.dialect === 'sqlite3' - ? SQLITE_LIST_BY_COMPANY - : MSSQL_LIST_BY_COMPANY; + isSqlite ? SQLITE_LIST_BY_COMPANY : MSSQL_LIST_BY_COMPANY; /** * Lists the number sequences of the user's selected company. + * Ensures that default sequences ('sku', 'entity', 'delivery') exist. * * @param {number} googleAuthId - The id of the Google auth user. * @param {object} [tx] - Optional transaction to run within. @@ -33,9 +42,14 @@ export default function list(provider) { */ return async (googleAuthId, tx) => { const { companyId } = await access(googleAuthId, tx); + const runner = makeRunner(provider, tx); - const result = await makeRunner(provider, tx).query(sql, { companyId }); + for (const type of DEFAULT_TYPES) { + await runner.query(ensureSql, { companyId, type }); + } + + const result = await runner.query(sql, { companyId }); return result.rows.map(shape); }; -} \ No newline at end of file +}