u
This commit is contained in:
190
src/store/stock/adjust.js
Normal file
190
src/store/stock/adjust.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/manifestDoc.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/manifestDoc.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||
import nextNumber from '../numbers/nextNumber.js';
|
||||
import ledgerAdd from '../ledger/add.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { parseDate } from '../manifestLines.js';
|
||||
|
||||
const ADJUSTMENT_TYPE = 'adjustment';
|
||||
|
||||
const GET_ARTICLE_NAME_SQL = `
|
||||
SELECT at.name
|
||||
FROM article a
|
||||
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||
WHERE a.id = @articleId;
|
||||
`;
|
||||
|
||||
export default function adjust(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const resolveArticle = resolveArticleBySku(provider);
|
||||
const allocateNumber = nextNumber(provider);
|
||||
const addLedger = ledgerAdd(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Adjusts article stock manually by creating an implicit manifest of type "adjustment",
|
||||
* allocating a number from the "adjustment" number scheme, and appending to the ledger.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input - Adjustment payload.
|
||||
* @param {string} [input.sku] - Article SKU (if single-item adjustment).
|
||||
* @param {number} [input.warehouseId] - Warehouse ID (can also pass warehouse_id).
|
||||
* @param {number} [input.amount] - Signed adjustment amount (+ to add, - to remove).
|
||||
* @param {string} [input.text] - Optional adjustment reason/memo.
|
||||
* @param {string} [input.externalNumber] - Optional external reference.
|
||||
* @param {string|Date} [input.date] - Optional date (defaults to now).
|
||||
* @param {Array<object>} [input.lines] - Optional array of items to adjust in one manifest.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{manifest: {id: number, number: string, type: string, date: string, createdAt: string}, lines: Array<object>, ledger: Array<object>}>}
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const rawLines = Array.isArray(input.lines)
|
||||
? input.lines
|
||||
: [
|
||||
{
|
||||
sku: input.sku,
|
||||
warehouseId: input.warehouseId ?? input.warehouse_id,
|
||||
amount: input.amount,
|
||||
text: input.text ?? input.transactionText ?? input.transaction_text,
|
||||
},
|
||||
];
|
||||
|
||||
if (rawLines.length === 0) {
|
||||
throw new Error('No items to adjust');
|
||||
}
|
||||
|
||||
const defaultWarehouseId = input.warehouseId ?? input.warehouse_id;
|
||||
const defaultText = input.text ?? input.transactionText ?? input.transaction_text;
|
||||
const externalNumber = input.externalNumber?.trim?.() ? input.externalNumber.trim() : null;
|
||||
const date = input.date ? parseDate(input.date) ?? new Date() : new Date();
|
||||
const createdAt = new Date();
|
||||
|
||||
const normalizedLines = rawLines.map((line, idx) => {
|
||||
const sku = typeof line.sku === 'string' ? line.sku.trim() : '';
|
||||
if (!sku) {
|
||||
throw new Error(`SKU required for item ${idx + 1}`);
|
||||
}
|
||||
|
||||
const rawWhId = line.warehouseId ?? line.warehouse_id ?? defaultWarehouseId;
|
||||
const warehouseId = Number(rawWhId);
|
||||
if (!Number.isInteger(warehouseId) || warehouseId <= 0) {
|
||||
throw new Error(`Warehouse required for item ${idx + 1} (${sku})`);
|
||||
}
|
||||
|
||||
const amount = Number(line.amount);
|
||||
if (!Number.isFinite(amount) || amount === 0) {
|
||||
throw new Error(`Invalid amount for item ${idx + 1} (${sku}): must be non-zero number`);
|
||||
}
|
||||
|
||||
const text = (line.text ?? line.transactionText ?? line.transaction_text ?? defaultText)?.trim?.() ||
|
||||
(amount > 0 ? `Manual stock addition (+${amount})` : `Manual stock removal (${amount})`);
|
||||
|
||||
return {
|
||||
sku,
|
||||
warehouseId,
|
||||
amount,
|
||||
text,
|
||||
};
|
||||
});
|
||||
|
||||
const run = async (runner, activeTx) => {
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
// Validate all articles exist and belong to the user's company
|
||||
const resolvedArticles = [];
|
||||
for (const item of normalizedLines) {
|
||||
const resolved = await resolveArticle(googleAuthId, item.sku, activeTx);
|
||||
const nameRes = await runner.query(GET_ARTICLE_NAME_SQL, {
|
||||
articleId: resolved.articleId,
|
||||
languageId: resolved.languageId,
|
||||
});
|
||||
const articleName = nameRes.rows[0]?.name ?? item.sku;
|
||||
resolvedArticles.push({ ...item, ...resolved, articleName });
|
||||
}
|
||||
|
||||
// Allocate unique adjustment number using the dedicated adjustment number scheme
|
||||
const number = await allocateNumber(companyId, ADJUSTMENT_TYPE, activeTx);
|
||||
|
||||
// Create the implicit adjustment manifest
|
||||
const manifestRes = await runner.query(queries.CREATE_MANIFEST, {
|
||||
companyId,
|
||||
number,
|
||||
type: ADJUSTMENT_TYPE,
|
||||
entityId: null,
|
||||
externalNumber,
|
||||
date,
|
||||
createdAt,
|
||||
});
|
||||
|
||||
const manifestId = manifestRes.rows[0]?.id ?? manifestRes.insertId;
|
||||
|
||||
const createdLines = [];
|
||||
const createdLedgerEntries = [];
|
||||
|
||||
for (const item of resolvedArticles) {
|
||||
// Create manifestarticle row
|
||||
const lineRes = await runner.query(queries.CREATE_LINE, {
|
||||
manifestId,
|
||||
sku: item.sku,
|
||||
name: item.articleName,
|
||||
pu: 1,
|
||||
amount: Math.abs(item.amount),
|
||||
net: 0,
|
||||
comment: item.text,
|
||||
rebate: 0,
|
||||
taxrate: 0,
|
||||
});
|
||||
|
||||
const manifestarticleId = lineRes.rows[0]?.id ?? lineRes.insertId;
|
||||
|
||||
// Append to ledger
|
||||
const ledgerRow = await addLedger(
|
||||
googleAuthId,
|
||||
{
|
||||
manifestarticle_id: manifestarticleId,
|
||||
warehouse_id: item.warehouseId,
|
||||
transaction_type: ADJUSTMENT_TYPE,
|
||||
amount: item.amount,
|
||||
transaction_text: item.text,
|
||||
},
|
||||
activeTx,
|
||||
);
|
||||
|
||||
createdLines.push({
|
||||
manifestarticle_id: manifestarticleId,
|
||||
sku: item.sku,
|
||||
name: item.articleName,
|
||||
warehouse_id: item.warehouseId,
|
||||
amount: item.amount,
|
||||
text: item.text,
|
||||
});
|
||||
|
||||
createdLedgerEntries.push(ledgerRow);
|
||||
}
|
||||
|
||||
return {
|
||||
manifest: {
|
||||
id: manifestId,
|
||||
number,
|
||||
type: ADJUSTMENT_TYPE,
|
||||
date: date.toISOString(),
|
||||
createdAt: createdAt.toISOString(),
|
||||
externalNumber,
|
||||
},
|
||||
lines: createdLines,
|
||||
ledger: createdLedgerEntries,
|
||||
};
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx), tx);
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction((runner, activeTx) => run(runner, activeTx));
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user