This commit is contained in:
seb
2026-10-26 19:22:01 +01:00
parent a5a9e51898
commit 69388c789f
23 changed files with 1307 additions and 28 deletions

359
src/api/routes/retoure.js Normal file
View File

@@ -0,0 +1,359 @@
import * as db from '../../db/retoure.js';
import { emit } from '../sse.js';
// #region Helpers
function requireSelectedCompany(req) {
const companyId = req.user.selectedCompanyId;
if (companyId == null) {
return { error: 'No company selected' };
}
return { companyId };
}
function retoureErrorStatus(error) {
if (error.message === 'No company selected') return 400;
if (error.message === 'Not authorized') return 403;
if (error.message === 'Company not found') return 404;
if (error.message === 'Retoure not found') return 404;
if (error.message === 'Manifest not found') return 404;
if (error.message === 'Entity not found') return 404;
if (error.message === 'Article not found') return 404;
if (error.message === 'No entity selected') return 400;
if (error.message === 'Retoure number already exists') return 409;
if (error.message === 'Invalid entityId') return 400;
if (error.message === 'Invalid retoure') return 400;
if (error.message === 'Invalid lines') return 400;
if (error.message === 'Invalid offset') return 400;
if (error.message === 'Invalid limit') return 400;
if (error.message === 'Invalid seekMonths') return 400;
if (error.message?.includes('parent article') || error.message?.includes('A parent article')) return 400;
return 500;
}
// #endregion
/**
* Registers retoure (customer return) routes for the authenticated user's selected company.
* @param {import('express').Application} app
* @param {string} [basePath='']
*/
export function register(app, basePath = '') {
/**
* @openapi
* /retoure:
* get:
* tags: [Retoure]
* summary: List retoures
* description: |
* Lists customer return manifests (`manifest` rows with `type = 'retoure'`)
* for the authenticated user's selected company.
* security:
* - cookieAuth: []
* parameters:
* - in: query
* name: offset
* schema:
* type: integer
* default: 0
* - in: query
* name: limit
* schema:
* type: integer
* default: 50
* - in: query
* name: search
* schema:
* type: string
* - in: query
* name: onlyUnpaid
* schema:
* type: boolean
* - in: query
* name: onlyUndelivered
* schema:
* type: boolean
* - in: query
* name: entityId
* schema:
* type: integer
* - in: query
* name: seekMonths
* schema:
* type: integer
* responses:
* 200:
* description: List of retoures
* 400:
* description: Validation error
* 403:
* description: Not authorized
*/
app.get(`${basePath}/retoure`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const opts = {};
if (req.query.offset !== undefined) opts.offset = req.query.offset;
if (req.query.limit !== undefined) opts.limit = req.query.limit;
if (typeof req.query.search === 'string') opts.search = req.query.search;
if (req.query.onlyUnpaid !== undefined) opts.onlyUnpaid = req.query.onlyUnpaid === 'true' || req.query.onlyUnpaid === '1';
if (req.query.onlyUndelivered !== undefined) opts.onlyUndelivered = req.query.onlyUndelivered === 'true' || req.query.onlyUndelivered === '1';
if (req.query.entityId !== undefined) opts.entityId = req.query.entityId;
if (req.query.seekMonths !== undefined) opts.seekMonths = Number(req.query.seekMonths);
try {
const result = await db.list(req.user.id, opts);
res.status(200).json(result);
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('List retoures failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure/{id}:
* get:
* tags: [Retoure]
* summary: Get retoure by ID
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Retoure details
* 404:
* description: Retoure not found
*/
app.get(`${basePath}/retoure/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const retoure = await db.getById(req.user.id, id);
res.status(200).json({ retoure });
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Get retoure failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure:
* post:
* tags: [Retoure]
* summary: Create retoure
* security:
* - cookieAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [date]
* properties:
* number:
* type: string
* date:
* type: string
* entityId:
* type: integer
* externalNumber:
* type: string
* lines:
* type: array
* responses:
* 201:
* description: Retoure created
*/
app.post(`${basePath}/retoure`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
try {
const retoure = await db.create(req.user.id, req.body);
emit('retoure', { companyId: companyCheck.companyId });
res.status(201).json({ retoure });
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Create retoure failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure/{id}:
* put:
* tags: [Retoure]
* summary: Update retoure
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* responses:
* 200:
* description: Retoure updated
*/
app.put(`${basePath}/retoure/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const retoure = await db.update(req.user.id, id, req.body);
emit('retoure', { companyId: companyCheck.companyId });
res.status(200).json({ retoure });
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Update retoure failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure/{id}:
* delete:
* tags: [Retoure]
* summary: Delete retoure
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 204:
* description: Retoure deleted
*/
app.delete(`${basePath}/retoure/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
await db.remove(req.user.id, id);
emit('retoure', { companyId: companyCheck.companyId });
res.sendStatus(204);
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Delete retoure failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure/{id}/shipment:
* post:
* tags: [Retoure]
* summary: Create shipment for retoure
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 201:
* description: Shipment created
*/
app.post(`${basePath}/retoure/:id/shipment`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const shipment = await db.createShipment(req.user.id, id, req.body);
emit('retoure', { companyId: companyCheck.companyId });
emit('shipment', { companyId: companyCheck.companyId });
res.status(201).json({ shipment });
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Create shipment for retoure failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /retoure/rebuild-summaries:
* post:
* tags: [Retoure]
* summary: Rebuild retoure summaries
* security:
* - cookieAuth: []
* responses:
* 200:
* description: Summaries rebuilt
*/
app.post(`${basePath}/retoure/rebuild-summaries`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
try {
const result = await db.rebuildSummaries(req.user.id);
res.status(200).json(result);
} catch (error) {
const status = retoureErrorStatus(error);
if (status === 500) console.error('Rebuild retoure summaries failed:', error);
res.status(status).json({ error: error.message });
}
});
}

319
src/api/routes/return.js Normal file
View File

@@ -0,0 +1,319 @@
import * as db from '../../db/return.js';
import { emit } from '../sse.js';
// #region Helpers
function requireSelectedCompany(req) {
const companyId = req.user.selectedCompanyId;
if (companyId == null) {
return { error: 'No company selected' };
}
return { companyId };
}
function returnErrorStatus(error) {
if (error.message === 'No company selected') return 400;
if (error.message === 'Not authorized') return 403;
if (error.message === 'Company not found') return 404;
if (error.message === 'Return not found') return 404;
if (error.message === 'Supplier not found') return 404;
if (error.message === 'No supplier selected') return 400;
if (error.message === 'Return number already exists') return 409;
if (error.message === 'Invalid supplierId') return 400;
if (error.message === 'Invalid return') return 400;
if (error.message === 'Invalid lines') return 400;
if (error.message === 'Invalid offset') return 400;
if (error.message === 'Invalid limit') return 400;
if (error.message === 'Return lines already booked on ledger') return 409;
if (error.message === 'Return in use in ledger') return 409;
return 500;
}
// #endregion
/**
* Registers return (supplier return) routes for the authenticated user's selected company.
* @param {import('express').Application} app
* @param {string} [basePath='']
*/
export function register(app, basePath = '') {
/**
* @openapi
* /return:
* get:
* tags: [Return]
* summary: List returns
* security:
* - cookieAuth: []
* parameters:
* - in: query
* name: offset
* schema:
* type: integer
* - in: query
* name: limit
* schema:
* type: integer
* - in: query
* name: search
* schema:
* type: string
* - in: query
* name: onlyUnbooked
* schema:
* type: boolean
* - in: query
* name: supplierId
* schema:
* type: integer
* responses:
* 200:
* description: List of returns
*/
app.get(`${basePath}/return`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const opts = {};
if (req.query.offset !== undefined) opts.offset = req.query.offset;
if (req.query.limit !== undefined) opts.limit = req.query.limit;
if (typeof req.query.search === 'string') opts.search = req.query.search;
if (req.query.onlyUnbooked !== undefined) opts.onlyUnbooked = req.query.onlyUnbooked === 'true' || req.query.onlyUnbooked === '1';
if (req.query.supplierId !== undefined) opts.supplierId = req.query.supplierId;
try {
const result = await db.list(req.user.id, opts);
res.status(200).json(result);
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('List returns failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return/{id}:
* get:
* tags: [Return]
* summary: Get return by ID
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Return details
*/
app.get(`${basePath}/return/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const ret = await db.getById(req.user.id, id);
res.status(200).json({ return: ret });
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Get return failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return:
* post:
* tags: [Return]
* summary: Create return
* security:
* - cookieAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [date, supplierId]
* responses:
* 201:
* description: Return created
*/
app.post(`${basePath}/return`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
try {
const ret = await db.create(req.user.id, req.body);
emit('return', { companyId: companyCheck.companyId });
res.status(201).json({ return: ret });
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Create return failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return/{id}:
* put:
* tags: [Return]
* summary: Update return
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Return updated
*/
app.put(`${basePath}/return/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const ret = await db.update(req.user.id, id, req.body);
emit('return', { companyId: companyCheck.companyId });
res.status(200).json({ return: ret });
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Update return failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return/{id}:
* delete:
* tags: [Return]
* summary: Delete return
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 204:
* description: Return deleted
*/
app.delete(`${basePath}/return/:id`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
await db.remove(req.user.id, id);
emit('return', { companyId: companyCheck.companyId });
res.sendStatus(204);
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Delete return failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return/{id}/book:
* post:
* tags: [Return]
* summary: Book return to ledger
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Return booked
*/
app.post(`${basePath}/return/:id/book`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'id must be an integer' });
}
try {
const ret = await db.book(req.user.id, { id, warehouseId: req.body?.warehouseId });
emit('return', { companyId: companyCheck.companyId });
emit('stock', { companyId: companyCheck.companyId });
res.status(200).json({ return: ret });
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Book return failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /return/rebuild-summaries:
* post:
* tags: [Return]
* summary: Rebuild return summaries
* security:
* - cookieAuth: []
* responses:
* 200:
* description: Summaries rebuilt
*/
app.post(`${basePath}/return/rebuild-summaries`, async (req, res) => {
const companyCheck = requireSelectedCompany(req);
if (companyCheck.error) {
return res.status(400).json({ error: companyCheck.error });
}
try {
const result = await db.rebuildSummaries(req.user.id);
res.status(200).json(result);
} catch (error) {
const status = returnErrorStatus(error);
if (status === 500) console.error('Rebuild return summaries failed:', error);
res.status(status).json({ error: error.message });
}
});
}

148
src/db/retoure.js Normal file
View File

@@ -0,0 +1,148 @@
import { getStore } from './store.js';
import { formatAttachments } from '../util/manifestAttachment.js';
function round2(num) {
return Math.round((Number(num) || 0) * 100) / 100;
}
function calculateGrossTotal(lines = []) {
let total = 0;
for (const line of lines) {
const amount = Number(line.amount || 0);
const net = Number(line.net || 0);
const rebate = Number(line.rebate || 0);
const taxrate = Number(line.taxrate || 0);
const netAfterRebate = net * (1 - rebate / 100);
const gross = netAfterRebate * (1 + taxrate / 100);
total += amount * gross;
}
return round2(total);
}
function calculatePaidTotal(payments = []) {
let total = 0;
for (const payment of payments) {
total += Number(payment.amount || 0);
}
return round2(total);
}
function calculateOrderedQuantity(lines = []) {
let qty = 0;
for (const line of lines) {
if (line.shippable === false) continue;
qty += Number(line.amount || 0);
}
return Math.round(qty * 1000) / 1000;
}
function calculateShippedQuantity(shipments = []) {
let qty = 0;
for (const shipment of shipments) {
for (const line of shipment.lines || []) {
qty += Number(line.amount || 0);
}
}
return Math.round(qty * 1000) / 1000;
}
async function enrichSingleRetoure(store, googleAuthId, retoure) {
if (!retoure) return retoure;
const [shippings, payments, shipments, rawFiles] = await Promise.all([
store.shipping.listByManifestId(googleAuthId, retoure.id).catch(() => []),
store.payment.listByManifestId(googleAuthId, retoure.id).catch(() => []),
store.shipment.listByOrderId(googleAuthId, retoure.id).catch(() => []),
store.manifestattachment.listByManifestId(googleAuthId, retoure.id).catch(() => []),
]);
const files = await formatAttachments(rawFiles);
const lines = Array.isArray(retoure.lines) ? retoure.lines : [];
const paymentsList = Array.isArray(payments) ? payments : [];
const shipmentsList = Array.isArray(shipments) ? shipments : [];
const calculatedGross = calculateGrossTotal(lines);
const calculatedPaid = calculatePaidTotal(paymentsList);
const grossTotal = retoure.grossTotal !== undefined ? retoure.grossTotal : calculatedGross;
const paidTotal = retoure.paidTotal !== undefined ? retoure.paidTotal : calculatedPaid;
const orderedQuantity = retoure.orderedQuantity !== undefined
? retoure.orderedQuantity
: calculateOrderedQuantity(lines);
const shippedQuantity = retoure.shippedQuantity !== undefined
? retoure.shippedQuantity
: calculateShippedQuantity(shipmentsList);
const isPaid = retoure.isPaid !== undefined
? retoure.isPaid
: (Math.abs(grossTotal) < 0.01 || Math.abs(paidTotal - grossTotal) < 0.01);
const isShipped = retoure.isShipped !== undefined
? retoure.isShipped
: (orderedQuantity !== 0 && Math.abs(shippedQuantity - orderedQuantity) < 0.001);
return {
...retoure,
grossTotal,
paidTotal,
orderedQuantity,
shippedQuantity,
isPaid,
isShipped,
positions: retoure.positions ?? lines.length,
shippings: Array.isArray(shippings) ? shippings : [],
payments: paymentsList,
shipments: shipmentsList,
files,
};
}
export async function list(googleAuthId, opts) {
const store = await getStore();
const result = await store.retoure.list(googleAuthId, opts);
if (Array.isArray(result)) {
return Promise.all(result.map((r) => enrichSingleRetoure(store, googleAuthId, r)));
}
if (result && Array.isArray(result.retoures)) {
const retoures = await Promise.all(
result.retoures.map((r) => enrichSingleRetoure(store, googleAuthId, r))
);
return {
...result,
retoures,
};
}
return result;
}
export async function rebuildSummaries(googleAuthId) {
const store = await getStore();
return store.retoure.rebuildSummaries(googleAuthId);
}
export async function getById(googleAuthId, id) {
const store = await getStore();
const retoure = await store.retoure.getById(googleAuthId, id);
return enrichSingleRetoure(store, googleAuthId, retoure);
}
export async function create(googleAuthId, data) {
const store = await getStore();
const retoure = await store.retoure.create(googleAuthId, data);
return enrichSingleRetoure(store, googleAuthId, retoure);
}
export async function update(googleAuthId, id, data) {
const store = await getStore();
const retoure = await store.retoure.update(googleAuthId, id, data);
return enrichSingleRetoure(store, googleAuthId, retoure);
}
export async function remove(googleAuthId, id) {
const store = await getStore();
return store.retoure.delete(googleAuthId, id);
}
export async function createShipment(googleAuthId, id, data) {
const store = await getStore();
return store.retoure.createShipment(googleAuthId, id, data);
}

36
src/db/return.js Normal file
View File

@@ -0,0 +1,36 @@
import { getStore } from './store.js';
export async function list(googleAuthId, opts) {
const store = await getStore();
return store.return.list(googleAuthId, opts);
}
export async function rebuildSummaries(googleAuthId) {
const store = await getStore();
return store.return.rebuildSummaries(googleAuthId);
}
export async function getById(googleAuthId, id) {
const store = await getStore();
return store.return.getById(googleAuthId, id);
}
export async function create(googleAuthId, data) {
const store = await getStore();
return store.return.create(googleAuthId, data);
}
export async function update(googleAuthId, id, data) {
const store = await getStore();
return store.return.update(googleAuthId, id, data);
}
export async function remove(googleAuthId, id) {
const store = await getStore();
return store.return.delete(googleAuthId, id);
}
export async function book(googleAuthId, data) {
const store = await getStore();
return store.return.book(googleAuthId, data);
}

View File

@@ -30,6 +30,8 @@ const STORE_NAMESPACES = [
'entitycontact',
'quote',
'order',
'retoure',
'return',
'shipment',
'numbers',
'companysetting',

View File

@@ -290,9 +290,18 @@ export async function importOrders({
const parsedDate = dateStr ? new Date(dateStr) : new Date();
const validDate = !Number.isNaN(parsedDate.getTime()) ? parsedDate.toISOString() : new Date().toISOString();
const orderTotal = Math.round(
lines.reduce(
(sum, l) => sum + (l.amount * l.net * (1 - (l.rebate || 0) / 100) * (1 + (l.taxrate || 0) / 100)),
0
) * 100
) / 100;
const isRetoure = orderTotal < 0;
const domainStore = isRetoure ? store.retoure : store.order;
let createdOrder = null;
try {
createdOrder = await store.order.create(googleAuthId, {
createdOrder = await domainStore.create(googleAuthId, {
number: orderNumber,
externalNumber: cleanValue(m.extnr ?? m.cExterneAuftragsnummer) || null,
date: validDate,
@@ -311,7 +320,7 @@ export async function importOrders({
if (l.sku === offendingSku) l.sku = null;
}
try {
createdOrder = await store.order.create(googleAuthId, {
createdOrder = await domainStore.create(googleAuthId, {
number: orderNumber,
externalNumber: cleanValue(m.extnr ?? m.cExterneAuftragsnummer) || null,
date: validDate,
@@ -322,10 +331,10 @@ export async function importOrders({
existingOrderNumbers.add(orderNumber);
ordersImported++;
} catch (retryErr) {
log.warn(`Failed to create order ${orderNumber} after stripping parent SKU ${offendingSku}:`, retryErr.message);
log.warn(`Failed to create ${isRetoure ? 'retoure' : 'order'} ${orderNumber} after stripping parent SKU ${offendingSku}:`, retryErr.message);
}
} else {
log.warn(`Failed to create order ${orderNumber}:`, err.message);
log.warn(`Failed to create ${isRetoure ? 'retoure' : 'order'} ${orderNumber}:`, err.message);
}
}
@@ -333,29 +342,20 @@ export async function importOrders({
// Handle Payment
const isPayed = String(m.payed ?? '').trim() === '1';
const paymentType = cleanValue(m.paymentType);
if (isPayed && paymentType) {
const orderTotal = Math.round(
lines.reduce(
(sum, l) => sum + (l.amount * l.net * (1 - (l.rebate || 0) / 100) * (1 + (l.taxrate || 0) / 100)),
0
) * 100
) / 100;
if (orderTotal > 0) {
try {
const accountId = await resolveAccountId(paymentType);
if (accountId) {
await store.payment.create(googleAuthId, {
accountId,
manifestId: createdOrder.id,
type: paymentType,
amount: orderTotal,
});
paymentsImported++;
}
} catch (err) {
log.warn(`Failed to create payment for order ${orderNumber}:`, err.message);
if (isPayed && paymentType && orderTotal !== 0) {
try {
const accountId = await resolveAccountId(paymentType);
if (accountId) {
await store.payment.create(googleAuthId, {
accountId,
manifestId: createdOrder.id,
type: paymentType,
amount: orderTotal,
});
paymentsImported++;
}
} catch (err) {
log.warn(`Failed to create payment for ${isRetoure ? 'retoure' : 'order'} ${orderNumber}:`, err.message);
}
}