diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 42ba8412b..0514d3d7d 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16357,7 +16357,7 @@ components: legal_name: type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: &customer_tax_id type: @@ -16405,7 +16405,7 @@ components: legal_name: &customer_legal_name_input type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: *customer_tax_id email: @@ -16416,7 +16416,7 @@ components: maxLength: 100 default_invoice_use: &customer_default_invoice_use_input type: [string, 'null'] - description: Default CFDI use for the customer. + description: CFDI use applied to income invoices when use is omitted or null. If the customer has no configured use, G03 is used. Foreign customers and generic RFCs only accept S01; omitting it or sending null or an empty string assigns S01. example: G01 address: type: [object, 'null'] @@ -16464,7 +16464,7 @@ components: type: string minLength: 1 description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: string @@ -16600,7 +16600,7 @@ components: legal_name: type: string description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin email: *customer_email_input phone: @@ -16610,8 +16610,8 @@ components: description: Customer's phone number. example: '6474010101' default_invoice_use: - type: string - description: Default CFDI use for the customer. + type: [string, 'null'] + description: CFDI use applied to income invoices when use is omitted or null. If the customer has no configured use, G03 is used. Foreign customers and generic RFCs only accept S01; omitting it or sending null or an empty string assigns S01. example: G01 CustomerNationalAddressInput: allOf: @@ -16700,17 +16700,26 @@ components: - '' default: '616' description: Foreign customers use 616. Omission, null or an empty string use the default. + default_invoice_use: &customer_generic_default_invoice_use_input + enum: [S01, '', null] + default: S01 + example: S01 + description: Only S01 is accepted. Omitting it or sending null or an empty string assigns S01. address: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: title: Generic RFC - description: Public general RFC XAXX010101000 or generic foreign RFC XEXX010101000. Fiscal name and RFC are required. The fiscal regime defaults to 616. If a Mexican address is supplied, a postal code is required. + description: Public general RFC XAXX010101000 or generic foreign RFC XEXX010101000. Fiscal name and RFC are required. The fiscal regime defaults to 616. The postal code is optional; null or an empty string is treated as omitted. allOf: - $ref: '#/components/schemas/CustomerCreateCommonInput' - type: object required: - tax_id properties: + legal_name: + type: string + description: | + Recipient legal or business name, without the corporate regime (e.g.: S.A. de C.V.). For an individual invoice with a generic RFC, provide the recipient name and omit global. For a global income invoice, use PUBLICO EN GENERAL with RFC XAXX010101000 and provide global with periodicity, months and year. tax_id: type: string enum: @@ -16721,10 +16730,39 @@ components: enum: - '616' default: '616' + description: Recipient fiscal regime. With a generic RFC, omission uses 616 (No tax obligations); if supplied explicitly, only 616 is accepted. + default_invoice_use: *customer_generic_default_invoice_use_input address: - anyOf: - - $ref: '#/components/schemas/CustomerNationalAddressInput' - - $ref: '#/components/schemas/CustomerForeignAddressInput' + type: object + allOf: + - if: + properties: + country: + enum: [MEX, '', null] + then: + properties: + zip: + pattern: '^(?:[\s-]*[0-9]){5}[\s-]*$|^$' + properties: + street: *customer_address_street_input + exterior: *customer_address_exterior_input + interior: *customer_address_interior_input + neighborhood: *customer_address_neighborhood_input + city: *customer_address_city_input + municipality: *customer_address_municipality_input + state: *customer_address_state_input + country: + anyOf: + - type: string + minLength: 3 + maxLength: 3 + - type: [string, 'null'] + enum: ['', null] + default: MEX + description: ISO 3166-1 alpha-3. + zip: + type: [string, 'null'] + description: Optional postal code. For Mexico, a supplied value must contain five digits. CustomerCreateInput: title: Customer description: Required fields depend on country and RFC. Omitted country means Mexico. With createEditLink=true, use CustomerCreateWithEditLinkInput instead. @@ -17236,7 +17274,7 @@ components: legal_name: type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: [string, 'null'] @@ -18317,11 +18355,11 @@ components: use: type: [string, "null"] description: | - If omitted or null, the customer default is used, falling back to G03. For foreign customers or the general public, S01 is used. + If omitted or null, the customer default is used, falling back to G03. For foreign customers or generic RFCs, S01 is used even if another valid catalog code is supplied. Code of Use of CFDI according to the SAT catalog. You can see the codes in [this table](#uso-cfdi), or use the constants included in our libraries. - For global invoices you must use the code `S01`. + Global invoices use `S01`. currency: type: string default: MXN @@ -18352,7 +18390,7 @@ components: - months - year description: | - Required object when creating a global invoice. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. properties: periodicity: type: string @@ -18679,7 +18717,7 @@ components: - months - year description: | - Object required when creating a global invoice. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. properties: periodicity: type: string diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 2554e2e5e..a183e5963 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16359,7 +16359,7 @@ components: legal_name: type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: &customer_tax_id type: @@ -16405,7 +16405,7 @@ components: legal_name: &customer_legal_name_input type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: *customer_tax_id email: @@ -16416,7 +16416,7 @@ components: maxLength: 100 default_invoice_use: &customer_default_invoice_use_input type: [string, 'null'] - description: Uso de CFDI por defecto. + description: Uso de CFDI que se utiliza en facturas de ingreso cuando se omite use o se envía null. Si el cliente no tiene un uso configurado, se utiliza G03. Para clientes extranjeros o con RFC genérico sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. example: G01 address: type: [object, 'null'] @@ -16464,7 +16464,7 @@ components: type: string minLength: 1 description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: string @@ -16598,7 +16598,7 @@ components: legal_name: type: string description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin email: *customer_email_input phone: @@ -16608,8 +16608,8 @@ components: description: Teléfono del cliente. example: '6474010101' default_invoice_use: - type: string - description: Uso de CFDI por defecto. + type: [string, 'null'] + description: Uso de CFDI que se utiliza en facturas de ingreso cuando se omite use o se envía null. Si el cliente no tiene un uso configurado, se utiliza G03. Para clientes extranjeros o con RFC genérico sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. example: G01 CustomerNationalAddressInput: allOf: @@ -16695,17 +16695,26 @@ components: - '' default: '616' description: Los clientes extranjeros usan 616. Omitirlo, enviar null o una cadena vacía usa el valor predeterminado. + default_invoice_use: &customer_generic_default_invoice_use_input + enum: [S01, '', null] + default: S01 + example: S01 + description: Sólo se admite S01. Omitirlo, enviar null o una cadena vacía asigna S01. address: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: title: RFC genérico - description: RFC de público en general XAXX010101000 o RFC genérico extranjero XEXX010101000. Requiere razón social y RFC. El régimen fiscal predeterminado es 616. Si se envía domicilio mexicano, requiere código postal. + description: RFC de público en general XAXX010101000 o RFC genérico extranjero XEXX010101000. Requiere razón social y RFC. El régimen fiscal predeterminado es 616. El código postal es opcional; null o una cadena vacía se tratan como omitidos. allOf: - $ref: '#/components/schemas/CustomerCreateCommonInput' - type: object required: - tax_id properties: + legal_name: + type: string + description: | + Nombre fiscal o razón social del receptor, sin el régimen societario (ej.: S.A. de C.V.). Para una factura individual con RFC genérico, envía el nombre del receptor y omite global. Para una factura global de ingreso, usa PUBLICO EN GENERAL con el RFC XAXX010101000 y envía global con periodicity, months y year. tax_id: type: string enum: @@ -16716,10 +16725,39 @@ components: enum: - '616' default: '616' + description: Régimen fiscal del receptor. Con RFC genérico, si se omite se utiliza 616 (Sin obligaciones fiscales); si se envía explícitamente, sólo se admite 616. + default_invoice_use: *customer_generic_default_invoice_use_input address: - anyOf: - - $ref: '#/components/schemas/CustomerNationalAddressInput' - - $ref: '#/components/schemas/CustomerForeignAddressInput' + type: object + allOf: + - if: + properties: + country: + enum: [MEX, '', null] + then: + properties: + zip: + pattern: '^(?:[\s-]*[0-9]){5}[\s-]*$|^$' + properties: + street: *customer_address_street_input + exterior: *customer_address_exterior_input + interior: *customer_address_interior_input + neighborhood: *customer_address_neighborhood_input + city: *customer_address_city_input + municipality: *customer_address_municipality_input + state: *customer_address_state_input + country: + anyOf: + - type: string + minLength: 3 + maxLength: 3 + - type: [string, 'null'] + enum: ['', null] + default: MEX + description: ISO 3166-1 alpha-3. + zip: + type: [string, 'null'] + description: Código postal opcional. Para México, si se proporciona debe tener cinco dígitos. CustomerCreateInput: title: Customer description: Los campos requeridos dependen del país y del RFC. Omitir el país equivale a México. Con createEditLink=true se usa CustomerCreateWithEditLinkInput. @@ -17190,7 +17228,7 @@ components: legal_name: type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente, *sin* incluir el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* incluir el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: [string, 'null'] @@ -18254,13 +18292,13 @@ components: use: type: [string, "null"] description: | - Si se omite o es null, se utiliza el uso configurado en el cliente; si no tiene uno, se utiliza G03. Para clientes extranjeros o público en general se utiliza S01. + Si se omite o es null, se utiliza el uso configurado en el cliente; si no tiene uno, se utiliza G03. Para clientes extranjeros o con RFC genérico se utiliza S01, incluso si se envía otro uso válido del catálogo. Código de Uso CFDI según el catálogo del SAT. Puedes ver los códigos en [esta tabla](#uso-cfdi), o utilizar las constantes incluidas en nuestras librerías. - Para factura global debe ingresarse la clave `S01`. + En una factura global, el uso es `S01`. currency: type: string default: MXN @@ -18289,7 +18327,7 @@ components: - months - year description: | - Objeto requerido al crear una factura global. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. properties: periodicity: type: string @@ -18614,7 +18652,7 @@ components: - months - year description: | - Objeto requerido al crear una factura global. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. properties: periodicity: type: string diff --git a/website/plugins/api-reference/presentation.js b/website/plugins/api-reference/presentation.js index d9f7b65f2..34f735278 100644 --- a/website/plugins/api-reference/presentation.js +++ b/website/plugins/api-reference/presentation.js @@ -9,8 +9,8 @@ module.exports = function preparePresentation(source, locale) { (variant, index) => ({ ...variant, title: (locale === "es" - ? ["Nacional", "Extranjero", "Público en general"] - : ["Mexican customer", "Foreign customer", "General public"])[ + ? ["Nacional", "Extranjero", "RFC genérico"] + : ["Mexican customer", "Foreign customer", "Generic RFC"])[ index ], }), @@ -21,25 +21,27 @@ module.exports = function preparePresentation(source, locale) { title: locale === "es" ? "Cliente registrado" : "Registered customer", }, ]; + spec.components.schemas.InvoiceCustomerInput.description = + locale === "es" + ? "Datos del cliente o ID de un cliente registrado." + : "Customer details or the ID of a registered customer."; + // References preserve the union description when ReDoc merges ordered fields. spec.components.schemas.InvoiceCommonInput.allOf[0].properties.customer = { + $ref: "#/components/schemas/InvoiceCustomerInput", + }; + spec.components.schemas.InvoiceDraftCustomerInput = { ...spec.components.schemas.InvoiceCustomerInput, - description: - locale === "es" - ? "Datos del cliente o ID de un cliente registrado." - : "Customer details or the ID of a registered customer.", + oneOf: [ + ...spec.components.schemas.InvoiceCustomerInput.oneOf, + { + type: "null", + title: locale === "es" ? "Sin cliente" : "No customer", + }, + ], + }; + spec.components.schemas.InvoiceDraftCommonInput.allOf[1].properties.customer = { + $ref: "#/components/schemas/InvoiceDraftCustomerInput", }; - 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", - }, - ], - }; for (const customer of [ spec.components.schemas.ReceiptInput.allOf[0].properties.customer, spec.components.schemas.ReceiptAssignCustomerInput.properties.customer, @@ -175,6 +177,8 @@ module.exports = function preparePresentation(source, locale) { .sort((a, b) => Number(required.has(b)) - Number(required.has(a))) .map((field) => [field, schema.properties?.[field] || {}]), ); + // ReDoc keeps the first description when merging allOf properties. + schema.allOf = [...schema.allOf].reverse(); } const orderedInputs = [ [ diff --git a/website/scripts/check-openapi.mjs b/website/scripts/check-openapi.mjs index 398840d22..a69e6cd99 100644 --- a/website/scripts/check-openapi.mjs +++ b/website/scripts/check-openapi.mjs @@ -98,6 +98,15 @@ for (const filename of ["openapi_v2.yaml", "openapi_v2.en.yaml"]) { ["ProductProperties", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, true], ["ProductCreateInput", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, false], ["CustomerNationalAddressInput", { zip: "83200", country: null }, true], + ...[undefined, null, "", "83200"].map(zip => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", ...(zip === undefined ? {} : { zip }) } }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", zip: "ABCDE" } }, false], + ...[undefined, null, "", "S01"].map(use => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", ...(use === undefined ? {} : { default_invoice_use: use }) }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", default_invoice_use: "G03" }, false], + ...[undefined, null, "", "S01", "G03"].map(use => ["CustomerForeignCreateInput", { legal_name: "Foreign customer", address: { country: "USA" }, ...(use === undefined ? {} : { default_invoice_use: use }) }, use !== "G03"]), + ...[undefined, null, "", "MEX", "USA"].map(country => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { ...(country === undefined ? {} : { country }) } }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "US" } }, false], + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: null }, false], + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: "" }, false], ["RelatedDocumentInput", { relationship: "01", documents: ["39c85a3f-275b-4341-b259-e8971d9f8a94"] }, true], ["OrganizationSeriesCreateInput", { series: "A" }, true], ["CustomerProperties", { legal_name: null, address: null }, true], diff --git a/website/test/openapi-types.fixture.txt b/website/test/openapi-types.fixture.txt index 7659e76b2..bcae7fac6 100644 --- a/website/test/openapi-types.fixture.txt +++ b/website/test/openapi-types.fixture.txt @@ -212,6 +212,13 @@ const nationalWithoutRegime: components['schemas']['CustomerNationalCreateInput' const nationalWithoutZip: components['schemas']['CustomerNationalCreateInput'] = { legal_name: 'Cliente', tax_id: 'ABC101010111', tax_system: '601', address: {} }; // @ts-expect-error foreign customers use the regime 616 const foreignWithWrongRegime: components['schemas']['CustomerForeignCreateInput'] = { legal_name: 'Foreign', tax_system: '601', address: { country: 'USA' } }; +// @ts-expect-error foreign customers only allow S01 as the default use +const foreignWithWrongUse: components['schemas']['CustomerForeignCreateInput'] = { legal_name: 'Foreign', address: { country: 'USA' }, default_invoice_use: 'G03' }; +const genericWithoutZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { country: 'MEX' } }; +const genericWithNullZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { zip: null } }; +const genericWithDefaultUse: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', default_invoice_use: null }; +// @ts-expect-error generic RFCs only allow S01 as the default use +const genericWithWrongUse: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', default_invoice_use: 'G03' }; // @ts-expect-error generic RFCs use 616 const genericWithWrongRegime: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', tax_system: '601' }; const defaultGlobal: components['schemas']['GlobalInvoiceInput'] = {};