This commit is contained in:
seb
2026-10-21 03:31:55 +02:00
parent 63f4c3a0e4
commit e4e4d2dcf0
21 changed files with 243 additions and 193 deletions

View File

@@ -974,7 +974,7 @@
* type: string
* ManifestLine:
* type: object
* required: [id, sku, name, pu, amount, net, rebate, taxrate, comment]
* required: [id, sku, name, pu, amount, net, rebate, taxrate, shippable, comment]
* description: Line shape for quote and order
* properties:
* id:
@@ -1001,6 +1001,9 @@
* comment:
* type: string
* nullable: true
* shippable:
* type: boolean
* description: false for non-shippable lines (e.g. shipping cost, rounding); excluded from shipment and from order completeness (`isShipped`)
* ManifestLineInput:
* type: object
* required: [amount, net, taxrate]
@@ -1030,6 +1033,9 @@
* comment:
* type: string
* nullable: true
* shippable:
* type: boolean
* description: Defaults to true. Set false for non-shippable lines (e.g. shipping cost, rounding); they cannot be shipped and do not make the order incomplete.
* QuoteSummary:
* type: object
* required: [id, number, type, date]

View File

@@ -151,7 +151,7 @@ export function register(app, basePath = '') {
* Creates a sales order with header and lines in one transaction.
* `date` is the document date (expected delivery).
* `entityId` must be a company customer (not Lieferanten).
* Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
* Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`; `shippable` defaults to `true` (`false` = not shippable, excluded from order completeness).
* security:
* - cookieAuth: []
* x-markdown: order/create.md

View File

@@ -181,11 +181,14 @@ export function register(app, basePath = '') {
* description: Positions to ship
* items:
* type: object
* required: [sku]
* description: Provide `sku` for order lines with a SKU, or `orderLineId` for SKU-less order lines (exactly one of them).
* properties:
* sku:
* type: string
* description: Article SKU
* description: Article SKU (omit for SKU-less order lines)
* orderLineId:
* type: integer
* description: Order line id (`lines[].id` of the order); required for SKU-less lines, ignored when `sku` is given
* amount:
* type: number
* description: Quantity to ship. Defaults to remaining unshipped quantity for this SKU.
@@ -212,7 +215,7 @@ export function register(app, basePath = '') {
* shipment:
* $ref: '#/components/schemas/Shipment'
* 400:
* description: Validation error or article not in order
* description: Validation error, article not in order, or article not shippable
* content:
* application/json:
* schema:

View File

@@ -30,6 +30,7 @@ function calculatePaidTotal(payments = []) {
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;

View File

@@ -538,6 +538,7 @@ export async function executeCompanyExport({
'"net"',
'"taxrate"',
'"sort"',
'"shippable"',
];
const manifestCsvRows = [manifestCsvHeaders.join(';')];
@@ -588,6 +589,7 @@ export async function executeCompanyExport({
formatCsvField(formatGermanFloat(line.net ?? 0)),
formatCsvField(formatGermanFloat(line.taxrate ?? 19)),
formatCsvField(line.sort ?? 0),
formatCsvField(line.shippable !== false ? '1' : '0'),
].join(';'));
}
}

View File

@@ -261,6 +261,14 @@ export async function importOrders({
const rebate = parseGermanFloat(it.rebate ?? it.fRabatt, 0);
const comment = cleanValue(it.comment ?? it.cHinweis) || null;
let shippable = true;
if (it.shippable !== undefined && it.shippable !== null && String(it.shippable).trim() !== '') {
const s = String(it.shippable).trim().toLowerCase();
shippable = s !== '0' && s !== 'false';
} else if (/^(summe glätten|shippingcost|versandkosten)$/i.test(name.trim())) {
shippable = false;
}
return {
sku,
name,
@@ -269,6 +277,7 @@ export async function importOrders({
taxrate,
rebate,
comment,
shippable,
};
});
@@ -355,9 +364,15 @@ export async function importOrders({
const isShipped = String(m.shipped ?? '').trim() === '1';
if (isShipped || shippingType) {
const shippableLines = lines
.filter((l) => l.sku)
.map((l) => ({ sku: l.sku, amount: l.amount }));
const orderLines = createdOrder?.lines || lines;
const shippableLines = orderLines
.filter((l) => l.shippable !== false)
.map((l) => {
if (l.sku) return { sku: l.sku, amount: l.amount };
if (l.id) return { orderLineId: l.id, amount: l.amount };
return null;
})
.filter(Boolean);
if (shippableLines.length > 0) {
try {