import { CurrencyData, Language, Note, Percentage, TextValueObject, UTCDateValue, UniqueID, } from "@shared/contexts"; import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; import { IQuoteProps, Quote, QuoteCustomer, QuoteCustomerReference, QuoteReference, } from "../../domain"; import { QuoteStatus } from "../../domain/entities/Quotes/QuoteStatus"; import { ISalesContext } from "../Sales.context"; import { DEFAULT_QUOTE_TAX_LABEL, QuoteCreationAttributes, Quote_Model } from "../sequelize"; import { createQuoteChapterMapper, IQuoteChapterMapper } from "./quoteChapter.mapper"; import { IQuoteItemMapper, createQuoteItemMapper } from "./quoteItem.mapper"; export interface IQuoteMapper extends ISequelizeMapper {} export const createQuoteMapper = (context: ISalesContext): IQuoteMapper => new QuoteMapper({ context, quoteItemMapper: createQuoteItemMapper(context), quoteChapterMapper: createQuoteChapterMapper(), }); class QuoteMapper extends SequelizeMapper implements IQuoteMapper { public constructor(props: { quoteChapterMapper: IQuoteChapterMapper; quoteItemMapper: IQuoteItemMapper; context: ISalesContext }) { super(props); } protected toDomainMappingImpl(source: Quote_Model): Quote { const id = this.mapsValue(source, "id", UniqueID.create); const items = (this.props.quoteItemMapper as IQuoteItemMapper).mapArrayToDomain(source.items ? [...source.items].sort((a, b) => a.position - b.position) : [], { sourceParent: source, }); const chapters = (this.props.quoteChapterMapper as IQuoteChapterMapper) .mapArrayToDomain([...(source.chapters ?? [])].sort((a, b) => a.position - b.position)).toArray(); const props: IQuoteProps = { chapters, showChapterLineAmounts: source.show_chapter_line_amounts ?? true, status: this.mapsValue(source, "status", QuoteStatus.create), date: this.mapsValue(source, "date", UTCDateValue.create), reference: this.mapsValue(source, "reference", QuoteReference.create), currency: this.mapsValue(source, "currency_code", CurrencyData.createFromCode), language: this.mapsValue( source, "lang_code", (value) => Language.createFromCode(value || Language.DEFAULT_LANGUAGE_CODE) ), customerReference: this.mapsValue( source, "customer_reference", QuoteCustomerReference.create ), customer: this.mapsValue(source, "customer_information", QuoteCustomer.create), validity: this.mapsValue(source, "validity", Note.create), paymentMethod: this.mapsValue(source, "payment_method", Note.create), notes: this.mapsValue(source, "notes", TextValueObject.create), showLineDiscountColumn: source.show_line_discount_column ?? true, additionalCurrency: source.additional_currency_code ? this.mapsValue(source, "additional_currency_code", CurrencyData.createFromCode) : undefined, additionalCurrencyRate: source.additional_currency_rate === null ? null : Number(source.additional_currency_rate), additionalCurrencyDate: source.additional_currency_date ? this.mapsValue(source, "additional_currency_date", UTCDateValue.create) : undefined, items, /*subtotal: this.mapsValue(source, "subtotal_price", (subtotal_price) => MoneyValue.create({ amount: subtotal_price, currencyCode: source.currency_code, precision: 2, }) ),*/ discount: this.mapsValue(source, "discount", (discount) => Percentage.create({ amount: discount, scale: 2, }) ), tax: this.mapsValue(source, "tax", (tax) => Percentage.create({ amount: tax, scale: 2, }) ), taxLabel: this.mapsValue(source, "tax_label", (label) => TextValueObject.create(label ?? DEFAULT_QUOTE_TAX_LABEL) ), /*totalPrice: this.mapsValue(source, "total_price", (total_price) => MoneyValue.create({ amount: total_price, currencyCode: source.currency_code, precision: 2, }) ),*/ dealerId: this.mapsValue(source, "dealer_id", UniqueID.create), dateSent: this.mapsValue(source, "date_sent", UTCDateValue.create), }; const quoteOrError = Quote.create(props, id); if (quoteOrError.isFailure) { throw quoteOrError.error; } return quoteOrError.object; } protected toPersistenceMappingImpl(source: Quote) { const items = (this.props.quoteItemMapper as IQuoteItemMapper).mapCollectionToPersistence( source.items, { sourceParent: source } ); const chapters = source.chapters.map((chapter, index) => (this.props.quoteChapterMapper as IQuoteChapterMapper).mapToPersistence(chapter, { index }) ); // Canonical flat order: chapters first, preserving the relative detail order. const orderedItems = chapters.length ? chapters.flatMap((chapter) => items.filter((item) => item.chapter_id === chapter.chapter_id)) : items; const quote: QuoteCreationAttributes = { chapters, show_chapter_line_amounts: source.showChapterLineAmounts, id: source.id.toPrimitive(), status: source.status.toPrimitive(), date: source.date.toPrimitive(), reference: source.reference.toPrimitive(), currency_code: source.currency.toPrimitive(), lang_code: source.language.toPrimitive(), customer_reference: source.customerReference.toPrimitive(), customer_information: source.customer.toPrimitive(), validity: source.validity.toPrimitive(), payment_method: source.paymentMethod.toPrimitive(), notes: source.notes.toPrimitive(), show_line_discount_column: source.showLineDiscountColumn, additional_currency_code: source.additionalCurrency?.code ?? null, additional_currency_rate: source.additionalCurrencyRate, additional_currency_date: source.additionalCurrencyDate?.toString() ?? null, additional_currency_total: source.additionalCurrencyTotal?.convertScale(2).toPrimitive() ?? null, subtotal_price: source.subtotalPrice.convertScale(2).toPrimitive(), discount: source.discount.convertScale(2).toPrimitive(), discount_price: source.discountPrice.convertScale(2).toPrimitive(), before_tax_price: source.beforeTaxPrice.convertScale(2).toPrimitive(), tax: source.tax.convertScale(2).toPrimitive(), tax_label: source.taxLabel.toPrimitive(), tax_price: source.taxPrice.convertScale(2).toPrimitive(), total_price: source.totalPrice.convertScale(2).toPrimitive(), items: orderedItems.map((item, position) => ({ ...item, position })), dealer_id: source.dealerId.toPrimitive(), date_sent: source.dateSent.toPrimitive() === "" ? null : source.dateSent.toPrimitive(), }; return quote; } }