feat(issued-orders): make line items optional (issue by sections alone)
Items are no longer required on issued orders — an order can be issued purely from its rich-text sections (Obsah). - DocumentItemsEditor: opt-in allowEmpty prop lets the list go to zero rows (default false keeps the offers/invoices at-least-one-item rule). - IssuedOrderDetail: allowEmpty on the editor; a saved order with no items reopens empty (not a blank starter row); submit guard now requires at least one item OR a non-empty section, not an item. - PDF: with no items the billing heading, items table and total row are omitted entirely — a sections-only order shows only its Obsah. - Service: finalize (draft->sent) and create-as-non-draft reject a completely blank order (no items AND no sections) with a Czech 400 (empty_document); drafts may still be saved empty as WIP. - Tests: sections-only finalize + blank-order rejection + sections-only PDF render; existing finalize fixtures given minimal content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -301,7 +301,9 @@ export default function IssuedOrderDetail() {
|
||||
// Issued orders have no Sleva column; keep the shared item shape happy.
|
||||
discount: 0,
|
||||
}))
|
||||
: [emptyDocumentItem()];
|
||||
: // Issued orders may be issued by sections alone — a saved order with
|
||||
// no items reopens with an empty list (not a blank starter row).
|
||||
[];
|
||||
setItems(mappedItems);
|
||||
|
||||
const mappedSections =
|
||||
@@ -401,8 +403,18 @@ export default function IssuedOrderDetail() {
|
||||
const newErrors: Record<string, string> = {};
|
||||
if (!form.supplier_id) newErrors.supplier_id = "Vyberte dodavatele";
|
||||
if (!form.order_date) newErrors.order_date = "Zadejte datum";
|
||||
if (items.length === 0 || items.every((i) => !i.description.trim())) {
|
||||
newErrors.items = "Přidejte alespoň jednu položku";
|
||||
// Items are optional (an order may be issued by sections alone), but a
|
||||
// completely blank order must not be finalizable: require at least one
|
||||
// item with a description OR one non-empty section (title or content).
|
||||
const hasItem = items.some((i) => i.description.trim());
|
||||
const hasSection = sections.some(
|
||||
(s) =>
|
||||
(s.title_cz || "").trim() ||
|
||||
(s.title || "").trim() ||
|
||||
(s.content || "").replace(/<[^>]*>/g, "").trim(),
|
||||
);
|
||||
if (!hasItem && !hasSection) {
|
||||
newErrors.items = "Přidejte alespoň jednu položku nebo obsah";
|
||||
}
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
@@ -899,6 +911,7 @@ export default function IssuedOrderDetail() {
|
||||
currency={form.currency}
|
||||
readOnly={!editable}
|
||||
error={errors.items}
|
||||
allowEmpty
|
||||
/>
|
||||
|
||||
{/* Rich-text PDF sections — the free-form PDF content lives here */}
|
||||
|
||||
Reference in New Issue
Block a user