import type { JsonTaxCatalogProvider } from "@erp/core"; import { DiscountPercentage, Tax } from "@erp/core/api"; import { type IProformaItemCreateProps, InvoiceSerie, ItemAmount, ItemDescription, ItemQuantity, type ProformaItemTaxesProps, } from "@erp/customer-invoices/api/domain"; import { City, Country, CurrencyCode, DomainError, EmailAddress, LanguageCode, Name, PhoneNumber, type PostalAddressProps, PostalCode, Province, Street, TINNumber, TextValue, URLAddress, type UniqueID, UtcDate, ValidationErrorCollection, type ValidationErrorDetail, extractOrPushError, isValidationErrorCollection, maybeFromNullableResult, } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import type { CreateProformaFromFactugesRequestDTO, CreateProformaItemFromFactugesRequestDTO, } from "../../../common"; export interface IProformaFromFactuGESProps { customerLookup: { tin: TINNumber; }; paymentLookup: { factuges_id: string; }; customerDraft: { //reference: Maybe; isCompany: boolean; name: Name; //tradeName: Maybe; tin: TINNumber; address: PostalAddressProps; emailPrimary: Maybe; emailSecondary: Maybe; phonePrimary: Maybe; phoneSecondary: Maybe; mobilePrimary: Maybe; mobileSecondary: Maybe; //fax: Maybe; website: Maybe; //legalRecord: Maybe; //defaultTaxes: CustomerTaxesProps; languageCode: LanguageCode; currencyCode: CurrencyCode; }; proformaDraft: { series: Maybe; invoiceDate: UtcDate; operationDate: Maybe; reference: Maybe; description: Maybe; notes: Maybe; languageCode: LanguageCode; currencyCode: CurrencyCode; items: IProformaItemCreateProps[]; globalDiscountPercentage: DiscountPercentage; }; paymentDraft: { factuges_id: string; description: string; }; } export interface ICreateProformaFromFactugesInputMapper { map( dto: CreateProformaFromFactugesRequestDTO, params: { companyId: UniqueID } ): Result; } export class CreateProformaFromFactugesInputMapper implements ICreateProformaFromFactugesInputMapper { private readonly taxCatalog: JsonTaxCatalogProvider; constructor(params: { taxCatalog: JsonTaxCatalogProvider }) { this.taxCatalog = params.taxCatalog; } public map( dto: CreateProformaFromFactugesRequestDTO, params: { companyId: UniqueID } ): Result { try { const errors: ValidationErrorDetail[] = []; const { companyId } = params; const currencyCode = CurrencyCode.fromEUR(); const proformaProps = this.mapProformaProps(dto, { companyId, currencyCode, errors, }); const customerProps = this.mapCustomerProps(dto.customer, { companyId, currencyCode, errors, }); const paymentProps = this.mapPaymentProps(dto, { companyId, currencyCode, errors, }); this.throwIfValidationErrors(errors); return Result.ok({ customerLookup: { tin: customerProps.tin, }, paymentLookup: { factuges_id: paymentProps.factuges_id, }, customerDraft: customerProps, proformaDraft: proformaProps, paymentDraft: paymentProps, }); } catch (err: unknown) { const error = isValidationErrorCollection(err) ? (err as ValidationErrorCollection) : new DomainError("Customer props mapping failed", { cause: (err as Error).message }); return Result.fail(error); } } private mapPaymentProps( dto: CreateProformaFromFactugesRequestDTO, params: { companyId: UniqueID; currencyCode: CurrencyCode; errors: ValidationErrorDetail[]; } ): IProformaFromFactuGESProps["paymentDraft"] { const errors: ValidationErrorDetail[] = []; const { companyId } = params; return { factuges_id: String(dto.payment_method_id), description: String(dto.payment_method_description), }; } private mapProformaProps( dto: CreateProformaFromFactugesRequestDTO, params: { companyId: UniqueID; currencyCode: CurrencyCode; errors: ValidationErrorDetail[]; } ): IProformaFromFactuGESProps["proformaDraft"] { const errors: ValidationErrorDetail[] = []; const { companyId } = params; //const defaultStatus = InvoiceStatus.fromApproved(); //const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", errors); const series = extractOrPushError( maybeFromNullableResult(dto.series, (value) => InvoiceSerie.create(value)), "series", errors ); /*const proformaNumber = extractOrPushError( InvoiceNumber.create(dto.), "invoice_number", errors );*/ //const factugesID = String(dto.factuges_id); const reference = extractOrPushError( maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))), "reference", errors ); const invoiceDate = extractOrPushError( UtcDate.createFromISO(dto.invoice_date), "invoice_date", errors ); const operationDate = extractOrPushError( maybeFromNullableResult(dto.operation_date, (value) => UtcDate.createFromISO(value)), "operation_date", errors ); const description = extractOrPushError( maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))), "description", errors ); const notes = extractOrPushError( maybeFromNullableResult(dto.notes, (value) => TextValue.create(value)), "notes", errors ); const globalDiscountPercentage = extractOrPushError( DiscountPercentage.create({ value: Number(dto.global_discount_percentage_value) }), "global_discount_percentage_value", params.errors ); const languageCode = extractOrPushError( LanguageCode.create(dto.customer.language_code), "language_code", errors ); const currencyCode = CurrencyCode.fromEUR(); const itemsProps = this.mapItemsProps(dto, { languageCode: languageCode!, currencyCode: currencyCode, globalDiscountPercentage: globalDiscountPercentage!, errors, }); const props: IProformaFromFactuGESProps["proformaDraft"] = { //companyId, //status: defaultStatus, //invoiceNumber: proformaNumber!, series: series!, invoiceDate: invoiceDate!, operationDate: operationDate!, //customerId: customerId!, //recipient, reference: reference!, description: description!, notes: notes!, languageCode: languageCode!, currencyCode: currencyCode!, globalDiscountPercentage: globalDiscountPercentage!, items: itemsProps, // ← IProformaItemProps[] }; return props; } private mapCustomerProps( dto: CreateProformaFromFactugesRequestDTO["customer"], params: { companyId: UniqueID; currencyCode: CurrencyCode; errors: ValidationErrorDetail[]; } ): IProformaFromFactuGESProps["customerDraft"] { const { errors, currencyCode } = params; const isCompany = dto.is_company === "1"; const name = extractOrPushError(Name.create(dto.name), "name", errors); const tinNumber = extractOrPushError(TINNumber.create(dto.tin), "tin", errors); const street = extractOrPushError( maybeFromNullableResult(dto.street, (value) => Street.create(value)), "street", errors ); const city = extractOrPushError( maybeFromNullableResult(dto.city, (value) => City.create(value)), "city", errors ); const province = extractOrPushError( maybeFromNullableResult(dto.province, (value) => Province.create(value)), "province", errors ); const postalCode = extractOrPushError( maybeFromNullableResult(dto.postal_code, (value) => PostalCode.create(value)), "postal_code", errors ); const country = extractOrPushError( maybeFromNullableResult(dto.country, (value) => Country.create(value)), "country", errors ); const primaryEmailAddress = extractOrPushError( maybeFromNullableResult(dto.email_primary, (value) => EmailAddress.create(value)), "email_primary", errors ); const secondaryEmailAddress = extractOrPushError( maybeFromNullableResult(dto.email_secondary, (value) => EmailAddress.create(value)), "email_secondary", errors ); const primaryPhoneNumber = extractOrPushError( maybeFromNullableResult(dto.phone_primary, (value) => PhoneNumber.create(value)), "phone_primary", errors ); const secondaryPhoneNumber = extractOrPushError( maybeFromNullableResult(dto.phone_secondary, (value) => PhoneNumber.create(value)), "phone_secondary", errors ); const primaryMobileNumber = extractOrPushError( maybeFromNullableResult(dto.mobile_primary, (value) => PhoneNumber.create(value)), "mobile_primary", errors ); const secondaryMobileNumber = extractOrPushError( maybeFromNullableResult(dto.mobile_secondary, (value) => PhoneNumber.create(value)), "mobile_secondary", errors ); const website = extractOrPushError( maybeFromNullableResult(dto.website, (value) => URLAddress.create(value)), "website", errors ); const languageCode = extractOrPushError( LanguageCode.create(dto.language_code), "language_code", errors ); this.throwIfValidationErrors(errors); const postalAddressProps: PostalAddressProps = { street: street!, street2: Maybe.none(), city: city!, postalCode: postalCode!, province: province!, country: country!, }; /*const customerTaxes = this.mapCustomerTaxesProps(dto, { errors, });*/ const customerProps: IProformaFromFactuGESProps["customerDraft"] = { //companyId, //status: status!, isCompany: isCompany, name: name!, tin: tinNumber!, //tradeName: Maybe.none(), //reference: Maybe.none(), address: postalAddressProps!, emailPrimary: primaryEmailAddress!, emailSecondary: secondaryEmailAddress!, phonePrimary: primaryPhoneNumber!, phoneSecondary: secondaryPhoneNumber!, mobilePrimary: primaryMobileNumber!, mobileSecondary: secondaryMobileNumber!, //fax: Maybe.none(), website: website!, //legalRecord: Maybe.none(), //defaultTaxes: customerTaxes, languageCode: languageCode!, currencyCode: currencyCode!, }; return customerProps; } private mapItemsProps( dto: CreateProformaFromFactugesRequestDTO, params: { languageCode: LanguageCode; currencyCode: CurrencyCode; globalDiscountPercentage: DiscountPercentage; errors: ValidationErrorDetail[]; } ): IProformaItemCreateProps[] { const itemsProps: IProformaItemCreateProps[] = []; dto.items.forEach((item, index) => { const description = extractOrPushError( maybeFromNullableResult(item.description, (value) => ItemDescription.create(value)), `items[${index}].description`, params.errors ); const quantity = extractOrPushError( maybeFromNullableResult(item.quantity_value, (value) => ItemQuantity.create({ value: Number(value) }) ), "items[$index].quantity_value", params.errors ); const unitAmount = extractOrPushError( maybeFromNullableResult(item.unit_value, (value) => ItemAmount.create({ value: Number(value) }) ), `items[${index}].unit_value`, params.errors ); const discountPercentage = extractOrPushError( maybeFromNullableResult(item.item_discount_percentage_value, (value) => DiscountPercentage.create({ value: Number(value) }) ), `items[${index}].item_discount_percentage_value`, params.errors ); const taxes = this.mapItempTaxesProps(item, { itemIndex: index, errors: params.errors, }); this.throwIfValidationErrors(params.errors); itemsProps.push({ description: description!, quantity: quantity!, unitAmount: unitAmount!, itemDiscountPercentage: discountPercentage!, taxes, globalDiscountPercentage: params.globalDiscountPercentage, languageCode: params.languageCode, currencyCode: params.currencyCode, }); }); return itemsProps; } /* Devuelve las propiedades de los impustos de una línea de detalle */ private mapItempTaxesProps( itemDTO: CreateProformaItemFromFactugesRequestDTO, params: { itemIndex: number; errors: ValidationErrorDetail[] } ): ProformaItemTaxesProps { const { itemIndex, errors } = params; const taxesProps: ProformaItemTaxesProps = { iva: Maybe.none(), retention: Maybe.none(), rec: Maybe.none(), }; // Normaliza: "" -> [] // IVA por defecto => iva_21 const taxStrCodes = [itemDTO.iva_code === "" ? "iva_21" : itemDTO.iva_code]; if (itemDTO.rec_code) taxStrCodes.push(itemDTO.rec_code); if (itemDTO.retention_code) taxStrCodes.push(itemDTO.retention_code); taxStrCodes.forEach((strCode, taxIndex) => { const taxResult = Tax.createFromCode(strCode, this.taxCatalog); if (!taxResult.isSuccess) { errors.push({ path: `items[${itemIndex}].taxes[${taxIndex}]`, message: taxResult.error.message, }); return; } const tax = taxResult.data; if (tax.isVATLike()) { if (taxesProps.iva.isSome()) { errors.push({ path: `items[${itemIndex}].taxes`, message: "Multiple taxes for group VAT are not allowed", }); } taxesProps.iva = Maybe.some(tax); } if (tax.isRetention()) { if (taxesProps.retention.isSome()) { errors.push({ path: `items[${itemIndex}].taxes`, message: "Multiple taxes for group retention are not allowed", }); } taxesProps.retention = Maybe.some(tax); } if (tax.isRec()) { if (taxesProps.rec.isSome()) { errors.push({ path: `items[${itemIndex}].taxes`, message: "Multiple taxes for group rec are not allowed", }); } taxesProps.rec = Maybe.some(tax); } }); this.throwIfValidationErrors(errors); return taxesProps; } private throwIfValidationErrors(errors: ValidationErrorDetail[]): void { if (errors.length > 0) { throw new ValidationErrorCollection("Customer proforma props mapping failed", errors); } } }