From 26bc608131777f0048b7896de4ac927b2e3a9b01 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Fri, 23 Aug 2024 18:47:51 +0200 Subject: [PATCH] . --- .../app/quotes/components/QuotesDataTable.tsx | 43 +- .../editors/QuoteGeneralCardEditor.tsx | 8 +- client/src/app/quotes/create.tsx | 19 +- client/src/app/quotes/edit.tsx | 3 +- client/src/app/quotes/hooks/useQuotes.tsx | 4 +- client/src/locales/en.json | 8 +- client/src/locales/es.json | 5 + .../application/Quote/CreateQuote.useCase.ts | 123 ++--- ...e.test.ts => GetQuote.useCase.test.ts.bak} | 0 .../application/Quote/UpdateQuote.useCase.ts | 7 + .../application/services/QuoteService.ts | 23 + .../sales/application/services/index.ts | 1 + .../sales/domain/entities/Dealer/Dealer.ts | 12 + .../sales/domain/entities/Quotes/Quote.ts | 6 + .../domain/entities/Quotes/QuoteCustomer.ts | 2 +- .../entities/Quotes/QuoteCustomerReference.ts | 45 ++ .../domain/entities/Quotes/QuoteReference.ts | 23 +- .../sales/domain/entities/Quotes/index.ts | 1 + server/src/contexts/sales/domain/index.ts | 1 + .../repository/QuoteRepository.interface.ts | 2 + ...uoteReferenceGeneratorService.interface.ts | 3 + .../contexts/sales/domain/services/index.ts | 1 + .../sales/infrastructure/Quote.repository.ts | 22 + .../sales/infrastructure/Sales.context.ts | 6 +- .../createQuote/CreateQuote.controller.ts | 8 +- .../presenter/CreateQuote.presenter.ts | 1 + .../getQuote/presenter/GetQuote.presenter.ts | 1 + .../presenter/ListQuotes.presenter.ts | 1 + .../reporter/ReportQuote.reporter.ts | 1 + .../reporter/templates/presupuesto.html | 466 ------------------ .../reporter/templates/quote copy.hbs | 319 ------------ .../reportQuote/reporter/templates/quote.css | 136 ----- .../reporter/templates/quote.tw.hbs | 267 ---------- .../reporter/templates/quote/template.hbs | 4 +- .../infrastructure/mappers/quote.mapper.ts | 14 +- .../infrastructure/sequelize/quote.model.ts | 8 + .../contexts/common/domain/entities/Name.ts | 18 + .../ICreateQuote_Request.dto.ts | 3 +- .../ICreateQuote_Response.dto.ts | 1 + .../GetQuote.dto/IGetQuote_Response.dto.ts | 1 + .../IListQuotes_Response.dto.ts | 1 + .../IReportQuote_Response.dto.ts | 4 - .../dto/Quote/ReportQuote.dto/index.ts | 1 - .../IUpdateQuote_Request.dto.ts | 2 + .../IUpdateQuote_Response.dto.ts | 1 + .../sales/application/dto/Quote/index.ts | 1 - 46 files changed, 334 insertions(+), 1293 deletions(-) rename server/src/contexts/sales/application/Quote/{GetQuote.useCase.test.ts => GetQuote.useCase.test.ts.bak} (100%) create mode 100644 server/src/contexts/sales/application/services/QuoteService.ts create mode 100644 server/src/contexts/sales/application/services/index.ts create mode 100644 server/src/contexts/sales/domain/entities/Quotes/QuoteCustomerReference.ts create mode 100644 server/src/contexts/sales/domain/services/QuoteReferenceGeneratorService.interface.ts create mode 100644 server/src/contexts/sales/domain/services/index.ts delete mode 100644 server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/presupuesto.html delete mode 100644 server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote copy.hbs delete mode 100644 server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.css delete mode 100644 server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.tw.hbs delete mode 100644 shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/IReportQuote_Response.dto.ts delete mode 100644 shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/index.ts diff --git a/client/src/app/quotes/components/QuotesDataTable.tsx b/client/src/app/quotes/components/QuotesDataTable.tsx index f545d79..0ce40ef 100644 --- a/client/src/app/quotes/components/QuotesDataTable.tsx +++ b/client/src/app/quotes/components/QuotesDataTable.tsx @@ -66,10 +66,28 @@ export const QuotesDataTable = ({ const columns = useMemo[]>( () => [ + { + id: "reference" as const, + accessorKey: "reference", + header: () => <>{t("quotes.list.columns.reference")}, + cell: ({ renderValue }) =>
{renderValue()}
, + enableResizing: false, + }, + + { + id: "status" as const, + accessorKey: "status", + header: () => <>{t("quotes.list.columns.status")}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cell: ({ row: { original } }) => , + enableResizing: false, + }, { id: "date" as const, accessor: "date", - header: () => <>{t("quotes.list.columns.date")}, + header: () => ( +
{t("quotes.list.columns.date")}
+ ), cell: ({ row: { original } }) => { const quoteDate = UTCDateValue.create(original.date); return ( @@ -80,6 +98,13 @@ export const QuotesDataTable = ({ }, enableResizing: false, }, + { + id: "customer_reference" as const, + accessorKey: "customer_reference", + header: () => <>{t("quotes.list.columns.customer_reference")}, + cell: ({ renderValue }) =>
{renderValue()}
, + enableResizing: false, + }, { id: "customer_information" as const, accessorKey: "customer_information", @@ -104,21 +129,7 @@ export const QuotesDataTable = ({ enableResizing: false, size: 640, }, - { - id: "reference" as const, - accessorKey: "reference", - header: () => <>{t("quotes.list.columns.reference")}, - cell: ({ renderValue }) =>
{renderValue()}
, - enableResizing: false, - }, - { - id: "status" as const, - accessorKey: "status", - header: () => <>{t("quotes.list.columns.status")}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - cell: ({ row: { original } }) => , - enableResizing: false, - }, + { id: "total_price" as const, accessor: "total_price", diff --git a/client/src/app/quotes/components/editors/QuoteGeneralCardEditor.tsx b/client/src/app/quotes/components/editors/QuoteGeneralCardEditor.tsx index 67d0dd6..2425554 100644 --- a/client/src/app/quotes/components/editors/QuoteGeneralCardEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteGeneralCardEditor.tsx @@ -27,11 +27,11 @@ export const QuoteGeneralCardEditor = () => { errors={formState.errors} /> diff --git a/client/src/app/quotes/create.tsx b/client/src/app/quotes/create.tsx index c75ca6d..a43bf3b 100644 --- a/client/src/app/quotes/create.tsx +++ b/client/src/app/quotes/create.tsx @@ -29,8 +29,9 @@ export const QuoteCreate = () => { const defaultValues = useMemo( () => ({ date: new Date(Date.now()).toUTCString(), + customer_reference: "", customer_information: "", - reference: "", + //reference: "", }), [] ); @@ -40,9 +41,10 @@ export const QuoteCreate = () => { defaultValues, resolver: joiResolver( Joi.object({ - reference: Joi.string().required(), - date: Joi.date().required(), + //reference: Joi.string().required(), customer_information: Joi.string().required(), + date: Joi.date().required(), + customer_reference: Joi.string(), }), { //messages: SpanishJoiMessages, @@ -99,13 +101,22 @@ export const QuoteCreate = () => {
- */} + + { () => ({ date: "", reference: "", + customer_reference: "", customer_information: "", lang_code: "", currency_code: "", @@ -244,7 +245,7 @@ export const QuoteEdit = () => {

- {t("quotes.edit.title")} + {t("quotes.edit.title")} {data.reference}

diff --git a/client/src/app/quotes/hooks/useQuotes.tsx b/client/src/app/quotes/hooks/useQuotes.tsx index a3e9924..8c46389 100644 --- a/client/src/app/quotes/hooks/useQuotes.tsx +++ b/client/src/app/quotes/hooks/useQuotes.tsx @@ -279,9 +279,9 @@ export const useQuotes = () => { }, }); - const download = (id: string, filename?: string) => { + const download = (id: string, filename: string) => { const url = actions.getQuotePDFDownloadURL(id); - return downloader.download(url, filename ?? "ssaas"); + return downloader.download(url, filename); }; return { diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 36ee66d..2aea287 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -112,6 +112,7 @@ "date": "Date", "reference": "Reference", "status": "Status", + "customer_reference": "Customer Ref.", "customer_information": "Customer", "total_price": "Imp. total", "actions": { @@ -197,6 +198,11 @@ "desc": "Quote currency", "placeholder": "" }, + "customer_reference": { + "label": "Customer reference", + "desc": "Customer reference for this quote", + "placeholder": "" + }, "customer_information": { "label": "Customer's contact data", "desc": "Recommendation: enter the customer's name on the first line, the address on the second line, and the zip code and city/state on the third line.", @@ -205,7 +211,7 @@ "payment_method": { "label": "Payment method", "placeholder": "", - "desc": "Method of payment of the quote" + "desc": "Method of payment for this quote" }, "notes": { "label": "Notes", diff --git a/client/src/locales/es.json b/client/src/locales/es.json index c4a6ff4..39094b3 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -194,6 +194,11 @@ "desc": "Moneda de la cotización", "placeholder": "" }, + "customer_reference": { + "label": "Referencia del cliente", + "desc": "Referencia para el cliente de esta cotización", + "placeholder": "" + }, "customer_information": { "label": "Datos del cliente", "desc": "Recomensación: escriba el nombre del cliente en la primera línea, la direccion en la segunda y el código postal y ciudad en la tercera.", diff --git a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts index 6d1dfc8..bf82718 100644 --- a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts @@ -3,6 +3,7 @@ import { IUseCase, IUseCaseError, UseCaseError } from "@/contexts/common/applica import { IRepositoryManager } from "@/contexts/common/domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; +import { SequelizeBusinessTransactionType } from "@/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction"; import { Collection, CurrencyData, @@ -25,11 +26,13 @@ import { IQuoteRepository, Quote, QuoteCustomer, + QuoteCustomerReference, QuoteItem, QuoteReference, QuoteStatus, } from "../../domain"; import { ISalesContext } from "../../infrastructure"; +import { generateQuoteReferenceForDealer } from "../services"; export type CreateQuoteResponseOrError = | Result // Misc errors (value objects) @@ -40,12 +43,13 @@ export class CreateQuoteUseCase { private _adapter: ISequelizeAdapter; private _repositoryManager: IRepositoryManager; - private _dealer?: Dealer; + private _dealer: Dealer; + private _transaction: SequelizeBusinessTransactionType; constructor(context: ISalesContext) { this._adapter = context.adapter; this._repositoryManager = context.repositoryManager; - this._dealer = context.dealer; + this._dealer = context.dealer!; } async execute(request: ICreateQuote_Request_DTO) { @@ -57,8 +61,6 @@ export class CreateQuoteUseCase return Result.fail(UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message)); } - const dealerId = this._dealer.id; - const idOrError = ensureIdIsValid(id); if (idOrError.isFailure) { const message = idOrError.error.message; //`Quote ID ${quoteDTO.id} is not valid`; @@ -67,58 +69,58 @@ export class CreateQuoteUseCase ); } - // Comprobar que no existe un quote previo con esos datos + this._transaction = this._adapter.startTransaction(); const quoteRepository = this._getQuoteRepository(); - const idExists = await quoteRepository().exists(idOrError.object); - if (idExists) { - const message = `Another quote with same ID exists`; - return Result.fail( - UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { - path: "id", - }) - ); - } - - // Crear quote - const quoteOrError = this._tryCreateQuoteInstance(request, idOrError.object, dealerId); - - if (quoteOrError.isFailure) { - const { error: domainError } = quoteOrError; - let errorCode = ""; - let message = ""; - - switch (domainError.code) { - case DomainError.INVALID_INPUT_DATA: - errorCode = UseCaseError.INVALID_INPUT_DATA; - message = "El presupuesto tiene algún dato erróneo."; - break; - - default: - errorCode = UseCaseError.UNEXCEPTED_ERROR; - message = domainError.message; - break; - } - - return Result.fail(UseCaseError.create(errorCode, message, domainError)); - } - - return this._saveQuote(quoteOrError.object); - } - - private async _saveQuote(quote: Quote) { - // Guardar el contacto - const transaction = this._adapter.startTransaction(); - const quoteRepository = this._getQuoteRepository(); - let quoteRepo: IQuoteRepository; - try { - await transaction.complete(async (t) => { - quoteRepo = quoteRepository({ transaction: t }); - await quoteRepo.create(quote); - }); + return await this._transaction.complete(async (t) => { + const quoteRepo = quoteRepository({ transaction: t }); - return Result.ok(quote); + // Comprobar que no existe un quote previo con esos datos + const idExists = await quoteRepo.exists(idOrError.object); + if (idExists) { + const message = `Another quote with same ID exists`; + return Result.fail( + UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { + path: "id", + }) + ); + } + + // Generate Reference + const quoteReference = await generateQuoteReferenceForDealer(this._dealer, quoteRepo); + + // Crear quote + const quoteOrError = this._tryCreateQuoteInstance( + request, + idOrError.object, + quoteReference + ); + + if (quoteOrError.isFailure) { + const { error: domainError } = quoteOrError; + let errorCode = ""; + let message = ""; + + switch (domainError.code) { + case DomainError.INVALID_INPUT_DATA: + errorCode = UseCaseError.INVALID_INPUT_DATA; + message = "El presupuesto tiene algún dato erróneo."; + break; + + default: + errorCode = UseCaseError.UNEXCEPTED_ERROR; + message = domainError.message; + break; + } + + return Result.fail(UseCaseError.create(errorCode, message, domainError)); + } + + await quoteRepo.create(quoteOrError.object); + + return Result.ok(quoteOrError.object); + }); } catch (error: unknown) { const _error = error as IInfrastructureError; return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message)); @@ -128,8 +130,10 @@ export class CreateQuoteUseCase private _tryCreateQuoteInstance( quoteDTO: ICreateQuote_Request_DTO, quoteId: UniqueID, - dealerId: UniqueID + quoteReference: QuoteReference ): Result { + const dealerId: UniqueID = this._dealer.id; + const statusOrError = QuoteStatus.create(quoteDTO.status); if (statusOrError.isFailure) { return Result.fail(statusOrError.error); @@ -140,11 +144,6 @@ export class CreateQuoteUseCase return Result.fail(dateOrError.error); } - const referenceOrError = QuoteReference.create(quoteDTO.reference); - if (referenceOrError.isFailure) { - return Result.fail(referenceOrError.error); - } - const languageOrError = Language.createFromCode( quoteDTO.lang_code ?? this._dealer?.language.code ); @@ -152,6 +151,11 @@ export class CreateQuoteUseCase return Result.fail(languageOrError.error); } + const customerReferenceOrError = QuoteCustomerReference.create(quoteDTO.customer_reference); + if (customerReferenceOrError.isFailure) { + return Result.fail(customerReferenceOrError.error); + } + const customerOrError = QuoteCustomer.create(quoteDTO.customer_information); if (customerOrError.isFailure) { return Result.fail(customerOrError.error); @@ -259,8 +263,9 @@ export class CreateQuoteUseCase { status: statusOrError.object, date: dateOrError.object, - reference: referenceOrError.object, + reference: quoteReference, language: languageOrError.object, + customerReference: customerReferenceOrError.object, customer: customerOrError.object, currency: currencyOrError.object, paymentMethod: paymentOrError.object, diff --git a/server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts b/server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts.bak similarity index 100% rename from server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts rename to server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts.bak diff --git a/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts index 5775a66..49b4c45 100644 --- a/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts @@ -31,6 +31,7 @@ import { IQuoteRepository, Quote, QuoteCustomer, + QuoteCustomerReference, QuoteItem, QuoteReference, QuoteStatus, @@ -153,6 +154,11 @@ export class UpdateQuoteUseCase return Result.fail(languageOrError.error); } + const customerReferenceOrError = QuoteCustomerReference.create(quoteDTO.customer_reference); + if (customerReferenceOrError.isFailure) { + return Result.fail(customerReferenceOrError.error); + } + const customerOrError = QuoteCustomer.create(quoteDTO.customer_information); if (customerOrError.isFailure) { return Result.fail(customerOrError.error); @@ -253,6 +259,7 @@ export class UpdateQuoteUseCase date: dateOrError.object, reference: referenceOrError.object, language: languageOrError.object, + customerReference: customerReferenceOrError.object, customer: customerOrError.object, currency: currencyOrError.object, paymentMethod: paymentOrError.object, diff --git a/server/src/contexts/sales/application/services/QuoteService.ts b/server/src/contexts/sales/application/services/QuoteService.ts new file mode 100644 index 0000000..a584004 --- /dev/null +++ b/server/src/contexts/sales/application/services/QuoteService.ts @@ -0,0 +1,23 @@ +import { IDealer, IQuoteRepository, QuoteReference } from "../../domain"; + +const generateQuoteReferenceForDealer = async ( + dealer: IDealer, + quoteRepository: IQuoteRepository +): Promise => { + // Obtener la última referencia del dealer + const lastQuote = await quoteRepository.findLastQuoteByDealerId(dealer.id); + + // Generar la nueva referencia usando el Value Object + const quoteReferenceResult = QuoteReference.fromPrevious( + lastQuote ? lastQuote.reference : null, + dealer + ); + + if (quoteReferenceResult.isFailure) { + throw new Error("Error al crear la referencia de presupuesto"); + } + + return quoteReferenceResult.object; +}; + +export { generateQuoteReferenceForDealer }; diff --git a/server/src/contexts/sales/application/services/index.ts b/server/src/contexts/sales/application/services/index.ts new file mode 100644 index 0000000..2af5997 --- /dev/null +++ b/server/src/contexts/sales/application/services/index.ts @@ -0,0 +1 @@ +export * from "./QuoteService"; diff --git a/server/src/contexts/sales/domain/entities/Dealer/Dealer.ts b/server/src/contexts/sales/domain/entities/Dealer/Dealer.ts index 8e8d35f..fab6313 100644 --- a/server/src/contexts/sales/domain/entities/Dealer/Dealer.ts +++ b/server/src/contexts/sales/domain/entities/Dealer/Dealer.ts @@ -29,6 +29,8 @@ export interface IDealer { additionalInfo: KeyValueMap; status: DealerStatus; currency: CurrencyData; + + getAcronym: () => string; } export class Dealer extends AggregateRoot implements IDealer { @@ -37,6 +39,12 @@ export class Dealer extends AggregateRoot implements IDealer { return Result.ok(user); } + public static generateDealerNameAcronyn(dealerName: Name | string): string { + return typeof dealerName === "string" + ? Name.generateAcronym(dealerName) + : dealerName.getAcronym(); + } + get user_id(): UniqueID { return this.props.user_id; } @@ -60,4 +68,8 @@ export class Dealer extends AggregateRoot implements IDealer { get currency(): CurrencyData { return this.props.currency; } + + public getAcronym(): string { + return this.props.name.getAcronym(); + } } diff --git a/server/src/contexts/sales/domain/entities/Quotes/Quote.ts b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts index 3114a1b..4c6559e 100644 --- a/server/src/contexts/sales/domain/entities/Quotes/Quote.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts @@ -20,6 +20,7 @@ export interface IQuoteProps { status: QuoteStatus; date: UTCDateValue; reference: QuoteReference; + customerReference: QuoteReference; customer: QuoteCustomer; language: Language; currency: CurrencyData; @@ -43,6 +44,7 @@ export interface IQuote { status: QuoteStatus; date: UTCDateValue; reference: QuoteReference; + customerReference: QuoteReference; customer: QuoteCustomer; language: Language; currency: CurrencyData; @@ -115,6 +117,10 @@ export class Quote extends AggregateRoot implements IQuote { return this.props.reference; } + get customerReference() { + return this.props.customerReference; + } + get customer() { return this.props.customer; } diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts index c2f8472..a442f32 100644 --- a/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts @@ -21,7 +21,7 @@ export class QuoteCustomer extends StringValueObject { .default("") .trim() .max(QuoteCustomer.MAX_LENGTH) - .label(options.label ? options.label : "value"); + .label(options.label ? options.label : "customer_information"); return RuleValidator.validate(rule, value); } diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomerReference.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomerReference.ts new file mode 100644 index 0000000..e69fbe5 --- /dev/null +++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomerReference.ts @@ -0,0 +1,45 @@ +import { + DomainError, + IStringValueObjectOptions, + Result, + RuleValidator, + StringValueObject, + handleDomainError, +} from "@shared/contexts"; +import { UndefinedOr } from "@shared/utilities"; +import Joi from "joi"; + +export interface IQuoteCustomerReferenceOptions extends IStringValueObjectOptions {} + +export class QuoteCustomerReference extends StringValueObject { + private static readonly MAX_LENGTH = 255; + + protected static validate(value: UndefinedOr, options: IQuoteCustomerReferenceOptions) { + const rule = Joi.string() + .allow(null) + .allow("") + .default("") + .trim() + .max(QuoteCustomerReference.MAX_LENGTH) + .label(options.label ? options.label : "value"); + + return RuleValidator.validate(rule, value); + } + + public static create(value: UndefinedOr, options: IQuoteCustomerReferenceOptions = {}) { + const _options = { + label: "customer_reference", + ...options, + }; + + const validationResult = QuoteCustomerReference.validate(value, _options); + + if (validationResult.isFailure) { + return Result.fail( + handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options) + ); + } + + return Result.ok(new QuoteCustomerReference(validationResult.object)); + } +} diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts index de023f9..e42ef6b 100644 --- a/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts @@ -6,8 +6,9 @@ import { StringValueObject, handleDomainError, } from "@shared/contexts"; -import { UndefinedOr } from "@shared/utilities"; +import { NullOr, UndefinedOr } from "@shared/utilities"; import Joi from "joi"; +import { IDealer } from "../Dealer"; export interface IQuoteReferenceOptions extends IStringValueObjectOptions {} @@ -42,4 +43,24 @@ export class QuoteReference extends StringValueObject { return Result.ok(new QuoteReference(validationResult.object)); } + + public static fromPrevious( + previousReference: NullOr, + dealer: IDealer + ): Result { + const lastNumber = previousReference + ? QuoteReference._extractSequenceFromReference(previousReference) + : 0; + + const newNumber = lastNumber + 1; + const dealerAcronym = dealer.getAcronym(); + const newReference = `${dealerAcronym}-${newNumber.toString().padStart(4, "0")}`; + + return QuoteReference.create(newReference); + } + + private static _extractSequenceFromReference(reference: QuoteReference): number { + const parts = reference.toString().split("-"); + return parseInt(parts[parts.length - 1], 10); + } } diff --git a/server/src/contexts/sales/domain/entities/Quotes/index.ts b/server/src/contexts/sales/domain/entities/Quotes/index.ts index aebae89..9b96261 100644 --- a/server/src/contexts/sales/domain/entities/Quotes/index.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/index.ts @@ -1,5 +1,6 @@ export * from "./Quote"; export * from "./QuoteCustomer"; +export * from "./QuoteCustomerReference"; export * from "./QuoteItem"; export * from "./QuoteReference"; export * from "./QuoteStatus"; diff --git a/server/src/contexts/sales/domain/index.ts b/server/src/contexts/sales/domain/index.ts index 6347a2b..a72da42 100644 --- a/server/src/contexts/sales/domain/index.ts +++ b/server/src/contexts/sales/domain/index.ts @@ -1,2 +1,3 @@ export * from "./entities"; export * from "./repository"; +export * from "./services"; diff --git a/server/src/contexts/sales/domain/repository/QuoteRepository.interface.ts b/server/src/contexts/sales/domain/repository/QuoteRepository.interface.ts index f02c4e1..d6f84f7 100644 --- a/server/src/contexts/sales/domain/repository/QuoteRepository.interface.ts +++ b/server/src/contexts/sales/domain/repository/QuoteRepository.interface.ts @@ -12,4 +12,6 @@ export interface IQuoteRepository extends IRepository { findAll(queryCriteria?: IQueryCriteria): Promise>; removeById(id: UniqueID): Promise; + + findLastQuoteByDealerId(dealerId: UniqueID): Promise; } diff --git a/server/src/contexts/sales/domain/services/QuoteReferenceGeneratorService.interface.ts b/server/src/contexts/sales/domain/services/QuoteReferenceGeneratorService.interface.ts new file mode 100644 index 0000000..fb05e4b --- /dev/null +++ b/server/src/contexts/sales/domain/services/QuoteReferenceGeneratorService.interface.ts @@ -0,0 +1,3 @@ +import { IApplicationService } from "@/contexts/common/application"; + +export interface IQuoteReferenceGeneratorService extends IApplicationService {} diff --git a/server/src/contexts/sales/domain/services/index.ts b/server/src/contexts/sales/domain/services/index.ts new file mode 100644 index 0000000..29048ac --- /dev/null +++ b/server/src/contexts/sales/domain/services/index.ts @@ -0,0 +1 @@ +export * from "./QuoteReferenceGeneratorService.interface"; diff --git a/server/src/contexts/sales/infrastructure/Quote.repository.ts b/server/src/contexts/sales/infrastructure/Quote.repository.ts index 5185614..5ca541c 100644 --- a/server/src/contexts/sales/infrastructure/Quote.repository.ts +++ b/server/src/contexts/sales/infrastructure/Quote.repository.ts @@ -112,6 +112,28 @@ export class QuoteRepository extends SequelizeRepository implements IQuot public async removeById(id: UniqueID, force: boolean = false): Promise { return this._removeById("Quote_Model", id); } + + public async findLastQuoteByDealerId(dealerId: UniqueID): Promise { + const Quote_Model: ModelDefined = this._adapter.getModel("Quote_Model"); + const QuoteItem_Model: ModelDefined = this._adapter.getModel("QuoteItem_Model"); + + const rawQuote: any = await Quote_Model.findOne({ + include: [ + { + model: QuoteItem_Model, + as: "items", + }, + ], + where: { dealer_id: dealerId.toString() }, + order: [["created_at", "DESC"]], // Ordenamos por referencia en orden descendente + }); + + if (!rawQuote === true) { + return null; + } + + return this.mapper.mapToDomain(rawQuote); + } } export const registerQuoteRepository = (context: ISalesContext) => { diff --git a/server/src/contexts/sales/infrastructure/Sales.context.ts b/server/src/contexts/sales/infrastructure/Sales.context.ts index dd63cea..f087b8e 100644 --- a/server/src/contexts/sales/infrastructure/Sales.context.ts +++ b/server/src/contexts/sales/infrastructure/Sales.context.ts @@ -1,7 +1,9 @@ import { IContext } from "@/contexts/common/infrastructure"; -import { Dealer } from "../domain"; +import { Dealer, IQuoteReferenceGeneratorService } from "../domain"; export interface ISalesContext extends IContext { - //services: IApplicationService; + services: { + QuoteReferenceGeneratorService: IQuoteReferenceGeneratorService; + }; dealer?: Dealer; } diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts index a7138d5..1cc4b83 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts @@ -35,16 +35,16 @@ export class CreateQuoteController extends ExpressController { async executeImpl() { try { const quoteDTO: ICreateQuote_Request_DTO = this.req.body; - /*const user = this.req.user; + const dealer = this.context.dealer; - if (!user) { - const errorMessage = "Unexpected missing user data"; + if (!dealer) { + const errorMessage = "Unexpected missing dealer data"; const infraError = InfrastructureError.create( InfrastructureError.UNEXCEPTED_ERROR, errorMessage ); return this.internalServerError(errorMessage, infraError); - }*/ + } // Validaciones de DTO const quoteDTOOrError = ensureCreateQuote_Request_DTOIsValid(quoteDTO); diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts index 843e7da..75ed734 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts @@ -18,6 +18,7 @@ export const CreateQuotePresenter: ICreateQuotePresenter = { status: quote.status.toString(), date: quote.date.toISO8601(), reference: quote.reference.toString(), + customer_reference: quote.customerReference.toString(), customer_information: quote.customer.toString(), lang_code: quote.language.toString(), currency_code: quote.currency.toString(), diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts index d61956e..87675aa 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts @@ -18,6 +18,7 @@ export const GetQuotePresenter: IGetQuotePresenter = { status: quote.status.toString(), date: quote.date.toISO8601(), reference: quote.reference.toString(), + customer_reference: quote.customerReference.toString(), customer_information: quote.customer.toString(), lang_code: quote.language.toString(), currency_code: quote.currency.toString(), diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts index a0c58ae..c72640a 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts @@ -23,6 +23,7 @@ export const ListQuotesPresenter: IListQuotesPresenter = { date: quote.date.toISO8601(), reference: quote.reference.toString(), customer_information: quote.customer.toString(), + customer_reference: quote.customerReference.toString(), lang_code: quote.language.toString(), currency_code: quote.currency.toString(), diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/ReportQuote.reporter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/ReportQuote.reporter.ts index ab5d091..db561b0 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/ReportQuote.reporter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/ReportQuote.reporter.ts @@ -64,6 +64,7 @@ const map = (quote: Quote, context: ISalesContext) => { status: quote.status.toString(), date: quote.date.toLocaleDateString(), reference: quote.reference.toString(), + customer_reference: quote.customerReference.toString(), customer_information: quote.customer.toString(), lang_code: quote.language.toString(), currency_code: quote.currency.toString(), diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/presupuesto.html b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/presupuesto.html deleted file mode 100644 index b7a2c30..0000000 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/presupuesto.html +++ /dev/null @@ -1,466 +0,0 @@ - - - - - - Presupuesto #29b6ac1a-ce83-44be-ac3a-714ceee1983f - - - - - -
- - -
-
- -
- -
- - - - - - - - - - - -
HEADER -
- -
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cant.DescripciónPrec. UnitarioSubtotalDto (%)Importe total
CORREDERAS
1 P1 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x1500 mm. incluyendo Kit de casonetto. 14,50 €14,50 €014,50 €
1 P7 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x820 mm incluyendo Kit de casonetto.9,60 €9,60 €09,60 €
1 P8 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x820 mm incluyendo Kit de casonetto.9,60 €9,60 €09,60 €
1 P9 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x960 mm incluyendo Kit de casonetto.9,95 €9,95 €09,95 €
1 P10 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x965 mm. incluyendo Kit de casonetto.9,95 €9,95 €09,95 €
1 P12 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x760 mm. incluyendo Kit de casonetto.8,86 €8,86 €08,86 €
1 P13 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x760 mm. incluyendo Kit de casonetto.8,86 €8,86 €08,86 €
1 P14 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x747 mm. incluyendo Kit de casonetto.8,86 €8,86 €08,86 €
1 P15 -Puerta de paso corredera lisa lacada BLANCO AV-501 a determinar de dimensiones 2600x900 mm. incluyendo Kit de casonetto.9,60 €9,60 €09,60 €
ABATIBLES
1 P2 -Puertas de paso abatible 1/c enrasada, sin cabecero, lisa lacado BLANCO AV-501 de 50 mm de espesor, de dimensiones de hoja 2600x860 mm. con cerco de 100/140x35, tapetas hasta 120x19 mm., resbalón unificado y bisagras ocultas negras. Apertura der11,05 €11,05 €011,05 €
1 P3 -Puertas de paso abatible 1/c enrasada, sin cabecero, lisa lacado BLANCO AV-501 de 50 mm de espesor, de dimensiones de hoja 2600x885 mm. con cerco de 100/140x35, tapetas hasta 120x19 mm., resbalón unificado y bisagras ocultas negras. Apertura der11,05 €11,05 €011,05 €
1,75Forrado paramentos 19 mm lacado m²1,56 €2,72 €02,72 €
1 P4 -Puertas de paso abatible 1/c enrasada, sin cabecero, lisa lacado BLANCO AV-501 de 50 mm de espesor, de dimensiones de hoja 2600x985 mm. con cerco de 100/140x35, tapetas hasta 120x19 mm., resbalón unificado y bisagras ocultas negras. Apertura der11,81 €11,81 €011,81 €
1 P5 -Puertas de paso abatible 1/c enrasada, sin cabecero, lisa lacado BLANCO AV-501 de 50 mm de espesor, de dimensiones de hoja 2600x885 mm. con cerco de 100/140x35, tapetas hasta 120x19 mm., resbalón unificado y bisagras ocultas negras. Apertura izq11,05 €11,05 €011,05 €
1 P6 -Puertas de paso abatible 1/c enrasada, sin cabecero, lisa lacado BLANCO AV-501 de 50 mm de espesor, de dimensiones de hoja 2600x885 mm. con cerco de 100/140x35, tapetas hasta 120x19 mm., resbalón unificado y bisagras ocultas negras. Apertura der11,05 €11,05 €011,05 €
NOTA.- --Casonetto propiedad del cliente. --Manivelas y/o condenas no incluidas.0,00 €0
DESCUENTO 0,00 €0
null
161 M.l. Rodapie lacado BLANCO AV-501 a determinar DE 200X19 mm con dos estrías horizontales 0,15 €24,79 €024,79 €
0 DESCUENTO 0,00 €0,00 €00,00 €
0null0,00 €0,00 €00,00 €
3 Entrega de material en domicilio cliente1,20 €3,60 €03,60 €
0 DESCUENTO 0,00 €0,00 €00,00 €
0null0,00 €0,00 €00,00 €
9 Instalación PUERTA DE PASO CORREDERA1,30 €11,70 €011,70 €
5 Instalación PUERTA DE PASO ABATIBLE1,00 €5,00 €05,00 €
159 Instalación M.L. RODAPIE 0,09 €13,52 €013,52 €
0 DESCUENTO 0,00 €0,00 €00,00 €
0null0,00 €0,00 €00,00 €
-
- -
- -
-
-
-

Forma de pago: 60% Aceptación presupuesto -30% Un día antes de la entrega del material -10% Restante a la finalización de la obra

-
-
-

Notas: Este presupuesto no se considerará en firme hasta confirmar medidas y diseños definitivos.

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - \ No newline at end of file diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote copy.hbs b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote copy.hbs deleted file mode 100644 index 738ae20..0000000 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote copy.hbs +++ /dev/null @@ -1,319 +0,0 @@ - - - - - Presupuesto #{{id}} - - - - - -
- - - - - - - - - - - -
-
-

Invoice

- - - -
-
-
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tincidunt metus eu consectetur rutrum. - Praesent tempor facilisis dapibus. Aliquam cursus diam ac vehicula pulvinar. Integer lacinia non odio et - condimentum. Aenean faucibus cursus mi, sed interdum turpis sagittis a. Quisque quis pellentesque mi. Ut - erat eros, posuere sed scelerisque ut, pharetra vitae tellus. Suspendisse ligula sapien, laoreet ac - hendrerit sit amet, viverra vel mi. Pellentesque faucibus nisl et dolor pharetra, vel mattis massa - venenatis. Integer congue condimentum nisi, sed tincidunt velit tincidunt non. Nulla sagittis sed lorem - pretium aliquam. Praesent consectetur volutpat nibh, quis pulvinar est volutpat id. Cras maximus odio - posuere suscipit venenatis. Donec rhoncus scelerisque metus, in tempus erat rhoncus sed. Morbi massa - sapien, - porttitor id urna vel, volutpat blandit velit. Cras sit amet sem eros. Quisque commodo facilisis - tristique. - Proin pellentesque sodales rutrum. Vestibulum purus neque, congue vel dapibus in, venenatis ut felis. - Donec - et ligula enim. Sed sapien sapien, tincidunt vitae lectus quis, ultricies rhoncus mi. Nunc dapibus nulla - tempus nunc interdum, sed facilisis ex pellentesque. Nunc vel lorem leo. Cras pharetra sodales metus. - Cras - lacus ex, consequat at consequat vel, laoreet ac dui. Curabitur aliquam, sapien quis congue feugiat, - nisi - nisl feugiat diam, sed vehicula velit nulla ac nisl. Aliquam quis nisi euismod massa blandit pharetra - nec - eget nunc. Etiam eros ante, auctor sit amet quam vel, fringilla faucibus leo. Morbi a pulvinar nulla. - Praesent sed vulputate nisl. Orci varius natoque penatibus et magnis dis parturient montes, nascetur - ridiculus mus. Aenean commodo mollis iaculis. Maecenas consectetur enim vitae mollis venenatis. Ut - scelerisque pretium orci id laoreet. In sit amet pharetra diam. Vestibulum in molestie lorem. Nunc - gravida, - eros non consequat fermentum, ex orci vestibulum orci, non accumsan sem velit ac lectus. Vivamus - malesuada - lacus nec velit dignissim, ac fermentum nulla pretium. Aenean mi nisi, convallis sed tempor in, - porttitor - eu - libero. Praesent et molestie ante. Duis suscipit vitae purus sit amet aliquam. Vestibulum lectus justo, - lobortis a purus a, dapibus efficitur metus. Suspendisse potenti. Duis dictum ex lorem. Suspendisse nec - ligula consectetur magna hendrerit ullamcorper et eget mauris. Etiam vestibulum sodales diam, eget - venenatis - nunc luctus quis. Ut fermentum placerat neque nec elementum. Praesent orci erat, rhoncus vitae est eu, - dictum molestie metus. Cras et fermentum elit. Aenean eget augue lacinia, varius ante in, ullamcorper - dolor. - Cras viverra purus non egestas consectetur. Nulla nec dolor ac lectus convallis aliquet sed a metus. - Suspendisse eu imperdiet nunc, id pulvinar risus. Maecenas varius sagittis est, vel fermentum risus - accumsan - at. Vestibulum sollicitudin dui pharetra sapien volutpat, id convallis mi vestibulum. Phasellus commodo - sit - amet lorem quis imperdiet. Proin nec diam sed urna euismod ultricies at sed urna. Quisque ornare, nulla - et - vehicula ultrices, massa purus vehicula urna, ac sodales lacus leo vitae mi. Sed congue placerat justo - at - placerat. Aenean suscipit fringilla vehicula. Quisque iaculis orci vitae arcu commodo maximus. Maecenas - nec - nunc rutrum, cursus elit quis, porttitor sapien. Sed ac hendrerit ipsum, lacinia fringilla velit. Donec - ultricies feugiat dictum. -
-
- - - - - - - - - - - - - {{#each items}} - - - - - - - - - {{/each}} - -
Cant.DescripciónPrec. UnitarioSubtotalDto (%)Importe total
{{quantity}}{{description}}{{unit_price}}{{subtotal_price}}{{discount}}{{total_price}}
-
- -
- -
- -
- - - - - - - - - - - - - - - -
Due byAccount numberTotal due
May 10, 2018132 456 789 012€10,545.00
-
- - - - \ No newline at end of file diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.css b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.css deleted file mode 100644 index a9b7942..0000000 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.css +++ /dev/null @@ -1,136 +0,0 @@ -@font-face { - font-family: Pacifico; - src: url(pacifico.ttf); -} -@font-face { - font-family: Source Sans Pro; - font-weight: 400; - src: url(sourcesanspro-regular.otf); -} -@font-face { - font-family: Source Sans Pro; - font-weight: 700; - src: url(sourcesanspro-bold.otf); -} - -@page { - font-family: Pacifico; - margin: 3cm; - @bottom-left { - color: #1ee494; - content: "♥ Thank you!"; - } - @bottom-right { - color: #a9a; - content: "contact@courtbouillon.org | courtbouillon.org"; - font-size: 9pt; - } -} - -.fullpage { - page: full; -} - -html { - color: #14213d; - font-family: Source Sans Pro; - font-size: 11pt; - line-height: 1.6; -} -body { - margin: 0; -} - -h1 { - color: #1ee494; - font-family: Pacifico; - font-size: 40pt; - margin: 0; -} - -aside { - display: flex; - margin: 2em 0 4em; -} -aside address { - font-style: normal; - white-space: pre-line; -} -aside address#from { - color: #a9a; - flex: 1; -} -aside address#to { - text-align: right; -} - -dl { - position: absolute; - right: 0; - text-align: right; - top: 0; -} -dt, -dd { - display: inline; - margin: 0; -} -dt { - color: #a9a; -} -dt::before { - content: ""; - display: block; -} -dt::after { - content: ":"; -} - -table { - border-collapse: collapse; - width: 100%; -} -th { - border-bottom: 0.2mm solid #a9a; - color: #a9a; - font-size: 10pt; - font-weight: 400; - padding-bottom: 0.25cm; - text-transform: uppercase; -} -td { - padding-top: 7mm; -} -td:last-of-type { - color: #1ee494; - font-weight: bold; - text-align: right; -} -th, -td { - text-align: center; -} -th:first-of-type, -td:first-of-type { - text-align: left; -} -th:last-of-type, -td:last-of-type { - text-align: right; -} -footer { - content: ""; - display: block; - height: 6cm; -} -table#total { - background: #f6f6f6; - border-color: #f6f6f6; - border-style: solid; - border-width: 2cm 3cm; - bottom: 0; - font-size: 20pt; - margin: 0 -3cm; - position: absolute; - width: 18cm; -} diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.tw.hbs b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.tw.hbs deleted file mode 100644 index d0926ae..0000000 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote.tw.hbs +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - Presupuesto #{{id}} - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tincidunt metus eu consectetur rutrum. - Praesent tempor facilisis dapibus. Aliquam cursus diam ac vehicula pulvinar. Integer lacinia non odio et - condimentum. Aenean faucibus cursus mi, sed interdum turpis sagittis a. Quisque quis pellentesque mi. Ut - erat eros, posuere sed scelerisque ut, pharetra vitae tellus. Suspendisse ligula sapien, laoreet ac - hendrerit sit amet, viverra vel mi. Pellentesque faucibus nisl et dolor pharetra, vel mattis massa - venenatis. Integer congue condimentum nisi, sed tincidunt velit tincidunt non. Nulla sagittis sed lorem - pretium aliquam. Praesent consectetur volutpat nibh, quis pulvinar est volutpat id. Cras maximus odio - posuere suscipit venenatis. Donec rhoncus scelerisque metus, in tempus erat rhoncus sed. Morbi massa - sapien, - porttitor id urna vel, volutpat blandit velit. Cras sit amet sem eros. Quisque commodo facilisis - tristique. - Proin pellentesque sodales rutrum. Vestibulum purus neque, congue vel dapibus in, venenatis ut felis. - Donec - et ligula enim. Sed sapien sapien, tincidunt vitae lectus quis, ultricies rhoncus mi. Nunc dapibus nulla - tempus nunc interdum, sed facilisis ex pellentesque. Nunc vel lorem leo. Cras pharetra sodales metus. Cras - lacus ex, consequat at consequat vel, laoreet ac dui. Curabitur aliquam, sapien quis congue feugiat, nisi - nisl feugiat diam, sed vehicula velit nulla ac nisl. Aliquam quis nisi euismod massa blandit pharetra nec - eget nunc. Etiam eros ante, auctor sit amet quam vel, fringilla faucibus leo. Morbi a pulvinar nulla. - Praesent sed vulputate nisl. Orci varius natoque penatibus et magnis dis parturient montes, nascetur - ridiculus mus. Aenean commodo mollis iaculis. Maecenas consectetur enim vitae mollis venenatis. Ut - scelerisque pretium orci id laoreet. In sit amet pharetra diam. Vestibulum in molestie lorem. Nunc - gravida, - eros non consequat fermentum, ex orci vestibulum orci, non accumsan sem velit ac lectus. Vivamus malesuada - lacus nec velit dignissim, ac fermentum nulla pretium. Aenean mi nisi, convallis sed tempor in, porttitor - eu - libero. Praesent et molestie ante. Duis suscipit vitae purus sit amet aliquam. Vestibulum lectus justo, - lobortis a purus a, dapibus efficitur metus. Suspendisse potenti. Duis dictum ex lorem. Suspendisse nec - ligula consectetur magna hendrerit ullamcorper et eget mauris. Etiam vestibulum sodales diam, eget - venenatis - nunc luctus quis. Ut fermentum placerat neque nec elementum. Praesent orci erat, rhoncus vitae est eu, - dictum molestie metus. Cras et fermentum elit. Aenean eget augue lacinia, varius ante in, ullamcorper - dolor. - Cras viverra purus non egestas consectetur. Nulla nec dolor ac lectus convallis aliquet sed a metus. - Suspendisse eu imperdiet nunc, id pulvinar risus. Maecenas varius sagittis est, vel fermentum risus - accumsan - at. Vestibulum sollicitudin dui pharetra sapien volutpat, id convallis mi vestibulum. Phasellus commodo - sit - amet lorem quis imperdiet. Proin nec diam sed urna euismod ultricies at sed urna. Quisque ornare, nulla et - vehicula ultrices, massa purus vehicula urna, ac sodales lacus leo vitae mi. Sed congue placerat justo at - placerat. Aenean suscipit fringilla vehicula. Quisque iaculis orci vitae arcu commodo maximus. Maecenas - nec - nunc rutrum, cursus elit quis, porttitor sapien. Sed ac hendrerit ipsum, lacinia fringilla velit. Donec - ultricies feugiat dictum. -
-
- - - - - - - - - - - - - {{#each items}} - - - - - - - - - {{/each}} - -
Cant.DescripciónPrec. UnitarioSubtotalDto (%)Importe total
{{quantity}}{{description}}{{unit_price}}{{subtotal_price}}{{discount}}{{total_price}}
-
- - -
- -
-
-
-

Forma de pago: {{payment_method}}

-
-
-

Notas: {{notes}}

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Importe neto{{subtotal_price}}
% Descuento{{discount.amount}}{{discount_price}}
Base imponible{{before_tax_price}}
% IVA{{tax}}{{tax_price}}
Importe total{{total_price}}
- -
-
-
-
- -
- - - - \ No newline at end of file diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/template.hbs b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/template.hbs index 4294e92..d62e5cd 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/template.hbs +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/reportQuote/reporter/templates/quote/template.hbs @@ -62,11 +62,11 @@
{{customer_information}}
diff --git a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts index 4cabc08..f201977 100644 --- a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts +++ b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts @@ -9,7 +9,13 @@ import { } from "@shared/contexts"; import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; -import { IQuoteProps, Quote, QuoteCustomer, QuoteReference } from "../../domain"; +import { + IQuoteProps, + Quote, + QuoteCustomer, + QuoteCustomerReference, + QuoteReference, +} from "../../domain"; import { QuoteStatus } from "../../domain/entities/Quotes/QuoteStatus"; import { ISalesContext } from "../Sales.context"; import { QuoteCreationAttributes, Quote_Model } from "../sequelize"; @@ -45,6 +51,11 @@ class QuoteMapper reference: this.mapsValue(source, "reference", QuoteReference.create), currency: this.mapsValue(source, "currency_code", CurrencyData.createFromCode), language: this.mapsValue(source, "lang_code", Language.createFromCode), + customerReference: this.mapsValue( + source, + "customer_reference", + QuoteCustomerReference.create + ), customer: this.mapsValue(source, "customer_information", QuoteCustomer.create), validity: this.mapsValue(source, "validity", Note.create), @@ -108,6 +119,7 @@ class QuoteMapper 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(), diff --git a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts index 82af486..3b752d7 100644 --- a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts +++ b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts @@ -44,6 +44,7 @@ export class Quote_Model extends Model< declare date: CreationOptional; declare reference: CreationOptional; declare lang_code: CreationOptional; + declare customer_reference: CreationOptional; declare customer_information: CreationOptional; declare currency_code: CreationOptional; declare payment_method: CreationOptional; @@ -100,6 +101,10 @@ export default (sequelize: Sequelize) => { defaultValue: "EUR", }, + customer_reference: { + type: new DataTypes.STRING(), + }, + customer_information: { type: DataTypes.TEXT, }, @@ -179,6 +184,9 @@ export default (sequelize: Sequelize) => { reference: { [Op.like]: `%${value}%`, }, + customer_reference: { + [Op.like]: `%${value}%`, + }, customer_information: { [Op.like]: `%${value}%`, }, diff --git a/shared/lib/contexts/common/domain/entities/Name.ts b/shared/lib/contexts/common/domain/entities/Name.ts index b9ad8a2..b02ebdd 100644 --- a/shared/lib/contexts/common/domain/entities/Name.ts +++ b/shared/lib/contexts/common/domain/entities/Name.ts @@ -40,4 +40,22 @@ export class Name extends StringValueObject { return Result.ok(new Name(validationResult.object)); } + + public static generateAcronym(name: string): string { + const words = name.split(" ").map((word) => word[0].toUpperCase()); + let acronym = words.join(""); + + // Asegurarse de que tenga 4 caracteres, recortando o añadiendo letras + if (acronym.length > 4) { + acronym = acronym.slice(0, 4); + } else if (acronym.length < 4) { + acronym = acronym.padEnd(4, "X"); // Se completa con 'X' si es necesario + } + + return acronym; + } + + public getAcronym(): string { + return Name.generateAcronym(this.toString()); + } } diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts index faf1280..c88fdcf 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts @@ -11,7 +11,7 @@ export interface ICreateQuote_Request_DTO { id: string; status: string; date: string; - reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; @@ -45,6 +45,7 @@ export function ensureCreateQuote_Request_DTOIsValid(quoteDTO: ICreateQuote_Requ status: Joi.string(), date: Joi.string(), reference: Joi.string(), + customer_reference: Joi.string(), customer_information: Joi.string(), }).unknown(true); diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts index de9cadb..91cb958 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts @@ -5,6 +5,7 @@ export interface ICreateQuote_Response_DTO { status: string; date: string; reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; diff --git a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts index d4cdd1a..7bc9233 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts @@ -5,6 +5,7 @@ export interface IGetQuote_Response_DTO { status: string; date: string; reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; diff --git a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts index e9a8e17..980b353 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts @@ -5,6 +5,7 @@ export interface IListQuotes_Response_DTO { status: string; date: string; reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; diff --git a/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/IReportQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/IReportQuote_Response.dto.ts deleted file mode 100644 index 8f46a4b..0000000 --- a/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/IReportQuote_Response.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IReportQuote_Response_DTO { - data: Uint8Array; - original: ArrayBuffer; -} diff --git a/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/index.ts b/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/index.ts deleted file mode 100644 index 32d9865..0000000 --- a/shared/lib/contexts/sales/application/dto/Quote/ReportQuote.dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./IReportQuote_Response.dto"; diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts index fa04197..ccd547b 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts @@ -11,6 +11,7 @@ export interface IUpdateQuote_Request_DTO { status: string; date: string; reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; @@ -43,6 +44,7 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ status: Joi.string(), date: Joi.string(), reference: Joi.string(), + customer_reference: Joi.string(), customer_information: Joi.string(), lang_code: Joi.string(), currency_code: Joi.string(), diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts index c867baa..f476391 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts @@ -5,6 +5,7 @@ export interface IUpdateQuote_Response_DTO { status: string; date: string; reference: string; + customer_reference: string; customer_information: string; lang_code: string; currency_code: string; diff --git a/shared/lib/contexts/sales/application/dto/Quote/index.ts b/shared/lib/contexts/sales/application/dto/Quote/index.ts index d3a08c1..6cbb1f3 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/index.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/index.ts @@ -1,5 +1,4 @@ export * from "./CreateQuote.dto"; export * from "./GetQuote.dto"; export * from "./ListQuotes.dto"; -export * from "./ReportQuote.dto"; export * from "./UpdateQuote.dto";