From 4ca7d57741f416236b4918426c6f2960e33e50ee Mon Sep 17 00:00:00 2001 From: javorosas Date: Tue, 20 Oct 2026 20:49:42 +0200 Subject: [PATCH 1/3] fix: keep invoice fields consistent across creation modes --- website/src/css/custom.css | 163 ++++++++++++++++++------------------- website/src/pages/api.tsx | 46 +++++++---- 2 files changed, 108 insertions(+), 101 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 55935fba4..22a826a5a 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -390,91 +390,6 @@ body[data-scrolled='true'] .navbar { display: none !important; } -/* Keep invoice mode and related fields together when requirements change. */ -[id='operation/createInvoice'] tbody:has(> tr > td[title='status'] + td select) { - display: grid; - grid-template-columns: max-content minmax(0, 1fr); - - > tr { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - } - - > tr > td { - width: auto; - } - - > tr > td[colspan] { - grid-column: 1 / -1; - } - - > tr:has(> td[colspan]) { - animation: invoice-field-details-enter 180ms ease-out; - transform-origin: top; - } - - > tr:has(> td[title='status']), - > tr:has(> td[title='status']) + tr:has(> td[colspan]) { - order: -11; - } - - > tr:has(> td[title='status']) { - > td + td > div { - display: flex; - flex-direction: column; - - > div:has(> select) { - order: -1; - margin-bottom: 0.5rem; - } - } - } - - > tr:has(> td[title='type']), - > tr:has(> td[title='type']) + tr:has(> td[colspan]) { order: -10; } - > tr:has(> td[title='customer']), - > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: -9; } - > tr:has(> td[title='third_party']), - > tr:has(> td[title='third_party']) + tr:has(> td[colspan]) { order: -8; } - > tr:has(> td:is([title='items'], [title='complements'])), - > tr:has(> td:is([title='items'], [title='complements'])) + tr:has(> td[colspan]) { order: -7; } - > tr:has(> td[title='use']), - > tr:has(> td[title='use']) + tr:has(> td[colspan]) { order: -6; } - > tr:has(> td:is([title='payment_form'], [title='payment_method'], [title='conditions'])), - > tr:has(> td:is([title='payment_form'], [title='payment_method'], [title='conditions'])) + tr:has(> td[colspan]) { order: -5; } - > tr:has(> td:is([title='currency'], [title='exchange'])), - > tr:has(> td:is([title='currency'], [title='exchange'])) + tr:has(> td[colspan]) { order: -4; } - > tr:has(> td:is([title='date'], [title='folio_number'], [title='series'])), - > tr:has(> td:is([title='date'], [title='folio_number'], [title='series'])) + tr:has(> td[colspan]) { order: -3; } - > tr:has(> td:is([title='related_documents'], [title='global'], [title='export'])), - > tr:has(> td:is([title='related_documents'], [title='global'], [title='export'])) + tr:has(> td[colspan]) { order: 1; } - > tr:has(> td:is([title='pdf_custom_section'], [title='addenda'], [title='namespaces'], [title='pdf_options'])), - > tr:has(> td:is([title='pdf_custom_section'], [title='addenda'], [title='namespaces'], [title='pdf_options'])) + tr:has(> td[colspan]) { order: 2; } - - @media (max-width: 50rem) { - grid-template-columns: minmax(0, 1fr); - } -} - -@keyframes invoice-field-details-enter { - from { - opacity: 0; - transform: translateY(-0.4rem); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -@media (prefers-reduced-motion: reduce) { - [id='operation/createInvoice'] tbody:has(> tr > td[title='status'] + td select) > tr:has(> td[colspan]) { - animation: none; - } -} - html[data-theme='dark'] .redocusaurus button[aria-expanded='true'] { border-color: transparent !important; } @@ -532,3 +447,81 @@ html[data-theme='dark'] .redocusaurus div[aria-hidden='false'] [tabindex='0'] { [id='operation/createInvoice'] tr:has(> td[title='customer']) + tr > td > div > div > div > span:first-child { display: none; } + +html[data-theme='dark'] .redocusaurus select, +html[data-theme='dark'] .redocusaurus select option { + color: var(--ifm-font-color-base); + background-color: var(--facturapi-surface-bg); + color-scheme: dark; +} + +/* Keep the same reading order when switching invoice creation modes. */ +[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { + display: grid; + grid-template-columns: max-content minmax(0, 1fr); + + > tr { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + order: 30; + } + + > tr > td { width: auto; } + > tr > td[colspan] { grid-column: 1 / -1; } + + > tr:has(> td[title='customer']), + > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: 0; } + > tr:has(> td[title='items']), + > tr:has(> td[title='items']) + tr:has(> td[colspan]) { order: 1; } + > tr:has(> td[title='payment_form']), + > tr:has(> td[title='payment_form']) + tr:has(> td[colspan]) { order: 2; } + > tr:has(> td[title='type']), + > tr:has(> td[title='type']) + tr:has(> td[colspan]) { order: 3; } + > tr:has(> td[title='status']), + > tr:has(> td[title='status']) + tr:has(> td[colspan]) { order: 4; } + > tr:has(> td[title='payment_method']), + > tr:has(> td[title='payment_method']) + tr:has(> td[colspan]) { order: 5; } + > tr:has(> td[title='use']), + > tr:has(> td[title='use']) + tr:has(> td[colspan]) { order: 6; } + > tr:has(> td[title='currency']), + > tr:has(> td[title='currency']) + tr:has(> td[colspan]) { order: 7; } + > tr:has(> td[title='exchange']), + > tr:has(> td[title='exchange']) + tr:has(> td[colspan]) { order: 8; } + > tr:has(> td[title='conditions']), + > tr:has(> td[title='conditions']) + tr:has(> td[colspan]) { order: 9; } + > tr:has(> td[title='date']), + > tr:has(> td[title='date']) + tr:has(> td[colspan]) { order: 10; } + > tr:has(> td[title='folio_number']), + > tr:has(> td[title='folio_number']) + tr:has(> td[colspan]) { order: 11; } + > tr:has(> td[title='series']), + > tr:has(> td[title='series']) + tr:has(> td[colspan]) { order: 12; } + > tr:has(> td[title='address']), + > tr:has(> td[title='address']) + tr:has(> td[colspan]) { order: 13; } + > tr:has(> td[title='third_party']), + > tr:has(> td[title='third_party']) + tr:has(> td[colspan]) { order: 14; } + > tr:has(> td[title='related_documents']), + > tr:has(> td[title='related_documents']) + tr:has(> td[colspan]) { order: 15; } + > tr:has(> td[title='global']), + > tr:has(> td[title='global']) + tr:has(> td[colspan]) { order: 16; } + > tr:has(> td[title='export']), + > tr:has(> td[title='export']) + tr:has(> td[colspan]) { order: 17; } + > tr:has(> td[title='complements']), + > tr:has(> td[title='complements']) + tr:has(> td[colspan]) { order: 18; } + > tr:has(> td[title='external_id']), + > tr:has(> td[title='external_id']) + tr:has(> td[colspan]) { order: 19; } + > tr:has(> td[title='idempotency_key']), + > tr:has(> td[title='idempotency_key']) + tr:has(> td[colspan]) { order: 20; } + > tr:has(> td[title='pdf_custom_section']), + > tr:has(> td[title='pdf_custom_section']) + tr:has(> td[colspan]) { order: 21; } + > tr:has(> td[title='addenda']), + > tr:has(> td[title='addenda']) + tr:has(> td[colspan]) { order: 22; } + > tr:has(> td[title='namespaces']), + > tr:has(> td[title='namespaces']) + tr:has(> td[colspan]) { order: 23; } + > tr:has(> td[title='pdf_options']), + > tr:has(> td[title='pdf_options']) + tr:has(> td[colspan]) { order: 24; } + + @media (max-width: 50rem) { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/website/src/pages/api.tsx b/website/src/pages/api.tsx index 90e65d814..b20ad8692 100644 --- a/website/src/pages/api.tsx +++ b/website/src/pages/api.tsx @@ -35,25 +35,39 @@ function CustomPage() { ? 'Datos del cliente o ID de un cliente registrado.' : 'Customer details or the ID of a registered customer.', }; - // ReDoc needs named discriminator variants to render a status selector. - // Keep this presentation hint out of the contract: status may be omitted. + spec.components.schemas.InvoiceDraftCommonInput.allOf[1].properties.customer = { + ...spec.components.schemas.InvoiceCommonInput.allOf[0].properties.customer, + oneOf: [ + ...spec.components.schemas.InvoiceCustomerInput.oneOf, + { + type: 'null', + title: locale === 'es' ? 'Sin cliente' : 'No customer', + }, + ], + }; + // Native oneOf tabs keep the viewing mode separate from request fields. spec.components.schemas.InvoiceCreateInput.oneOf = spec.components.schemas.InvoiceCreateInput.oneOf.map( - (invoiceType: {oneOf: {allOf: {properties: {status: {const: string}}}[]}[]}, typeIndex: number) => { - const mapping: Record = {}; - return { - ...invoiceType, - oneOf: invoiceType.oneOf.map((variant) => { - const status = variant.allOf.at(-1)!.properties.status.const; - const name = `InvoiceCreateDisplay${typeIndex}${status}`; - spec.components.schemas[name] = variant; - mapping[status] = `#/components/schemas/${name}`; - return {$ref: mapping[status]}; - }), - discriminator: {propertyName: 'status', mapping}, - }; - }, + (invoiceType: {oneOf: {title: string}[]}) => ({ + ...invoiceType, + oneOf: invoiceType.oneOf.map((variant) => ({ + ...variant, + title: variant.title === 'draft' + ? (locale === 'es' ? 'Borrador' : 'Draft') + : (locale === 'es' ? 'Timbrar' : 'Issue'), + })), + }), ); + for (const [name, schema] of Object.entries<{allOf?: {properties?: {type?: {description: string}}}[]; properties?: {type?: {description: string}}}>(spec.components.schemas)) { + if (!name.startsWith('Invoice') || !name.endsWith('Input')) continue; + for (const part of [schema, ...(schema.allOf || [])]) { + if (part.properties?.type) { + part.properties.type.description = locale === 'es' + ? 'Código del tipo de factura seleccionado arriba. Selecciona otro tipo para consultar sus campos y código correspondiente.' + : 'Code for the invoice type selected above. Select another type to see its fields and corresponding code.'; + } + } + } return {...specData, spec}; }, [specData, locale]); return ( From 98001b4fd702fec10340b022286415e92fb51fb6 Mon Sep 17 00:00:00 2001 From: javorosas Date: Tue, 20 Oct 2026 21:11:49 +0200 Subject: [PATCH 2/3] fix: align customer and product choices across reference pages --- website/src/css/custom.css | 44 ++++++++++++++++++++++++++++++++++---- website/src/pages/api.tsx | 32 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 22a826a5a..1677e9d16 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -456,7 +456,8 @@ html[data-theme='dark'] .redocusaurus select option { } /* Keep the same reading order when switching invoice creation modes. */ -[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { +[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']), +[id='operation/createRetention'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { display: grid; grid-template-columns: max-content minmax(0, 1fr); @@ -470,6 +471,13 @@ html[data-theme='dark'] .redocusaurus select option { > tr > td { width: auto; } > tr > td[colspan] { grid-column: 1 / -1; } + + @media (max-width: 50rem) { + grid-template-columns: minmax(0, 1fr); + } +} + +[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { > tr:has(> td[title='customer']), > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: 0; } > tr:has(> td[title='items']), @@ -521,7 +529,35 @@ html[data-theme='dark'] .redocusaurus select option { > tr:has(> td[title='pdf_options']), > tr:has(> td[title='pdf_options']) + tr:has(> td[colspan]) { order: 24; } - @media (max-width: 50rem) { - grid-template-columns: minmax(0, 1fr); - } +} + +[id='operation/createRetention'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { + > tr:has(> td[title='customer']), + > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: 0; } + > tr:has(> td[title='cve_retenc']), + > tr:has(> td[title='cve_retenc']) + tr:has(> td[colspan]) { order: 1; } + > tr:has(> td[title='periodo']), + > tr:has(> td[title='periodo']) + tr:has(> td[colspan]) { order: 2; } + > tr:has(> td[title='totales']), + > tr:has(> td[title='totales']) + tr:has(> td[colspan]) { order: 3; } + > tr:has(> td[title='status']), + > tr:has(> td[title='status']) + tr:has(> td[colspan]) { order: 4; } + > tr:has(> td[title='fecha_exp']), + > tr:has(> td[title='fecha_exp']) + tr:has(> td[colspan]) { order: 5; } + > tr:has(> td[title='desc_retenc']), + > tr:has(> td[title='desc_retenc']) + tr:has(> td[colspan]) { order: 6; } + > tr:has(> td[title='folio_int']), + > tr:has(> td[title='folio_int']) + tr:has(> td[colspan]) { order: 7; } + > tr:has(> td[title='external_id']), + > tr:has(> td[title='external_id']) + tr:has(> td[colspan]) { order: 8; } + > tr:has(> td[title='idempotency_key']), + > tr:has(> td[title='idempotency_key']) + tr:has(> td[colspan]) { order: 9; } + > tr:has(> td[title='complements']), + > tr:has(> td[title='complements']) + tr:has(> td[colspan]) { order: 10; } + > tr:has(> td[title='pdf_custom_section']), + > tr:has(> td[title='pdf_custom_section']) + tr:has(> td[colspan]) { order: 11; } + > tr:has(> td[title='addenda']), + > tr:has(> td[title='addenda']) + tr:has(> td[colspan]) { order: 12; } + > tr:has(> td[title='namespaces']), + > tr:has(> td[title='namespaces']) + tr:has(> td[colspan]) { order: 13; } } diff --git a/website/src/pages/api.tsx b/website/src/pages/api.tsx index b20ad8692..f62680329 100644 --- a/website/src/pages/api.tsx +++ b/website/src/pages/api.tsx @@ -45,6 +45,38 @@ function CustomPage() { }, ], }; + for (const customer of [ + spec.components.schemas.ReceiptInput.allOf[0].properties.customer, + spec.components.schemas.ReceiptAssignCustomerInput.properties.customer, + spec.components.schemas.InvoiceReceiptInput.allOf[0].properties.customer, + spec.components.schemas.ToInvoiceInput.properties.customer, + spec.components.schemas.ToInvoicePreviewInput.properties.customer, + spec.components.schemas.RetentionUpdateInput.properties.customer, + ]) { + customer.oneOf = customer.oneOf.map((variant: {$ref?: string; type?: string}) => { + if (variant.$ref === '#/components/schemas/CustomerCreateInput') { + return spec.components.schemas.InvoiceCustomerInput.oneOf[0]; + } + return { + ...variant, + title: variant.type === 'null' + ? (locale === 'es' ? 'Sin cliente' : 'No customer') + : (locale === 'es' ? 'Cliente registrado' : 'Registered customer'), + }; + }); + } + for (const name of ['InvoiceItemInput', 'InvoiceItemEgresoInput', 'InvoiceItemTrasladoInput']) { + const product = spec.components.schemas[name].properties.product; + product.description = locale === 'es' + ? 'Datos del producto o servicio, o ID de un producto registrado.' + : 'Product or service details, or the ID of a registered product.'; + product.oneOf = product.oneOf.map((variant: {$ref?: string; type?: string}) => ({ + ...variant, + title: variant.type === 'string' + ? (locale === 'es' ? 'Producto registrado' : 'Registered product') + : (locale === 'es' ? 'Datos del producto' : 'Product details'), + })); + } // Native oneOf tabs keep the viewing mode separate from request fields. spec.components.schemas.InvoiceCreateInput.oneOf = spec.components.schemas.InvoiceCreateInput.oneOf.map( From 9c970b0a673091d5d5354065cd9ba2034718f3a6 Mon Sep 17 00:00:00 2001 From: javorosas Date: Wed, 21 Oct 2026 12:46:35 +0200 Subject: [PATCH 3/3] fix: align reference reading and visual field order --- website/src/css/custom.css | 107 ------------------------------------- website/src/pages/api.tsx | 48 ++++++++++++++++- 2 files changed, 47 insertions(+), 108 deletions(-) diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 1677e9d16..b10a951b8 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -454,110 +454,3 @@ html[data-theme='dark'] .redocusaurus select option { background-color: var(--facturapi-surface-bg); color-scheme: dark; } - -/* Keep the same reading order when switching invoice creation modes. */ -[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']), -[id='operation/createRetention'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { - display: grid; - grid-template-columns: max-content minmax(0, 1fr); - - > tr { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / -1; - order: 30; - } - - > tr > td { width: auto; } - > tr > td[colspan] { grid-column: 1 / -1; } - - - @media (max-width: 50rem) { - grid-template-columns: minmax(0, 1fr); - } -} - -[id='operation/createInvoice'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { - > tr:has(> td[title='customer']), - > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: 0; } - > tr:has(> td[title='items']), - > tr:has(> td[title='items']) + tr:has(> td[colspan]) { order: 1; } - > tr:has(> td[title='payment_form']), - > tr:has(> td[title='payment_form']) + tr:has(> td[colspan]) { order: 2; } - > tr:has(> td[title='type']), - > tr:has(> td[title='type']) + tr:has(> td[colspan]) { order: 3; } - > tr:has(> td[title='status']), - > tr:has(> td[title='status']) + tr:has(> td[colspan]) { order: 4; } - > tr:has(> td[title='payment_method']), - > tr:has(> td[title='payment_method']) + tr:has(> td[colspan]) { order: 5; } - > tr:has(> td[title='use']), - > tr:has(> td[title='use']) + tr:has(> td[colspan]) { order: 6; } - > tr:has(> td[title='currency']), - > tr:has(> td[title='currency']) + tr:has(> td[colspan]) { order: 7; } - > tr:has(> td[title='exchange']), - > tr:has(> td[title='exchange']) + tr:has(> td[colspan]) { order: 8; } - > tr:has(> td[title='conditions']), - > tr:has(> td[title='conditions']) + tr:has(> td[colspan]) { order: 9; } - > tr:has(> td[title='date']), - > tr:has(> td[title='date']) + tr:has(> td[colspan]) { order: 10; } - > tr:has(> td[title='folio_number']), - > tr:has(> td[title='folio_number']) + tr:has(> td[colspan]) { order: 11; } - > tr:has(> td[title='series']), - > tr:has(> td[title='series']) + tr:has(> td[colspan]) { order: 12; } - > tr:has(> td[title='address']), - > tr:has(> td[title='address']) + tr:has(> td[colspan]) { order: 13; } - > tr:has(> td[title='third_party']), - > tr:has(> td[title='third_party']) + tr:has(> td[colspan]) { order: 14; } - > tr:has(> td[title='related_documents']), - > tr:has(> td[title='related_documents']) + tr:has(> td[colspan]) { order: 15; } - > tr:has(> td[title='global']), - > tr:has(> td[title='global']) + tr:has(> td[colspan]) { order: 16; } - > tr:has(> td[title='export']), - > tr:has(> td[title='export']) + tr:has(> td[colspan]) { order: 17; } - > tr:has(> td[title='complements']), - > tr:has(> td[title='complements']) + tr:has(> td[colspan]) { order: 18; } - > tr:has(> td[title='external_id']), - > tr:has(> td[title='external_id']) + tr:has(> td[colspan]) { order: 19; } - > tr:has(> td[title='idempotency_key']), - > tr:has(> td[title='idempotency_key']) + tr:has(> td[colspan]) { order: 20; } - > tr:has(> td[title='pdf_custom_section']), - > tr:has(> td[title='pdf_custom_section']) + tr:has(> td[colspan]) { order: 21; } - > tr:has(> td[title='addenda']), - > tr:has(> td[title='addenda']) + tr:has(> td[colspan]) { order: 22; } - > tr:has(> td[title='namespaces']), - > tr:has(> td[title='namespaces']) + tr:has(> td[colspan]) { order: 23; } - > tr:has(> td[title='pdf_options']), - > tr:has(> td[title='pdf_options']) + tr:has(> td[colspan]) { order: 24; } - -} - -[id='operation/createRetention'] tbody:has(> tr > td[title='customer']):has(> tr > td[title='status']) { - > tr:has(> td[title='customer']), - > tr:has(> td[title='customer']) + tr:has(> td[colspan]) { order: 0; } - > tr:has(> td[title='cve_retenc']), - > tr:has(> td[title='cve_retenc']) + tr:has(> td[colspan]) { order: 1; } - > tr:has(> td[title='periodo']), - > tr:has(> td[title='periodo']) + tr:has(> td[colspan]) { order: 2; } - > tr:has(> td[title='totales']), - > tr:has(> td[title='totales']) + tr:has(> td[colspan]) { order: 3; } - > tr:has(> td[title='status']), - > tr:has(> td[title='status']) + tr:has(> td[colspan]) { order: 4; } - > tr:has(> td[title='fecha_exp']), - > tr:has(> td[title='fecha_exp']) + tr:has(> td[colspan]) { order: 5; } - > tr:has(> td[title='desc_retenc']), - > tr:has(> td[title='desc_retenc']) + tr:has(> td[colspan]) { order: 6; } - > tr:has(> td[title='folio_int']), - > tr:has(> td[title='folio_int']) + tr:has(> td[colspan]) { order: 7; } - > tr:has(> td[title='external_id']), - > tr:has(> td[title='external_id']) + tr:has(> td[colspan]) { order: 8; } - > tr:has(> td[title='idempotency_key']), - > tr:has(> td[title='idempotency_key']) + tr:has(> td[colspan]) { order: 9; } - > tr:has(> td[title='complements']), - > tr:has(> td[title='complements']) + tr:has(> td[colspan]) { order: 10; } - > tr:has(> td[title='pdf_custom_section']), - > tr:has(> td[title='pdf_custom_section']) + tr:has(> td[colspan]) { order: 11; } - > tr:has(> td[title='addenda']), - > tr:has(> td[title='addenda']) + tr:has(> td[colspan]) { order: 12; } - > tr:has(> td[title='namespaces']), - > tr:has(> td[title='namespaces']) + tr:has(> td[colspan]) { order: 13; } -} diff --git a/website/src/pages/api.tsx b/website/src/pages/api.tsx index f62680329..d93a741b9 100644 --- a/website/src/pages/api.tsx +++ b/website/src/pages/api.tsx @@ -100,7 +100,53 @@ function CustomPage() { } } } - return {...specData, spec}; + type OrderedSchema = { + $ref?: string; + allOf?: OrderedSchema[]; + properties?: Record; + }; + function propertyNames(schema: OrderedSchema): string[] { + if (schema.$ref) { + return propertyNames(spec.components.schemas[schema.$ref.split('/').pop()!]); + } + return [ + ...Object.keys(schema.properties || {}), + ...(schema.allOf || []).flatMap(propertyNames), + ]; + } + const orderedInputs: [OrderedSchema[], string[]][] = [ + [ + spec.components.schemas.InvoiceCreateInput.oneOf.flatMap( + (invoiceType: {oneOf: OrderedSchema[]}) => invoiceType.oneOf, + ), + ['customer', 'items', 'payment_form', 'type', 'status', 'payment_method', + 'use', 'currency', 'exchange', 'conditions', 'date', 'folio_number', + 'series', 'address', 'third_party', 'related_documents', 'global', + 'export', 'complements', 'external_id', 'idempotency_key', + 'pdf_custom_section', 'addenda', 'namespaces', 'pdf_options'], + ], + [ + spec.components.schemas.RetentionInput.oneOf, + ['customer', 'cve_retenc', 'periodo', 'totales', 'status', 'fecha_exp', + 'desc_retenc', 'folio_int', 'external_id', 'idempotency_key', + 'complements', 'pdf_custom_section', 'addenda', 'namespaces'], + ], + ]; + for (const [variants, order] of orderedInputs) { + for (const variant of variants) { + const names = new Set(propertyNames(variant)); + // Empty schemas establish insertion order; allOf keeps every field constraint. + variant.properties = Object.fromEntries([ + ...order.filter((name) => names.has(name)), + ...[...names].filter((name) => !order.includes(name)), + ].map((name) => [name, variant.properties?.[name] || {}])); + } + } + return { + ...specData, + spec, + optionsOverrides: {sortRequiredPropsFirst: false}, + }; }, [specData, locale]); return (