275 lines
10 KiB
JavaScript
275 lines
10 KiB
JavaScript
import {
|
|
COUNT_MANIFESTS as MSSQL_COUNT_MANIFESTS,
|
|
GET_MANIFEST_LINES_BY_MANIFEST_IDS as MSSQL_LINES_BY_MANIFEST_IDS,
|
|
RETOURE_TYPE as MSSQL_RETOURE_TYPE,
|
|
} from '../../providers/mssql/queries/manifestDoc.js';
|
|
import {
|
|
COUNT_MANIFESTS as SQLITE_COUNT_MANIFESTS,
|
|
GET_MANIFEST_LINES_BY_MANIFEST_IDS as SQLITE_LINES_BY_MANIFEST_IDS,
|
|
RETOURE_TYPE as SQLITE_RETOURE_TYPE,
|
|
} from '../../providers/sqlite3/queries/manifestDoc.js';
|
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
|
import { shapeSummary } from '../manifestDoc/helpers.js';
|
|
import { shapeLine } from '../manifestLines.js';
|
|
import makeRunner from '../runner.js';
|
|
|
|
export default function list(provider) {
|
|
const access = resolveSelectedCompanyAccess(provider);
|
|
const isSqlite = provider?.dialect === 'sqlite3';
|
|
const sqlLinesByManifestIds = isSqlite
|
|
? SQLITE_LINES_BY_MANIFEST_IDS
|
|
: MSSQL_LINES_BY_MANIFEST_IDS;
|
|
const retoureType = isSqlite ? SQLITE_RETOURE_TYPE : MSSQL_RETOURE_TYPE;
|
|
const sqlCountManifests = isSqlite
|
|
? SQLITE_COUNT_MANIFESTS
|
|
: MSSQL_COUNT_MANIFESTS;
|
|
|
|
/**
|
|
* Lists the retoures of the user's selected company.
|
|
*
|
|
* @param {number} googleAuthId - The id of the Google auth user.
|
|
* @param {object} [opts] - Optional pagination and filter options.
|
|
* @param {number} [opts.offset=0] - Number of retoures to skip.
|
|
* @param {number} [opts.limit=50] - Number of retoures to return.
|
|
* @param {string} [opts.search] - Free-text search matching retoure number, external number, or customer name.
|
|
* @param {boolean} [opts.onlyUnpaid=false] - When true, returns only unpaid retoures.
|
|
* @param {boolean} [opts.onlyUndelivered=false] - When true, returns only undelivered retoures.
|
|
* @param {number} [opts.seekMonths] - Negative integer for cutoff months ago (e.g. -1).
|
|
* @param {object} [tx] - Optional transaction.
|
|
* @returns {Promise<{retoures: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>}
|
|
*/
|
|
return async (googleAuthId, opts, tx) => {
|
|
let offset = null;
|
|
let limit = null;
|
|
let isPaged = false;
|
|
let resolvedTx = tx;
|
|
|
|
const hasFilter = opts != null && typeof opts === 'object' && !opts._isTx && (
|
|
opts.offset !== undefined ||
|
|
opts.limit !== undefined ||
|
|
(typeof opts.search === 'string' && opts.search.trim() !== '') ||
|
|
opts.onlyUnpaid === true ||
|
|
opts.onlyUndelivered === true ||
|
|
opts.entityId !== undefined ||
|
|
opts.seekMonths !== undefined
|
|
);
|
|
|
|
if (opts != null && typeof opts === 'object' && !opts._isTx) {
|
|
if (hasFilter || opts.offset !== undefined || opts.limit !== undefined) {
|
|
isPaged = true;
|
|
const parsedOffset = Number(opts.offset ?? 0);
|
|
const parsedLimit = Number(opts.limit ?? 50);
|
|
if (!Number.isInteger(parsedOffset) || parsedOffset < 0) {
|
|
throw new Error('Invalid offset');
|
|
}
|
|
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
|
throw new Error('Invalid limit');
|
|
}
|
|
offset = parsedOffset;
|
|
limit = parsedLimit;
|
|
}
|
|
} else if (opts != null) {
|
|
resolvedTx = opts;
|
|
}
|
|
|
|
const runner = makeRunner(provider, resolvedTx);
|
|
const { companyId } = await access(googleAuthId, runner);
|
|
|
|
const conditions = ['m.company_id = @companyId', `m.${isSqlite ? '"type"' : '[type]'} = @type`];
|
|
const params = {
|
|
companyId,
|
|
type: retoureType,
|
|
};
|
|
|
|
if (opts?.entityId !== undefined && opts.entityId !== null) {
|
|
const entityId = Number(opts.entityId);
|
|
if (!Number.isInteger(entityId) || entityId <= 0) {
|
|
throw new Error('Invalid entityId');
|
|
}
|
|
params.entityId = entityId;
|
|
conditions.push('m.entity_id = @entityId');
|
|
}
|
|
|
|
if (opts?.seekMonths !== undefined) {
|
|
if (typeof opts.seekMonths !== 'number' || !Number.isInteger(opts.seekMonths)) {
|
|
throw new Error('Invalid seekMonths');
|
|
}
|
|
const d = new Date();
|
|
d.setMonth(d.getMonth() - Math.abs(opts.seekMonths));
|
|
params.dateTo = d.toISOString();
|
|
conditions.push(`m.${isSqlite ? '"date"' : '[date]'} <= @dateTo`);
|
|
}
|
|
|
|
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
|
|
params.searchPattern = `%${opts.search.trim()}%`;
|
|
if (isSqlite) {
|
|
conditions.push(`(
|
|
m."number" LIKE @searchPattern
|
|
OR m.external_number LIKE @searchPattern
|
|
OR e.name LIKE @searchPattern
|
|
OR e.first_name LIKE @searchPattern
|
|
OR (IFNULL(e.first_name, '') || ' ' || IFNULL(e.name, '')) LIKE @searchPattern
|
|
OR (IFNULL(e.name, '') || ' ' || IFNULL(e.first_name, '')) LIKE @searchPattern
|
|
)`);
|
|
} else {
|
|
conditions.push(`(
|
|
m.[number] LIKE @searchPattern
|
|
OR m.external_number LIKE @searchPattern
|
|
OR e.name LIKE @searchPattern
|
|
OR e.first_name LIKE @searchPattern
|
|
OR (ISNULL(e.first_name, '') + ' ' + ISNULL(e.name, '')) LIKE @searchPattern
|
|
OR (ISNULL(e.name, '') + ' ' + ISNULL(e.first_name, '')) LIKE @searchPattern
|
|
)`);
|
|
}
|
|
}
|
|
|
|
if (opts?.onlyUnpaid === true) {
|
|
conditions.push('COALESCE(rs.is_paid, 0) = 0');
|
|
}
|
|
|
|
if (opts?.onlyUndelivered === true) {
|
|
conditions.push('COALESCE(rs.is_shipped, 0) = 0');
|
|
}
|
|
|
|
const whereClause = conditions.join('\n AND ');
|
|
const sqlCount = `
|
|
SELECT COUNT(*) AS total
|
|
FROM manifest m
|
|
LEFT JOIN retouresummary rs ON rs.manifest_id = m.id
|
|
LEFT JOIN entity e ON e.id = m.entity_id
|
|
WHERE ${whereClause}
|
|
`;
|
|
|
|
let sqlList;
|
|
if (isSqlite) {
|
|
sqlList = `
|
|
SELECT
|
|
m.id,
|
|
m."number",
|
|
m."type",
|
|
m.entity_id AS entityId,
|
|
m.external_number AS externalNumber,
|
|
m."date",
|
|
m.created_at AS createdAt,
|
|
rs.positions,
|
|
rs.gross_total AS grossTotal,
|
|
rs.paid_total AS paidTotal,
|
|
rs.ordered_quantity AS orderedQuantity,
|
|
rs.shipped_quantity AS shippedQuantity,
|
|
rs.is_paid AS isPaid,
|
|
rs.is_shipped AS isShipped,
|
|
rs.customer_name AS customerName
|
|
FROM manifest m
|
|
LEFT JOIN retouresummary rs ON rs.manifest_id = m.id
|
|
LEFT JOIN entity e ON e.id = m.entity_id
|
|
WHERE ${whereClause}
|
|
ORDER BY m."date" DESC, m.id DESC
|
|
${isPaged ? 'LIMIT @limit OFFSET @offset' : ''}
|
|
`;
|
|
} else {
|
|
sqlList = `
|
|
SELECT
|
|
m.id,
|
|
m.[number],
|
|
m.[type],
|
|
m.entity_id AS entityId,
|
|
m.external_number AS externalNumber,
|
|
m.[date],
|
|
m.created_at AS createdAt,
|
|
rs.positions,
|
|
rs.gross_total AS grossTotal,
|
|
rs.paid_total AS paidTotal,
|
|
rs.ordered_quantity AS orderedQuantity,
|
|
rs.shipped_quantity AS shippedQuantity,
|
|
rs.is_paid AS isPaid,
|
|
rs.is_shipped AS isShipped,
|
|
rs.customer_name AS customerName
|
|
FROM manifest m
|
|
LEFT JOIN retouresummary rs ON rs.manifest_id = m.id
|
|
LEFT JOIN entity e ON e.id = m.entity_id
|
|
WHERE ${whereClause}
|
|
ORDER BY m.[date] DESC, m.id DESC
|
|
${isPaged ? 'OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY' : ''}
|
|
`;
|
|
}
|
|
|
|
const hasUserFilters = (
|
|
opts?.seekMonths !== undefined ||
|
|
(typeof opts?.search === 'string' && opts.search.trim() !== '') ||
|
|
opts?.onlyUnpaid === true ||
|
|
opts?.onlyUndelivered === true
|
|
);
|
|
|
|
let manifestRows;
|
|
let total = 0;
|
|
let unfilteredTotal = 0;
|
|
|
|
if (isPaged) {
|
|
params.offset = offset;
|
|
params.limit = limit;
|
|
const [countResult, listResult, unfilteredCountResult] = await Promise.all([
|
|
runner.query(sqlCount, params),
|
|
runner.query(sqlList, params),
|
|
hasUserFilters ? runner.query(sqlCountManifests, { companyId, type: retoureType }) : null,
|
|
]);
|
|
total = Number(countResult.rows[0]?.total ?? 0);
|
|
unfilteredTotal = hasUserFilters
|
|
? Number(unfilteredCountResult?.rows[0]?.total ?? 0)
|
|
: total;
|
|
manifestRows = listResult.rows;
|
|
} else {
|
|
const result = await runner.query(sqlList, params);
|
|
manifestRows = result.rows;
|
|
total = manifestRows.length;
|
|
if (hasUserFilters) {
|
|
const countRes = await runner.query(sqlCountManifests, { companyId, type: retoureType });
|
|
unfilteredTotal = Number(countRes.rows[0]?.total ?? 0);
|
|
} else {
|
|
unfilteredTotal = total;
|
|
}
|
|
}
|
|
|
|
const manifestIds = manifestRows.map((r) => r.id);
|
|
const linesByManifestId = new Map();
|
|
|
|
if (manifestIds.length > 0) {
|
|
const linesResult = await runner.query(sqlLinesByManifestIds, {
|
|
manifestIdsJson: JSON.stringify(manifestIds),
|
|
});
|
|
for (const row of linesResult.rows) {
|
|
let lines = linesByManifestId.get(row.manifestId);
|
|
if (!lines) {
|
|
lines = [];
|
|
linesByManifestId.set(row.manifestId, lines);
|
|
}
|
|
lines.push(shapeLine(row));
|
|
}
|
|
}
|
|
|
|
const retoures = manifestRows.map((row) => ({
|
|
...shapeSummary(retoureType, row),
|
|
lines: linesByManifestId.get(row.id) ?? [],
|
|
positions: row.positions !== undefined && row.positions !== null ? Number(row.positions) : undefined,
|
|
grossTotal: row.grossTotal !== undefined && row.grossTotal !== null ? Number(row.grossTotal) : undefined,
|
|
paidTotal: row.paidTotal !== undefined && row.paidTotal !== null ? Number(row.paidTotal) : undefined,
|
|
orderedQuantity: row.orderedQuantity !== undefined && row.orderedQuantity !== null ? Number(row.orderedQuantity) : undefined,
|
|
shippedQuantity: row.shippedQuantity !== undefined && row.shippedQuantity !== null ? Number(row.shippedQuantity) : undefined,
|
|
isPaid: row.isPaid !== undefined && row.isPaid !== null ? Boolean(row.isPaid) : undefined,
|
|
isShipped: row.isShipped !== undefined && row.isShipped !== null ? Boolean(row.isShipped) : undefined,
|
|
customerName: row.customerName ?? undefined,
|
|
}));
|
|
|
|
if (isPaged) {
|
|
return {
|
|
retoures,
|
|
total,
|
|
unfilteredTotal,
|
|
offset,
|
|
limit,
|
|
};
|
|
}
|
|
|
|
return retoures;
|
|
};
|
|
}
|