From 941ad254015e18d14819bb13dcc70d3d1169c620 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 3 Mar 2026 12:05:09 +0100 Subject: [PATCH] . --- .vscode/settings.json | 3 +- apps/server/package.json | 3 +- biome.json | 4 +- eslint.config.mjs | 59 ++ .../src/api/domain/value-objects/tax.vo.ts | 60 +- .../src/api/infrastructure/di/catalogs.di.ts | 13 + .../core/src/api/infrastructure/di/index.ts | 1 + .../api/infrastructure/di/transactions.di.ts | 2 +- modules/core/src/api/infrastructure/index.ts | 2 +- .../api/infrastructure/persistence/index.ts | 1 + .../{ => persistence}/sequelize/index.ts | 0 .../sequelize/mappers/index.ts | 0 .../mappers/sequelize-domain-mapper.ts | 2 +- .../mappers/sequelize-mapper.interface.ts | 2 +- .../mappers/sequelize-read-model-mapper.ts | 8 +- .../sequelize/sequelize-error-translator.ts | 6 +- .../sequelize/sequelize-func.ts | 0 .../sequelize/sequelize-repository.ts | 0 .../sequelize-transaction-manager.ts | 6 +- .../catalogs/taxes/spain-tax-catalog.json | 32 +- ...proforma-to-issued-invoice-materializer.ts | 6 +- .../src/api/application/proformas/di/index.ts | 1 + .../proformas/di/proforma-creator.di.ts | 13 +- .../proformas/di/proforma-input-mappers.di.ts | 19 + .../proformas/di/proforma-use-cases.di.ts | 15 +- .../application/proformas/mappers/index.ts | 3 +- .../inputs/create-proforma-input.mapper.ts | 319 +++++++++ .../proformas/mappers/inputs/index.ts | 2 + .../update-proforma-input.mapper.ts} | 8 +- .../create-proforma.use-case.ts | 10 +- .../application/proformas/use-cases/index.ts | 2 +- .../aggregates/issued-invoice.aggregate.ts | 12 +- .../aggregates/proforma.aggregate.ts | 128 +++- .../proforma-items/proforma-item.entity.ts | 42 +- .../proforma-items.collection.ts | 43 +- .../src/api/domain/proformas/errors/index.ts | 1 + .../errors/proforma-item-not-valid-error.ts | 32 + modules/customer-invoices/src/api/index.ts | 15 +- .../proformas/proformas-api-error-mapper.ts | 13 + .../sequelize-issued-invoice-domain.mapper.ts | 4 +- ...elize-issued-invoice-item-domain.mapper.ts | 6 +- ...-issued-invoice-recipient-domain.mapper.ts | 4 +- ...lize-issued-invoice-taxes-domain.mapper.ts | 4 +- ...equelize-verifactu-record-domain.mapper.ts | 4 +- .../proformas/di/proforma-documents.di.ts | 2 +- .../di/proforma-persistence-mappers.di.ts | 31 + .../proformas/di/proforma-repositories.di.ts | 21 +- .../proformas/di/proformas.di.ts | 31 +- .../create-proforma-request-mapper.ts} | 38 +- .../proformas/express/mappers/index.ts | 0 .../sequelize-proforma-domain.mapper.ts | 4 +- .../sequelize-proforma-item-domain.mapper.ts | 10 +- ...uelize-proforma-recipient-domain.mapper.ts | 4 +- .../repositories/proforma.repository.ts | 12 +- modules/customer-invoices/tsconfig.json | 3 +- package.json | 5 + pnpm-lock.yaml | 624 +++++++++++++++++- turbo.json | 29 +- 58 files changed, 1501 insertions(+), 223 deletions(-) create mode 100644 eslint.config.mjs create mode 100644 modules/core/src/api/infrastructure/di/catalogs.di.ts create mode 100644 modules/core/src/api/infrastructure/persistence/index.ts rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/index.ts (100%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/mappers/index.ts (100%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/mappers/sequelize-domain-mapper.ts (96%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/mappers/sequelize-mapper.interface.ts (93%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/mappers/sequelize-read-model-mapper.ts (84%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/sequelize-error-translator.ts (93%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/sequelize-func.ts (100%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/sequelize-repository.ts (100%) rename modules/core/src/api/infrastructure/{ => persistence}/sequelize/sequelize-transaction-manager.ts (96%) create mode 100644 modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts create mode 100644 modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts create mode 100644 modules/customer-invoices/src/api/application/proformas/mappers/inputs/index.ts rename modules/customer-invoices/src/api/application/proformas/mappers/{update-proforma-props.mapper.ts => inputs/update-proforma-input.mapper.ts} (92%) create mode 100644 modules/customer-invoices/src/api/domain/proformas/errors/proforma-item-not-valid-error.ts create mode 100644 modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts rename modules/customer-invoices/src/api/{application/proformas/mappers/create-proforma-props.mapper.ts => infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts} (89%) create mode 100644 modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 5761145c..59d6d2f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,7 +47,8 @@ "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit", "source.fixAll.biome": "explicit", - "source.removeUnusedImports": "always" + "source.removeUnusedImports": "always", + "source.fixAll.eslint": "explicit" }, // other vscode settings diff --git a/apps/server/package.json b/apps/server/package.json index d47bdf51..a085fc3e 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,7 +8,8 @@ "dev": "node --import=tsx --watch src/index.ts", "clean": "rimraf .turbo node_modules dist", "typecheck": "tsc --noEmit", - "lint": "biome lint --fix", + "lint": "biome check . && eslint .", + "lint:fix": "biome check --write . && eslint . --fix", "format": "biome format --write" }, "devDependencies": { diff --git a/biome.json b/biome.json index 3571e689..72d2d7fd 100644 --- a/biome.json +++ b/biome.json @@ -39,7 +39,9 @@ "indentWidth": 2, "lineWidth": 100, "lineEnding": "lf", - "attributePosition": "auto" + "attributePosition": "auto", + "bracketSpacing": true, + "bracketSameLine": true }, "linter": { "enabled": true, diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..7a8ff6a8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,59 @@ +import tseslint from "@typescript-eslint/eslint-plugin"; +import parser from "@typescript-eslint/parser"; + +export default [ + { + files: ["**/*.ts", "**/*.tsx"], + ignores: [ + "**/dist/**", + "**/.turbo/**", + "**/node_modules/**" + ], + languageOptions: { + parser, + }, + plugins: { + "@typescript-eslint": tseslint, + }, + rules: { + "@typescript-eslint/member-ordering": [ + "error", + { + default: [ + "signature", + + // Static + "public-static-field", + "protected-static-field", + "private-static-field", + + "public-static-method", + "protected-static-method", + "private-static-method", + + // Instance fields + "public-instance-field", + "protected-instance-field", + "private-instance-field", + + "constructor", + + // Accessors + "public-instance-get", + "protected-instance-get", + "private-instance-get", + + "public-instance-set", + "protected-instance-set", + "private-instance-set", + + // Methods + "public-instance-method", + "protected-instance-method", + "private-instance-method", + ], + }, + ], + }, + }, +]; \ No newline at end of file diff --git a/modules/core/src/api/domain/value-objects/tax.vo.ts b/modules/core/src/api/domain/value-objects/tax.vo.ts index 59034b3c..609ced39 100644 --- a/modules/core/src/api/domain/value-objects/tax.vo.ts +++ b/modules/core/src/api/domain/value-objects/tax.vo.ts @@ -5,25 +5,22 @@ import { z } from "zod/v4"; import { TaxPercentage } from "./tax-percentage.vo"; -const DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE; -const DEFAULT_MIN_VALUE = TaxPercentage.MIN_VALUE; -const DEFAULT_MAX_VALUE = TaxPercentage.MAX_VALUE; - -const DEFAULT_MIN_SCALE = TaxPercentage.MIN_SCALE; -const DEFAULT_MAX_SCALE = TaxPercentage.MAX_SCALE; +const TAX_GROUPS = ["IVA", "IPSI", "IGIC", "retention", "rec"] as const; +type TaxGroup = (typeof TAX_GROUPS)[number]; export interface TaxProps { code: string; // iva_21 name: string; // 21% IVA value: number; // 2100 + group: TaxGroup; } export class Tax extends ValueObject { - static DEFAULT_SCALE = DEFAULT_SCALE; - static MIN_VALUE = DEFAULT_MIN_VALUE; - static MAX_VALUE = DEFAULT_MAX_VALUE; - static MIN_SCALE = DEFAULT_MIN_SCALE; - static MAX_SCALE = DEFAULT_MAX_SCALE; + static readonly DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE; + static readonly MIN_VALUE = TaxPercentage.MIN_VALUE; + static readonly MAX_VALUE = TaxPercentage.MAX_VALUE; + static readonly MIN_SCALE = TaxPercentage.MIN_SCALE; + static readonly MAX_SCALE = TaxPercentage.MAX_SCALE; private static CODE_REGEX = /^[a-z0-9_:-]+$/; @@ -45,20 +42,21 @@ export class Tax extends ValueObject { .min(1, "El código del impuesto es obligatorio.") .max(40, "El código del impuesto no puede exceder 40 caracteres.") .regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos."), + group: z.enum(TAX_GROUPS, "El impuesto debe ser un IVA, retención o rec. equivalencia"), }); return schema.safeParse(values); } static create(props: TaxProps): Result { - const { value, name, code } = props; + const { value, name, code, group } = props; - const validationResult = Tax.validate({ value, name, code }); + const validationResult = Tax.validate({ value, name, code, group }); if (!validationResult.success) { return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", "))); } - return Result.ok(new Tax({ value, name, code })); + return Result.ok(new Tax({ value, name, code, group })); } /** @@ -89,21 +87,16 @@ export class Tax extends ValueObject { } const item = maybeItem.unwrap(); + // Delegamos en create para reusar validación y límites return Tax.create({ value: Number(item.value), name: item.name, code: item.code, // guardamos el code tal cual viene del catálogo + group: item.group as TaxGroup, }); } - protected constructor(props: TaxProps) { - super(props); - this._percentage = TaxPercentage.create({ - value: this.props.value, - }).data; - } - get value(): number { return this.props.value; } @@ -117,8 +110,24 @@ export class Tax extends ValueObject { return this.props.code; } + get group(): string { + return this.props.group; + } + get percentage(): TaxPercentage { - return this._percentage; + return TaxPercentage.create({ value: this.value }).data; + } + + isVATLike(): boolean { + return this.group === "IVA" || this.group === "IGIC" || this.group === "IPSI"; + } + + isRetention(): boolean { + return this.group === "retention"; + } + + isRec(): boolean { + return this.group === "rec"; } getProps(): TaxProps { @@ -129,21 +138,20 @@ export class Tax extends ValueObject { return this.getProps(); } - /** Devuelve el valor real de la tasa como número decimal (ej: 21.00) */ toNumber(): number { return this.value / 10 ** this.scale; } - /** Devuelve la tasa formateada como porcentaje (ej: "21.00%") */ toString(): string { return `${this.toNumber().toFixed(this.scale)}%`; } isZero(): boolean { - return this.toNumber() === 0; + return this.value === 0; } + isPositive(): boolean { - return this.toNumber() > 0; + return this.value > 0; } equalsTo(other: Tax): boolean { diff --git a/modules/core/src/api/infrastructure/di/catalogs.di.ts b/modules/core/src/api/infrastructure/di/catalogs.di.ts new file mode 100644 index 00000000..ba57d7d0 --- /dev/null +++ b/modules/core/src/api/infrastructure/di/catalogs.di.ts @@ -0,0 +1,13 @@ +import { type JsonTaxCatalogProvider, SpainTaxCatalogProvider } from "../../../common"; + +export interface ICatalogs { + taxCatalog: JsonTaxCatalogProvider; +} + +export const buildCatalogs = (): ICatalogs => { + const taxCatalog = SpainTaxCatalogProvider(); + + return { + taxCatalog, + }; +}; diff --git a/modules/core/src/api/infrastructure/di/index.ts b/modules/core/src/api/infrastructure/di/index.ts index be783695..841d22d6 100644 --- a/modules/core/src/api/infrastructure/di/index.ts +++ b/modules/core/src/api/infrastructure/di/index.ts @@ -1,2 +1,3 @@ +export * from "./catalogs.di"; export * from "./documents.di"; export * from "./transactions.di"; diff --git a/modules/core/src/api/infrastructure/di/transactions.di.ts b/modules/core/src/api/infrastructure/di/transactions.di.ts index 2ac72836..74bc7c1b 100644 --- a/modules/core/src/api/infrastructure/di/transactions.di.ts +++ b/modules/core/src/api/infrastructure/di/transactions.di.ts @@ -1,6 +1,6 @@ import type { Sequelize } from "sequelize"; -import { SequelizeTransactionManager } from "../sequelize"; +import { SequelizeTransactionManager } from "../persistence/sequelize"; export const buildTransactionManager = (database: Sequelize) => new SequelizeTransactionManager(database); diff --git a/modules/core/src/api/infrastructure/index.ts b/modules/core/src/api/infrastructure/index.ts index a1a983b1..595b159f 100644 --- a/modules/core/src/api/infrastructure/index.ts +++ b/modules/core/src/api/infrastructure/index.ts @@ -6,4 +6,4 @@ export * from "./errors"; export * from "./express"; export * from "./logger"; export * from "./mappers"; -export * from "./sequelize"; +export * from "./persistence"; diff --git a/modules/core/src/api/infrastructure/persistence/index.ts b/modules/core/src/api/infrastructure/persistence/index.ts new file mode 100644 index 00000000..62f8ac11 --- /dev/null +++ b/modules/core/src/api/infrastructure/persistence/index.ts @@ -0,0 +1 @@ +export * from "./sequelize"; diff --git a/modules/core/src/api/infrastructure/sequelize/index.ts b/modules/core/src/api/infrastructure/persistence/sequelize/index.ts similarity index 100% rename from modules/core/src/api/infrastructure/sequelize/index.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/index.ts diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/index.ts b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/index.ts similarity index 100% rename from modules/core/src/api/infrastructure/sequelize/mappers/index.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/mappers/index.ts diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-domain-mapper.ts b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-domain-mapper.ts similarity index 96% rename from modules/core/src/api/infrastructure/sequelize/mappers/sequelize-domain-mapper.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-domain-mapper.ts index ca866c49..ec8bb20b 100644 --- a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-domain-mapper.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-domain-mapper.ts @@ -1,7 +1,7 @@ import { Collection, Result, ResultCollection } from "@repo/rdx-utils"; import type { Model } from "sequelize"; -import type { MapperParamsType } from "../../../domain"; +import type { MapperParamsType } from "../../../../domain"; import type { ISequelizeDomainMapper } from "./sequelize-mapper.interface"; diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper.interface.ts b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-mapper.interface.ts similarity index 93% rename from modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper.interface.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-mapper.interface.ts index 2f771669..ae32a8c5 100644 --- a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-mapper.interface.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-mapper.interface.ts @@ -1,4 +1,4 @@ -import type { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../domain"; +import type { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../../domain"; export interface ISequelizeDomainMapper extends DomainMapperWithBulk {} diff --git a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-read-model-mapper.ts similarity index 84% rename from modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-read-model-mapper.ts index 245ff928..7351e319 100644 --- a/modules/core/src/api/infrastructure/sequelize/mappers/sequelize-read-model-mapper.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/mappers/sequelize-read-model-mapper.ts @@ -1,7 +1,9 @@ import { Collection, Result } from "@repo/rdx-utils"; -import { Model } from "sequelize"; -import { MapperParamsType } from "../../../domain"; -import { ISequelizeQueryMapper } from "./sequelize-mapper.interface"; +import type { Model } from "sequelize"; + +import type { MapperParamsType } from "../../../../domain"; + +import type { ISequelizeQueryMapper } from "./sequelize-mapper.interface"; export abstract class SequelizeQueryMapper implements ISequelizeQueryMapper diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-error-translator.ts similarity index 93% rename from modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/sequelize-error-translator.ts index 3b70afb7..333cffcc 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-error-translator.ts @@ -7,9 +7,9 @@ import { UniqueConstraintError, } from "sequelize"; -import { DuplicateEntityError, EntityNotFoundError } from "../../domain"; -import { InfrastructureRepositoryError } from "../errors/infrastructure-repository-error"; -import { InfrastructureUnavailableError } from "../errors/infrastructure-unavailable-error"; +import { DuplicateEntityError, EntityNotFoundError } from "../../../domain"; +import { InfrastructureRepositoryError } from "../../errors/infrastructure-repository-error"; +import { InfrastructureUnavailableError } from "../../errors/infrastructure-unavailable-error"; /** * Traduce errores específicos de Sequelize a errores de dominio/infraestructura diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-func.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-func.ts similarity index 100% rename from modules/core/src/api/infrastructure/sequelize/sequelize-func.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/sequelize-func.ts diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-repository.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-repository.ts similarity index 100% rename from modules/core/src/api/infrastructure/sequelize/sequelize-repository.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/sequelize-repository.ts diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts similarity index 96% rename from modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts rename to modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts index 02262afa..46cb68b6 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-transaction-manager.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts @@ -1,9 +1,9 @@ import { Result } from "@repo/rdx-utils"; import { type Sequelize, Transaction } from "sequelize"; -import { TransactionManager } from "../database"; -import { InfrastructureError, InfrastructureUnavailableError } from "../errors"; -import { logger } from "../logger"; +import { TransactionManager } from "../../database"; +import { InfrastructureError, InfrastructureUnavailableError } from "../../errors"; +import { logger } from "../../logger"; export class SequelizeTransactionManager extends TransactionManager { protected _database: Sequelize | null = null; diff --git a/modules/core/src/common/catalogs/taxes/spain-tax-catalog.json b/modules/core/src/common/catalogs/taxes/spain-tax-catalog.json index d4dcc72e..fa04e008 100644 --- a/modules/core/src/common/catalogs/taxes/spain-tax-catalog.json +++ b/modules/core/src/common/catalogs/taxes/spain-tax-catalog.json @@ -134,13 +134,12 @@ "description": "Inversión del sujeto pasivo.", "aeat_code": "09" }, - { "name": "Retenc. 35%", "code": "retencion_35", "value": "3500", "scale": "2", - "group": "Retención", + "group": "retention", "description": "Retenc. profesional o fiscal tipo máximo.", "aeat_code": null }, @@ -149,7 +148,7 @@ "code": "retencion_19", "value": "1900", "scale": "2", - "group": "Retención", + "group": "retention", "description": "Retenc. IRPF general.", "aeat_code": "R1" }, @@ -158,7 +157,7 @@ "code": "retencion_15", "value": "1500", "scale": "2", - "group": "Retención", + "group": "retention", "description": "Retenc. para autónomos y profesionales.", "aeat_code": "R2" }, @@ -167,7 +166,7 @@ "code": "retencion_7", "value": "700", "scale": "2", - "group": "Retención", + "group": "retention", "description": "Retenc. para nuevos autónomos.", "aeat_code": null }, @@ -176,17 +175,16 @@ "code": "retencion_2", "value": "200", "scale": "2", - "group": "Retención", + "group": "retention", "description": "Retenc. sobre arrendamientos de inmuebles urbanos.", "aeat_code": "R3" }, - { "name": "Rec. 5,2%", "code": "rec_5_2", "value": "520", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo general para IVA 21%.", "aeat_code": "51" }, @@ -195,7 +193,7 @@ "code": "rec_1_75", "value": "175", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo para IVA 10%.", "aeat_code": "52" }, @@ -204,7 +202,7 @@ "code": "rec_1_4", "value": "140", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo para IVA 5%.", "aeat_code": null }, @@ -213,7 +211,7 @@ "code": "rec_1", "value": "100", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo especial.", "aeat_code": null }, @@ -222,7 +220,7 @@ "code": "rec_0_62", "value": "62", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo para IVA reducido especial.", "aeat_code": null }, @@ -231,7 +229,7 @@ "code": "rec_0_5", "value": "50", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo especial.", "aeat_code": null }, @@ -240,7 +238,7 @@ "code": "rec_0_26", "value": "26", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Recargo mínimo.", "aeat_code": null }, @@ -249,11 +247,10 @@ "code": "rec_0", "value": "0", "scale": "2", - "group": "Recargo de equivalencia", + "group": "rec", "description": "Sin recargo.", "aeat_code": null }, - { "name": "IGIC 7%", "code": "igic_7", @@ -335,7 +332,6 @@ "description": "Operación exenta de IGIC.", "aeat_code": "12" }, - { "name": "IPSI 10%", "code": "ipsi_10", @@ -372,4 +368,4 @@ "description": "Operación exenta de IPSI.", "aeat_code": null } -] +] \ No newline at end of file diff --git a/modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-materializer.ts b/modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-materializer.ts index e7541eca..c616595c 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-materializer.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/services/proforma-to-issued-invoice-materializer.ts @@ -1,17 +1,17 @@ import type { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; -import type { IssuedInvoiceProps, Proforma } from "../../../domain"; +import type { IIssuedInvoiceProps, Proforma } from "../../../domain"; export interface IProformaToIssuedInvoiceMaterializer { - materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result; + materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result; } export class ProformaToIssuedInvoiceMaterializer implements IProformaToIssuedInvoiceMaterializer { public materialize( proforma: Proforma, issuedInvoiceId: UniqueID - ): Result { + ): Result { const amounts = proforma.calculateAllAmounts(); const taxGroups = proforma.getTaxes(); diff --git a/modules/customer-invoices/src/api/application/proformas/di/index.ts b/modules/customer-invoices/src/api/application/proformas/di/index.ts index 7a607d8b..6c7b5c47 100644 --- a/modules/customer-invoices/src/api/application/proformas/di/index.ts +++ b/modules/customer-invoices/src/api/application/proformas/di/index.ts @@ -1,4 +1,5 @@ export * from "./proforma-creator.di"; export * from "./proforma-finder.di"; +export * from "./proforma-input-mappers.di"; export * from "./proforma-snapshot-builders.di"; export * from "./proforma-use-cases.di"; diff --git a/modules/customer-invoices/src/api/application/proformas/di/proforma-creator.di.ts b/modules/customer-invoices/src/api/application/proformas/di/proforma-creator.di.ts index d1ad7238..dee9f3e2 100644 --- a/modules/customer-invoices/src/api/application/proformas/di/proforma-creator.di.ts +++ b/modules/customer-invoices/src/api/application/proformas/di/proforma-creator.di.ts @@ -2,13 +2,16 @@ import { ProformaFactory } from "../factories"; import type { IProformaRepository } from "../repositories"; import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services"; -export const buildProformaCreator = ( - numberService: IProformaNumberGenerator, - repository: IProformaRepository -): IProformaCreator => { +export const buildProformaCreator = (params: { + numberService: IProformaNumberGenerator; + repository: IProformaRepository; +}): IProformaCreator => { + const { numberService, repository } = params; + const factory = new ProformaFactory(); + return new ProformaCreator({ numberService, - factory: new ProformaFactory(), + factory, repository, }); }; diff --git a/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts b/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts new file mode 100644 index 00000000..fba0a410 --- /dev/null +++ b/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts @@ -0,0 +1,19 @@ +import type { ICatalogs } from "@erp/core/api"; + +import { CreateProformaInputMapper, type ICreateProformaInputMapper } from "../mappers"; + +export interface IProformaInputMappers { + createInputMapper: ICreateProformaInputMapper; +} + +export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => { + const { taxCatalog } = catalogs; + + // Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado + const createInputMapper = new CreateProformaInputMapper({ taxCatalog }); + //const updateProformaInputMapper = new UpdateProformaInputMapper(); + + return { + createInputMapper, + }; +}; diff --git a/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts b/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts index 166ab095..bd7d7991 100644 --- a/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts +++ b/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts @@ -1,12 +1,18 @@ import type { ITransactionManager } from "@erp/core/api"; -import type { IProformaFinder, ProformaDocumentGeneratorService } from "../services"; +import type { ICreateProformaInputMapper } from "../mappers"; +import type { + IProformaCreator, + IProformaFinder, + ProformaDocumentGeneratorService, +} from "../services"; import type { IProformaListItemSnapshotBuilder, IProformaReportSnapshotBuilder, } from "../snapshot-builders"; import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full"; import { GetProformaByIdUseCase, ListProformasUseCase, ReportProformaUseCase } from "../use-cases"; +import { CreateProformaUseCase } from "../use-cases/create-proforma"; export function buildGetProformaByIdUseCase(deps: { finder: IProformaFinder; @@ -40,18 +46,19 @@ export function buildReportProformaUseCase(deps: { ); } -/*export function buildCreateProformaUseCase(deps: { +export function buildCreateProformaUseCase(deps: { creator: IProformaCreator; + dtoMapper: ICreateProformaInputMapper; fullSnapshotBuilder: IProformaFullSnapshotBuilder; transactionManager: ITransactionManager; }) { return new CreateProformaUseCase({ - mapper: new CreateProformaPropsMapper(), + dtoMapper: deps.dtoMapper, creator: deps.creator, fullSnapshotBuilder: deps.fullSnapshotBuilder, transactionManager: deps.transactionManager, }); -}*/ +} /*export function buildUpdateProformaUseCase(deps: { finder: IProformaFinder; diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/index.ts b/modules/customer-invoices/src/api/application/proformas/mappers/index.ts index 472cff61..3e39ef5b 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/index.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/index.ts @@ -1,4 +1,3 @@ -export * from "./create-proforma-props.mapper"; +export * from "./inputs"; export * from "./proforma-domain-mapper.interface"; export * from "./proforma-list-mapper.interface"; -//export * from "./update-proforma-props.mapper"; diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts new file mode 100644 index 00000000..721cc186 --- /dev/null +++ b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/create-proforma-input.mapper.ts @@ -0,0 +1,319 @@ +import type { JsonTaxCatalogProvider } from "@erp/core"; +import { DiscountPercentage, Tax } from "@erp/core/api"; +import { + CurrencyCode, + DomainError, + LanguageCode, + Percentage, + TextValue, + UniqueID, + UtcDate, + ValidationErrorCollection, + type ValidationErrorDetail, + extractOrPushError, + maybeFromNullableResult, +} from "@repo/rdx-ddd"; +import { Maybe, Result } from "@repo/rdx-utils"; + +import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common"; +import { + type IProformaItemProps, + type IProformaProps, + InvoiceNumber, + InvoicePaymentMethod, + type InvoiceRecipient, + InvoiceSerie, + InvoiceStatus, + ItemAmount, + ItemDescription, + ItemQuantity, + type ProformaItemTaxesProps, +} from "../../../../domain"; + +/** + * CreateProformaPropsMapper + * Convierte el DTO a las props validadas (CustomerProps). + * No construye directamente el agregado. + * + * @param dto - DTO con los datos de la factura de cliente + * @returns + + * + */ + +export interface ICreateProformaInputMapper + extends IDTOInputToPropsMapper< + CreateProformaRequestDTO, + { id: UniqueID; props: Omit & { items: IProformaItemProps[] } } + > {} + +export class CreateProformaInputMapper implements ICreateProformaInputMapper { + private readonly taxCatalog: JsonTaxCatalogProvider; + + constructor(params: { taxCatalog: JsonTaxCatalogProvider }) { + this.taxCatalog = params.taxCatalog; + } + + public map( + dto: CreateProformaRequestDTO, + params: { companyId: UniqueID } + ): Result<{ id: UniqueID; props: IProformaProps }> { + const errors: ValidationErrorDetail[] = []; + const { companyId } = params; + + try { + const defaultStatus = InvoiceStatus.createDraft(); + + const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", errors); + + const customerId = extractOrPushError( + UniqueID.create(dto.customer_id), + "customer_id", + errors + ); + + const recipient = Maybe.none(); + + const proformaNumber = extractOrPushError( + InvoiceNumber.create(dto.invoice_number), + "invoice_number", + errors + ); + + const series = extractOrPushError( + maybeFromNullableResult(dto.series, (value) => InvoiceSerie.create(value)), + "series", + 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 reference = extractOrPushError( + maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))), + "reference", + 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 languageCode = extractOrPushError( + LanguageCode.create(dto.language_code), + "language_code", + errors + ); + + const currencyCode = extractOrPushError( + CurrencyCode.create(dto.currency_code), + "currency_code", + errors + ); + + const paymentMethod = extractOrPushError( + maybeFromNullableResult(dto.payment_method, (value) => + InvoicePaymentMethod.create({ paymentDescription: value }) + ), + "payment_method", + errors + ); + + const globalDiscountPercentage = extractOrPushError( + Percentage.create({ + value: Number(dto.discount_percentage.value), + scale: Number(dto.discount_percentage.scale), + }), + "discount_percentage", + errors + ); + + const items = this.mapItems(dto, { + languageCode: languageCode!, + currencyCode: currencyCode!, + globalDiscountPercentage: globalDiscountPercentage!, + errors, + }); + + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice props mapping failed", errors) + ); + } + + const props: Omit & { items: IProformaItemProps[] } = { + companyId, + status: defaultStatus, + + invoiceNumber: proformaNumber!, + series: series!, + + invoiceDate: invoiceDate!, + operationDate: operationDate!, + + customerId: customerId!, + recipient, + + reference: reference!, + description: description!, + notes: notes!, + + languageCode: languageCode!, + currencyCode: currencyCode!, + + paymentMethod: paymentMethod!, + globalDiscountPercentage: globalDiscountPercentage!, + + items, // ← IProformaItemProps[] + }; + + return Result.ok({ + id: proformaId!, + props, + }); + } catch (err: unknown) { + return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err })); + } + } + + private mapItems( + dto: CreateProformaRequestDTO, + params: { + languageCode: LanguageCode; + currencyCode: CurrencyCode; + globalDiscountPercentage: DiscountPercentage; + errors: ValidationErrorDetail[]; + } + ): IProformaItemProps[] { + const itemsProps: IProformaItemProps[] = []; + + dto.items.forEach((item, index) => { + const description = extractOrPushError( + maybeFromNullableResult(item.description, (v) => ItemDescription.create(v)), + `items[${index}].description`, + params.errors + ); + + const quantity = extractOrPushError( + maybeFromNullableResult(item.quantity, (v) => ItemQuantity.create(v)), + `items[${index}].quantity`, + params.errors + ); + + const unitAmount = extractOrPushError( + maybeFromNullableResult(item.unit_amount, (v) => ItemAmount.create(v)), + `items[${index}].unit_amount`, + params.errors + ); + + const discountPercentage = extractOrPushError( + maybeFromNullableResult(item.discount_percentage, (v) => DiscountPercentage.create(v)), + `items[${index}].discount_percentage`, + params.errors + ); + + const taxes = this.mapTaxes(item.taxes, { + itemIndex: index, + errors: params.errors, + }); + + itemsProps.push({ + globalDiscountPercentage: params.globalDiscountPercentage, + languageCode: params.languageCode, + currencyCode: params.currencyCode, + + description: description!, + quantity: quantity!, + unitAmount: unitAmount!, + itemDiscountPercentage: discountPercentage!, + taxes, + }); + }); + + return itemsProps; + } + + /* Devuelve las propiedades de los impustos de una línea de detalle */ + + private mapTaxes( + taxesDTO: Pick["taxes"], + params: { itemIndex: number; errors: ValidationErrorDetail[] } + ): ProformaItemTaxesProps { + const { itemIndex, errors } = params; + + const taxesProps: ProformaItemTaxesProps = { + iva: Maybe.none(), + retention: Maybe.none(), + rec: Maybe.none(), + }; + + // Normaliza: "" -> [] + const taxStrCodes = taxesDTO + .split(",") + .map((s) => s.trim()) + .filter((s) => s.length > 0); + + 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); + } + }); + + return taxesProps; + } +} diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/inputs/index.ts b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/index.ts new file mode 100644 index 00000000..dff878fb --- /dev/null +++ b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/index.ts @@ -0,0 +1,2 @@ +export * from "./create-proforma-input.mapper"; +export * from "./update-proforma-input.mapper"; diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-props.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts similarity index 92% rename from modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-props.mapper.ts rename to modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts index 30fb650c..de4ab4c6 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-props.mapper.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/inputs/update-proforma-input.mapper.ts @@ -1,3 +1,4 @@ +import { InvoiceSerie, type ProformaPatchProps } from "@erp/customer-invoices/api/domain"; import { CurrencyCode, DomainError, @@ -13,7 +14,6 @@ import { import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto"; -import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../../domain"; /** * UpdateProformaPropsMapper @@ -29,14 +29,14 @@ import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../. * */ -export function UpdateProformaPropsMapper(dto: UpdateProformaByIdRequestDTO) { +export function UpdateProformaInputMapper(dto: UpdateProformaByIdRequestDTO) { try { const errors: ValidationErrorDetail[] = []; - const props: CustomerInvoicePatchProps = {}; + const props: ProformaPatchProps = {}; toPatchField(dto.series).ifSet((series) => { props.series = extractOrPushError( - maybeFromNullableResult(series, (value) => CustomerInvoiceSerie.create(value)), + maybeFromNullableResult(series, (value) => InvoiceSerie.create(value)), "reference", errors ); diff --git a/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts b/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts index c10abd94..1d30c053 100644 --- a/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/proformas/use-cases/create-proforma/create-proforma.use-case.ts @@ -3,7 +3,7 @@ import type { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import type { CreateProformaRequestDTO } from "../../../../../common"; -import type { CreateProformaPropsMapper } from "../../mappers"; +import type { ICreateProformaInputMapper } from "../../mappers"; import type { IProformaCreator } from "../../services"; import type { IProformaFullSnapshotBuilder } from "../../snapshot-builders"; @@ -13,20 +13,20 @@ type CreateProformaUseCaseInput = { }; type CreateProformaUseCaseDeps = { - mapper: CreateProformaPropsMapper; + dtoMapper: ICreateProformaInputMapper; creator: IProformaCreator; fullSnapshotBuilder: IProformaFullSnapshotBuilder; transactionManager: ITransactionManager; }; export class CreateProformaUseCase { - private readonly mapper: CreateProformaPropsMapper; + private readonly dtoMapper: ICreateProformaInputMapper; private readonly creator: IProformaCreator; private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder; private readonly transactionManager: ITransactionManager; constructor(deps: CreateProformaUseCaseDeps) { - this.mapper = deps.mapper; + this.dtoMapper = deps.dtoMapper; this.creator = deps.creator; this.fullSnapshotBuilder = deps.fullSnapshotBuilder; this.transactionManager = deps.transactionManager; @@ -36,7 +36,7 @@ export class CreateProformaUseCase { const { dto, companyId } = params; // 1) Mapear DTO → props de dominio - const mappedResult = this.mapper.map(dto, companyId); + const mappedResult = this.dtoMapper.map(dto, companyId); if (mappedResult.isFailure) { return Result.fail(mappedResult.error); } diff --git a/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts b/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts index e252b3be..5560787a 100644 --- a/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts +++ b/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts @@ -1,5 +1,5 @@ //export * from "./change-status-proforma.use-case"; -//export * from "./create-proforma"; +export * from "./create-proforma"; //export * from "./delete-proforma.use-case"; export * from "./get-proforma-by-id.use-case"; //export * from "./issue-proforma.use-case"; diff --git a/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts b/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts index f1abf349..6288df13 100644 --- a/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts +++ b/modules/customer-invoices/src/api/domain/issued-invoices/aggregates/issued-invoice.aggregate.ts @@ -21,7 +21,7 @@ import type { } from "../../common"; import { IssuedInvoiceItems, type IssuedInvoiceTaxes, type VerifactuRecord } from "../entities"; -export type IssuedInvoiceProps = { +export interface IIssuedInvoiceProps { companyId: UniqueID; status: InvoiceStatus; @@ -65,12 +65,12 @@ export type IssuedInvoiceProps = { totalAmount: InvoiceAmount; verifactu: Maybe; -}; +} -export class IssuedInvoice extends AggregateRoot { +export class IssuedInvoice extends AggregateRoot { private _items!: IssuedInvoiceItems; - protected constructor(props: IssuedInvoiceProps, id?: UniqueID) { + protected constructor(props: IIssuedInvoiceProps, id?: UniqueID) { super(props, id); this._items = props.items || @@ -81,7 +81,7 @@ export class IssuedInvoice extends AggregateRoot { }); } - static create(props: IssuedInvoiceProps, id?: UniqueID): Result { + static create(props: IIssuedInvoiceProps, id?: UniqueID): Result { if (!props.recipient) { return Result.fail( new DomainValidationError( @@ -231,7 +231,7 @@ export class IssuedInvoice extends AggregateRoot { return this.paymentMethod.isSome(); } - public getProps(): IssuedInvoiceProps { + public getProps(): IIssuedInvoiceProps { return this.props; } } diff --git a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts index 6522eb56..802f2e7a 100644 --- a/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts +++ b/modules/customer-invoices/src/api/domain/proformas/aggregates/proforma.aggregate.ts @@ -20,10 +20,18 @@ import { type InvoiceStatus, type ItemAmount, } from "../../common/value-objects"; -import { ProformaItems } from "../entities/proforma-items"; +import { + type IProformaItemProps, + type IProformaItems, + type IProformaItemsProps, + ProformaItem, + ProformaItems, +} from "../entities/proforma-items"; +import { ProformaItemMismatch } from "../errors"; import { type IProformaTaxTotals, ProformaTaxesCalculator } from "../services"; +import { ProformaItemTaxes } from "../value-objects"; -export type ProformaProps = { +export interface IProformaProps { companyId: UniqueID; status: InvoiceStatus; @@ -45,9 +53,9 @@ export type ProformaProps = { paymentMethod: Maybe; - items: ProformaItems; + items: IProformaItemsProps[]; globalDiscountPercentage: DiscountPercentage; -}; +} export interface IProformaTotals { subtotalAmount: InvoiceAmount; @@ -88,31 +96,31 @@ export interface IProforma { paymentMethod: Maybe; - items: ProformaItems; + items: IProformaItems; taxes(): Collection; totals(): IProformaTotals; } -export type ProformaPatchProps = Partial> & { +export type ProformaPatchProps = Partial> & { items?: ProformaItems; }; -export class Proforma extends AggregateRoot implements IProforma { - private _items!: ProformaItems; +type CreateProformaProps = IProformaProps; +type InternalProformaProps = Omit; - protected constructor(props: ProformaProps, id?: UniqueID) { - super(props, id); - this._items = - props.items || - ProformaItems.create({ - languageCode: props.languageCode, - currencyCode: props.currencyCode, - globalDiscountPercentage: props.globalDiscountPercentage, - }); - } +export class Proforma extends AggregateRoot implements IProforma { + private readonly _items: ProformaItems; - static create(props: ProformaProps, id?: UniqueID): Result { - const proforma = new Proforma(props, id); + // Creación funcional + static create(props: CreateProformaProps, id?: UniqueID): Result { + const { items, ...internalProps } = props; + const proforma = new Proforma(internalProps, id); + + const addItemsResult = proforma.initializeItems(items); + + if (addItemsResult.isFailure) { + return Result.fail(addItemsResult.error); + } // Reglas de negocio / validaciones @@ -123,15 +131,30 @@ export class Proforma extends AggregateRoot implements IProforma return Result.ok(proforma); } - // Mutabilidad + // Rehidratación desde persistencia + static rehydrate(props: InternalProformaProps, id: UniqueID): Proforma { + return new Proforma(props, id); + } + protected constructor(props: InternalProformaProps, id?: UniqueID) { + super(props, id); + + this._items = ProformaItems.create({ + languageCode: props.languageCode, + currencyCode: props.currencyCode, + globalDiscountPercentage: props.globalDiscountPercentage, + items: [], + }); + } + + // Mutabilidad public update( - partialProforma: Partial> + partialProforma: Partial> ): Result { const updatedProps = { ...this.props, ...partialProforma, - } as ProformaProps; + } as IProformaProps; return Proforma.create(updatedProps, this.id); } @@ -214,8 +237,7 @@ export class Proforma extends AggregateRoot implements IProforma return this.props.globalDiscountPercentage; } - // Method to get the complete list of line items - public get items(): ProformaItems { + public get items(): IProformaItems { return this._items; } @@ -258,12 +280,64 @@ export class Proforma extends AggregateRoot implements IProforma return new ProformaTaxesCalculator(this.items).calculate(); } - public getProps(): ProformaProps { - return this.props; + public addItem(props: IProformaItemProps): Result { + const taxesResult = ProformaItemTaxes.create(props.taxes); + if (taxesResult.isFailure) return Result.fail(taxesResult.error); + + const itemResult = ProformaItem.create({ + ...props, + taxes: taxesResult.data, + }); + + if (itemResult.isFailure) return Result.fail(itemResult.error); + + const added = this._items.add(itemResult.data); + + if (!added) { + return Result.fail(new Error("Item rejected due to currency/language mismatch")); + } + + return Result.ok(); } + /*public updateItem(itemId: UniqueID, props: IProformaItemProps): Result { + const item = this._items.find((i) => i.id.equals(itemId)); + if (!item) { + return Result.fail(new Error("Item not found")); + } + + return item.update(props); + }*/ + + /*public removeItem(itemId: UniqueID): Result { + const removed = this._items.removeWhere(i => i.id.equals(itemId)); + + if (!removed) { + return Result.fail(new Error("Item not found")); + } + + return Result.ok(); + }*/ + // Helpers + private initializeItems(itemsProps: IProformaItemProps[]): Result { + for (const [index, itemProps] of itemsProps.entries()) { + const itemResult = ProformaItem.create(itemProps); + + if (itemResult.isFailure) { + return Result.fail(itemResult.error); + } + + const added = this._items.add(itemResult.data); + + if (!added) { + return Result.fail(new ProformaItemMismatch(index)); + } + } + return Result.ok(); + } + /** * @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea). */ diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts index 52d7769d..fb18c080 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-item.entity.ts @@ -3,7 +3,10 @@ import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } fro import { type Maybe, Result } from "@repo/rdx-utils"; import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common"; -import type { ProformaItemTaxes } from "../../value-objects/proforma-item-taxes.vo"; +import { + ProformaItemTaxes, + type ProformaItemTaxesProps, +} from "../../value-objects/proforma-item-taxes.vo"; /** * @@ -22,7 +25,7 @@ import type { ProformaItemTaxes } from "../../value-objects/proforma-item-taxes. * */ -export type ProformaItemProps = { +export interface IProformaItemProps { description: Maybe; quantity: Maybe; // Cantidad de unidades @@ -30,14 +33,14 @@ export type ProformaItemProps = { itemDiscountPercentage: Maybe; // % descuento de línea - taxes: ProformaItemTaxes; + taxes: ProformaItemTaxesProps; // Estos campos vienen de la cabecera, // pero se necesitan para cálculos y representaciones de la línea. globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera languageCode: LanguageCode; // Para formateos específicos de idioma currencyCode: CurrencyCode; // Para cálculos y formateos de moneda -}; +} export interface IProformaItemTotals { subtotalAmount: ItemAmount; @@ -84,9 +87,26 @@ export interface IProformaItem { isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado" } -export class ProformaItem extends DomainEntity implements IProformaItem { - public static create(props: ProformaItemProps, id?: UniqueID): Result { - const item = new ProformaItem(props, id); +type CreateProformaItemProps = IProformaItemProps; + +type InternalProformaItemProps = Omit & { + taxes: ProformaItemTaxes; +}; + +export class ProformaItem extends DomainEntity implements IProformaItem { + public static create(props: CreateProformaItemProps, id?: UniqueID): Result { + const taxesResult = ProformaItemTaxes.create(props.taxes); + if (taxesResult.isFailure) { + return Result.fail(taxesResult.error); + } + + const item = new ProformaItem( + { + ...props, + taxes: taxesResult.data, + }, + id + ); // Reglas de negocio / validaciones // ... @@ -95,7 +115,11 @@ export class ProformaItem extends DomainEntity implements IPr return Result.ok(item); } - protected constructor(props: ProformaItemProps, id?: UniqueID) { + static rehydrate(props: InternalProformaItemProps, id: UniqueID): ProformaItem { + return new ProformaItem(props, id); + } + + protected constructor(props: InternalProformaItemProps, id?: UniqueID) { super(props, id); } @@ -131,7 +155,7 @@ export class ProformaItem extends DomainEntity implements IPr return this.props.taxes; } - getProps(): ProformaItemProps { + getProps(): IProformaItemProps { return this.props; } diff --git a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts index 1bed806a..ef769637 100644 --- a/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts +++ b/modules/customer-invoices/src/api/domain/proformas/entities/proforma-items/proforma-items.collection.ts @@ -1,28 +1,37 @@ import type { DiscountPercentage } from "@erp/core/api"; import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd"; -import { Collection } from "@repo/rdx-utils"; +import { Collection, Result } from "@repo/rdx-utils"; +import { ProformaItemMismatch } from "../../errors"; import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator"; -import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity"; +import type { + ICreateProformaItemProps, + IProformaItem, + IProformaItemTotals, + ProformaItem, +} from "./proforma-item.entity"; -export type ProformaItemsProps = { - items?: ProformaItem[]; +export interface IProformaItemsProps { + items?: ICreateProformaItemProps[]; // Estos campos vienen de la cabecera, // pero se necesitan para cálculos y representaciones de la línea. globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera languageCode: LanguageCode; // Para formateos específicos de idioma currencyCode: CurrencyCode; // Para cálculos y formateos de moneda -}; +} + +export interface IProformaItems { + // OJO, no extendemos de Collection para no exponer + // públicamente métodos para manipular la colección. -export interface IProformaItems extends Collection { valued(): IProformaItem[]; // Devuelve solo las líneas valoradas. totals(): IProformaItemTotals; - globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera - languageCode: LanguageCode; // Para formateos específicos de idioma - currencyCode: CurrencyCode; // Para cálculos y formateos de moneda + readonly globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera + readonly languageCode: LanguageCode; // Para formateos específicos de idioma + readonly currencyCode: CurrencyCode; // Para cálculos y formateos de moneda } export class ProformaItems extends Collection implements IProformaItems { @@ -30,7 +39,7 @@ export class ProformaItems extends Collection implements IProforma public readonly currencyCode!: CurrencyCode; public readonly globalDiscountPercentage!: DiscountPercentage; - constructor(props: ProformaItemsProps) { + constructor(props: IProformaItemsProps) { super(props.items ?? []); this.languageCode = props.languageCode; this.currencyCode = props.currencyCode; @@ -39,7 +48,7 @@ export class ProformaItems extends Collection implements IProforma this.ensureSameCurrencyAndLanguage(this.items); } - public static create(props: ProformaItemsProps): ProformaItems { + public static create(props: IProformaItemsProps): ProformaItems { return new ProformaItems(props); } @@ -53,10 +62,10 @@ export class ProformaItems extends Collection implements IProforma * @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado. * @remarks * Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con - * los de la colección. Si no coinciden, el método devuelve `false` sin modificar + * los de la colección. Si no coinciden, el método devuelve un resultado fallido sin modificar * la colección. */ - public add(item: ProformaItem): boolean { + public addItem(item: ProformaItem): Result { // Antes de añadir un nuevo item, debo comprobar que el item a añadir // tiene el mismo "currencyCode" y "languageCode" que la colección de items. const same = @@ -64,9 +73,11 @@ export class ProformaItems extends Collection implements IProforma this.currencyCode.equals(item.currencyCode) && this.globalDiscountPercentage.equals(item.globalDiscountPercentage); - if (!same) return false; - - return super.add(item); + if (!same) { + return Result.fail(new ProformaItemMismatch(this.size())); + } + super.add(item); + return Result.ok(); } // Cálculos diff --git a/modules/customer-invoices/src/api/domain/proformas/errors/index.ts b/modules/customer-invoices/src/api/domain/proformas/errors/index.ts index 5d141a19..edfaf7b8 100644 --- a/modules/customer-invoices/src/api/domain/proformas/errors/index.ts +++ b/modules/customer-invoices/src/api/domain/proformas/errors/index.ts @@ -3,3 +3,4 @@ export * from "./entity-is-not-proforma-error"; export * from "./invalid-proforma-transition-error"; export * from "./proforma-cannot-be-converted-to-invoice-error"; export * from "./proforma-cannot-be-deleted-error"; +export * from "./proforma-item-not-valid-error"; diff --git a/modules/customer-invoices/src/api/domain/proformas/errors/proforma-item-not-valid-error.ts b/modules/customer-invoices/src/api/domain/proformas/errors/proforma-item-not-valid-error.ts new file mode 100644 index 00000000..949a8df5 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/proformas/errors/proforma-item-not-valid-error.ts @@ -0,0 +1,32 @@ +import { DomainError } from "@repo/rdx-ddd"; + +/** + * Error de dominio que indica que al añadir un nuevo item a la lista + * de detalles, este no tiene el mismo "currencyCode" y "languageCode" + * que la colección de items. + * + */ +export class ProformaItemMismatch extends DomainError { + /** + * Crea una instancia del error con el identificador del item. + * + * @param position - Posición del item + * @param options - Opciones nativas de Error (puedes pasar `cause`). + */ + constructor(position: number, options?: ErrorOptions) { + super( + `Error. Proforma item with position '${position}' rejected due to currency/language mismatch.`, + options + ); + this.name = "ProformaItemMismatch"; + } +} + +/** + * *Type guard* para `ProformaItemNotValid`. + * + * @param e - Error desconocido + * @returns `true` si `e` es `ProformaItemNotValid` + */ +export const isProformaItemMismatch = (e: unknown): e is ProformaItemMismatch => + e instanceof ProformaItemMismatch; diff --git a/modules/customer-invoices/src/api/index.ts b/modules/customer-invoices/src/api/index.ts index e745f0f1..33fd4cde 100644 --- a/modules/customer-invoices/src/api/index.ts +++ b/modules/customer-invoices/src/api/index.ts @@ -2,14 +2,14 @@ import type { IModuleServer } from "@erp/core/api"; import { type IssuedInvoicesInternalDeps, + type ProformasInternalDeps, buildIssuedInvoiceServices, buildIssuedInvoicesDependencies, buildProformaServices, buildProformasDependencies, models, } from "./infrastructure"; -import { issuedInvoicesRouter } from "./infrastructure/express"; -import { proformasRouter } from './infrastructure/express'; +import { issuedInvoicesRouter, proformasRouter } from "./infrastructure/express"; export const customerInvoicesAPIModule: IModuleServer = { name: "customer-invoices", @@ -43,13 +43,13 @@ export const customerInvoicesAPIModule: IModuleServer = { // Servicios expuestos a otros módulos services: { issuedInvoices: issuedInvoicesServices, - proformas: proformasServices + proformas: proformasServices, }, // Implementación privada del módulo internal: { issuedInvoices: issuedInvoicesInternalDeps, - proformas: proformasInternalDeps + proformas: proformasInternalDeps, }, }; }, @@ -69,8 +69,11 @@ export const customerInvoicesAPIModule: IModuleServer = { "customer-invoices", "issuedInvoices" ); - - const proformasInternalDeps = getInternal("customer-invoices", "proformas"); + + const proformasInternalDeps = getInternal( + "customer-invoices", + "proformas" + ); // Registro de rutas HTTP issuedInvoicesRouter(params, issuedInvoicesInternalDeps); diff --git a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts index 243ac245..ae7c7cf2 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/proformas/proformas-api-error-mapper.ts @@ -13,11 +13,13 @@ import { type EntityIsNotProformaError, type InvalidProformaTransitionError, type ProformaCannotBeConvertedToInvoiceError, + type ProformaItemMismatch, isCustomerInvoiceIdAlreadyExistsError, isEntityIsNotProformaError, isInvalidProformaTransitionError, isProformaCannotBeConvertedToInvoiceError, isProformaCannotBeDeletedError, + isProformaItemMismatch, } from "../../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) @@ -40,6 +42,16 @@ const entityIsNotProformaError: ErrorToApiRule = { ), }; +const proformaItemMismatchError: ErrorToApiRule = { + priority: 120, + matches: (e) => isProformaItemMismatch(e), + build: (e) => + new ValidationApiError( + (e as ProformaItemMismatch).message || + "Proforma item rejected due to currency/language mismatch" + ), +}; + const proformaTransitionRule: ErrorToApiRule = { priority: 120, matches: (e) => isInvalidProformaTransitionError(e), @@ -71,6 +83,7 @@ const proformaCannotBeDeletedRule: ErrorToApiRule = { // Cómo aplicarla: crea una nueva instancia del mapper con la regla extra export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() .register(invoiceDuplicateRule) + .register(proformaItemMismatchError) .register(entityIsNotProformaError) .register(proformaConversionRule) .register(proformaCannotBeDeletedRule) diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts index cc150555..d41ed268 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-domain.mapper.ts @@ -16,6 +16,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import type { IIssuedInvoiceDomainMapper } from "../../../../../../application"; import { DiscountPercentage, + type IIssuedInvoiceProps, InvoiceAmount, InvoiceNumber, InvoicePaymentMethod, @@ -23,7 +24,6 @@ import { InvoiceStatus, IssuedInvoice, IssuedInvoiceItems, - type IssuedInvoiceProps, IssuedInvoiceTaxes, } from "../../../../../../domain"; import type { @@ -351,7 +351,7 @@ export class SequelizeIssuedInvoiceDomainMapper currencyCode: attributes.currencyCode!, }); - const invoiceProps: IssuedInvoiceProps = { + const invoiceProps: IIssuedInvoiceProps = { companyId: attributes.companyId!, proformaId: attributes.proformaId!, diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts index ccce1737..ad2aed4f 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-item-domain.mapper.ts @@ -14,10 +14,10 @@ import { Result } from "@repo/rdx-utils"; import { DiscountPercentage, + type IIssuedInvoiceProps, type IssuedInvoice, IssuedInvoiceItem, type IssuedInvoiceItemProps, - type IssuedInvoiceProps, ItemAmount, ItemDescription, ItemDiscountPercentage, @@ -56,7 +56,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe const { errors, index, attributes } = params as { index: number; errors: ValidationErrorDetail[]; - attributes: Partial; + attributes: Partial; }; const itemId = extractOrPushError( @@ -263,7 +263,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe const { errors, index } = params as { index: number; errors: ValidationErrorDetail[]; - attributes: Partial; + attributes: Partial; }; // 1) Valores escalares (atributos generales) diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-recipient-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-recipient-domain.mapper.ts index 9c4b1ef0..4b64b20a 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-recipient-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-recipient-domain.mapper.ts @@ -16,9 +16,9 @@ import { import { Maybe, Result } from "@repo/rdx-utils"; import { + type IIssuedInvoiceProps, InvoiceRecipient, type IssuedInvoice, - type IssuedInvoiceProps, } from "../../../../../../domain"; import type { CustomerInvoiceModel } from "../../../../../common"; @@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper { const { errors, attributes } = params as { errors: ValidationErrorDetail[]; - attributes: Partial; + attributes: Partial; }; const _name = source.customer_name!; diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts index 852981a0..67a8dc8a 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-issued-invoice-taxes-domain.mapper.ts @@ -14,9 +14,9 @@ import { import { Result } from "@repo/rdx-utils"; import { + type IIssuedInvoiceProps, InvoiceAmount, type IssuedInvoice, - type IssuedInvoiceProps, IssuedInvoiceTax, ItemAmount, ItemDiscountPercentage, @@ -66,7 +66,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp const { errors, index, attributes } = params as { index: number; errors: ValidationErrorDetail[]; - attributes: Partial; + attributes: Partial; }; const taxableAmount = extractOrPushError( diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-verifactu-record-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-verifactu-record-domain.mapper.ts index d76b9deb..d1b43afa 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-verifactu-record-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/persistence/sequelize/mappers/domain/sequelize-verifactu-record-domain.mapper.ts @@ -12,8 +12,8 @@ import { import { Maybe, Result } from "@repo/rdx-utils"; import { + type IIssuedInvoiceProps, type IssuedInvoice, - type IssuedInvoiceProps, VerifactuRecord, VerifactuRecordEstado, } from "../../../../../../domain"; @@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceVerifactuDomainMapper extends SequelizeDomain ): Result, Error> { const { errors, attributes } = params as { errors: ValidationErrorDetail[]; - attributes: Partial; + attributes: Partial; }; if (!source) { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-documents.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-documents.di.ts index 03839b84..9fabff81 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-documents.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-documents.di.ts @@ -5,7 +5,7 @@ import { type ProformaDocumentPipelineFactoryDeps, } from "../documents"; -export const buildproformaDocumentService = (params: ModuleParams) => { +export const buildProformaDocumentService = (params: ModuleParams) => { const { documentRenderers, documentSigning, documentStorage } = buildCoreDocumentsDI(params); const pipelineDeps: ProformaDocumentPipelineFactoryDeps = { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts new file mode 100644 index 00000000..e5bb51a8 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts @@ -0,0 +1,31 @@ +import type { ICatalogs, IProformaDomainMapper, IProformaListMapper } from "../../../application"; +import { SequelizeProformaDomainMapper, SequelizeProformaListMapper } from "../persistence"; + +export interface IProformaPersistenceMappers { + domainMapper: IProformaDomainMapper; + listMapper: IProformaListMapper; + + createMapper: CreateProformaInputMapper; +} + +export const buildProformaPersistenceMappers = ( + catalogs: ICatalogs +): IProformaPersistenceMappers => { + const { taxCatalog } = catalogs; + + // Mappers para el repositorio + const domainMapper = new SequelizeProformaDomainMapper({ + taxCatalog, + }); + const listMapper = new SequelizeProformaListMapper(); + + // Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado + const createMapper = new CreateProformaInputMapper({ taxCatalog }); + + return { + domainMapper, + listMapper, + + createMapper, + }; +}; diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-repositories.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-repositories.di.ts index 851c162a..f8658327 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-repositories.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-repositories.di.ts @@ -1,19 +1,14 @@ -import { SpainTaxCatalogProvider } from "@erp/core"; import type { Sequelize } from "sequelize"; -import { - ProformaRepository, - SequelizeProformaDomainMapper, - SequelizeProformaListMapper, -} from "../persistence"; +import { ProformaRepository } from "../persistence"; -export const buildProformaRepository = (database: Sequelize) => { - const taxCatalog = SpainTaxCatalogProvider(); +import type { IProformaPersistenceMappers } from "./proforma-persistence-mappers.di"; - const domainMapper = new SequelizeProformaDomainMapper({ - taxCatalog, - }); - const listMapper = new SequelizeProformaListMapper(); +export const buildProformaRepository = (params: { + database: Sequelize; + mappers: IProformaPersistenceMappers; +}) => { + const { database, mappers } = params; - return new ProformaRepository(domainMapper, listMapper, database); + return new ProformaRepository(mappers.domainMapper, mappers.listMapper, database); }; diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts index 620f0181..50b0fa2c 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts @@ -1,17 +1,23 @@ -import { type ModuleParams, buildTransactionManager } from "@erp/core/api"; +import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/core/api"; import { + type CreateProformaUseCase, type GetProformaByIdUseCase, type ListProformasUseCase, type ReportProformaUseCase, + buildCreateProformaUseCase, buildGetProformaByIdUseCase, buildListProformasUseCase, + buildProformaCreator, buildProformaFinder, + buildProformaInputMappers, buildProformaSnapshotBuilders, buildReportProformaUseCase, } from "../../../application"; -import { buildproformaDocumentService } from "./proforma-documents.di"; +import { buildProformaDocumentService } from "./proforma-documents.di"; +import { buildProformaNumberGenerator } from "./proforma-number-generator.di"; +import { buildProformaPersistenceMappers } from "./proforma-persistence-mappers.di"; import { buildProformaRepository } from "./proforma-repositories.di"; export type ProformasInternalDeps = { @@ -19,8 +25,9 @@ export type ProformasInternalDeps = { listProformas: () => ListProformasUseCase; getProformaById: () => GetProformaByIdUseCase; reportProforma: () => ReportProformaUseCase; + createProforma: () => CreateProformaUseCase; - /*createProforma: () => CreateProformaUseCase; + /* updateProforma: () => UpdateProformaUseCase; deleteProforma: () => DeleteProformaUseCase; issueProforma: () => IssueProformaUseCase; @@ -33,14 +40,19 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter // Infrastructure const transactionManager = buildTransactionManager(database); - const repository = buildProformaRepository(database); - //const numberService = buildProformaNumberGenerator(); + const catalogs = buildCatalogs(); + const persistenceMappers = buildProformaPersistenceMappers(catalogs); + + const repository = buildProformaRepository({ database, mappers: persistenceMappers }); + const numberService = buildProformaNumberGenerator(); // Application helpers + const inputMappers = buildProformaInputMappers(catalogs); const finder = buildProformaFinder(repository); - //const creator = buildProformaCreator(numberService, repository); + const creator = buildProformaCreator({ numberService, repository }); + const snapshotBuilders = buildProformaSnapshotBuilders(); - const documentGeneratorPipeline = buildproformaDocumentService(params); + const documentGeneratorPipeline = buildProformaDocumentService(params); // Internal use cases (factories) return { @@ -68,12 +80,13 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter transactionManager, }), - /*createProforma: () => + createProforma: () => buildCreateProformaUseCase({ creator, + dtoMapper: inputMappers.createInputMapper, fullSnapshotBuilder: snapshotBuilders.full, transactionManager, - }),*/ + }), }, }; } diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-props.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts similarity index 89% rename from modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-props.mapper.ts rename to modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts index 1baf4c5f..345890d4 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-props.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/create-proforma-request-mapper.ts @@ -1,5 +1,4 @@ import type { JsonTaxCatalogProvider } from "@erp/core"; -import { Tax } from "@erp/core/api"; import { CurrencyCode, DomainError, @@ -15,8 +14,10 @@ import { } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; -import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../common"; +import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common"; import { + type IProformaItemProps, + type IProformaProps, InvoiceNumber, InvoicePaymentMethod, type InvoiceRecipient, @@ -26,9 +27,8 @@ import { type IssuedInvoiceItemProps, ItemAmount, ItemDescription, - ItemDiscountPercentage, ItemQuantity, -} from "../../../domain"; +} from "../../../../domain"; /** * CreateProformaPropsMapper @@ -41,7 +41,8 @@ import { * */ -export class CreateProformaPropsMapper { + +export class CreateProformaRequestMapper { private readonly taxCatalog: JsonTaxCatalogProvider; private errors: ValidationErrorDetail[] = []; private languageCode?: LanguageCode; @@ -52,7 +53,8 @@ export class CreateProformaPropsMapper { this.errors = []; } - public map(dto: CreateProformaRequestDTO, companyId: UniqueID) { + public map(dto: CreateProformaRequestDTO, params: { companyId: UniqueID }) { + const { companyId } = params; try { this.errors = []; @@ -60,8 +62,6 @@ export class CreateProformaPropsMapper { const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors); - const isProforma = true; - const customerId = extractOrPushError( UniqueID.create(dto.customer_id), "customer_id", @@ -132,7 +132,7 @@ export class CreateProformaPropsMapper { this.errors ); - const discountPercentage = extractOrPushError( + const globalDiscountPercentage = extractOrPushError( Percentage.create({ value: Number(dto.discount_percentage.value), scale: Number(dto.discount_percentage.scale), @@ -149,10 +149,8 @@ export class CreateProformaPropsMapper { ); } - const proformaProps: IProformaProps = { + const proformaProps: Omit & { items: IProformaItemProps[] } = { companyId, - isProforma, - proformaId: Maybe.none(), status: defaultStatus!, invoiceNumber: proformaNumber!, @@ -169,13 +167,13 @@ export class CreateProformaPropsMapper { notes: notes!, languageCode: this.languageCode!, - currencyCode: this.currencyCode!, - - items: items, + currencyCode: this.currencyCode!, paymentMethod: paymentMethod!, - discountPercentage: discountPercentage!, + globalDiscountPercentage: globalDiscountPercentage!, + + items: }; return Result.ok({ id: proformaId!, props: proformaProps }); @@ -184,8 +182,8 @@ export class CreateProformaPropsMapper { } } - private mapItems(items: CreateProformaItemRequestDTO[]) { - const invoiceItems = CustomerInvoiceItems.create({ + private mapItems(items: CreateProformaItemRequestDTO[]): IProformaItemProps[] { + const proformaItems = CustomerInvoiceItems.create({ currencyCode: this.currencyCode!, languageCode: this.languageCode!, items: [], @@ -232,7 +230,7 @@ export class CreateProformaPropsMapper { const itemResult = IssuedInvoiceItem.create(itemProps); if (itemResult.isSuccess) { - invoiceItems.add(itemResult.data); + proformaItems.add(itemResult.data); } else { this.errors.push({ path: `items[${index}]`, @@ -240,7 +238,7 @@ export class CreateProformaPropsMapper { }); } }); - return invoiceItems; + return proformaItems; } private mapTaxes(item: CreateProformaItemRequestDTO, itemIndex: number) { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/index.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/mappers/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts index bfae62e1..8dca65e6 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-domain.mapper.ts @@ -15,13 +15,13 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import type { IProformaDomainMapper } from "../../../../../../application"; import { + type IProformaProps, InvoiceNumber, InvoicePaymentMethod, InvoiceSerie, InvoiceStatus, Proforma, ProformaItems, - type ProformaProps, } from "../../../../../../domain"; import type { CustomerInvoiceCreationAttributes, @@ -217,7 +217,7 @@ export class SequelizeProformaDomainMapper items: itemsResults.data.getAll(), }); - const invoiceProps: ProformaProps = { + const invoiceProps: IProformaProps = { companyId: attributes.companyId!, status: attributes.status!, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts index 6c9e611f..2e7925fb 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts @@ -16,15 +16,15 @@ import { import { Result } from "@repo/rdx-utils"; import { + type IProformaItemProps, + type IProformaProps, ItemAmount, ItemDescription, ItemQuantity, type Proforma, ProformaItem, - type ProformaItemProps, ProformaItemTaxes, type ProformaItemTaxesProps, - type ProformaProps, } from "../../../../../../domain"; import type { CustomerInvoiceItemCreationAttributes, @@ -54,11 +54,11 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper< private mapAttributesToDomain( raw: CustomerInvoiceItemModel, params?: MapperParamsType - ): Partial & { itemId?: UniqueID } { + ): Partial & { itemId?: UniqueID } { const { errors, index, parent } = params as { index: number; errors: ValidationErrorDetail[]; - parent: Partial; + parent: Partial; }; const itemId = extractOrPushError( @@ -139,7 +139,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper< const { errors, index } = params as { index: number; errors: ValidationErrorDetail[]; - parent: Partial; + parent: Partial; }; // 1) Valores escalares (atributos generales) diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-recipient-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-recipient-domain.mapper.ts index 3f72c3f6..fefab8f7 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-recipient-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-recipient-domain.mapper.ts @@ -14,7 +14,7 @@ import { } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; -import { InvoiceRecipient, type ProformaProps } from "../../../../../../domain"; +import { type IProformaProps, InvoiceRecipient } from "../../../../../../domain"; import type { CustomerInvoiceModel } from "../../../../../common"; export class SequelizeProformaRecipientDomainMapper { @@ -28,7 +28,7 @@ export class SequelizeProformaRecipientDomainMapper { const { errors, parent } = params as { errors: ValidationErrorDetail[]; - parent: Partial; + parent: Partial; }; /* if (!source.current_customer) { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts index c5b7552a..b4ec2068 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/repositories/proforma.repository.ts @@ -9,22 +9,26 @@ import type { UniqueID } from "@repo/rdx-ddd"; import { type Collection, Result } from "@repo/rdx-utils"; import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize"; -import type { IProformaRepository, ProformaListDTO } from "../../../../../application"; +import type { + IProformaDomainMapper, + IProformaListMapper, + IProformaRepository, + ProformaListDTO, +} from "../../../../../application"; import type { InvoiceStatus, Proforma } from "../../../../../domain"; import { CustomerInvoiceItemModel, CustomerInvoiceModel, CustomerInvoiceTaxModel, } from "../../../../common"; -import type { SequelizeProformaDomainMapper, SequelizeProformaListMapper } from "../mappers"; export class ProformaRepository extends SequelizeRepository implements IProformaRepository { constructor( - private readonly domainMapper: SequelizeProformaDomainMapper, - private readonly listMapper: SequelizeProformaListMapper, + private readonly domainMapper: IProformaDomainMapper, + private readonly listMapper: IProformaListMapper, database: Sequelize ) { super({ database }); diff --git a/modules/customer-invoices/tsconfig.json b/modules/customer-invoices/tsconfig.json index d8c98291..98ee25b1 100644 --- a/modules/customer-invoices/tsconfig.json +++ b/modules/customer-invoices/tsconfig.json @@ -31,7 +31,8 @@ "include": [ "src", "../core/src/api/domain/value-objects/tax-percentage.vo.ts", - "../core/src/api/domain/value-objects/discount-percentage.vo.ts" + "../core/src/api/domain/value-objects/discount-percentage.vo.ts", + "../core/src/api/infrastructure/di/catalogs.di.ts" ], "exclude": ["node_modules"] } diff --git a/package.json b/package.json index 7243c3ee..3645516c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "dev": "turbo dev", "dev:server": "turbo dev --filter=server", "dev:client": "turbo dev --filter=client", + "lint": "turbo run lint", + "lint:fix": "turbo run lint:fix", "format-and-lint": "biome check .", "format-and-lint:fix": "biome check . --write", "ui:add": "pnpm --filter @repo/shadcn-ui ui:add", @@ -23,7 +25,10 @@ "devDependencies": { "@biomejs/biome": "2.3.1", "@repo/typescript-config": "workspace:*", + "@typescript-eslint/eslint-plugin": "^8.56.1", + "@typescript-eslint/parser": "^8.56.1", "change-case": "^5.4.4", + "eslint": "^10.0.2", "inquirer": "^12.10.0", "plop": "^4.0.4", "rimraf": "^5.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75a63c70..687677f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,18 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:packages/typescript-config + '@typescript-eslint/eslint-plugin': + specifier: ^8.56.1 + version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.56.1 + version: 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) change-case: specifier: ^5.4.4 version: 5.4.4 + eslint: + specifier: ^10.0.2 + version: 10.0.2(jiti@2.6.1) inquirer: specifier: ^12.10.0 version: 12.10.0(@types/node@22.19.0) @@ -1200,24 +1209,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.3.1': resolution: {integrity: sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.3.1': resolution: {integrity: sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.3.1': resolution: {integrity: sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.3.1': resolution: {integrity: sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==} @@ -1492,6 +1505,36 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.2': + resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.2': + resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.1.0': + resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.2': + resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.0': + resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -1524,6 +1567,22 @@ packages: peerDependencies: react-hook-form: ^7.55.0 + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@inquirer/ansi@1.0.1': resolution: {integrity: sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==} engines: {node: '>=18'} @@ -1737,36 +1796,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -2451,56 +2516,67 @@ packages: resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.5': resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.5': resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.5': resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.5': resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.5': resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.5': resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.5': resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.5': resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.5': resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.5': resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.5': resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} @@ -2584,24 +2660,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.16': resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.16': resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.16': resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.16': resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} @@ -2747,6 +2827,9 @@ packages: '@types/dinero.js@1.9.4': resolution: {integrity: sha512-mtJnan4ajy9MqvoJGVXu0tC9EAAzFjeoKc3d+8AW+H/Od9+8IiC59ymjrZF+JdTToyDvkLReacTsc50Z8eYr6Q==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2777,6 +2860,9 @@ packages: '@types/inquirer@9.0.9': resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} @@ -2879,6 +2965,65 @@ packages: '@types/validator@13.15.4': resolution: {integrity: sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==} + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2892,6 +3037,11 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -2901,6 +3051,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + add@2.0.6: resolution: {integrity: sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==} @@ -2916,6 +3071,9 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -3007,6 +3165,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -3038,6 +3200,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3397,6 +3563,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -3613,11 +3782,45 @@ packages: engines: {node: '>=6.0'} hasBin: true + eslint-scope@9.1.1: + resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.0.2: + resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.1.1: + resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -3658,6 +3861,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.3.2: resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==} engines: {node: '>=6.0.0'} @@ -3666,6 +3872,12 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -3685,6 +3897,10 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-stream-rotator@0.6.1: resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} @@ -3702,6 +3918,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + findup-sync@5.0.0: resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} engines: {node: '>= 10.13.0'} @@ -3717,6 +3937,13 @@ packages: resolution: {integrity: sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==} engines: {node: '>= 10.13.0'} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.4: + resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -3831,6 +4058,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -3990,6 +4221,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-size@0.5.5: resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} @@ -4002,6 +4237,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -4188,9 +4427,18 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4209,6 +4457,9 @@ packages: jws@3.2.2: resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} @@ -4217,6 +4468,10 @@ packages: engines: {node: '>=14'} hasBin: true + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + libphonenumber-js@1.12.25: resolution: {integrity: sha512-u90tUu/SEF8b+RaDKCoW7ZNFDakyBtFlX1ex3J+VH+ElWes/UaitJLt/w4jGu8uAE41lltV/s+kMVtywcMEg7g==} @@ -4259,24 +4514,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4318,6 +4577,10 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -4480,6 +4743,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -4580,6 +4847,9 @@ packages: nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + needle@3.3.1: resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} @@ -4695,6 +4965,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@4.1.1: resolution: {integrity: sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==} engines: {node: '>=8'} @@ -4707,6 +4981,14 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@3.0.0: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} @@ -4776,6 +5058,10 @@ packages: path-case@2.1.1: resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -4935,6 +5221,10 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -5604,6 +5894,12 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -5705,6 +6001,10 @@ packages: tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -5769,6 +6069,9 @@ packages: upper-case@1.1.3: resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -5954,6 +6257,10 @@ packages: wkx@0.5.0: resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -5986,6 +6293,10 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} @@ -6371,6 +6682,36 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.2(jiti@2.6.1))': + dependencies: + eslint: 10.0.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.2': + dependencies: + '@eslint/object-schema': 3.0.2 + debug: 4.4.3 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.2': + dependencies: + '@eslint/core': 1.1.0 + + '@eslint/core@1.1.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.2': {} + + '@eslint/plugin-kit@0.6.0': + dependencies: + '@eslint/core': 1.1.0 + levn: 0.4.1 + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -6415,6 +6756,17 @@ snapshots: '@standard-schema/utils': 0.3.0 react-hook-form: 7.66.0(react@19.2.0) + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@1.0.1': {} '@inquirer/checkbox@4.3.0(@types/node@22.19.0)': @@ -7640,6 +7992,8 @@ snapshots: '@types/dinero.js@1.9.4': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.19.7': @@ -7682,6 +8036,8 @@ snapshots: '@types/through': 0.0.33 rxjs: 7.8.2 + '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 @@ -7704,7 +8060,7 @@ snapshots: '@types/minimatch@6.0.0': dependencies: - minimatch: 9.0.5 + minimatch: 10.1.1 '@types/ms@2.1.0': {} @@ -7799,6 +8155,97 @@ snapshots: '@types/validator@13.15.4': {} + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 10.0.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 10.0.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.0.2(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 10.0.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(sass@1.93.3)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.6))': dependencies: '@babel/core': 7.28.5 @@ -7818,12 +8265,18 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-walk@8.3.4: dependencies: acorn: 8.15.0 acorn@8.15.0: {} + acorn@8.16.0: {} + add@2.0.6: {} agent-base@6.0.2: @@ -7839,6 +8292,13 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -7918,6 +8378,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.8.24: {} @@ -7966,6 +8428,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -8298,6 +8764,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -8518,8 +8986,70 @@ snapshots: optionalDependencies: source-map: 0.6.1 + eslint-scope@9.1.1: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.0.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.2 + '@eslint/config-helpers': 0.5.2 + '@eslint/core': 1.1.0 + '@eslint/plugin-kit': 0.6.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.1 + eslint-visitor-keys: 5.0.1 + espree: 11.1.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@11.1.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + estraverse@5.3.0: {} estree-walker@2.0.2: {} @@ -8592,6 +9122,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-deep-equal@3.1.3: {} + fast-equals@5.3.2: {} fast-glob@3.3.3: @@ -8602,6 +9134,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -8616,6 +9152,10 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-stream-rotator@0.6.1: dependencies: moment: 2.30.1 @@ -8642,6 +9182,11 @@ snapshots: find-root@1.1.0: {} + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + findup-sync@5.0.0: dependencies: detect-file: 1.0.0 @@ -8665,6 +9210,13 @@ snapshots: flagged-respawn@2.0.0: {} + flat-cache@4.0.1: + dependencies: + flatted: 3.3.4 + keyv: 4.5.4 + + flatted@3.3.4: {} + fn.name@1.1.0: {} follow-redirects@1.15.11: {} @@ -8781,6 +9333,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -8978,6 +9534,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + image-size@0.5.5: optional: true @@ -8988,6 +9546,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} inflection@1.13.4: {} @@ -9178,8 +9738,14 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} jsonfile@6.2.0: @@ -9212,6 +9778,10 @@ snapshots: jwa: 1.4.2 safe-buffer: 5.2.1 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kuler@2.0.0: {} less@4.4.2: @@ -9228,6 +9798,11 @@ snapshots: needle: 3.3.1 source-map: 0.6.1 + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + libphonenumber-js@1.12.25: {} liftoff@5.0.1: @@ -9303,6 +9878,10 @@ snapshots: loader-utils@3.3.1: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} lodash.get@4.4.2: {} @@ -9432,6 +10011,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -9528,6 +10111,8 @@ snapshots: dependencies: picocolors: 1.1.1 + natural-compare@1.4.0: {} + needle@3.3.1: dependencies: iconv-lite: 0.6.3 @@ -9654,6 +10239,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@4.1.1: dependencies: chalk: 3.0.0 @@ -9679,6 +10273,14 @@ snapshots: os-tmpdir@1.0.2: {} + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@3.0.0: dependencies: aggregate-error: 3.1.0 @@ -9766,6 +10368,8 @@ snapshots: dependencies: no-case: 2.3.2 + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -9906,6 +10510,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -10596,6 +11202,10 @@ snapshots: triple-beam@1.4.1: {} + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} ts-node@10.9.2(@types/node@22.19.0)(typescript@5.9.3): @@ -10696,6 +11306,10 @@ snapshots: tw-animate-css@1.4.0: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.21.3: {} type-is@1.6.18: @@ -10763,6 +11377,10 @@ snapshots: upper-case@1.1.3: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): dependencies: react: 19.2.0 @@ -10949,6 +11567,8 @@ snapshots: dependencies: '@types/node': 22.19.0 + word-wrap@1.2.5: {} + wordwrap@1.0.0: {} wrap-ansi@6.2.0: @@ -10979,6 +11599,8 @@ snapshots: yn@3.1.1: {} + yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} zod@3.25.76: {} diff --git a/turbo.json b/turbo.json index dec22a3b..7e489191 100644 --- a/turbo.json +++ b/turbo.json @@ -1,8 +1,16 @@ { "$schema": "https://turbo.build/schema.json", "ui": "tui", - "globalDependencies": ["**/.env.*local"], + "globalDependencies": [ + "**/.env.*local" + ], "tasks": { + "lint": { + "outputs": [] + }, + "lint:fix": { + "outputs": [] + }, "dev": { "cache": false, "persistent": true @@ -13,16 +21,25 @@ }, "build": { "cache": false, - "dependsOn": ["^build"], - "inputs": ["$TURBO_DEFAULT$", ".env*"], - "outputs": ["dist/**"] + "dependsOn": [ + "^build" + ], + "inputs": [ + "$TURBO_DEFAULT$", + ".env*" + ], + "outputs": [ + "dist/**" + ] }, "build:templates": { "dependsOn": [], - "outputs": ["dist/templates/**"] + "outputs": [ + "dist/templates/**" + ] }, "clean": { "cache": false } } -} +} \ No newline at end of file