sqlite
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { LIST_BY_MANIFEST } from '../../providers/mssql/queries/manifestattachment.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlManifestAttachmentQueries from '../../providers/mssql/queries/manifestattachment.js';
|
||||
import * as sqliteManifestAttachmentQueries from '../../providers/sqlite3/queries/manifestattachment.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import generateDocumentThumb from '../../util/generateDocumentThumb.js';
|
||||
import { parseMeta } from '../manifestattachment/listByManifestId.js';
|
||||
|
||||
@@ -33,12 +34,25 @@ export function shapeAttachmentMeta(row) {
|
||||
/**
|
||||
* Loads attachment metadata (without binary content) for a manifest.
|
||||
*/
|
||||
export async function loadAttachments(pool, tx, manifestId) {
|
||||
const result = await request(pool, tx)
|
||||
.input('manifestId', manifestId)
|
||||
.query(LIST_BY_MANIFEST);
|
||||
export async function loadAttachments(provider, tx, manifestId) {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteManifestAttachmentQueries : mssqlManifestAttachmentQueries;
|
||||
|
||||
return result.recordset.map(shapeAttachmentMeta);
|
||||
const result = await runner(queries.LIST_BY_MANIFEST, { manifestId });
|
||||
const rows = await Promise.all(
|
||||
result.rows.map(async (row) => {
|
||||
let thumbavif = row.thumbavif;
|
||||
if (isSqlite) {
|
||||
thumbavif = await (runner.readThumbAvif ? runner.readThumbAvif(row.hash) : provider?.readThumbAvif?.(row.hash));
|
||||
}
|
||||
return shapeAttachmentMeta({
|
||||
...row,
|
||||
thumbavif,
|
||||
});
|
||||
}),
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
function normalizeAttachment(raw, index) {
|
||||
@@ -57,25 +71,26 @@ function normalizeAttachment(raw, index) {
|
||||
throw new Error('Invalid file');
|
||||
}
|
||||
|
||||
if (raw.content == null) {
|
||||
if (rawContent(raw) == null) {
|
||||
throw new Error('Invalid file');
|
||||
}
|
||||
let content;
|
||||
if (Buffer.isBuffer(raw.content)) {
|
||||
content = raw.content;
|
||||
} else if (typeof raw.content === 'string') {
|
||||
const trimmed = raw.content.trim();
|
||||
const rawData = rawContent(raw);
|
||||
if (Buffer.isBuffer(rawData)) {
|
||||
content = rawData;
|
||||
} else if (typeof rawData === 'string') {
|
||||
const trimmed = rawData.trim();
|
||||
if (mimetype === 'application/pdf' || trimmed.startsWith('JVBER') || /^[A-Za-z0-9+/=]+$/.test(trimmed.slice(0, 100))) {
|
||||
try {
|
||||
content = Buffer.from(trimmed, 'base64');
|
||||
} catch {
|
||||
content = Buffer.from(raw.content, 'utf8');
|
||||
content = Buffer.from(rawData, 'utf8');
|
||||
}
|
||||
} else {
|
||||
content = Buffer.from(raw.content, 'utf8');
|
||||
content = Buffer.from(rawData, 'utf8');
|
||||
}
|
||||
} else {
|
||||
content = Buffer.from(raw.content);
|
||||
content = Buffer.from(rawData);
|
||||
}
|
||||
|
||||
const size = Number.isFinite(raw.size) && raw.size >= 0
|
||||
@@ -126,54 +141,80 @@ function normalizeAttachment(raw, index) {
|
||||
};
|
||||
}
|
||||
|
||||
function rawContent(raw) {
|
||||
return raw.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts pdf/txt attachments for a delivery (manifest) inside an existing
|
||||
* transaction. Sort orders continue after the current maximum.
|
||||
*
|
||||
* @param {object} pool
|
||||
* @param {object} provider
|
||||
* @param {object} tx - Active transaction.
|
||||
* @param {object} sql - SQL statements from delivery.js.
|
||||
* @param {number} manifestId
|
||||
* @param {Array<{mimetype: string, name: string, content: Buffer, sort?: number, size?: number, created?: Date|string, modified?: Date|string, hash?: string}>} [files]
|
||||
* @returns {Promise<Array<object>>} Attachment metadata rows.
|
||||
*/
|
||||
export async function insertAttachments(pool, tx, sql, manifestId, files) {
|
||||
export async function insertAttachments(provider, tx, sql, manifestId, files) {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const attachments = (files ?? []).map(normalizeAttachment);
|
||||
|
||||
const sortRes = await request(pool, tx)
|
||||
.input('manifestId', manifestId)
|
||||
.query(sql.GET_NEXT_ATTACHMENT_SORT);
|
||||
let nextSort = sortRes.recordset[0].sort;
|
||||
const sortRes = await runner(sql.GET_NEXT_ATTACHMENT_SORT, { manifestId });
|
||||
let nextSort = sortRes.rows[0].sort;
|
||||
|
||||
const results = [];
|
||||
for (const a of attachments) {
|
||||
const sort = a.sort ?? nextSort;
|
||||
nextSort = Math.max(nextSort, sort) + 1;
|
||||
|
||||
// Generate thumbnail server-side (e.g. pdftocairo + sharp for PDF)
|
||||
const { thumbavif, thumbx, thumby } = await generateDocumentThumb(a.content, a.mimetype, { filename: a.name });
|
||||
// Use client-provided thumbavif if present, otherwise generate server-side
|
||||
let thumbavif = a.thumbavif
|
||||
? (Buffer.isBuffer(a.thumbavif)
|
||||
? a.thumbavif
|
||||
: Buffer.from(a.thumbavif, typeof a.thumbavif === 'string' ? 'base64' : undefined))
|
||||
: null;
|
||||
let thumbx = Number.isFinite(a.thumbx) ? Number(a.thumbx) : null;
|
||||
let thumby = Number.isFinite(a.thumby) ? Number(a.thumby) : null;
|
||||
|
||||
if (!thumbavif) {
|
||||
const generated = await generateDocumentThumb(a.content, a.mimetype, { filename: a.name });
|
||||
thumbavif = generated.thumbavif;
|
||||
thumbx = generated.thumbx;
|
||||
thumby = generated.thumby;
|
||||
}
|
||||
|
||||
if (provider?.dialect === 'sqlite3') {
|
||||
if (runner.writeFileContent) {
|
||||
await runner.writeFileContent(a.hash, a.content, a.mimetype);
|
||||
if (thumbavif) await runner.writeThumbAvif(a.hash, thumbavif);
|
||||
} else if (provider?.writeFileContent) {
|
||||
await provider.writeFileContent(a.hash, a.content, a.mimetype);
|
||||
if (thumbavif) await provider.writeThumbAvif(a.hash, thumbavif);
|
||||
}
|
||||
}
|
||||
|
||||
const fileRes = await request(pool, tx)
|
||||
.input('hash', a.hash)
|
||||
.input('mimetype', a.mimetype)
|
||||
.input('name', a.name)
|
||||
.input('size', a.size)
|
||||
.input('created', a.created)
|
||||
.input('modified', a.modified)
|
||||
.input('content', a.content)
|
||||
.input('thumbavif', thumbavif)
|
||||
.input('thumbx', thumbx)
|
||||
.input('thumby', thumby)
|
||||
.input('meta', a.meta === null ? null : JSON.stringify(a.meta))
|
||||
.query(sql.CREATE_FILE);
|
||||
const fileRes = await runner(sql.CREATE_FILE, {
|
||||
hash: a.hash,
|
||||
mimetype: a.mimetype,
|
||||
name: a.name,
|
||||
size: a.size,
|
||||
created: a.created,
|
||||
modified: a.modified,
|
||||
content: a.content,
|
||||
thumbavif,
|
||||
thumbx,
|
||||
thumby,
|
||||
meta: a.meta === null ? null : JSON.stringify(a.meta),
|
||||
});
|
||||
|
||||
const fileId = fileRes.recordset[0].id;
|
||||
const fileId = fileRes.rows[0]?.id ?? fileRes.insertId;
|
||||
|
||||
await request(pool, tx)
|
||||
.input('manifestId', manifestId)
|
||||
.input('fileId', fileId)
|
||||
.input('sort', sort)
|
||||
.query(sql.CREATE_ATTACHMENT);
|
||||
await runner(sql.CREATE_ATTACHMENT, {
|
||||
manifestId,
|
||||
fileId,
|
||||
sort,
|
||||
});
|
||||
|
||||
results.push({
|
||||
manifestId,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
CREATE_MANIFEST,
|
||||
CREATE_FILE,
|
||||
CREATE_ATTACHMENT,
|
||||
GET_NEXT_ATTACHMENT_SORT,
|
||||
DELIVERY_TYPE,
|
||||
} from '../../providers/mssql/queries/delivery.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import nextNumber from '../numbers/nextNumber.js';
|
||||
import taxEnsure from '../tax/ensure.js';
|
||||
@@ -13,30 +7,15 @@ import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
|
||||
import { loadDelivery } from './helpers.js';
|
||||
import { insertAttachments } from './attachments.js';
|
||||
import { assertSupplier } from './writeLines.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function create(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
const ensureTax = taxEnsure(pool);
|
||||
const allocateNumber = nextNumber(pool);
|
||||
export default function create(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const ensureTax = taxEnsure(provider);
|
||||
const allocateNumber = nextNumber(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Creates a new delivery for the user's selected company with its lines.
|
||||
* The delivery number is allocated internally via `getnextnumberwithprefixandpadding`
|
||||
* (`type` `delivery`); supply `externalNumber` for an external (supplier) reference.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} body - The delivery input.
|
||||
* @param {string} body.date - The delivery date.
|
||||
* @param {number} body.supplierId - The id of the supplier.
|
||||
* @param {string} [body.externalNumber] - An external (supplier) reference number.
|
||||
* @param {Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null}>} [body.lines] - The delivery lines.
|
||||
* @returns {Promise<{id: number, number: string, type: string, entityId: number, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>} The created delivery.
|
||||
* @throws {Error} If the delivery is invalid.
|
||||
*
|
||||
* @example
|
||||
* const delivery = await store.delivery.create(42, { date: '2026-01-01', supplierId: 5, lines: [...] });
|
||||
* // => { id, number, type, entityId, externalNumber, date, lines: [...] }
|
||||
*/
|
||||
return async (googleAuthId, body = {}) => {
|
||||
const date = parseDate(body.date);
|
||||
if (!date) {
|
||||
@@ -50,58 +29,51 @@ export default function create(pool) {
|
||||
|
||||
const { companyId } = await access(googleAuthId);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const run = async (runner, tx) => {
|
||||
const supplierId = await assertSupplier(
|
||||
pool,
|
||||
provider,
|
||||
tx,
|
||||
companyId,
|
||||
body.supplierId ?? null,
|
||||
);
|
||||
|
||||
const number = await allocateNumber(companyId, DELIVERY_TYPE, tx);
|
||||
const number = await allocateNumber(companyId, queries.DELIVERY_TYPE, tx);
|
||||
|
||||
const created = await request(pool, tx)
|
||||
.input('companyId', companyId)
|
||||
.input('number', number)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.input('entityId', supplierId)
|
||||
.input('externalNumber', externalNumber)
|
||||
.input('date', date)
|
||||
.query(CREATE_MANIFEST);
|
||||
const created = await runner(queries.CREATE_MANIFEST, {
|
||||
companyId,
|
||||
number,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
entityId: supplierId,
|
||||
externalNumber,
|
||||
date,
|
||||
});
|
||||
|
||||
const manifestId = created.recordset[0].id;
|
||||
const manifestId = created.rows[0]?.id ?? created.insertId;
|
||||
|
||||
await insertLines(pool, tx, {
|
||||
await insertLines(provider, tx, {
|
||||
manifestId,
|
||||
lines,
|
||||
ensureTax,
|
||||
});
|
||||
|
||||
await insertAttachments(
|
||||
pool,
|
||||
provider,
|
||||
tx,
|
||||
{
|
||||
GET_NEXT_ATTACHMENT_SORT,
|
||||
CREATE_FILE,
|
||||
CREATE_ATTACHMENT,
|
||||
GET_NEXT_ATTACHMENT_SORT: queries.GET_NEXT_ATTACHMENT_SORT,
|
||||
CREATE_FILE: queries.CREATE_FILE,
|
||||
CREATE_ATTACHMENT: queries.CREATE_ATTACHMENT,
|
||||
},
|
||||
manifestId,
|
||||
body.files,
|
||||
);
|
||||
|
||||
const delivery = await loadDelivery(pool, tx, companyId, manifestId);
|
||||
await tx.commit();
|
||||
return delivery;
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
return loadDelivery(provider, tx, companyId, manifestId);
|
||||
};
|
||||
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction((runner, tx) => run(runner, tx));
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
import { DELETE_MANIFEST, DELIVERY_TYPE } from '../../providers/mssql/queries/delivery.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function del(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
export default function del(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Deletes a delivery by its id within the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The id of the delivery to delete.
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If the delivery is not found.
|
||||
*
|
||||
* @example
|
||||
* await store.delivery.delete(42, 5);
|
||||
*/
|
||||
return async (googleAuthId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
|
||||
const { companyId } = await access(googleAuthId);
|
||||
const runner = makeRunner(provider);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.query(DELETE_MANIFEST);
|
||||
const result = await runner(queries.DELETE_MANIFEST, {
|
||||
id,
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
const rowsAffected = result.rowsAffected?.[0] ?? (typeof result.rowsAffected === 'number' ? result.rowsAffected : 0);
|
||||
if (!rowsAffected) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import { loadDelivery } from './helpers.js';
|
||||
|
||||
export default function getById(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
export default function getById(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
|
||||
/**
|
||||
* Returns a single delivery by its id within the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The id of the delivery.
|
||||
* @returns {Promise<{id: number, number: string, type: string, entityId: number, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>} The delivery.
|
||||
* @throws {Error} If the delivery is not found.
|
||||
*
|
||||
* @example
|
||||
* const delivery = await store.delivery.getById(42, 5);
|
||||
* // => { id, number, type, entityId, externalNumber, date, lines: [...] }
|
||||
*/
|
||||
return async (googleAuthId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Delivery not found');
|
||||
@@ -23,7 +11,7 @@ export default function getById(pool) {
|
||||
|
||||
const { companyId } = await access(googleAuthId);
|
||||
|
||||
const delivery = await loadDelivery(pool, null, companyId, id);
|
||||
const delivery = await loadDelivery(provider, null, companyId, id);
|
||||
if (!delivery) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import {
|
||||
DELIVERY_TYPE,
|
||||
GET_DELIVERY_HEADER,
|
||||
GET_DELIVERY_LINES,
|
||||
} from '../../providers/mssql/queries/delivery.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { shapeLine } from '../manifestLines.js';
|
||||
import { loadAttachments } from './attachments.js';
|
||||
|
||||
export const DELIVERY_TYPE = mssqlQueries.DELIVERY_TYPE;
|
||||
|
||||
export function shapeSummary(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
@@ -18,27 +17,31 @@ export function shapeSummary(row) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadDelivery(pool, tx, companyId, id) {
|
||||
const header = await request(pool, tx)
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.query(GET_DELIVERY_HEADER);
|
||||
export async function loadDelivery(provider, tx, companyId, id) {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
const row = header.recordset[0];
|
||||
const header = await runner(queries.GET_DELIVERY_HEADER, {
|
||||
id,
|
||||
companyId,
|
||||
type: DELIVERY_TYPE,
|
||||
});
|
||||
|
||||
const row = header.rows[0];
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lines = await request(pool, tx)
|
||||
.input('manifestId', id)
|
||||
.query(GET_DELIVERY_LINES);
|
||||
const lines = await runner(queries.GET_DELIVERY_LINES, {
|
||||
manifestId: id,
|
||||
});
|
||||
|
||||
const files = await loadAttachments(pool, tx, id);
|
||||
const files = await loadAttachments(provider, tx, id);
|
||||
|
||||
return {
|
||||
...shapeSummary(row),
|
||||
lines: lines.recordset.map(shapeLine),
|
||||
lines: lines.rows.map(shapeLine),
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,45 +1,37 @@
|
||||
import { DELIVERY_TYPE, LIST_DELIVERIES, LIST_DELIVERY_FILES } from '../../providers/mssql/queries/delivery.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import { shapeSummary } from './helpers.js';
|
||||
import { shapeAttachmentMeta } from './attachments.js';
|
||||
|
||||
export default function list(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Lists the deliveries of the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @returns {Promise<Array<{id: number, number: string, type: string, entityId: number, externalNumber: string|null, date: string}>>} The deliveries.
|
||||
*
|
||||
* @example
|
||||
* const deliveries = await store.delivery.list(42);
|
||||
* // => [{ id, number, type, entityId, externalNumber, date }]
|
||||
*/
|
||||
return async (googleAuthId) => {
|
||||
const { companyId } = await access(googleAuthId);
|
||||
const runner = makeRunner(provider);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.query(LIST_DELIVERIES);
|
||||
const result = await runner(queries.LIST_DELIVERIES, {
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
|
||||
const filesResult = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.query(LIST_DELIVERY_FILES);
|
||||
const filesResult = await runner(queries.LIST_DELIVERY_FILES, {
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
|
||||
const filesByManifest = new Map();
|
||||
for (const row of filesResult.recordset) {
|
||||
for (const row of filesResult.rows) {
|
||||
const list = filesByManifest.get(row.manifestId) ?? [];
|
||||
list.push(shapeAttachmentMeta(row));
|
||||
filesByManifest.set(row.manifestId, list);
|
||||
}
|
||||
|
||||
return result.recordset.map((row) => ({
|
||||
return result.rows.map((row) => ({
|
||||
...shapeSummary(row),
|
||||
files: filesByManifest.get(row.id) ?? [],
|
||||
}));
|
||||
|
||||
@@ -1,42 +1,19 @@
|
||||
import {
|
||||
DELETE_LINES_BY_MANIFEST,
|
||||
DELIVERY_TYPE,
|
||||
GET_DELIVERY_HEADER,
|
||||
GET_NEXT_ATTACHMENT_SORT,
|
||||
CREATE_FILE,
|
||||
CREATE_ATTACHMENT,
|
||||
UPDATE_MANIFEST,
|
||||
} from '../../providers/mssql/queries/delivery.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/delivery.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/delivery.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import taxEnsure from '../tax/ensure.js';
|
||||
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
|
||||
import { loadDelivery, shapeSummary } from './helpers.js';
|
||||
import { insertAttachments } from './attachments.js';
|
||||
import { assertSupplier } from './writeLines.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveSelectedCompanyAccess(pool);
|
||||
const ensureTax = taxEnsure(pool);
|
||||
export default function update(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const ensureTax = taxEnsure(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Updates a delivery's header and/or lines.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The id of the delivery to update.
|
||||
* @param {object} patch - The update patch.
|
||||
* @param {string} [patch.number] - The new delivery number.
|
||||
* @param {string} [patch.date] - The new delivery date.
|
||||
* @param {number} [patch.supplierId] - The new supplier id.
|
||||
* @param {string} [patch.externalNumber] - The new external (supplier) reference number.
|
||||
* @param {Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null}>} [patch.lines] - The new delivery lines.
|
||||
* @returns {Promise<{id: number, number: string, type: string, entityId: number, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>} The updated delivery.
|
||||
* @throws {Error} If the delivery is not found.
|
||||
*
|
||||
* @example
|
||||
* const delivery = await store.delivery.update(42, 5, { number: 'D-2', lines: [...] });
|
||||
* // => { id, number, type, entityId, date, lines: [...] }
|
||||
*/
|
||||
return async (googleAuthId, id, patch = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Delivery not found');
|
||||
@@ -63,17 +40,14 @@ export default function update(pool) {
|
||||
|
||||
const { companyId } = await access(googleAuthId);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
const run = async (runner, tx) => {
|
||||
const existing = await runner(queries.GET_DELIVERY_HEADER, {
|
||||
id,
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
});
|
||||
|
||||
try {
|
||||
const existing = await request(pool, tx)
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.query(GET_DELIVERY_HEADER);
|
||||
|
||||
const current = existing.recordset[0];
|
||||
const current = existing.rows[0];
|
||||
if (!current) {
|
||||
throw new Error('Delivery not found');
|
||||
}
|
||||
@@ -99,7 +73,7 @@ export default function update(pool) {
|
||||
let supplierId = summary.supplierId;
|
||||
if (hasSupplierId) {
|
||||
supplierId = await assertSupplier(
|
||||
pool,
|
||||
provider,
|
||||
tx,
|
||||
companyId,
|
||||
patch.supplierId ?? null,
|
||||
@@ -113,22 +87,22 @@ export default function update(pool) {
|
||||
: null;
|
||||
}
|
||||
|
||||
await request(pool, tx)
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('type', DELIVERY_TYPE)
|
||||
.input('number', number)
|
||||
.input('entityId', supplierId)
|
||||
.input('externalNumber', externalNumber)
|
||||
.input('date', date)
|
||||
.query(UPDATE_MANIFEST);
|
||||
await runner(queries.UPDATE_MANIFEST, {
|
||||
id,
|
||||
companyId,
|
||||
type: queries.DELIVERY_TYPE,
|
||||
number,
|
||||
entityId: supplierId,
|
||||
externalNumber,
|
||||
date,
|
||||
});
|
||||
|
||||
if (hasLines) {
|
||||
await request(pool, tx)
|
||||
.input('manifestId', id)
|
||||
.query(DELETE_LINES_BY_MANIFEST);
|
||||
await runner(queries.DELETE_LINES_BY_MANIFEST, {
|
||||
manifestId: id,
|
||||
});
|
||||
|
||||
await insertLines(pool, tx, {
|
||||
await insertLines(provider, tx, {
|
||||
manifestId: id,
|
||||
lines: normalizedLines,
|
||||
ensureTax,
|
||||
@@ -137,28 +111,24 @@ export default function update(pool) {
|
||||
|
||||
if (hasFiles) {
|
||||
await insertAttachments(
|
||||
pool,
|
||||
provider,
|
||||
tx,
|
||||
{
|
||||
GET_NEXT_ATTACHMENT_SORT,
|
||||
CREATE_FILE,
|
||||
CREATE_ATTACHMENT,
|
||||
GET_NEXT_ATTACHMENT_SORT: queries.GET_NEXT_ATTACHMENT_SORT,
|
||||
CREATE_FILE: queries.CREATE_FILE,
|
||||
CREATE_ATTACHMENT: queries.CREATE_ATTACHMENT,
|
||||
},
|
||||
id,
|
||||
patch.files,
|
||||
);
|
||||
}
|
||||
|
||||
const delivery = await loadDelivery(pool, tx, companyId, id);
|
||||
await tx.commit();
|
||||
return delivery;
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
return loadDelivery(provider, tx, companyId, id);
|
||||
};
|
||||
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction((runner, tx) => run(runner, tx));
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { GET_SUPPLIER_BY_ID, SUPPLIER_GROUP_NAME } from '../../providers/mssql/queries/entity.js';
|
||||
import request from '../request.js';
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/entity.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/entity.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export async function assertSupplier(pool, tx, companyId, supplierId) {
|
||||
export async function assertSupplier(provider, tx, companyId, supplierId) {
|
||||
if (supplierId == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -9,13 +10,17 @@ export async function assertSupplier(pool, tx, companyId, supplierId) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
|
||||
const result = await request(pool, tx)
|
||||
.input('id', supplierId)
|
||||
.input('companyId', companyId)
|
||||
.input('groupName', SUPPLIER_GROUP_NAME)
|
||||
.query(GET_SUPPLIER_BY_ID);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
const runner = makeRunner(provider, tx);
|
||||
|
||||
if (!result.recordset[0]) {
|
||||
const result = await runner(queries.GET_SUPPLIER_BY_ID, {
|
||||
id: supplierId,
|
||||
companyId,
|
||||
groupName: queries.SUPPLIER_GROUP_NAME,
|
||||
});
|
||||
|
||||
if (!result.rows[0]) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
return supplierId;
|
||||
|
||||
Reference in New Issue
Block a user