.
This commit is contained in:
parent
821b4d3ff7
commit
941ad25401
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -47,7 +47,8 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports.biome": "explicit",
|
"source.organizeImports.biome": "explicit",
|
||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit",
|
||||||
"source.removeUnusedImports": "always"
|
"source.removeUnusedImports": "always",
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
|
|
||||||
// other vscode settings
|
// other vscode settings
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
"dev": "node --import=tsx --watch src/index.ts",
|
"dev": "node --import=tsx --watch src/index.ts",
|
||||||
"clean": "rimraf .turbo node_modules dist",
|
"clean": "rimraf .turbo node_modules dist",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "biome lint --fix",
|
"lint": "biome check . && eslint .",
|
||||||
|
"lint:fix": "biome check --write . && eslint . --fix",
|
||||||
"format": "biome format --write"
|
"format": "biome format --write"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -39,7 +39,9 @@
|
|||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
"lineWidth": 100,
|
"lineWidth": 100,
|
||||||
"lineEnding": "lf",
|
"lineEnding": "lf",
|
||||||
"attributePosition": "auto"
|
"attributePosition": "auto",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": true
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
59
eslint.config.mjs
Normal file
59
eslint.config.mjs
Normal file
@ -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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -5,25 +5,22 @@ import { z } from "zod/v4";
|
|||||||
|
|
||||||
import { TaxPercentage } from "./tax-percentage.vo";
|
import { TaxPercentage } from "./tax-percentage.vo";
|
||||||
|
|
||||||
const DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE;
|
const TAX_GROUPS = ["IVA", "IPSI", "IGIC", "retention", "rec"] as const;
|
||||||
const DEFAULT_MIN_VALUE = TaxPercentage.MIN_VALUE;
|
type TaxGroup = (typeof TAX_GROUPS)[number];
|
||||||
const DEFAULT_MAX_VALUE = TaxPercentage.MAX_VALUE;
|
|
||||||
|
|
||||||
const DEFAULT_MIN_SCALE = TaxPercentage.MIN_SCALE;
|
|
||||||
const DEFAULT_MAX_SCALE = TaxPercentage.MAX_SCALE;
|
|
||||||
|
|
||||||
export interface TaxProps {
|
export interface TaxProps {
|
||||||
code: string; // iva_21
|
code: string; // iva_21
|
||||||
name: string; // 21% IVA
|
name: string; // 21% IVA
|
||||||
value: number; // 2100
|
value: number; // 2100
|
||||||
|
group: TaxGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tax extends ValueObject<TaxProps> {
|
export class Tax extends ValueObject<TaxProps> {
|
||||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
static readonly DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE;
|
||||||
static MIN_VALUE = DEFAULT_MIN_VALUE;
|
static readonly MIN_VALUE = TaxPercentage.MIN_VALUE;
|
||||||
static MAX_VALUE = DEFAULT_MAX_VALUE;
|
static readonly MAX_VALUE = TaxPercentage.MAX_VALUE;
|
||||||
static MIN_SCALE = DEFAULT_MIN_SCALE;
|
static readonly MIN_SCALE = TaxPercentage.MIN_SCALE;
|
||||||
static MAX_SCALE = DEFAULT_MAX_SCALE;
|
static readonly MAX_SCALE = TaxPercentage.MAX_SCALE;
|
||||||
|
|
||||||
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
|
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
|
||||||
|
|
||||||
@ -45,20 +42,21 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
.min(1, "El código del impuesto es obligatorio.")
|
.min(1, "El código del impuesto es obligatorio.")
|
||||||
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
|
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
|
||||||
.regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos."),
|
.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);
|
return schema.safeParse(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: TaxProps): Result<Tax> {
|
static create(props: TaxProps): Result<Tax> {
|
||||||
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) {
|
if (!validationResult.success) {
|
||||||
return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
|
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<TaxProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const item = maybeItem.unwrap();
|
const item = maybeItem.unwrap();
|
||||||
|
|
||||||
// Delegamos en create para reusar validación y límites
|
// Delegamos en create para reusar validación y límites
|
||||||
return Tax.create({
|
return Tax.create({
|
||||||
value: Number(item.value),
|
value: Number(item.value),
|
||||||
name: item.name,
|
name: item.name,
|
||||||
code: item.code, // guardamos el code tal cual viene del catálogo
|
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 {
|
get value(): number {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
@ -117,8 +110,24 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
return this.props.code;
|
return this.props.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get group(): string {
|
||||||
|
return this.props.group;
|
||||||
|
}
|
||||||
|
|
||||||
get percentage(): TaxPercentage {
|
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 {
|
getProps(): TaxProps {
|
||||||
@ -129,21 +138,20 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
return this.getProps();
|
return this.getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Devuelve el valor real de la tasa como número decimal (ej: 21.00) */
|
|
||||||
toNumber(): number {
|
toNumber(): number {
|
||||||
return this.value / 10 ** this.scale;
|
return this.value / 10 ** this.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Devuelve la tasa formateada como porcentaje (ej: "21.00%") */
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `${this.toNumber().toFixed(this.scale)}%`;
|
return `${this.toNumber().toFixed(this.scale)}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
isZero(): boolean {
|
isZero(): boolean {
|
||||||
return this.toNumber() === 0;
|
return this.value === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPositive(): boolean {
|
isPositive(): boolean {
|
||||||
return this.toNumber() > 0;
|
return this.value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
equalsTo(other: Tax): boolean {
|
equalsTo(other: Tax): boolean {
|
||||||
|
|||||||
13
modules/core/src/api/infrastructure/di/catalogs.di.ts
Normal file
13
modules/core/src/api/infrastructure/di/catalogs.di.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { type JsonTaxCatalogProvider, SpainTaxCatalogProvider } from "../../../common";
|
||||||
|
|
||||||
|
export interface ICatalogs {
|
||||||
|
taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildCatalogs = (): ICatalogs => {
|
||||||
|
const taxCatalog = SpainTaxCatalogProvider();
|
||||||
|
|
||||||
|
return {
|
||||||
|
taxCatalog,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,2 +1,3 @@
|
|||||||
|
export * from "./catalogs.di";
|
||||||
export * from "./documents.di";
|
export * from "./documents.di";
|
||||||
export * from "./transactions.di";
|
export * from "./transactions.di";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import { SequelizeTransactionManager } from "../sequelize";
|
import { SequelizeTransactionManager } from "../persistence/sequelize";
|
||||||
|
|
||||||
export const buildTransactionManager = (database: Sequelize) =>
|
export const buildTransactionManager = (database: Sequelize) =>
|
||||||
new SequelizeTransactionManager(database);
|
new SequelizeTransactionManager(database);
|
||||||
|
|||||||
@ -6,4 +6,4 @@ export * from "./errors";
|
|||||||
export * from "./express";
|
export * from "./express";
|
||||||
export * from "./logger";
|
export * from "./logger";
|
||||||
export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./sequelize";
|
export * from "./persistence";
|
||||||
|
|||||||
1
modules/core/src/api/infrastructure/persistence/index.ts
Normal file
1
modules/core/src/api/infrastructure/persistence/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Collection, Result, ResultCollection } from "@repo/rdx-utils";
|
import { Collection, Result, ResultCollection } from "@repo/rdx-utils";
|
||||||
import type { Model } from "sequelize";
|
import type { Model } from "sequelize";
|
||||||
|
|
||||||
import type { MapperParamsType } from "../../../domain";
|
import type { MapperParamsType } from "../../../../domain";
|
||||||
|
|
||||||
import type { ISequelizeDomainMapper } from "./sequelize-mapper.interface";
|
import type { ISequelizeDomainMapper } from "./sequelize-mapper.interface";
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../domain";
|
import type { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../../domain";
|
||||||
|
|
||||||
export interface ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
|
export interface ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
|
||||||
extends DomainMapperWithBulk<TModel | TModelAttributes, TEntity> {}
|
extends DomainMapperWithBulk<TModel | TModelAttributes, TEntity> {}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Model } from "sequelize";
|
import type { Model } from "sequelize";
|
||||||
import { MapperParamsType } from "../../../domain";
|
|
||||||
import { ISequelizeQueryMapper } from "./sequelize-mapper.interface";
|
import type { MapperParamsType } from "../../../../domain";
|
||||||
|
|
||||||
|
import type { ISequelizeQueryMapper } from "./sequelize-mapper.interface";
|
||||||
|
|
||||||
export abstract class SequelizeQueryMapper<TModel extends Model, TEntity>
|
export abstract class SequelizeQueryMapper<TModel extends Model, TEntity>
|
||||||
implements ISequelizeQueryMapper<TModel, TEntity>
|
implements ISequelizeQueryMapper<TModel, TEntity>
|
||||||
@ -7,9 +7,9 @@ import {
|
|||||||
UniqueConstraintError,
|
UniqueConstraintError,
|
||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
|
|
||||||
import { DuplicateEntityError, EntityNotFoundError } from "../../domain";
|
import { DuplicateEntityError, EntityNotFoundError } from "../../../domain";
|
||||||
import { InfrastructureRepositoryError } from "../errors/infrastructure-repository-error";
|
import { InfrastructureRepositoryError } from "../../errors/infrastructure-repository-error";
|
||||||
import { InfrastructureUnavailableError } from "../errors/infrastructure-unavailable-error";
|
import { InfrastructureUnavailableError } from "../../errors/infrastructure-unavailable-error";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traduce errores específicos de Sequelize a errores de dominio/infraestructura
|
* Traduce errores específicos de Sequelize a errores de dominio/infraestructura
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { type Sequelize, Transaction } from "sequelize";
|
import { type Sequelize, Transaction } from "sequelize";
|
||||||
|
|
||||||
import { TransactionManager } from "../database";
|
import { TransactionManager } from "../../database";
|
||||||
import { InfrastructureError, InfrastructureUnavailableError } from "../errors";
|
import { InfrastructureError, InfrastructureUnavailableError } from "../../errors";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../../logger";
|
||||||
|
|
||||||
export class SequelizeTransactionManager extends TransactionManager {
|
export class SequelizeTransactionManager extends TransactionManager {
|
||||||
protected _database: Sequelize | null = null;
|
protected _database: Sequelize | null = null;
|
||||||
@ -134,13 +134,12 @@
|
|||||||
"description": "Inversión del sujeto pasivo.",
|
"description": "Inversión del sujeto pasivo.",
|
||||||
"aeat_code": "09"
|
"aeat_code": "09"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Retenc. 35%",
|
"name": "Retenc. 35%",
|
||||||
"code": "retencion_35",
|
"code": "retencion_35",
|
||||||
"value": "3500",
|
"value": "3500",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Retención",
|
"group": "retention",
|
||||||
"description": "Retenc. profesional o fiscal tipo máximo.",
|
"description": "Retenc. profesional o fiscal tipo máximo.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -149,7 +148,7 @@
|
|||||||
"code": "retencion_19",
|
"code": "retencion_19",
|
||||||
"value": "1900",
|
"value": "1900",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Retención",
|
"group": "retention",
|
||||||
"description": "Retenc. IRPF general.",
|
"description": "Retenc. IRPF general.",
|
||||||
"aeat_code": "R1"
|
"aeat_code": "R1"
|
||||||
},
|
},
|
||||||
@ -158,7 +157,7 @@
|
|||||||
"code": "retencion_15",
|
"code": "retencion_15",
|
||||||
"value": "1500",
|
"value": "1500",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Retención",
|
"group": "retention",
|
||||||
"description": "Retenc. para autónomos y profesionales.",
|
"description": "Retenc. para autónomos y profesionales.",
|
||||||
"aeat_code": "R2"
|
"aeat_code": "R2"
|
||||||
},
|
},
|
||||||
@ -167,7 +166,7 @@
|
|||||||
"code": "retencion_7",
|
"code": "retencion_7",
|
||||||
"value": "700",
|
"value": "700",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Retención",
|
"group": "retention",
|
||||||
"description": "Retenc. para nuevos autónomos.",
|
"description": "Retenc. para nuevos autónomos.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -176,17 +175,16 @@
|
|||||||
"code": "retencion_2",
|
"code": "retencion_2",
|
||||||
"value": "200",
|
"value": "200",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Retención",
|
"group": "retention",
|
||||||
"description": "Retenc. sobre arrendamientos de inmuebles urbanos.",
|
"description": "Retenc. sobre arrendamientos de inmuebles urbanos.",
|
||||||
"aeat_code": "R3"
|
"aeat_code": "R3"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Rec. 5,2%",
|
"name": "Rec. 5,2%",
|
||||||
"code": "rec_5_2",
|
"code": "rec_5_2",
|
||||||
"value": "520",
|
"value": "520",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo general para IVA 21%.",
|
"description": "Recargo general para IVA 21%.",
|
||||||
"aeat_code": "51"
|
"aeat_code": "51"
|
||||||
},
|
},
|
||||||
@ -195,7 +193,7 @@
|
|||||||
"code": "rec_1_75",
|
"code": "rec_1_75",
|
||||||
"value": "175",
|
"value": "175",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo para IVA 10%.",
|
"description": "Recargo para IVA 10%.",
|
||||||
"aeat_code": "52"
|
"aeat_code": "52"
|
||||||
},
|
},
|
||||||
@ -204,7 +202,7 @@
|
|||||||
"code": "rec_1_4",
|
"code": "rec_1_4",
|
||||||
"value": "140",
|
"value": "140",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo para IVA 5%.",
|
"description": "Recargo para IVA 5%.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -213,7 +211,7 @@
|
|||||||
"code": "rec_1",
|
"code": "rec_1",
|
||||||
"value": "100",
|
"value": "100",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo especial.",
|
"description": "Recargo especial.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -222,7 +220,7 @@
|
|||||||
"code": "rec_0_62",
|
"code": "rec_0_62",
|
||||||
"value": "62",
|
"value": "62",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo para IVA reducido especial.",
|
"description": "Recargo para IVA reducido especial.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -231,7 +229,7 @@
|
|||||||
"code": "rec_0_5",
|
"code": "rec_0_5",
|
||||||
"value": "50",
|
"value": "50",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo especial.",
|
"description": "Recargo especial.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -240,7 +238,7 @@
|
|||||||
"code": "rec_0_26",
|
"code": "rec_0_26",
|
||||||
"value": "26",
|
"value": "26",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Recargo mínimo.",
|
"description": "Recargo mínimo.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
@ -249,11 +247,10 @@
|
|||||||
"code": "rec_0",
|
"code": "rec_0",
|
||||||
"value": "0",
|
"value": "0",
|
||||||
"scale": "2",
|
"scale": "2",
|
||||||
"group": "Recargo de equivalencia",
|
"group": "rec",
|
||||||
"description": "Sin recargo.",
|
"description": "Sin recargo.",
|
||||||
"aeat_code": null
|
"aeat_code": null
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "IGIC 7%",
|
"name": "IGIC 7%",
|
||||||
"code": "igic_7",
|
"code": "igic_7",
|
||||||
@ -335,7 +332,6 @@
|
|||||||
"description": "Operación exenta de IGIC.",
|
"description": "Operación exenta de IGIC.",
|
||||||
"aeat_code": "12"
|
"aeat_code": "12"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "IPSI 10%",
|
"name": "IPSI 10%",
|
||||||
"code": "ipsi_10",
|
"code": "ipsi_10",
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { IssuedInvoiceProps, Proforma } from "../../../domain";
|
import type { IIssuedInvoiceProps, Proforma } from "../../../domain";
|
||||||
|
|
||||||
export interface IProformaToIssuedInvoiceMaterializer {
|
export interface IProformaToIssuedInvoiceMaterializer {
|
||||||
materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result<IssuedInvoiceProps, Error>;
|
materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result<IIssuedInvoiceProps, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaToIssuedInvoiceMaterializer implements IProformaToIssuedInvoiceMaterializer {
|
export class ProformaToIssuedInvoiceMaterializer implements IProformaToIssuedInvoiceMaterializer {
|
||||||
public materialize(
|
public materialize(
|
||||||
proforma: Proforma,
|
proforma: Proforma,
|
||||||
issuedInvoiceId: UniqueID
|
issuedInvoiceId: UniqueID
|
||||||
): Result<IssuedInvoiceProps, Error> {
|
): Result<IIssuedInvoiceProps, Error> {
|
||||||
const amounts = proforma.calculateAllAmounts();
|
const amounts = proforma.calculateAllAmounts();
|
||||||
const taxGroups = proforma.getTaxes();
|
const taxGroups = proforma.getTaxes();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./proforma-creator.di";
|
export * from "./proforma-creator.di";
|
||||||
export * from "./proforma-finder.di";
|
export * from "./proforma-finder.di";
|
||||||
|
export * from "./proforma-input-mappers.di";
|
||||||
export * from "./proforma-snapshot-builders.di";
|
export * from "./proforma-snapshot-builders.di";
|
||||||
export * from "./proforma-use-cases.di";
|
export * from "./proforma-use-cases.di";
|
||||||
|
|||||||
@ -2,13 +2,16 @@ import { ProformaFactory } from "../factories";
|
|||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services";
|
import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services";
|
||||||
|
|
||||||
export const buildProformaCreator = (
|
export const buildProformaCreator = (params: {
|
||||||
numberService: IProformaNumberGenerator,
|
numberService: IProformaNumberGenerator;
|
||||||
repository: IProformaRepository
|
repository: IProformaRepository;
|
||||||
): IProformaCreator => {
|
}): IProformaCreator => {
|
||||||
|
const { numberService, repository } = params;
|
||||||
|
const factory = new ProformaFactory();
|
||||||
|
|
||||||
return new ProformaCreator({
|
return new ProformaCreator({
|
||||||
numberService,
|
numberService,
|
||||||
factory: new ProformaFactory(),
|
factory,
|
||||||
repository,
|
repository,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,12 +1,18 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
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 {
|
import type {
|
||||||
IProformaListItemSnapshotBuilder,
|
IProformaListItemSnapshotBuilder,
|
||||||
IProformaReportSnapshotBuilder,
|
IProformaReportSnapshotBuilder,
|
||||||
} from "../snapshot-builders";
|
} from "../snapshot-builders";
|
||||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full";
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full";
|
||||||
import { GetProformaByIdUseCase, ListProformasUseCase, ReportProformaUseCase } from "../use-cases";
|
import { GetProformaByIdUseCase, ListProformasUseCase, ReportProformaUseCase } from "../use-cases";
|
||||||
|
import { CreateProformaUseCase } from "../use-cases/create-proforma";
|
||||||
|
|
||||||
export function buildGetProformaByIdUseCase(deps: {
|
export function buildGetProformaByIdUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
@ -40,18 +46,19 @@ export function buildReportProformaUseCase(deps: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export function buildCreateProformaUseCase(deps: {
|
export function buildCreateProformaUseCase(deps: {
|
||||||
creator: IProformaCreator;
|
creator: IProformaCreator;
|
||||||
|
dtoMapper: ICreateProformaInputMapper;
|
||||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}) {
|
}) {
|
||||||
return new CreateProformaUseCase({
|
return new CreateProformaUseCase({
|
||||||
mapper: new CreateProformaPropsMapper(),
|
dtoMapper: deps.dtoMapper,
|
||||||
creator: deps.creator,
|
creator: deps.creator,
|
||||||
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
||||||
transactionManager: deps.transactionManager,
|
transactionManager: deps.transactionManager,
|
||||||
});
|
});
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/*export function buildUpdateProformaUseCase(deps: {
|
/*export function buildUpdateProformaUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export * from "./create-proforma-props.mapper";
|
export * from "./inputs";
|
||||||
export * from "./proforma-domain-mapper.interface";
|
export * from "./proforma-domain-mapper.interface";
|
||||||
export * from "./proforma-list-mapper.interface";
|
export * from "./proforma-list-mapper.interface";
|
||||||
//export * from "./update-proforma-props.mapper";
|
|
||||||
|
|||||||
@ -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<IProformaProps, "items"> & { 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<InvoiceRecipient>();
|
||||||
|
|
||||||
|
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<IProformaProps, "items"> & { 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<CreateProformaItemRequestDTO, "taxes">["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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-proforma-input.mapper";
|
||||||
|
export * from "./update-proforma-input.mapper";
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { InvoiceSerie, type ProformaPatchProps } from "@erp/customer-invoices/api/domain";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
DomainError,
|
DomainError,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
|
import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
|
||||||
import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../../domain";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateProformaPropsMapper
|
* UpdateProformaPropsMapper
|
||||||
@ -29,14 +29,14 @@ import { type CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../.
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function UpdateProformaPropsMapper(dto: UpdateProformaByIdRequestDTO) {
|
export function UpdateProformaInputMapper(dto: UpdateProformaByIdRequestDTO) {
|
||||||
try {
|
try {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
const props: CustomerInvoicePatchProps = {};
|
const props: ProformaPatchProps = {};
|
||||||
|
|
||||||
toPatchField(dto.series).ifSet((series) => {
|
toPatchField(dto.series).ifSet((series) => {
|
||||||
props.series = extractOrPushError(
|
props.series = extractOrPushError(
|
||||||
maybeFromNullableResult(series, (value) => CustomerInvoiceSerie.create(value)),
|
maybeFromNullableResult(series, (value) => InvoiceSerie.create(value)),
|
||||||
"reference",
|
"reference",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -3,7 +3,7 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { CreateProformaRequestDTO } from "../../../../../common";
|
import type { CreateProformaRequestDTO } from "../../../../../common";
|
||||||
import type { CreateProformaPropsMapper } from "../../mappers";
|
import type { ICreateProformaInputMapper } from "../../mappers";
|
||||||
import type { IProformaCreator } from "../../services";
|
import type { IProformaCreator } from "../../services";
|
||||||
import type { IProformaFullSnapshotBuilder } from "../../snapshot-builders";
|
import type { IProformaFullSnapshotBuilder } from "../../snapshot-builders";
|
||||||
|
|
||||||
@ -13,20 +13,20 @@ type CreateProformaUseCaseInput = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type CreateProformaUseCaseDeps = {
|
type CreateProformaUseCaseDeps = {
|
||||||
mapper: CreateProformaPropsMapper;
|
dtoMapper: ICreateProformaInputMapper;
|
||||||
creator: IProformaCreator;
|
creator: IProformaCreator;
|
||||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CreateProformaUseCase {
|
export class CreateProformaUseCase {
|
||||||
private readonly mapper: CreateProformaPropsMapper;
|
private readonly dtoMapper: ICreateProformaInputMapper;
|
||||||
private readonly creator: IProformaCreator;
|
private readonly creator: IProformaCreator;
|
||||||
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
private readonly transactionManager: ITransactionManager;
|
private readonly transactionManager: ITransactionManager;
|
||||||
|
|
||||||
constructor(deps: CreateProformaUseCaseDeps) {
|
constructor(deps: CreateProformaUseCaseDeps) {
|
||||||
this.mapper = deps.mapper;
|
this.dtoMapper = deps.dtoMapper;
|
||||||
this.creator = deps.creator;
|
this.creator = deps.creator;
|
||||||
this.fullSnapshotBuilder = deps.fullSnapshotBuilder;
|
this.fullSnapshotBuilder = deps.fullSnapshotBuilder;
|
||||||
this.transactionManager = deps.transactionManager;
|
this.transactionManager = deps.transactionManager;
|
||||||
@ -36,7 +36,7 @@ export class CreateProformaUseCase {
|
|||||||
const { dto, companyId } = params;
|
const { dto, companyId } = params;
|
||||||
|
|
||||||
// 1) Mapear DTO → props de dominio
|
// 1) Mapear DTO → props de dominio
|
||||||
const mappedResult = this.mapper.map(dto, companyId);
|
const mappedResult = this.dtoMapper.map(dto, companyId);
|
||||||
if (mappedResult.isFailure) {
|
if (mappedResult.isFailure) {
|
||||||
return Result.fail(mappedResult.error);
|
return Result.fail(mappedResult.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//export * from "./change-status-proforma.use-case";
|
//export * from "./change-status-proforma.use-case";
|
||||||
//export * from "./create-proforma";
|
export * from "./create-proforma";
|
||||||
//export * from "./delete-proforma.use-case";
|
//export * from "./delete-proforma.use-case";
|
||||||
export * from "./get-proforma-by-id.use-case";
|
export * from "./get-proforma-by-id.use-case";
|
||||||
//export * from "./issue-proforma.use-case";
|
//export * from "./issue-proforma.use-case";
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import type {
|
|||||||
} from "../../common";
|
} from "../../common";
|
||||||
import { IssuedInvoiceItems, type IssuedInvoiceTaxes, type VerifactuRecord } from "../entities";
|
import { IssuedInvoiceItems, type IssuedInvoiceTaxes, type VerifactuRecord } from "../entities";
|
||||||
|
|
||||||
export type IssuedInvoiceProps = {
|
export interface IIssuedInvoiceProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
|
||||||
@ -65,12 +65,12 @@ export type IssuedInvoiceProps = {
|
|||||||
totalAmount: InvoiceAmount;
|
totalAmount: InvoiceAmount;
|
||||||
|
|
||||||
verifactu: Maybe<VerifactuRecord>;
|
verifactu: Maybe<VerifactuRecord>;
|
||||||
};
|
}
|
||||||
|
|
||||||
export class IssuedInvoice extends AggregateRoot<IssuedInvoiceProps> {
|
export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
||||||
private _items!: IssuedInvoiceItems;
|
private _items!: IssuedInvoiceItems;
|
||||||
|
|
||||||
protected constructor(props: IssuedInvoiceProps, id?: UniqueID) {
|
protected constructor(props: IIssuedInvoiceProps, id?: UniqueID) {
|
||||||
super(props, id);
|
super(props, id);
|
||||||
this._items =
|
this._items =
|
||||||
props.items ||
|
props.items ||
|
||||||
@ -81,7 +81,7 @@ export class IssuedInvoice extends AggregateRoot<IssuedInvoiceProps> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: IssuedInvoiceProps, id?: UniqueID): Result<IssuedInvoice, Error> {
|
static create(props: IIssuedInvoiceProps, id?: UniqueID): Result<IssuedInvoice, Error> {
|
||||||
if (!props.recipient) {
|
if (!props.recipient) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
@ -231,7 +231,7 @@ export class IssuedInvoice extends AggregateRoot<IssuedInvoiceProps> {
|
|||||||
return this.paymentMethod.isSome();
|
return this.paymentMethod.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProps(): IssuedInvoiceProps {
|
public getProps(): IIssuedInvoiceProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,18 @@ import {
|
|||||||
type InvoiceStatus,
|
type InvoiceStatus,
|
||||||
type ItemAmount,
|
type ItemAmount,
|
||||||
} from "../../common/value-objects";
|
} 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 { type IProformaTaxTotals, ProformaTaxesCalculator } from "../services";
|
||||||
|
import { ProformaItemTaxes } from "../value-objects";
|
||||||
|
|
||||||
export type ProformaProps = {
|
export interface IProformaProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
|
||||||
@ -45,9 +53,9 @@ export type ProformaProps = {
|
|||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
items: ProformaItems;
|
items: IProformaItemsProps[];
|
||||||
globalDiscountPercentage: DiscountPercentage;
|
globalDiscountPercentage: DiscountPercentage;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IProformaTotals {
|
export interface IProformaTotals {
|
||||||
subtotalAmount: InvoiceAmount;
|
subtotalAmount: InvoiceAmount;
|
||||||
@ -88,31 +96,31 @@ export interface IProforma {
|
|||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
items: ProformaItems;
|
items: IProformaItems;
|
||||||
taxes(): Collection<IProformaTaxTotals>;
|
taxes(): Collection<IProformaTaxTotals>;
|
||||||
totals(): IProformaTotals;
|
totals(): IProformaTotals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProformaPatchProps = Partial<Omit<ProformaProps, "companyId" | "items">> & {
|
export type ProformaPatchProps = Partial<Omit<IProformaProps, "companyId" | "items">> & {
|
||||||
items?: ProformaItems;
|
items?: ProformaItems;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Proforma extends AggregateRoot<ProformaProps> implements IProforma {
|
type CreateProformaProps = IProformaProps;
|
||||||
private _items!: ProformaItems;
|
type InternalProformaProps = Omit<IProformaProps, "items">;
|
||||||
|
|
||||||
protected constructor(props: ProformaProps, id?: UniqueID) {
|
export class Proforma extends AggregateRoot<InternalProformaProps> implements IProforma {
|
||||||
super(props, id);
|
private readonly _items: ProformaItems;
|
||||||
this._items =
|
|
||||||
props.items ||
|
|
||||||
ProformaItems.create({
|
|
||||||
languageCode: props.languageCode,
|
|
||||||
currencyCode: props.currencyCode,
|
|
||||||
globalDiscountPercentage: props.globalDiscountPercentage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(props: ProformaProps, id?: UniqueID): Result<Proforma, Error> {
|
// Creación funcional
|
||||||
const proforma = new Proforma(props, id);
|
static create(props: CreateProformaProps, id?: UniqueID): Result<Proforma, Error> {
|
||||||
|
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
|
// Reglas de negocio / validaciones
|
||||||
|
|
||||||
@ -123,15 +131,30 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
return Result.ok(proforma);
|
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(
|
public update(
|
||||||
partialProforma: Partial<Omit<ProformaProps, "companyId">>
|
partialProforma: Partial<Omit<IProformaProps, "companyId">>
|
||||||
): Result<Proforma, Error> {
|
): Result<Proforma, Error> {
|
||||||
const updatedProps = {
|
const updatedProps = {
|
||||||
...this.props,
|
...this.props,
|
||||||
...partialProforma,
|
...partialProforma,
|
||||||
} as ProformaProps;
|
} as IProformaProps;
|
||||||
|
|
||||||
return Proforma.create(updatedProps, this.id);
|
return Proforma.create(updatedProps, this.id);
|
||||||
}
|
}
|
||||||
@ -214,8 +237,7 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
return this.props.globalDiscountPercentage;
|
return this.props.globalDiscountPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to get the complete list of line items
|
public get items(): IProformaItems {
|
||||||
public get items(): ProformaItems {
|
|
||||||
return this._items;
|
return this._items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,12 +280,64 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
return new ProformaTaxesCalculator(this.items).calculate();
|
return new ProformaTaxesCalculator(this.items).calculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProps(): ProformaProps {
|
public addItem(props: IProformaItemProps): Result<void, Error> {
|
||||||
return this.props;
|
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<void, Error> {
|
||||||
|
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<void, Error> {
|
||||||
|
const removed = this._items.removeWhere(i => i.id.equals(itemId));
|
||||||
|
|
||||||
|
if (!removed) {
|
||||||
|
return Result.fail(new Error("Item not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}*/
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
|
private initializeItems(itemsProps: IProformaItemProps[]): Result<void, Error> {
|
||||||
|
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).
|
* @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } fro
|
|||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common";
|
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<ItemDescription>;
|
description: Maybe<ItemDescription>;
|
||||||
|
|
||||||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||||||
@ -30,14 +33,14 @@ export type ProformaItemProps = {
|
|||||||
|
|
||||||
itemDiscountPercentage: Maybe<DiscountPercentage>; // % descuento de línea
|
itemDiscountPercentage: Maybe<DiscountPercentage>; // % descuento de línea
|
||||||
|
|
||||||
taxes: ProformaItemTaxes;
|
taxes: ProformaItemTaxesProps;
|
||||||
|
|
||||||
// Estos campos vienen de la cabecera,
|
// Estos campos vienen de la cabecera,
|
||||||
// pero se necesitan para cálculos y representaciones de la línea.
|
// pero se necesitan para cálculos y representaciones de la línea.
|
||||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IProformaItemTotals {
|
export interface IProformaItemTotals {
|
||||||
subtotalAmount: ItemAmount;
|
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"
|
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaItem extends DomainEntity<ProformaItemProps> implements IProformaItem {
|
type CreateProformaItemProps = IProformaItemProps;
|
||||||
public static create(props: ProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> {
|
|
||||||
const item = new ProformaItem(props, id);
|
type InternalProformaItemProps = Omit<IProformaItemProps, "taxes"> & {
|
||||||
|
taxes: ProformaItemTaxes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ProformaItem extends DomainEntity<InternalProformaItemProps> implements IProformaItem {
|
||||||
|
public static create(props: CreateProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> {
|
||||||
|
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
|
// Reglas de negocio / validaciones
|
||||||
// ...
|
// ...
|
||||||
@ -95,7 +115,11 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> implements IPr
|
|||||||
return Result.ok(item);
|
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);
|
super(props, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +155,7 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> implements IPr
|
|||||||
return this.props.taxes;
|
return this.props.taxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): ProformaItemProps {
|
getProps(): IProformaItemProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +1,37 @@
|
|||||||
import type { DiscountPercentage } from "@erp/core/api";
|
import type { DiscountPercentage } from "@erp/core/api";
|
||||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
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 { 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 = {
|
export interface IProformaItemsProps {
|
||||||
items?: ProformaItem[];
|
items?: ICreateProformaItemProps[];
|
||||||
|
|
||||||
// Estos campos vienen de la cabecera,
|
// Estos campos vienen de la cabecera,
|
||||||
// pero se necesitan para cálculos y representaciones de la línea.
|
// pero se necesitan para cálculos y representaciones de la línea.
|
||||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export interface IProformaItems {
|
||||||
|
// OJO, no extendemos de Collection<IProformaItem> para no exponer
|
||||||
|
// públicamente métodos para manipular la colección.
|
||||||
|
|
||||||
export interface IProformaItems extends Collection<IProformaItem> {
|
|
||||||
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
||||||
totals(): IProformaItemTotals;
|
totals(): IProformaItemTotals;
|
||||||
|
|
||||||
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
readonly globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
languageCode: LanguageCode; // Para formateos específicos de idioma
|
readonly languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
readonly currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
|
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
|
||||||
@ -30,7 +39,7 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
public readonly currencyCode!: CurrencyCode;
|
public readonly currencyCode!: CurrencyCode;
|
||||||
public readonly globalDiscountPercentage!: DiscountPercentage;
|
public readonly globalDiscountPercentage!: DiscountPercentage;
|
||||||
|
|
||||||
constructor(props: ProformaItemsProps) {
|
constructor(props: IProformaItemsProps) {
|
||||||
super(props.items ?? []);
|
super(props.items ?? []);
|
||||||
this.languageCode = props.languageCode;
|
this.languageCode = props.languageCode;
|
||||||
this.currencyCode = props.currencyCode;
|
this.currencyCode = props.currencyCode;
|
||||||
@ -39,7 +48,7 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
this.ensureSameCurrencyAndLanguage(this.items);
|
this.ensureSameCurrencyAndLanguage(this.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(props: ProformaItemsProps): ProformaItems {
|
public static create(props: IProformaItemsProps): ProformaItems {
|
||||||
return new ProformaItems(props);
|
return new ProformaItems(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +62,10 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
* @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado.
|
* @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado.
|
||||||
* @remarks
|
* @remarks
|
||||||
* Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con
|
* 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.
|
* la colección.
|
||||||
*/
|
*/
|
||||||
public add(item: ProformaItem): boolean {
|
public addItem(item: ProformaItem): Result<void, Error> {
|
||||||
// Antes de añadir un nuevo item, debo comprobar que el item a añadir
|
// 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.
|
// tiene el mismo "currencyCode" y "languageCode" que la colección de items.
|
||||||
const same =
|
const same =
|
||||||
@ -64,9 +73,11 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
this.currencyCode.equals(item.currencyCode) &&
|
this.currencyCode.equals(item.currencyCode) &&
|
||||||
this.globalDiscountPercentage.equals(item.globalDiscountPercentage);
|
this.globalDiscountPercentage.equals(item.globalDiscountPercentage);
|
||||||
|
|
||||||
if (!same) return false;
|
if (!same) {
|
||||||
|
return Result.fail(new ProformaItemMismatch(this.size()));
|
||||||
return super.add(item);
|
}
|
||||||
|
super.add(item);
|
||||||
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cálculos
|
// Cálculos
|
||||||
|
|||||||
@ -3,3 +3,4 @@ export * from "./entity-is-not-proforma-error";
|
|||||||
export * from "./invalid-proforma-transition-error";
|
export * from "./invalid-proforma-transition-error";
|
||||||
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
||||||
export * from "./proforma-cannot-be-deleted-error";
|
export * from "./proforma-cannot-be-deleted-error";
|
||||||
|
export * from "./proforma-item-not-valid-error";
|
||||||
|
|||||||
@ -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;
|
||||||
@ -2,14 +2,14 @@ import type { IModuleServer } from "@erp/core/api";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type IssuedInvoicesInternalDeps,
|
type IssuedInvoicesInternalDeps,
|
||||||
|
type ProformasInternalDeps,
|
||||||
buildIssuedInvoiceServices,
|
buildIssuedInvoiceServices,
|
||||||
buildIssuedInvoicesDependencies,
|
buildIssuedInvoicesDependencies,
|
||||||
buildProformaServices,
|
buildProformaServices,
|
||||||
buildProformasDependencies,
|
buildProformasDependencies,
|
||||||
models,
|
models,
|
||||||
} from "./infrastructure";
|
} from "./infrastructure";
|
||||||
import { issuedInvoicesRouter } from "./infrastructure/express";
|
import { issuedInvoicesRouter, proformasRouter } from "./infrastructure/express";
|
||||||
import { proformasRouter } from './infrastructure/express';
|
|
||||||
|
|
||||||
export const customerInvoicesAPIModule: IModuleServer = {
|
export const customerInvoicesAPIModule: IModuleServer = {
|
||||||
name: "customer-invoices",
|
name: "customer-invoices",
|
||||||
@ -43,13 +43,13 @@ export const customerInvoicesAPIModule: IModuleServer = {
|
|||||||
// Servicios expuestos a otros módulos
|
// Servicios expuestos a otros módulos
|
||||||
services: {
|
services: {
|
||||||
issuedInvoices: issuedInvoicesServices,
|
issuedInvoices: issuedInvoicesServices,
|
||||||
proformas: proformasServices
|
proformas: proformasServices,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Implementación privada del módulo
|
// Implementación privada del módulo
|
||||||
internal: {
|
internal: {
|
||||||
issuedInvoices: issuedInvoicesInternalDeps,
|
issuedInvoices: issuedInvoicesInternalDeps,
|
||||||
proformas: proformasInternalDeps
|
proformas: proformasInternalDeps,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -70,7 +70,10 @@ export const customerInvoicesAPIModule: IModuleServer = {
|
|||||||
"issuedInvoices"
|
"issuedInvoices"
|
||||||
);
|
);
|
||||||
|
|
||||||
const proformasInternalDeps = getInternal("customer-invoices", "proformas");
|
const proformasInternalDeps = getInternal<ProformasInternalDeps>(
|
||||||
|
"customer-invoices",
|
||||||
|
"proformas"
|
||||||
|
);
|
||||||
|
|
||||||
// Registro de rutas HTTP
|
// Registro de rutas HTTP
|
||||||
issuedInvoicesRouter(params, issuedInvoicesInternalDeps);
|
issuedInvoicesRouter(params, issuedInvoicesInternalDeps);
|
||||||
|
|||||||
@ -13,11 +13,13 @@ import {
|
|||||||
type EntityIsNotProformaError,
|
type EntityIsNotProformaError,
|
||||||
type InvalidProformaTransitionError,
|
type InvalidProformaTransitionError,
|
||||||
type ProformaCannotBeConvertedToInvoiceError,
|
type ProformaCannotBeConvertedToInvoiceError,
|
||||||
|
type ProformaItemMismatch,
|
||||||
isCustomerInvoiceIdAlreadyExistsError,
|
isCustomerInvoiceIdAlreadyExistsError,
|
||||||
isEntityIsNotProformaError,
|
isEntityIsNotProformaError,
|
||||||
isInvalidProformaTransitionError,
|
isInvalidProformaTransitionError,
|
||||||
isProformaCannotBeConvertedToInvoiceError,
|
isProformaCannotBeConvertedToInvoiceError,
|
||||||
isProformaCannotBeDeletedError,
|
isProformaCannotBeDeletedError,
|
||||||
|
isProformaItemMismatch,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
|
|
||||||
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
// 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 = {
|
const proformaTransitionRule: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isInvalidProformaTransitionError(e),
|
matches: (e) => isInvalidProformaTransitionError(e),
|
||||||
@ -71,6 +83,7 @@ const proformaCannotBeDeletedRule: ErrorToApiRule = {
|
|||||||
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
||||||
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
.register(invoiceDuplicateRule)
|
.register(invoiceDuplicateRule)
|
||||||
|
.register(proformaItemMismatchError)
|
||||||
.register(entityIsNotProformaError)
|
.register(entityIsNotProformaError)
|
||||||
.register(proformaConversionRule)
|
.register(proformaConversionRule)
|
||||||
.register(proformaCannotBeDeletedRule)
|
.register(proformaCannotBeDeletedRule)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
|||||||
import type { IIssuedInvoiceDomainMapper } from "../../../../../../application";
|
import type { IIssuedInvoiceDomainMapper } from "../../../../../../application";
|
||||||
import {
|
import {
|
||||||
DiscountPercentage,
|
DiscountPercentage,
|
||||||
|
type IIssuedInvoiceProps,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
@ -23,7 +24,6 @@ import {
|
|||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
IssuedInvoice,
|
IssuedInvoice,
|
||||||
IssuedInvoiceItems,
|
IssuedInvoiceItems,
|
||||||
type IssuedInvoiceProps,
|
|
||||||
IssuedInvoiceTaxes,
|
IssuedInvoiceTaxes,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
@ -351,7 +351,7 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceProps: IssuedInvoiceProps = {
|
const invoiceProps: IIssuedInvoiceProps = {
|
||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
|
|
||||||
proformaId: attributes.proformaId!,
|
proformaId: attributes.proformaId!,
|
||||||
|
|||||||
@ -14,10 +14,10 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DiscountPercentage,
|
DiscountPercentage,
|
||||||
|
type IIssuedInvoiceProps,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
IssuedInvoiceItem,
|
IssuedInvoiceItem,
|
||||||
type IssuedInvoiceItemProps,
|
type IssuedInvoiceItemProps,
|
||||||
type IssuedInvoiceProps,
|
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemDiscountPercentage,
|
ItemDiscountPercentage,
|
||||||
@ -56,7 +56,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemId = extractOrPushError(
|
const itemId = extractOrPushError(
|
||||||
@ -263,7 +263,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
const { errors, index } = params as {
|
const { errors, index } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
|
|||||||
@ -16,9 +16,9 @@ import {
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IIssuedInvoiceProps,
|
||||||
InvoiceRecipient,
|
InvoiceRecipient,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
type IssuedInvoiceProps,
|
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type { CustomerInvoiceModel } from "../../../../../common";
|
import type { CustomerInvoiceModel } from "../../../../../common";
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
|
|||||||
|
|
||||||
const { errors, attributes } = params as {
|
const { errors, attributes } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _name = source.customer_name!;
|
const _name = source.customer_name!;
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import {
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IIssuedInvoiceProps,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
type IssuedInvoiceProps,
|
|
||||||
IssuedInvoiceTax,
|
IssuedInvoiceTax,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDiscountPercentage,
|
ItemDiscountPercentage,
|
||||||
@ -66,7 +66,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
const taxableAmount = extractOrPushError(
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import {
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IIssuedInvoiceProps,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
type IssuedInvoiceProps,
|
|
||||||
VerifactuRecord,
|
VerifactuRecord,
|
||||||
VerifactuRecordEstado,
|
VerifactuRecordEstado,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceVerifactuDomainMapper extends SequelizeDomain
|
|||||||
): Result<Maybe<VerifactuRecord>, Error> {
|
): Result<Maybe<VerifactuRecord>, Error> {
|
||||||
const { errors, attributes } = params as {
|
const { errors, attributes } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
type ProformaDocumentPipelineFactoryDeps,
|
type ProformaDocumentPipelineFactoryDeps,
|
||||||
} from "../documents";
|
} from "../documents";
|
||||||
|
|
||||||
export const buildproformaDocumentService = (params: ModuleParams) => {
|
export const buildProformaDocumentService = (params: ModuleParams) => {
|
||||||
const { documentRenderers, documentSigning, documentStorage } = buildCoreDocumentsDI(params);
|
const { documentRenderers, documentSigning, documentStorage } = buildCoreDocumentsDI(params);
|
||||||
|
|
||||||
const pipelineDeps: ProformaDocumentPipelineFactoryDeps = {
|
const pipelineDeps: ProformaDocumentPipelineFactoryDeps = {
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,19 +1,14 @@
|
|||||||
import { SpainTaxCatalogProvider } from "@erp/core";
|
|
||||||
import type { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import {
|
import { ProformaRepository } from "../persistence";
|
||||||
ProformaRepository,
|
|
||||||
SequelizeProformaDomainMapper,
|
|
||||||
SequelizeProformaListMapper,
|
|
||||||
} from "../persistence";
|
|
||||||
|
|
||||||
export const buildProformaRepository = (database: Sequelize) => {
|
import type { IProformaPersistenceMappers } from "./proforma-persistence-mappers.di";
|
||||||
const taxCatalog = SpainTaxCatalogProvider();
|
|
||||||
|
|
||||||
const domainMapper = new SequelizeProformaDomainMapper({
|
export const buildProformaRepository = (params: {
|
||||||
taxCatalog,
|
database: Sequelize;
|
||||||
});
|
mappers: IProformaPersistenceMappers;
|
||||||
const listMapper = new SequelizeProformaListMapper();
|
}) => {
|
||||||
|
const { database, mappers } = params;
|
||||||
|
|
||||||
return new ProformaRepository(domainMapper, listMapper, database);
|
return new ProformaRepository(mappers.domainMapper, mappers.listMapper, database);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,23 @@
|
|||||||
import { type ModuleParams, buildTransactionManager } from "@erp/core/api";
|
import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/core/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type CreateProformaUseCase,
|
||||||
type GetProformaByIdUseCase,
|
type GetProformaByIdUseCase,
|
||||||
type ListProformasUseCase,
|
type ListProformasUseCase,
|
||||||
type ReportProformaUseCase,
|
type ReportProformaUseCase,
|
||||||
|
buildCreateProformaUseCase,
|
||||||
buildGetProformaByIdUseCase,
|
buildGetProformaByIdUseCase,
|
||||||
buildListProformasUseCase,
|
buildListProformasUseCase,
|
||||||
|
buildProformaCreator,
|
||||||
buildProformaFinder,
|
buildProformaFinder,
|
||||||
|
buildProformaInputMappers,
|
||||||
buildProformaSnapshotBuilders,
|
buildProformaSnapshotBuilders,
|
||||||
buildReportProformaUseCase,
|
buildReportProformaUseCase,
|
||||||
} from "../../../application";
|
} 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";
|
import { buildProformaRepository } from "./proforma-repositories.di";
|
||||||
|
|
||||||
export type ProformasInternalDeps = {
|
export type ProformasInternalDeps = {
|
||||||
@ -19,8 +25,9 @@ export type ProformasInternalDeps = {
|
|||||||
listProformas: () => ListProformasUseCase;
|
listProformas: () => ListProformasUseCase;
|
||||||
getProformaById: () => GetProformaByIdUseCase;
|
getProformaById: () => GetProformaByIdUseCase;
|
||||||
reportProforma: () => ReportProformaUseCase;
|
reportProforma: () => ReportProformaUseCase;
|
||||||
|
createProforma: () => CreateProformaUseCase;
|
||||||
|
|
||||||
/*createProforma: () => CreateProformaUseCase;
|
/*
|
||||||
updateProforma: () => UpdateProformaUseCase;
|
updateProforma: () => UpdateProformaUseCase;
|
||||||
deleteProforma: () => DeleteProformaUseCase;
|
deleteProforma: () => DeleteProformaUseCase;
|
||||||
issueProforma: () => IssueProformaUseCase;
|
issueProforma: () => IssueProformaUseCase;
|
||||||
@ -33,14 +40,19 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
const transactionManager = buildTransactionManager(database);
|
const transactionManager = buildTransactionManager(database);
|
||||||
const repository = buildProformaRepository(database);
|
const catalogs = buildCatalogs();
|
||||||
//const numberService = buildProformaNumberGenerator();
|
const persistenceMappers = buildProformaPersistenceMappers(catalogs);
|
||||||
|
|
||||||
|
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
||||||
|
const numberService = buildProformaNumberGenerator();
|
||||||
|
|
||||||
// Application helpers
|
// Application helpers
|
||||||
|
const inputMappers = buildProformaInputMappers(catalogs);
|
||||||
const finder = buildProformaFinder(repository);
|
const finder = buildProformaFinder(repository);
|
||||||
//const creator = buildProformaCreator(numberService, repository);
|
const creator = buildProformaCreator({ numberService, repository });
|
||||||
|
|
||||||
const snapshotBuilders = buildProformaSnapshotBuilders();
|
const snapshotBuilders = buildProformaSnapshotBuilders();
|
||||||
const documentGeneratorPipeline = buildproformaDocumentService(params);
|
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
||||||
|
|
||||||
// Internal use cases (factories)
|
// Internal use cases (factories)
|
||||||
return {
|
return {
|
||||||
@ -68,12 +80,13 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/*createProforma: () =>
|
createProforma: () =>
|
||||||
buildCreateProformaUseCase({
|
buildCreateProformaUseCase({
|
||||||
creator,
|
creator,
|
||||||
|
dtoMapper: inputMappers.createInputMapper,
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),*/
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { Tax } from "@erp/core/api";
|
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
DomainError,
|
DomainError,
|
||||||
@ -15,8 +14,10 @@ import {
|
|||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../common";
|
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common";
|
||||||
import {
|
import {
|
||||||
|
type IProformaItemProps,
|
||||||
|
type IProformaProps,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
@ -26,9 +27,8 @@ import {
|
|||||||
type IssuedInvoiceItemProps,
|
type IssuedInvoiceItemProps,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
} from "../../../domain";
|
} from "../../../../domain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CreateProformaPropsMapper
|
* CreateProformaPropsMapper
|
||||||
@ -41,7 +41,8 @@ import {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class CreateProformaPropsMapper {
|
|
||||||
|
export class CreateProformaRequestMapper {
|
||||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
private readonly taxCatalog: JsonTaxCatalogProvider;
|
||||||
private errors: ValidationErrorDetail[] = [];
|
private errors: ValidationErrorDetail[] = [];
|
||||||
private languageCode?: LanguageCode;
|
private languageCode?: LanguageCode;
|
||||||
@ -52,7 +53,8 @@ export class CreateProformaPropsMapper {
|
|||||||
this.errors = [];
|
this.errors = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public map(dto: CreateProformaRequestDTO, companyId: UniqueID) {
|
public map(dto: CreateProformaRequestDTO, params: { companyId: UniqueID }) {
|
||||||
|
const { companyId } = params;
|
||||||
try {
|
try {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
@ -60,8 +62,6 @@ export class CreateProformaPropsMapper {
|
|||||||
|
|
||||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
||||||
|
|
||||||
const isProforma = true;
|
|
||||||
|
|
||||||
const customerId = extractOrPushError(
|
const customerId = extractOrPushError(
|
||||||
UniqueID.create(dto.customer_id),
|
UniqueID.create(dto.customer_id),
|
||||||
"customer_id",
|
"customer_id",
|
||||||
@ -132,7 +132,7 @@ export class CreateProformaPropsMapper {
|
|||||||
this.errors
|
this.errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const discountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
Percentage.create({
|
Percentage.create({
|
||||||
value: Number(dto.discount_percentage.value),
|
value: Number(dto.discount_percentage.value),
|
||||||
scale: Number(dto.discount_percentage.scale),
|
scale: Number(dto.discount_percentage.scale),
|
||||||
@ -149,10 +149,8 @@ export class CreateProformaPropsMapper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const proformaProps: IProformaProps = {
|
const proformaProps: Omit<IProformaProps, "items"> & { items: IProformaItemProps[] } = {
|
||||||
companyId,
|
companyId,
|
||||||
isProforma,
|
|
||||||
proformaId: Maybe.none(),
|
|
||||||
status: defaultStatus!,
|
status: defaultStatus!,
|
||||||
|
|
||||||
invoiceNumber: proformaNumber!,
|
invoiceNumber: proformaNumber!,
|
||||||
@ -171,11 +169,11 @@ export class CreateProformaPropsMapper {
|
|||||||
languageCode: this.languageCode!,
|
languageCode: this.languageCode!,
|
||||||
currencyCode: this.currencyCode!,
|
currencyCode: this.currencyCode!,
|
||||||
|
|
||||||
items: items,
|
|
||||||
|
|
||||||
paymentMethod: paymentMethod!,
|
paymentMethod: paymentMethod!,
|
||||||
|
|
||||||
discountPercentage: discountPercentage!,
|
globalDiscountPercentage: globalDiscountPercentage!,
|
||||||
|
|
||||||
|
items:
|
||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok({ id: proformaId!, props: proformaProps });
|
return Result.ok({ id: proformaId!, props: proformaProps });
|
||||||
@ -184,8 +182,8 @@ export class CreateProformaPropsMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapItems(items: CreateProformaItemRequestDTO[]) {
|
private mapItems(items: CreateProformaItemRequestDTO[]): IProformaItemProps[] {
|
||||||
const invoiceItems = CustomerInvoiceItems.create({
|
const proformaItems = CustomerInvoiceItems.create({
|
||||||
currencyCode: this.currencyCode!,
|
currencyCode: this.currencyCode!,
|
||||||
languageCode: this.languageCode!,
|
languageCode: this.languageCode!,
|
||||||
items: [],
|
items: [],
|
||||||
@ -232,7 +230,7 @@ export class CreateProformaPropsMapper {
|
|||||||
|
|
||||||
const itemResult = IssuedInvoiceItem.create(itemProps);
|
const itemResult = IssuedInvoiceItem.create(itemProps);
|
||||||
if (itemResult.isSuccess) {
|
if (itemResult.isSuccess) {
|
||||||
invoiceItems.add(itemResult.data);
|
proformaItems.add(itemResult.data);
|
||||||
} else {
|
} else {
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
path: `items[${index}]`,
|
path: `items[${index}]`,
|
||||||
@ -240,7 +238,7 @@ export class CreateProformaPropsMapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return invoiceItems;
|
return proformaItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapTaxes(item: CreateProformaItemRequestDTO, itemIndex: number) {
|
private mapTaxes(item: CreateProformaItemRequestDTO, itemIndex: number) {
|
||||||
@ -15,13 +15,13 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import type { IProformaDomainMapper } from "../../../../../../application";
|
import type { IProformaDomainMapper } from "../../../../../../application";
|
||||||
import {
|
import {
|
||||||
|
type IProformaProps,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
Proforma,
|
Proforma,
|
||||||
ProformaItems,
|
ProformaItems,
|
||||||
type ProformaProps,
|
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceCreationAttributes,
|
CustomerInvoiceCreationAttributes,
|
||||||
@ -217,7 +217,7 @@ export class SequelizeProformaDomainMapper
|
|||||||
items: itemsResults.data.getAll(),
|
items: itemsResults.data.getAll(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceProps: ProformaProps = {
|
const invoiceProps: IProformaProps = {
|
||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
|
|
||||||
status: attributes.status!,
|
status: attributes.status!,
|
||||||
|
|||||||
@ -16,15 +16,15 @@ import {
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IProformaItemProps,
|
||||||
|
type IProformaProps,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
type Proforma,
|
type Proforma,
|
||||||
ProformaItem,
|
ProformaItem,
|
||||||
type ProformaItemProps,
|
|
||||||
ProformaItemTaxes,
|
ProformaItemTaxes,
|
||||||
type ProformaItemTaxesProps,
|
type ProformaItemTaxesProps,
|
||||||
type ProformaProps,
|
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
@ -54,11 +54,11 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
private mapAttributesToDomain(
|
private mapAttributesToDomain(
|
||||||
raw: CustomerInvoiceItemModel,
|
raw: CustomerInvoiceItemModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Partial<ProformaItemProps & ProformaItemTaxesProps> & { itemId?: UniqueID } {
|
): Partial<IProformaItemProps & ProformaItemTaxesProps> & { itemId?: UniqueID } {
|
||||||
const { errors, index, parent } = params as {
|
const { errors, index, parent } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
parent: Partial<ProformaProps>;
|
parent: Partial<IProformaProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemId = extractOrPushError(
|
const itemId = extractOrPushError(
|
||||||
@ -139,7 +139,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
const { errors, index } = params as {
|
const { errors, index } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
parent: Partial<ProformaProps>;
|
parent: Partial<IProformaProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { InvoiceRecipient, type ProformaProps } from "../../../../../../domain";
|
import { type IProformaProps, InvoiceRecipient } from "../../../../../../domain";
|
||||||
import type { CustomerInvoiceModel } from "../../../../../common";
|
import type { CustomerInvoiceModel } from "../../../../../common";
|
||||||
|
|
||||||
export class SequelizeProformaRecipientDomainMapper {
|
export class SequelizeProformaRecipientDomainMapper {
|
||||||
@ -28,7 +28,7 @@ export class SequelizeProformaRecipientDomainMapper {
|
|||||||
|
|
||||||
const { errors, parent } = params as {
|
const { errors, parent } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
parent: Partial<ProformaProps>;
|
parent: Partial<IProformaProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* if (!source.current_customer) {
|
/* if (!source.current_customer) {
|
||||||
|
|||||||
@ -9,22 +9,26 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { type Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { FindOptions, InferAttributes, OrderItem, Sequelize, Transaction } from "sequelize";
|
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 type { InvoiceStatus, Proforma } from "../../../../../domain";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
CustomerInvoiceModel,
|
CustomerInvoiceModel,
|
||||||
CustomerInvoiceTaxModel,
|
CustomerInvoiceTaxModel,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
import type { SequelizeProformaDomainMapper, SequelizeProformaListMapper } from "../mappers";
|
|
||||||
|
|
||||||
export class ProformaRepository
|
export class ProformaRepository
|
||||||
extends SequelizeRepository<Proforma>
|
extends SequelizeRepository<Proforma>
|
||||||
implements IProformaRepository
|
implements IProformaRepository
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly domainMapper: SequelizeProformaDomainMapper,
|
private readonly domainMapper: IProformaDomainMapper,
|
||||||
private readonly listMapper: SequelizeProformaListMapper,
|
private readonly listMapper: IProformaListMapper,
|
||||||
database: Sequelize
|
database: Sequelize
|
||||||
) {
|
) {
|
||||||
super({ database });
|
super({ database });
|
||||||
|
|||||||
@ -31,7 +31,8 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src",
|
"src",
|
||||||
"../core/src/api/domain/value-objects/tax-percentage.vo.ts",
|
"../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"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"dev:server": "turbo dev --filter=server",
|
"dev:server": "turbo dev --filter=server",
|
||||||
"dev:client": "turbo dev --filter=client",
|
"dev:client": "turbo dev --filter=client",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"lint:fix": "turbo run lint:fix",
|
||||||
"format-and-lint": "biome check .",
|
"format-and-lint": "biome check .",
|
||||||
"format-and-lint:fix": "biome check . --write",
|
"format-and-lint:fix": "biome check . --write",
|
||||||
"ui:add": "pnpm --filter @repo/shadcn-ui ui:add",
|
"ui:add": "pnpm --filter @repo/shadcn-ui ui:add",
|
||||||
@ -23,7 +25,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.3.1",
|
"@biomejs/biome": "2.3.1",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
"change-case": "^5.4.4",
|
"change-case": "^5.4.4",
|
||||||
|
"eslint": "^10.0.2",
|
||||||
"inquirer": "^12.10.0",
|
"inquirer": "^12.10.0",
|
||||||
"plop": "^4.0.4",
|
"plop": "^4.0.4",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
|
|||||||
624
pnpm-lock.yaml
624
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
27
turbo.json
27
turbo.json
@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://turbo.build/schema.json",
|
"$schema": "https://turbo.build/schema.json",
|
||||||
"ui": "tui",
|
"ui": "tui",
|
||||||
"globalDependencies": ["**/.env.*local"],
|
"globalDependencies": [
|
||||||
|
"**/.env.*local"
|
||||||
|
],
|
||||||
"tasks": {
|
"tasks": {
|
||||||
|
"lint": {
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"lint:fix": {
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
"persistent": true
|
"persistent": true
|
||||||
@ -13,13 +21,22 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": [
|
||||||
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
"^build"
|
||||||
"outputs": ["dist/**"]
|
],
|
||||||
|
"inputs": [
|
||||||
|
"$TURBO_DEFAULT$",
|
||||||
|
".env*"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"build:templates": {
|
"build:templates": {
|
||||||
"dependsOn": [],
|
"dependsOn": [],
|
||||||
"outputs": ["dist/templates/**"]
|
"outputs": [
|
||||||
|
"dist/templates/**"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"clean": {
|
"clean": {
|
||||||
"cache": false
|
"cache": false
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user