u
This commit is contained in:
@@ -177,7 +177,13 @@ export default function addChild(provider) {
|
||||
|
||||
// Auto-create child article
|
||||
const rawTrack = options.track_stock ?? options.trackStock;
|
||||
const finalTrackStock = rawTrack == null ? parent.track_stock : (rawTrack ? 1 : 0);
|
||||
const finalTrackStock = rawTrack == null ? (parent.track_stock ? 1 : 0) : (rawTrack ? 1 : 0);
|
||||
if (finalTrackStock === 1) {
|
||||
const whRes = await runner.query(queries.CHECK_HAS_WAREHOUSE, { companyId });
|
||||
if (!whRes.rows || whRes.rows.length === 0) {
|
||||
throw new Error('At least one warehouse is required to enable stock tracking');
|
||||
}
|
||||
}
|
||||
|
||||
const childRes = await runner.query(queries.CREATE_CHILD, {
|
||||
companyId,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { CREATE as MSSQL_CREATE_ARTICLE } from '../../providers/mssql/queries/article.js';
|
||||
import { CREATE as SQLITE_CREATE_ARTICLE } from '../../providers/sqlite3/queries/article.js';
|
||||
import {
|
||||
CREATE as MSSQL_CREATE_ARTICLE,
|
||||
CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE,
|
||||
} from '../../providers/mssql/queries/article.js';
|
||||
import {
|
||||
CREATE as SQLITE_CREATE_ARTICLE,
|
||||
CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE,
|
||||
} from '../../providers/sqlite3/queries/article.js';
|
||||
import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js';
|
||||
import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js';
|
||||
import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js';
|
||||
@@ -20,6 +26,7 @@ export default function createUnderParent(provider) {
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const createArticleSql = isSqlite ? SQLITE_CREATE_ARTICLE : MSSQL_CREATE_ARTICLE;
|
||||
const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE;
|
||||
const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE;
|
||||
const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT;
|
||||
|
||||
@@ -33,7 +40,7 @@ export default function createUnderParent(provider) {
|
||||
* @param {string} input.name - The article name.
|
||||
* @param {string} [input.desc] - The article description text/HTML.
|
||||
* @param {string} [input.seo] - The article SEO text.
|
||||
* @param {boolean|number} [input.track_stock=true] - Whether inventory stock is tracked.
|
||||
* @param {boolean|number} [input.track_stock=false] - Whether inventory stock is tracked.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, parentId: number, name: string, sku: string, track_stock: boolean}>} The created article.
|
||||
*
|
||||
@@ -43,7 +50,7 @@ export default function createUnderParent(provider) {
|
||||
*/
|
||||
return async ({ googleAuthId, parentId, name, desc, description, seo, track_stock, trackStock, sku: inputSku }, tx) => {
|
||||
const rawTrack = track_stock ?? trackStock;
|
||||
const finalTrackStock = rawTrack == null ? 1 : (rawTrack ? 1 : 0);
|
||||
const finalTrackStock = rawTrack == null ? 0 : (rawTrack ? 1 : 0);
|
||||
|
||||
const runCreate = async (runner) => {
|
||||
const { companyId, languageId, companyuserId } = await access(
|
||||
@@ -52,6 +59,13 @@ export default function createUnderParent(provider) {
|
||||
runner,
|
||||
);
|
||||
|
||||
if (finalTrackStock === 1) {
|
||||
const whRes = await runner.query(checkHasWarehouseSql, { companyId });
|
||||
if (!whRes.rows || whRes.rows.length === 0) {
|
||||
throw new Error('At least one warehouse is required to enable stock tracking');
|
||||
}
|
||||
}
|
||||
|
||||
const unitId = await ensureUnit(runner, languageId, companyId);
|
||||
const sku = (typeof inputSku === 'string' && inputSku.trim()) ? inputSku.trim() : await allocateSku(companyId, runner);
|
||||
const slug = await resolveSlug(runner, { companyId, languageId, name });
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function getBySku(provider) {
|
||||
seo: a.seo ?? null,
|
||||
unitamount: a.unitamount,
|
||||
weight: a.weight,
|
||||
track_stock: Boolean(a.track_stock ?? 1),
|
||||
track_stock: Boolean(a.track_stock ?? 0),
|
||||
unit: {
|
||||
si: a.unitSi,
|
||||
basesi: a.unitBasesi,
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function importRows(provider) {
|
||||
* @param {number|string} [input.rows[].net] - The POS net price (accepts German decimals).
|
||||
* @param {number|string} [input.rows[].tax] - The tax rate in percent (e.g. `19`).
|
||||
* @param {Array<string>|string} [input.rows[].eans] - The EANs to set.
|
||||
* @param {boolean|number} [input.rows[].track_stock=true] - Whether inventory stock is tracked.
|
||||
* @param {boolean|number} [input.rows[].track_stock=false] - Whether inventory stock is tracked.
|
||||
* @param {string} [input.rows[].parentSku] - The parent article SKU for a V/K compound link.
|
||||
* @param {number|string} [input.rows[].deposit] - The deposit (Pfand) value; currently ignored.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
@@ -174,7 +174,13 @@ export default function importRows(provider) {
|
||||
const slug = await resolveSlug(runner, { companyId, languageId, name });
|
||||
|
||||
const rawTrack = row.track_stock ?? row.trackStock;
|
||||
const trackStock = rawTrack == null ? 1 : (rawTrack ? 1 : 0);
|
||||
const trackStock = rawTrack == null ? 0 : (rawTrack ? 1 : 0);
|
||||
if (trackStock === 1) {
|
||||
const whRes = await runner.query(articleQueries.CHECK_HAS_WAREHOUSE, { companyId });
|
||||
if (!whRes.rows || whRes.rows.length === 0) {
|
||||
throw new Error('At least one warehouse is required to enable stock tracking');
|
||||
}
|
||||
}
|
||||
|
||||
const article = await runner.query(articleQueries.CREATE, {
|
||||
companyId,
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function listExport(provider) {
|
||||
unitSi: row.unitSi,
|
||||
unitSymbol: row.unitSymbol,
|
||||
unitamount: row.unitamount,
|
||||
track_stock: Boolean(row.track_stock ?? 1),
|
||||
track_stock: Boolean(row.track_stock ?? 0),
|
||||
brandName: row.brandName,
|
||||
net: row.net,
|
||||
taxRate: row.taxRate,
|
||||
|
||||
@@ -10,7 +10,7 @@ function shapeArticleDetail(row, categoryIds, photos, eans, prices) {
|
||||
slug: row.slug,
|
||||
unitamount: row.unitamount,
|
||||
weight: row.weight,
|
||||
track_stock: Boolean(row.track_stock ?? 1),
|
||||
track_stock: Boolean(row.track_stock ?? 0),
|
||||
unit: {
|
||||
si: row.unitSi,
|
||||
basesi: row.unitBasesi,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK } from '../../providers/mssql/queries/article.js';
|
||||
import { UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK } from '../../providers/sqlite3/queries/article.js';
|
||||
import {
|
||||
UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK,
|
||||
CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE,
|
||||
} from '../../providers/mssql/queries/article.js';
|
||||
import {
|
||||
UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK,
|
||||
CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE,
|
||||
} from '../../providers/sqlite3/queries/article.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import { recordTrackStockChangeHistory } from '../articlehistory/record.js';
|
||||
@@ -8,10 +14,9 @@ import makeRunner from '../runner.js';
|
||||
export default function setTrackStockBySku(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = queryBySku(provider);
|
||||
const sql =
|
||||
provider?.dialect === 'sqlite3'
|
||||
? SQLITE_UPDATE_TRACK_STOCK
|
||||
: MSSQL_UPDATE_TRACK_STOCK;
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const sql = isSqlite ? SQLITE_UPDATE_TRACK_STOCK : MSSQL_UPDATE_TRACK_STOCK;
|
||||
const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE;
|
||||
|
||||
/**
|
||||
* Sets the track_stock flag of an article by its SKU.
|
||||
@@ -21,7 +26,7 @@ export default function setTrackStockBySku(provider) {
|
||||
* @param {boolean|number} trackStock - Whether stock tracking is enabled.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The updated article.
|
||||
* @throws {Error} If trackStock is invalid or the article is not found.
|
||||
* @throws {Error} If trackStock is invalid, article not found, or no warehouse exists when enabling.
|
||||
*
|
||||
* @example
|
||||
* const article = await store.article.setTrackStockBySku(42, 'SKU-1', true);
|
||||
@@ -33,15 +38,22 @@ export default function setTrackStockBySku(provider) {
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
const { articleId, languageId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const newTrack = Boolean(trackStock);
|
||||
if (newTrack) {
|
||||
const whRes = await runner.query(checkHasWarehouseSql, { companyId });
|
||||
if (!whRes.rows || whRes.rows.length === 0) {
|
||||
throw new Error('At least one warehouse is required to enable stock tracking');
|
||||
}
|
||||
}
|
||||
|
||||
// Read old track_stock for history
|
||||
const oldRes = await runner.query(
|
||||
'SELECT track_stock FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const oldTrack = Boolean(oldRes.rows[0]?.track_stock ?? 1);
|
||||
const newTrack = Boolean(trackStock);
|
||||
const oldTrack = Boolean(oldRes.rows[0]?.track_stock ?? 0);
|
||||
|
||||
const result = await runner.query(sql, {
|
||||
articleId,
|
||||
|
||||
@@ -98,6 +98,13 @@ export default function updateBySku(provider) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
if (hasTrackStock && Boolean(rawTrackStock)) {
|
||||
const whRes = await runner.query(articleQueries.CHECK_HAS_WAREHOUSE, { companyId });
|
||||
if (!whRes.rows || whRes.rows.length === 0) {
|
||||
throw new Error('At least one warehouse is required to enable stock tracking');
|
||||
}
|
||||
}
|
||||
|
||||
let unitId;
|
||||
let newUnitSymbol = current.unitSymbol ?? current.unitSi ?? '';
|
||||
if (hasUnitSi) {
|
||||
@@ -139,7 +146,7 @@ export default function updateBySku(provider) {
|
||||
}
|
||||
}
|
||||
if (hasTrackStock) {
|
||||
const oldTrack = Boolean(current.track_stock ?? 1);
|
||||
const oldTrack = Boolean(current.track_stock ?? 0);
|
||||
const newTrack = Boolean(rawTrackStock);
|
||||
if (oldTrack !== newTrack) {
|
||||
historyLines.push(
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function queryCategoryArticles(provider) {
|
||||
} = row;
|
||||
return {
|
||||
...rest,
|
||||
track_stock: Boolean(rest.track_stock ?? 1),
|
||||
track_stock: Boolean(rest.track_stock ?? 0),
|
||||
// MSSQL returns BIT as boolean, SQLite as 0/1 integer. Normalize so both
|
||||
// dialects expose isParent as a real boolean, matching article/getBySku.
|
||||
isParent: Boolean(rest.isParent),
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {
|
||||
DELETE_BY_COMPANY_AND_ID as MSSQL_DELETE,
|
||||
CHECK_LEDGER_USAGE as MSSQL_CHECK_USAGE,
|
||||
COUNT_BY_COMPANY as MSSQL_COUNT,
|
||||
CHECK_TRACK_STOCK_ARTICLES as MSSQL_CHECK_TRACK_STOCK,
|
||||
} from '../../providers/mssql/queries/warehouse.js';
|
||||
import {
|
||||
DELETE_BY_COMPANY_AND_ID as SQLITE_DELETE,
|
||||
CHECK_LEDGER_USAGE as SQLITE_CHECK_USAGE,
|
||||
COUNT_BY_COMPANY as SQLITE_COUNT,
|
||||
CHECK_TRACK_STOCK_ARTICLES as SQLITE_CHECK_TRACK_STOCK,
|
||||
} from '../../providers/sqlite3/queries/warehouse.js';
|
||||
import { isForeignKeyConstraintError } from '../../providers/provider.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
@@ -15,15 +19,18 @@ export default function del(provider) {
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
const checkUsageSql = isSqlite ? SQLITE_CHECK_USAGE : MSSQL_CHECK_USAGE;
|
||||
const countSql = isSqlite ? SQLITE_COUNT : MSSQL_COUNT;
|
||||
const checkTrackStockSql = isSqlite ? SQLITE_CHECK_TRACK_STOCK : MSSQL_CHECK_TRACK_STOCK;
|
||||
|
||||
/**
|
||||
* Deletes a warehouse for the user's selected company, unless referenced in the ledger.
|
||||
* Deletes a warehouse for the user's selected company, unless referenced in the ledger
|
||||
* or it is the last warehouse while articles have stock tracking enabled.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The warehouse ID to delete.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If warehouse not found or is in use in the ledger.
|
||||
* @throws {Error} If warehouse not found, in use in the ledger, or is the last warehouse with track_stock articles.
|
||||
*
|
||||
* @example
|
||||
* await store.warehouse.delete(googleAuthId, 1);
|
||||
@@ -43,6 +50,16 @@ export default function del(provider) {
|
||||
throw new Error('Warehouse in use in ledger');
|
||||
}
|
||||
|
||||
// Cannot delete the last warehouse if there are articles with track_stock enabled
|
||||
const countRes = await runner.query(countSql, { companyId });
|
||||
const count = Number(countRes.rows[0]?.count ?? 0);
|
||||
if (count <= 1) {
|
||||
const trackRes = await runner.query(checkTrackStockSql, { companyId });
|
||||
if (trackRes.rows && trackRes.rows.length > 0) {
|
||||
throw new Error('Cannot delete the last warehouse while articles have stock tracking enabled');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runner.query(deleteSql, {
|
||||
id: numericId,
|
||||
|
||||
Reference in New Issue
Block a user