.
This commit is contained in:
parent
e9824ecf80
commit
cc38faed94
@ -1,4 +1,4 @@
|
|||||||
import { Tax } from "../tax";
|
import { Tax } from "../tax.vo";
|
||||||
|
|
||||||
describe("Tax Value Object", () => {
|
describe("Tax Value Object", () => {
|
||||||
describe("Creación", () => {
|
describe("Creación", () => {
|
||||||
@ -244,7 +244,7 @@ describe("Tax Value Object", () => {
|
|||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
const tax = result.value;
|
const tax = result.value;
|
||||||
const json = tax.toJSON();
|
const json = tax.toJSON();
|
||||||
|
|
||||||
expect(json).toEqual({
|
expect(json).toEqual({
|
||||||
value: 2100,
|
value: 2100,
|
||||||
scale: 2,
|
scale: 2,
|
||||||
@ -285,7 +285,7 @@ describe("Tax Value Object", () => {
|
|||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
const tax = result.value;
|
const tax = result.value;
|
||||||
const props = tax.getProps();
|
const props = tax.getProps();
|
||||||
|
|
||||||
// Intentar modificar las propiedades debe fallar
|
// Intentar modificar las propiedades debe fallar
|
||||||
expect(() => {
|
expect(() => {
|
||||||
(props as any).value = 3000;
|
(props as any).value = 3000;
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
type DiscountPercentageProps = Pick<PercentageProps, "value">;
|
||||||
|
|
||||||
|
export class DiscountPercentage extends Percentage {
|
||||||
|
static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
|
static create({ value }: DiscountPercentageProps): Result<Percentage> {
|
||||||
|
return Percentage.create({
|
||||||
|
value,
|
||||||
|
scale: DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static zero() {
|
||||||
|
return DiscountPercentage.create({ value: 0 }).data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,3 @@
|
|||||||
export * from "./tax";
|
export * from "./discount-percentage.vo";
|
||||||
|
export * from "./tax.vo";
|
||||||
|
export * from "./tax-percentage.vo";
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
type TaxPercentageProps = Pick<PercentageProps, "value">;
|
||||||
|
|
||||||
|
export class TaxPercentage extends Percentage {
|
||||||
|
static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
|
static create({ value }: TaxPercentageProps): Result<Percentage> {
|
||||||
|
return Percentage.create({
|
||||||
|
value,
|
||||||
|
scale: TaxPercentage.DEFAULT_SCALE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static zero() {
|
||||||
|
return TaxPercentage.create({ value: 0 }).data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,21 @@
|
|||||||
import type { TaxCatalogProvider } from "@erp/core";
|
import type { TaxCatalogProvider } from "@erp/core";
|
||||||
import { Percentage, ValueObject } from "@repo/rdx-ddd";
|
import { ValueObject } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
const DEFAULT_SCALE = 2;
|
import { TaxPercentage } from "./tax-percentage.vo";
|
||||||
const DEFAULT_MIN_VALUE = 0;
|
|
||||||
const DEFAULT_MAX_VALUE = 100;
|
|
||||||
|
|
||||||
const DEFAULT_MIN_SCALE = 0;
|
const DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE;
|
||||||
const DEFAULT_MAX_SCALE = 4;
|
const DEFAULT_MIN_VALUE = TaxPercentage.MIN_VALUE;
|
||||||
|
const DEFAULT_MAX_VALUE = TaxPercentage.MAX_VALUE;
|
||||||
|
|
||||||
|
const DEFAULT_MIN_SCALE = TaxPercentage.MIN_SCALE;
|
||||||
|
const DEFAULT_MAX_SCALE = TaxPercentage.MAX_SCALE;
|
||||||
|
|
||||||
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
|
||||||
scale: number; // 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tax extends ValueObject<TaxProps> {
|
export class Tax extends ValueObject<TaxProps> {
|
||||||
@ -26,7 +27,7 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
|
|
||||||
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
|
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
|
||||||
|
|
||||||
private _percentage!: Percentage;
|
private _percentage!: TaxPercentage;
|
||||||
|
|
||||||
protected static validate(values: TaxProps) {
|
protected static validate(values: TaxProps) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
@ -55,19 +56,14 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(props: TaxProps): Result<Tax> {
|
static create(props: TaxProps): Result<Tax> {
|
||||||
const { value, scale = Tax.DEFAULT_SCALE, name, code } = props;
|
const { value, name, code } = props;
|
||||||
|
|
||||||
const validationResult = Tax.validate({ value, scale, name, code });
|
const validationResult = Tax.validate({ value, name, code });
|
||||||
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(", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
const realValue = value / 10 ** scale;
|
return Result.ok(new Tax({ value, name, code }));
|
||||||
if (realValue > Tax.MAX_VALUE) {
|
|
||||||
return Result.fail(new Error("La tasa de impuesto no puede ser mayor a 100%."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(new Tax({ value, scale, name, code }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +97,6 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
// 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),
|
||||||
scale: Number(item.scale) ?? Tax.DEFAULT_SCALE,
|
|
||||||
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
|
||||||
});
|
});
|
||||||
@ -109,9 +104,8 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
|
|
||||||
protected constructor(props: TaxProps) {
|
protected constructor(props: TaxProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this._percentage = Percentage.create({
|
this._percentage = TaxPercentage.create({
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
scale: this.props.scale,
|
|
||||||
}).data;
|
}).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +113,7 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
get scale(): number {
|
get scale(): number {
|
||||||
return this.props.scale;
|
return Tax.DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this.props.name;
|
return this.props.name;
|
||||||
@ -128,7 +122,7 @@ export class Tax extends ValueObject<TaxProps> {
|
|||||||
return this.props.code;
|
return this.props.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
get percentage(): Percentage {
|
get percentage(): TaxPercentage {
|
||||||
return this._percentage;
|
return this._percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { Tax } from "./tax";
|
|
||||||
|
|
||||||
export class Taxes extends Collection<Tax> {
|
|
||||||
public static create<T extends Taxes>(this: new (items: Tax[]) => T, items: Tax[]): T {
|
|
||||||
return new Taxes(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
export * from "./application-models";
|
export * from "./application-models";
|
||||||
export * from "./di";
|
export * from "./di";
|
||||||
export * from "./dtos";
|
export * from "./dtos";
|
||||||
//export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
export * from "./services";
|
export * from "./services";
|
||||||
export * from "./snapshot-builders";
|
export * from "./snapshot-builders";
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from "./invoice-payment-method";
|
export * from "./invoice-payment-method";
|
||||||
export * from "./invoice-taxes";
|
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./invoice-tax";
|
|
||||||
export * from "./invoice-taxes";
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import type { Tax } from "@erp/core/api";
|
|
||||||
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoiceAmount } from "../../value-objects/invoice-amount.vo";
|
|
||||||
|
|
||||||
export interface InvoiceTaxProps {
|
|
||||||
tax: Tax;
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InvoiceTax extends DomainEntity<InvoiceTaxProps> {
|
|
||||||
static create(props: InvoiceTaxProps, id?: UniqueID): Result<InvoiceTax, Error> {
|
|
||||||
const invoiceTax = new InvoiceTax(props, id);
|
|
||||||
|
|
||||||
// Reglas de negocio / validaciones
|
|
||||||
// ...
|
|
||||||
// ...
|
|
||||||
|
|
||||||
return Result.ok(invoiceTax);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get tax(): Tax {
|
|
||||||
return this.props.tax;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps(): InvoiceTaxProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
toPrimitive() {
|
|
||||||
return this.getProps();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxAmount(taxableAmount: InvoiceAmount): InvoiceAmount {
|
|
||||||
return taxableAmount.percentage(this.tax.percentage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { InvoiceAmount, type InvoiceTaxGroup } from "../../value-objects";
|
|
||||||
|
|
||||||
export type InvoiceTaxTotal = {};
|
|
||||||
|
|
||||||
export class InvoiceTaxes extends Collection<InvoiceTaxGroup> {
|
|
||||||
constructor(items: InvoiceTaxGroup[] = [], totalItems: number | null = null) {
|
|
||||||
super(items, totalItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getIVAAmount(): InvoiceAmount {
|
|
||||||
return this.getAll().reduce(
|
|
||||||
(total, tax) => total.add(taxableAmount.percentage(tax.percentage)),
|
|
||||||
InvoiceAmount.zero(taxableAmount.currencyCode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxesAmountByTaxCode(taxCode: string, taxableAmount: InvoiceAmount): InvoiceAmount {
|
|
||||||
const currencyCode = taxableAmount.currencyCode;
|
|
||||||
|
|
||||||
return this.filter((itemTax) => itemTax.code === taxCode).reduce((totalAmount, itemTax) => {
|
|
||||||
return taxableAmount.percentage(itemTax.percentage).add(totalAmount);
|
|
||||||
}, InvoiceAmount.zero(currencyCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxesAmountByTaxes(taxableAmount: InvoiceAmount): InvoiceTaxTotal[] {
|
|
||||||
return this.getAll().map((taxItem) => ({
|
|
||||||
taxableAmount,
|
|
||||||
tax: taxItem,
|
|
||||||
taxesAmount: this.getTaxesAmountByTaxCode(taxItem.code, taxableAmount),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCodesToString(): string {
|
|
||||||
return this.getAll()
|
|
||||||
.map((taxItem) => taxItem.code)
|
|
||||||
.join(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,9 @@
|
|||||||
export * from "./invoice-address-type.vo";
|
export * from "./invoice-address-type.vo";
|
||||||
export * from "./invoice-amount.vo";
|
export * from "./invoice-amount.vo";
|
||||||
export * from "./invoice-discount-percentage.vo";
|
|
||||||
export * from "./invoice-number.vo";
|
export * from "./invoice-number.vo";
|
||||||
export * from "./invoice-recipient";
|
export * from "./invoice-recipient";
|
||||||
export * from "./invoice-serie.vo";
|
export * from "./invoice-serie.vo";
|
||||||
export * from "./invoice-status.vo";
|
export * from "./invoice-status.vo";
|
||||||
export * from "./invoice-tax-group.vo";
|
|
||||||
export * from "./invoice-tax-percentage.vo";
|
|
||||||
export * from "./item-amount.vo";
|
export * from "./item-amount.vo";
|
||||||
export * from "./item-description.vo";
|
export * from "./item-description.vo";
|
||||||
export * from "./item-discount-percentage.vo";
|
|
||||||
export * from "./item-quantity.vo";
|
export * from "./item-quantity.vo";
|
||||||
export * from "./item-tax-percentage.vo";
|
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
|
||||||
import type { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type InvoiceDiscountPercentageProps = Pick<PercentageProps, "value">;
|
|
||||||
|
|
||||||
export class InvoiceDiscountPercentage extends Percentage {
|
|
||||||
static DEFAULT_SCALE = 2;
|
|
||||||
|
|
||||||
static create({ value }: InvoiceDiscountPercentageProps): Result<Percentage> {
|
|
||||||
return Percentage.create({
|
|
||||||
value,
|
|
||||||
scale: InvoiceDiscountPercentage.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static zero() {
|
|
||||||
return InvoiceDiscountPercentage.create({ value: 0 }).data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
import type { Tax } from "@erp/core/api";
|
|
||||||
import { ValueObject } from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ProformaItemTaxGroup } from "../../proformas/value-objects/proforma-item-tax-group.vo";
|
|
||||||
|
|
||||||
import { InvoiceAmount } from "./invoice-amount.vo";
|
|
||||||
|
|
||||||
export type InvoiceTaxGroupProps = {
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
iva: Tax;
|
|
||||||
rec: Maybe<Tax>; // si existe
|
|
||||||
retention: Maybe<Tax>; // si existe
|
|
||||||
};
|
|
||||||
|
|
||||||
export class InvoiceTaxGroup extends ValueObject<InvoiceTaxGroupProps> {
|
|
||||||
static create(props: InvoiceTaxGroupProps) {
|
|
||||||
return Result.ok(new InvoiceTaxGroup(props));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crea un grupo vacío a partir de un ItemTaxGroup (línea)
|
|
||||||
*/
|
|
||||||
static fromItem(lineTaxes: ProformaItemTaxGroup, taxableAmount: InvoiceAmount): InvoiceTaxGroup {
|
|
||||||
const iva = lineTaxes.iva.unwrap(); // iva siempre obligatorio
|
|
||||||
const rec = lineTaxes.rec;
|
|
||||||
const retention = lineTaxes.retention;
|
|
||||||
|
|
||||||
return new InvoiceTaxGroup({
|
|
||||||
iva,
|
|
||||||
rec,
|
|
||||||
retention,
|
|
||||||
taxableAmount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateAmounts() {
|
|
||||||
const taxableAmount = this.props.taxableAmount;
|
|
||||||
const ivaAmount = taxableAmount.percentage(this.props.iva.percentage);
|
|
||||||
|
|
||||||
const recAmount = this.props.rec.match(
|
|
||||||
(rec) => taxableAmount.percentage(rec.percentage),
|
|
||||||
() => InvoiceAmount.zero(taxableAmount.currencyCode)
|
|
||||||
);
|
|
||||||
|
|
||||||
const retentionAmount = this.props.retention.match(
|
|
||||||
(retention) => taxableAmount.percentage(retention.percentage).multiply(-1),
|
|
||||||
() => InvoiceAmount.zero(taxableAmount.currencyCode)
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalAmount = ivaAmount.add(recAmount).add(retentionAmount);
|
|
||||||
|
|
||||||
return { ivaAmount, recAmount, retentionAmount, totalAmount };
|
|
||||||
}
|
|
||||||
|
|
||||||
get iva(): Tax {
|
|
||||||
return this.props.iva;
|
|
||||||
}
|
|
||||||
|
|
||||||
get rec(): Maybe<Tax> {
|
|
||||||
return this.props.rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
get retention(): Maybe<Tax> {
|
|
||||||
return this.props.retention;
|
|
||||||
}
|
|
||||||
|
|
||||||
get taxableAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clave única del grupo: iva|rec|ret
|
|
||||||
*/
|
|
||||||
public getKey(): string {
|
|
||||||
const iva = this.props.iva.code;
|
|
||||||
|
|
||||||
const rec = this.props.rec.match(
|
|
||||||
(t) => t.code,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
const retention = this.props.retention.match(
|
|
||||||
(t) => t.code,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${iva}|${rec}|${retention}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suma una base imponible a este grupo.
|
|
||||||
*
|
|
||||||
* Devuelve un nuevo InvoiceTaxGroup (inmutabilidad).
|
|
||||||
*/
|
|
||||||
public addTaxable(amount: InvoiceAmount): InvoiceTaxGroup {
|
|
||||||
return new InvoiceTaxGroup({
|
|
||||||
...this.props,
|
|
||||||
taxableAmount: this.props.taxableAmount.add(amount),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Devuelve únicamente los códigos existentes: ["iva_21", "rec_5_2"]
|
|
||||||
*/
|
|
||||||
public getCodesArray(): string[] {
|
|
||||||
const codes: string[] = [];
|
|
||||||
|
|
||||||
// IVA
|
|
||||||
codes.push(this.props.iva.code);
|
|
||||||
|
|
||||||
this.props.rec.match(
|
|
||||||
(t) => codes.push(t.code),
|
|
||||||
() => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.props.retention.match(
|
|
||||||
(t) => codes.push(t.code),
|
|
||||||
() => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return codes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Devuelve una cadena tipo: "iva_21, rec_5_2"
|
|
||||||
*/
|
|
||||||
public getCodesToString(): string {
|
|
||||||
return this.getCodesArray().join(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps() {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
toPrimitive() {
|
|
||||||
return this.getProps();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
|
||||||
import type { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type InvoiceTaxPercentageProps = Pick<PercentageProps, "value">;
|
|
||||||
|
|
||||||
export class InvoiceTaxPercentage extends Percentage {
|
|
||||||
static DEFAULT_SCALE = 2;
|
|
||||||
|
|
||||||
static create({ value }: InvoiceTaxPercentageProps): Result<Percentage> {
|
|
||||||
return Percentage.create({
|
|
||||||
value,
|
|
||||||
scale: InvoiceTaxPercentage.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static zero() {
|
|
||||||
return InvoiceTaxPercentage.create({ value: 0 }).data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
|
||||||
import type { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type ItemTaxPercentageProps = Pick<PercentageProps, "value">;
|
|
||||||
|
|
||||||
export class ItemTaxPercentage extends Percentage {
|
|
||||||
static DEFAULT_SCALE = 2;
|
|
||||||
|
|
||||||
static create({ value }: ItemTaxPercentageProps): Result<Percentage> {
|
|
||||||
return Percentage.create({
|
|
||||||
value,
|
|
||||||
scale: ItemTaxPercentage.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static zero() {
|
|
||||||
return ItemTaxPercentage.create({ value: 0 }).data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { DiscountPercentage } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
AggregateRoot,
|
AggregateRoot,
|
||||||
type CurrencyCode,
|
type CurrencyCode,
|
||||||
@ -50,7 +51,7 @@ export type IssuedInvoiceProps = {
|
|||||||
subtotalAmount: InvoiceAmount;
|
subtotalAmount: InvoiceAmount;
|
||||||
|
|
||||||
itemsDiscountAmount: InvoiceAmount;
|
itemsDiscountAmount: InvoiceAmount;
|
||||||
globalDiscountPercentage: Percentage;
|
globalDiscountPercentage: DiscountPercentage;
|
||||||
globalDiscountAmount: InvoiceAmount;
|
globalDiscountAmount: InvoiceAmount;
|
||||||
totalDiscountAmount: InvoiceAmount;
|
totalDiscountAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
import {
|
import type { DiscountPercentage, TaxPercentage } from "@erp/core/api";
|
||||||
type CurrencyCode,
|
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
||||||
DomainEntity,
|
|
||||||
type LanguageCode,
|
|
||||||
type Percentage,
|
|
||||||
type UniqueID,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type {
|
import type { ItemAmount, ItemDescription, ItemQuantity } from "../../../common";
|
||||||
ItemAmount,
|
|
||||||
ItemDescription,
|
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
|
||||||
} from "../../../common";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -32,10 +22,10 @@ export type IssuedInvoiceItemProps = {
|
|||||||
|
|
||||||
subtotalAmount: ItemAmount;
|
subtotalAmount: ItemAmount;
|
||||||
|
|
||||||
itemDiscountPercentage: Maybe<ItemDiscountPercentage>;
|
itemDiscountPercentage: Maybe<DiscountPercentage>;
|
||||||
itemDiscountAmount: ItemAmount;
|
itemDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
globalDiscountPercentage: Maybe<ItemDiscountPercentage>;
|
globalDiscountPercentage: Maybe<DiscountPercentage>;
|
||||||
globalDiscountAmount: ItemAmount;
|
globalDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
totalDiscountAmount: ItemAmount;
|
totalDiscountAmount: ItemAmount;
|
||||||
@ -43,15 +33,15 @@ export type IssuedInvoiceItemProps = {
|
|||||||
taxableAmount: ItemAmount;
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
ivaCode: Maybe<string>;
|
ivaCode: Maybe<string>;
|
||||||
ivaPercentage: Maybe<ItemDiscountPercentage>;
|
ivaPercentage: Maybe<DiscountPercentage>;
|
||||||
ivaAmount: ItemAmount;
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recPercentage: Maybe<ItemDiscountPercentage>;
|
recPercentage: Maybe<DiscountPercentage>;
|
||||||
recAmount: ItemAmount;
|
recAmount: ItemAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionPercentage: Maybe<ItemDiscountPercentage>;
|
retentionPercentage: Maybe<DiscountPercentage>;
|
||||||
retentionAmount: ItemAmount;
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
taxesAmount: ItemAmount;
|
taxesAmount: ItemAmount;
|
||||||
@ -136,7 +126,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
|
|||||||
public get ivaCode(): Maybe<string> {
|
public get ivaCode(): Maybe<string> {
|
||||||
return this.props.ivaCode;
|
return this.props.ivaCode;
|
||||||
}
|
}
|
||||||
public get ivaPercentage(): Maybe<Percentage> {
|
public get ivaPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.ivaPercentage;
|
return this.props.ivaPercentage;
|
||||||
}
|
}
|
||||||
public get ivaAmount(): ItemAmount {
|
public get ivaAmount(): ItemAmount {
|
||||||
@ -146,7 +136,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
|
|||||||
public get recCode(): Maybe<string> {
|
public get recCode(): Maybe<string> {
|
||||||
return this.props.recCode;
|
return this.props.recCode;
|
||||||
}
|
}
|
||||||
public get recPercentage(): Maybe<Percentage> {
|
public get recPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.recPercentage;
|
return this.props.recPercentage;
|
||||||
}
|
}
|
||||||
public get recAmount(): ItemAmount {
|
public get recAmount(): ItemAmount {
|
||||||
@ -156,7 +146,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
|
|||||||
public get retentionCode(): Maybe<string> {
|
public get retentionCode(): Maybe<string> {
|
||||||
return this.props.retentionCode;
|
return this.props.retentionCode;
|
||||||
}
|
}
|
||||||
public get retentionPercentage(): Maybe<Percentage> {
|
public get retentionPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.retentionPercentage;
|
return this.props.retentionPercentage;
|
||||||
}
|
}
|
||||||
public get retentionAmount(): ItemAmount {
|
public get retentionAmount(): ItemAmount {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { TaxPercentage } from "@erp/core/api";
|
||||||
import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd";
|
import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
@ -12,11 +13,11 @@ export type IssuedInvoiceTaxProps = {
|
|||||||
|
|
||||||
recCode: Maybe<string>;
|
recCode: Maybe<string>;
|
||||||
recPercentage: Maybe<Percentage>;
|
recPercentage: Maybe<Percentage>;
|
||||||
recAmount: Maybe<InvoiceAmount>;
|
recAmount: InvoiceAmount;
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
retentionCode: Maybe<string>;
|
||||||
retentionPercentage: Maybe<Percentage>;
|
retentionPercentage: Maybe<Percentage>;
|
||||||
retentionAmount: Maybe<InvoiceAmount>;
|
retentionAmount: InvoiceAmount;
|
||||||
|
|
||||||
taxesAmount: InvoiceAmount;
|
taxesAmount: InvoiceAmount;
|
||||||
};
|
};
|
||||||
@ -36,7 +37,7 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
public get ivaCode(): string {
|
public get ivaCode(): string {
|
||||||
return this.props.ivaCode;
|
return this.props.ivaCode;
|
||||||
}
|
}
|
||||||
public get ivaPercentage(): Percentage {
|
public get ivaPercentage(): TaxPercentage {
|
||||||
return this.props.ivaPercentage;
|
return this.props.ivaPercentage;
|
||||||
}
|
}
|
||||||
public get ivaAmount(): InvoiceAmount {
|
public get ivaAmount(): InvoiceAmount {
|
||||||
@ -46,20 +47,20 @@ export class IssuedInvoiceTax extends DomainEntity<IssuedInvoiceTaxProps> {
|
|||||||
public get recCode(): Maybe<string> {
|
public get recCode(): Maybe<string> {
|
||||||
return this.props.recCode;
|
return this.props.recCode;
|
||||||
}
|
}
|
||||||
public get recPercentage(): Maybe<Percentage> {
|
public get recPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.recPercentage;
|
return this.props.recPercentage;
|
||||||
}
|
}
|
||||||
public get recAmount(): Maybe<InvoiceAmount> {
|
public get recAmount(): InvoiceAmount {
|
||||||
return this.props.recAmount;
|
return this.props.recAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get retentionCode(): Maybe<string> {
|
public get retentionCode(): Maybe<string> {
|
||||||
return this.props.retentionCode;
|
return this.props.retentionCode;
|
||||||
}
|
}
|
||||||
public get retentionPercentage(): Maybe<Percentage> {
|
public get retentionPercentage(): Maybe<TaxPercentage> {
|
||||||
return this.props.retentionPercentage;
|
return this.props.retentionPercentage;
|
||||||
}
|
}
|
||||||
public get retentionAmount(): Maybe<InvoiceAmount> {
|
public get retentionAmount(): InvoiceAmount {
|
||||||
return this.props.retentionAmount;
|
return this.props.retentionAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { DiscountPercentage } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
AggregateRoot,
|
AggregateRoot,
|
||||||
type CurrencyCode,
|
type CurrencyCode,
|
||||||
@ -8,7 +9,7 @@ import {
|
|||||||
type UniqueID,
|
type UniqueID,
|
||||||
type UtcDate,
|
type UtcDate,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Collection, type Maybe, Result } from "@repo/rdx-utils";
|
import { type Collection, type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { InvoicePaymentMethod } from "../../common/entities";
|
import type { InvoicePaymentMethod } from "../../common/entities";
|
||||||
import {
|
import {
|
||||||
@ -17,11 +18,10 @@ import {
|
|||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
type InvoiceSerie,
|
type InvoiceSerie,
|
||||||
type InvoiceStatus,
|
type InvoiceStatus,
|
||||||
InvoiceTaxGroup,
|
|
||||||
type ItemAmount,
|
type ItemAmount,
|
||||||
} from "../../common/value-objects";
|
} from "../../common/value-objects";
|
||||||
import type { ProformaTaxes } from "../entities";
|
|
||||||
import { ProformaItems } from "../entities/proforma-items";
|
import { ProformaItems } from "../entities/proforma-items";
|
||||||
|
import { type IProformaTaxTotals, ProformaTaxCalculator } from "../services";
|
||||||
|
|
||||||
export type ProformaProps = {
|
export type ProformaProps = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -46,27 +46,51 @@ export type ProformaProps = {
|
|||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
items: ProformaItems;
|
items: ProformaItems;
|
||||||
globalDiscountPercentage: Percentage;
|
globalDiscountPercentage: DiscountPercentage;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IProforma extends AggregateRoot<ProformaProps> {
|
export interface IProformaTotals {
|
||||||
getTaxes: ProformaTaxes;
|
subtotalAmount: InvoiceAmount;
|
||||||
|
|
||||||
getSubtotalAmount: InvoiceAmount;
|
itemDiscountAmount: InvoiceAmount;
|
||||||
|
globalDiscountAmount: InvoiceAmount;
|
||||||
|
totalDiscountAmount: InvoiceAmount;
|
||||||
|
|
||||||
getItemsDiscountAmount: InvoiceAmount;
|
taxableAmount: InvoiceAmount;
|
||||||
getGlobalDiscountPercentage: Percentage;
|
|
||||||
getGlobalDiscountAmount: InvoiceAmount;
|
|
||||||
getTotalDiscountAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
getTaxableAmount: InvoiceAmount;
|
ivaAmount: InvoiceAmount;
|
||||||
|
recAmount: InvoiceAmount;
|
||||||
|
retentionAmount: InvoiceAmount;
|
||||||
|
|
||||||
getIvaAmount: InvoiceAmount;
|
taxesAmount: InvoiceAmount;
|
||||||
getRecAmount: InvoiceAmount;
|
totalAmount: InvoiceAmount;
|
||||||
getRetentionAmount: InvoiceAmount;
|
}
|
||||||
|
|
||||||
getTaxesAmount: InvoiceAmount;
|
export interface IProforma {
|
||||||
getTotalAmount: InvoiceAmount;
|
companyId: UniqueID;
|
||||||
|
status: InvoiceStatus;
|
||||||
|
|
||||||
|
series: Maybe<InvoiceSerie>;
|
||||||
|
invoiceNumber: InvoiceNumber;
|
||||||
|
|
||||||
|
invoiceDate: UtcDate;
|
||||||
|
operationDate: Maybe<UtcDate>;
|
||||||
|
|
||||||
|
customerId: UniqueID;
|
||||||
|
recipient: Maybe<InvoiceRecipient>;
|
||||||
|
|
||||||
|
reference: Maybe<string>;
|
||||||
|
description: Maybe<string>;
|
||||||
|
notes: Maybe<TextValue>;
|
||||||
|
|
||||||
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
|
items: ProformaItems;
|
||||||
|
taxes(): Collection<IProformaTaxTotals>;
|
||||||
|
totals(): IProformaTotals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProformaPatchProps = Partial<Omit<ProformaProps, "companyId" | "items">> & {
|
export type ProformaPatchProps = Partial<Omit<ProformaProps, "companyId" | "items">> & {
|
||||||
@ -142,10 +166,6 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
return this.props.status;
|
return this.props.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
canTransitionTo(nextStatus: string): boolean {
|
|
||||||
return this.props.status.canTransitionTo(nextStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get series(): Maybe<InvoiceSerie> {
|
public get series(): Maybe<InvoiceSerie> {
|
||||||
return this.props.series;
|
return this.props.series;
|
||||||
}
|
}
|
||||||
@ -207,6 +227,41 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
return this.paymentMethod.isSome();
|
return this.paymentMethod.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cálculos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Calcula todos los totales de factura a partir de los totales de las líneas.
|
||||||
|
* La cabecera NO recalcula lógica de porcentaje — toda la lógica está en Item/Items.
|
||||||
|
*/
|
||||||
|
public totals(): IProformaTotals {
|
||||||
|
const itemsTotals = this.items.totals();
|
||||||
|
|
||||||
|
return {
|
||||||
|
subtotalAmount: this.toInvoiceAmount(itemsTotals.subtotalAmount),
|
||||||
|
|
||||||
|
itemDiscountAmount: this.toInvoiceAmount(itemsTotals.itemDiscountAmount),
|
||||||
|
globalDiscountAmount: this.toInvoiceAmount(itemsTotals.globalDiscountAmount),
|
||||||
|
totalDiscountAmount: this.toInvoiceAmount(itemsTotals.totalDiscountAmount),
|
||||||
|
|
||||||
|
taxableAmount: this.toInvoiceAmount(itemsTotals.taxableAmount),
|
||||||
|
|
||||||
|
ivaAmount: this.toInvoiceAmount(itemsTotals.ivaAmount),
|
||||||
|
recAmount: this.toInvoiceAmount(itemsTotals.recAmount),
|
||||||
|
retentionAmount: this.toInvoiceAmount(itemsTotals.retentionAmount),
|
||||||
|
|
||||||
|
taxesAmount: this.toInvoiceAmount(itemsTotals.taxesAmount),
|
||||||
|
totalAmount: this.toInvoiceAmount(itemsTotals.totalAmount),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
public taxes(): Collection<IProformaTaxTotals> {
|
||||||
|
return new ProformaTaxCalculator(this.items).calculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProps(): ProformaProps {
|
||||||
|
return this.props;
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,101 +273,4 @@ export class Proforma extends AggregateRoot<ProformaProps> implements IProforma
|
|||||||
currency_code: this.currencyCode.code,
|
currency_code: this.currencyCode.code,
|
||||||
}).data;
|
}).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cálculos
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Calcula todos los totales de factura a partir de los totales de las líneas.
|
|
||||||
* La cabecera NO recalcula lógica de porcentaje — toda la lógica está en Item/Items.
|
|
||||||
*/
|
|
||||||
public calculateAllAmounts() {
|
|
||||||
const itemsTotals = this.items.calculateAllAmounts();
|
|
||||||
|
|
||||||
const subtotalAmount = this.toInvoiceAmount(itemsTotals.subtotalAmount);
|
|
||||||
|
|
||||||
const itemDiscountAmount = this.toInvoiceAmount(itemsTotals.itemDiscountAmount);
|
|
||||||
const globalDiscountAmount = this.toInvoiceAmount(itemsTotals.globalDiscountAmount);
|
|
||||||
const totalDiscountAmount = this.toInvoiceAmount(itemsTotals.totalDiscountAmount);
|
|
||||||
|
|
||||||
const taxableAmount = this.toInvoiceAmount(itemsTotals.taxableAmount);
|
|
||||||
const taxesAmount = this.toInvoiceAmount(itemsTotals.taxesAmount);
|
|
||||||
const totalAmount = this.toInvoiceAmount(itemsTotals.totalAmount);
|
|
||||||
|
|
||||||
const taxGroups = this.getTaxes();
|
|
||||||
|
|
||||||
return {
|
|
||||||
subtotalAmount,
|
|
||||||
itemDiscountAmount,
|
|
||||||
globalDiscountAmount,
|
|
||||||
totalDiscountAmount,
|
|
||||||
taxableAmount,
|
|
||||||
taxesAmount,
|
|
||||||
totalAmount,
|
|
||||||
taxGroups,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Métodos públicos
|
|
||||||
|
|
||||||
public getProps(): ProformaProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSubtotalAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().subtotalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getItemDiscountAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().itemDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getGlobalDiscountAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().globalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalDiscountAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().totalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxableAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxesAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().taxesAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalAmount(): InvoiceAmount {
|
|
||||||
return this.calculateAllAmounts().totalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Agrupa impuestos a nivel factura usando el trío (iva|rec|ret),
|
|
||||||
* construyendo InvoiceTaxGroup desde los datos de los ítems.
|
|
||||||
*/
|
|
||||||
public getTaxes(): Collection<InvoiceTaxGroup> {
|
|
||||||
const map = this.items.groupTaxesByCode();
|
|
||||||
const groups: InvoiceTaxGroup[] = [];
|
|
||||||
|
|
||||||
for (const [, entry] of map.entries()) {
|
|
||||||
const { taxes, taxable } = entry;
|
|
||||||
|
|
||||||
const iva = taxes.iva.unwrap(); // IVA siempre obligatorio
|
|
||||||
const rec = taxes.rec; // Maybe<Tax>
|
|
||||||
const retention = taxes.retention; // Maybe<Tax>
|
|
||||||
|
|
||||||
const taxableAmount = this.toInvoiceAmount(taxable);
|
|
||||||
|
|
||||||
const group = InvoiceTaxGroup.create({
|
|
||||||
iva,
|
|
||||||
rec,
|
|
||||||
retention,
|
|
||||||
taxableAmount,
|
|
||||||
}).data;
|
|
||||||
|
|
||||||
groups.push(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Collection(groups);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from "./proforma-items";
|
export * from "./proforma-items";
|
||||||
export * from "./proforma-taxes";
|
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import type { Tax } from "@erp/core/api";
|
import { DiscountPercentage, type Tax, type TaxPercentage } from "@erp/core/api";
|
||||||
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import { ItemAmount, type ItemDescription, type ItemQuantity } from "../../../common";
|
||||||
ItemAmount,
|
import type { ProformaItemTaxes } from "../../value-objects/proforma-item-taxes.vo";
|
||||||
type ItemDescription,
|
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
|
||||||
} from "../../../common";
|
|
||||||
import type { ProformaItemTaxGroup } from "../../value-objects/proforma-item-tax-group.vo";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -33,60 +28,63 @@ export type ProformaItemProps = {
|
|||||||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||||||
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
||||||
|
|
||||||
itemDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de línea
|
itemDiscountPercentage: Maybe<DiscountPercentage>; // % descuento de línea
|
||||||
|
|
||||||
taxes: ProformaItemTaxGroup;
|
taxes: ProformaItemTaxes;
|
||||||
|
|
||||||
// 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: Maybe<ItemDiscountPercentage>; // % 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 IProformaItem extends ProformaItemProps {
|
export interface IProformaItemTotals {
|
||||||
|
subtotalAmount: ItemAmount;
|
||||||
|
|
||||||
|
itemDiscountAmount: ItemAmount;
|
||||||
|
globalDiscountAmount: ItemAmount;
|
||||||
|
totalDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
|
ivaAmount: ItemAmount;
|
||||||
|
recAmount: ItemAmount;
|
||||||
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
|
taxesAmount: ItemAmount;
|
||||||
|
totalAmount: ItemAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProformaItem {
|
||||||
description: Maybe<ItemDescription>;
|
description: Maybe<ItemDescription>;
|
||||||
|
|
||||||
isValued: boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
quantity: Maybe<ItemQuantity>;
|
quantity: Maybe<ItemQuantity>;
|
||||||
unitAmount: Maybe<ItemAmount>;
|
unitAmount: Maybe<ItemAmount>;
|
||||||
|
|
||||||
getSubtotalAmount: Maybe<ItemAmount>;
|
taxes: ProformaItemTaxes;
|
||||||
|
|
||||||
itemDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de línea
|
itemDiscountPercentage: Maybe<DiscountPercentage>; // Descuento en línea
|
||||||
getItemDiscountAmount: Maybe<ItemAmount>;
|
globalDiscountPercentage: DiscountPercentage; // Descuento en cabecera
|
||||||
|
|
||||||
globalDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de la cabecera
|
ivaCode(): Maybe<string>;
|
||||||
getGlobalDiscountAmount: Maybe<ItemAmount>;
|
ivaPercentage(): Maybe<TaxPercentage>;
|
||||||
|
|
||||||
getTotalDiscountAmount: Maybe<ItemAmount>;
|
recCode(): Maybe<string>;
|
||||||
|
recPercentage(): Maybe<TaxPercentage>;
|
||||||
|
|
||||||
getTaxableAmount: Maybe<ItemAmount>;
|
retentionCode(): Maybe<string>;
|
||||||
|
retentionPercentage(): Maybe<TaxPercentage>;
|
||||||
|
|
||||||
getIva: Maybe<Tax>;
|
totals(): IProformaItemTotals;
|
||||||
getIvaCode: Maybe<string>;
|
|
||||||
getIvaPercentage: Maybe<ItemDiscountPercentage>;
|
|
||||||
getIvaAmount: ItemAmount;
|
|
||||||
|
|
||||||
getRec: Maybe<Tax>;
|
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
||||||
getRecCode: Maybe<string>;
|
|
||||||
getRecPercentage: Maybe<ItemDiscountPercentage>;
|
|
||||||
getRecAmount: ItemAmount;
|
|
||||||
|
|
||||||
getRetention: Maybe<Tax>;
|
|
||||||
getRetentionCode: Maybe<string>;
|
|
||||||
getRetentionPercentage: Maybe<ItemDiscountPercentage>;
|
|
||||||
getRetentionAmount: ItemAmount;
|
|
||||||
|
|
||||||
getTaxesAmount: ItemAmount;
|
|
||||||
getTotalAmount: ItemAmount;
|
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
export class ProformaItem extends DomainEntity<ProformaItemProps> implements IProformaItem {
|
||||||
public static create(props: ProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> {
|
public static create(props: ProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> {
|
||||||
const item = new ProformaItem(props, id);
|
const item = new ProformaItem(props, id);
|
||||||
|
|
||||||
@ -101,16 +99,18 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
super(props, id);
|
super(props, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
|
|
||||||
get isValued(): boolean {
|
|
||||||
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
return this.props.description;
|
return this.props.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get languageCode() {
|
||||||
|
return this.props.languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currencyCode() {
|
||||||
|
return this.props.currencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
get quantity() {
|
get quantity() {
|
||||||
return this.props.quantity;
|
return this.props.quantity;
|
||||||
}
|
}
|
||||||
@ -131,14 +131,6 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
return this.props.taxes;
|
return this.props.taxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
get languageCode() {
|
|
||||||
return this.props.languageCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currencyCode() {
|
|
||||||
return this.props.currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps(): ProformaItemProps {
|
getProps(): ProformaItemProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
@ -147,93 +139,87 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
return this.getProps();
|
return this.getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters específicos para cálculos y representaciones
|
// Cálculos y representaciones
|
||||||
|
// Todos a 4 decimales
|
||||||
|
|
||||||
public getSubtotalAmount(): ItemAmount {
|
public isValued(): boolean {
|
||||||
return this.calculateAllAmounts().subtotalAmount;
|
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getItemDiscountAmount(): ItemAmount {
|
public subtotalAmount(): ItemAmount {
|
||||||
return this.calculateAllAmounts().itemDiscountAmount;
|
if (!this.isValued()) {
|
||||||
|
return ItemAmount.zero(this.currencyCode.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const quantity = this.quantity.unwrap();
|
||||||
|
const unitAmount = this.unitAmount.unwrap();
|
||||||
|
|
||||||
|
return unitAmount.multiply(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGlobalDiscountAmount(): ItemAmount {
|
public iva(): Maybe<Tax> {
|
||||||
return this.calculateAllAmounts().globalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalDiscountAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().totalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxableAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getIva(): Maybe<Tax> {
|
|
||||||
return this.taxes.iva;
|
return this.taxes.iva;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIvaCode(): Maybe<string> {
|
public ivaCode(): Maybe<string> {
|
||||||
return this.taxes.iva.map((tax) => tax.code);
|
return this.taxes.iva.map((tax) => tax.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIvaPercentage(): Maybe<ItemDiscountPercentage> {
|
public ivaPercentage(): Maybe<DiscountPercentage> {
|
||||||
return this.taxes.iva.map((tax) => tax.percentage);
|
return this.taxes.iva.map((tax) => tax.percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIvaAmount(): ItemAmount {
|
public rec(): Maybe<Tax> {
|
||||||
return this.calculateAllAmounts().ivaAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRec(): Maybe<Tax> {
|
|
||||||
return this.taxes.rec;
|
return this.taxes.rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRecCode(): Maybe<string> {
|
public recCode(): Maybe<string> {
|
||||||
return this.taxes.rec.map((tax) => tax.code);
|
return this.taxes.rec.map((tax) => tax.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRecPercentage(): Maybe<ItemDiscountPercentage> {
|
public recPercentage(): Maybe<DiscountPercentage> {
|
||||||
return this.taxes.rec.map((tax) => tax.percentage);
|
return this.taxes.rec.map((tax) => tax.percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIndividualTaxAmounts() {
|
public retention(): Maybe<Tax> {
|
||||||
const { ivaAmount, recAmount, retentionAmount } = this.calculateAllAmounts();
|
return this.taxes.retention;
|
||||||
return { ivaAmount, recAmount, retentionAmount };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTaxesAmount(): ItemAmount {
|
public retentionCode(): Maybe<string> {
|
||||||
return this.calculateAllAmounts().taxesAmount;
|
return this.taxes.retention.map((tax) => tax.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTotalAmount(): ItemAmount {
|
public retentionPercentage(): Maybe<DiscountPercentage> {
|
||||||
return this.calculateAllAmounts().totalAmount;
|
return this.taxes.retention.map((tax) => tax.percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ayudantes
|
// Cálculos / Ayudantes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Helper puro para calcular el subtotal.
|
* @summary Helper puro para calcular el subtotal.
|
||||||
*/
|
*/
|
||||||
private _calculateSubtotalAmount(): ItemAmount {
|
private _calculateSubtotalAmount(): ItemAmount {
|
||||||
const qty = this.quantity.match(
|
if (!this.isValued()) {
|
||||||
(quantity) => quantity,
|
return ItemAmount.zero(this.currencyCode.code);
|
||||||
() => ItemQuantity.zero()
|
}
|
||||||
);
|
|
||||||
const unit = this.unitAmount.match(
|
const quantity = this.quantity.unwrap();
|
||||||
(unitAmount) => unitAmount,
|
const unitAmount = this.unitAmount.unwrap();
|
||||||
() => ItemAmount.zero(this.currencyCode.code)
|
|
||||||
);
|
return unitAmount.multiply(quantity);
|
||||||
return unit.multiply(qty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Helper puro para calcular el descuento de línea.
|
* @summary Helper puro para calcular el descuento de línea.
|
||||||
*/
|
*/
|
||||||
private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount {
|
private _calculateItemDiscountAmount(subtotal: ItemAmount): ItemAmount {
|
||||||
|
if (!this.isValued() || this.props.itemDiscountPercentage.isNone()) {
|
||||||
|
return ItemAmount.zero(this.currencyCode.code);
|
||||||
|
}
|
||||||
|
|
||||||
const discountPercentage = this.props.itemDiscountPercentage.match(
|
const discountPercentage = this.props.itemDiscountPercentage.match(
|
||||||
(discount) => discount,
|
(discount) => discount,
|
||||||
() => ItemDiscountPercentage.zero()
|
() => DiscountPercentage.zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
return subtotal.percentage(discountPercentage);
|
return subtotal.percentage(discountPercentage);
|
||||||
@ -246,13 +232,13 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
subtotalAmount: ItemAmount,
|
subtotalAmount: ItemAmount,
|
||||||
discountAmount: ItemAmount
|
discountAmount: ItemAmount
|
||||||
): ItemAmount {
|
): ItemAmount {
|
||||||
|
if (!this.isValued()) {
|
||||||
|
return ItemAmount.zero(this.currencyCode.code);
|
||||||
|
}
|
||||||
|
|
||||||
const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount);
|
const amountAfterLineDiscount = subtotalAmount.subtract(discountAmount);
|
||||||
|
|
||||||
const globalDiscount = this.props.globalDiscountPercentage.match(
|
const globalDiscount = this.props.globalDiscountPercentage;
|
||||||
(discount) => discount,
|
|
||||||
() => ItemDiscountPercentage.zero()
|
|
||||||
);
|
|
||||||
|
|
||||||
return amountAfterLineDiscount.percentage(globalDiscount);
|
return amountAfterLineDiscount.percentage(globalDiscount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,8 +253,6 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
return itemDiscountAmount.add(globalDiscountAmount);
|
return itemDiscountAmount.add(globalDiscountAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cálculos
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Cálculo centralizado de todos los valores intermedios.
|
* @summary Cálculo centralizado de todos los valores intermedios.
|
||||||
* @returns Devuelve un objeto inmutable con todos los valores necesarios:
|
* @returns Devuelve un objeto inmutable con todos los valores necesarios:
|
||||||
@ -284,7 +268,7 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
* - totalAmount
|
* - totalAmount
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public calculateAllAmounts() {
|
public totals(): IProformaItemTotals {
|
||||||
const subtotalAmount = this._calculateSubtotalAmount();
|
const subtotalAmount = this._calculateSubtotalAmount();
|
||||||
|
|
||||||
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount);
|
const itemDiscountAmount = this._calculateItemDiscountAmount(subtotalAmount);
|
||||||
@ -320,6 +304,6 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
|
|||||||
|
|
||||||
taxesAmount,
|
taxesAmount,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
} as const;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +1,52 @@
|
|||||||
import type { CurrencyCode, LanguageCode, Percentage } from "@repo/rdx-ddd";
|
import type { DiscountPercentage } from "@erp/core/api";
|
||||||
|
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount, ItemDiscountPercentage, type ItemTaxGroup } from "../../../common";
|
import { ItemAmount } from "../../../common";
|
||||||
|
|
||||||
import type { ProformaItem } from "./proforma-item.entity";
|
import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity";
|
||||||
|
|
||||||
export type ProformaItemsProps = {
|
export type ProformaItemsProps = {
|
||||||
items?: ProformaItem[];
|
items?: ProformaItem[];
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
// Estos campos vienen de la cabecera,
|
||||||
globalDiscountPercentage: Percentage;
|
// pero se necesitan para cálculos y representaciones de la línea.
|
||||||
|
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
|
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
|
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ProformaItems extends Collection<ProformaItem> {
|
export interface IProformaItems extends Collection<IProformaItem> {
|
||||||
private languageCode!: LanguageCode;
|
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
||||||
private currencyCode!: CurrencyCode;
|
totals(): IProformaItemTotals;
|
||||||
private globalDiscountPercentage!: Percentage;
|
|
||||||
|
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
|
languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
|
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
|
||||||
|
public readonly languageCode!: LanguageCode;
|
||||||
|
public readonly currencyCode!: CurrencyCode;
|
||||||
|
public readonly globalDiscountPercentage!: DiscountPercentage;
|
||||||
|
|
||||||
constructor(props: ProformaItemsProps) {
|
constructor(props: ProformaItemsProps) {
|
||||||
super(props.items ?? []);
|
super(props.items ?? []);
|
||||||
this.languageCode = props.languageCode;
|
this.languageCode = props.languageCode;
|
||||||
this.currencyCode = props.currencyCode;
|
this.currencyCode = props.currencyCode;
|
||||||
this.globalDiscountPercentage = props.globalDiscountPercentage;
|
this.globalDiscountPercentage = props.globalDiscountPercentage;
|
||||||
|
|
||||||
|
this.ensureSameCurrencyAndLanguage(this.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(props: ProformaItemsProps): ProformaItems {
|
public static create(props: ProformaItemsProps): ProformaItems {
|
||||||
return new ProformaItems(props);
|
return new ProformaItems(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
public valued(): IProformaItem[] {
|
||||||
|
return this.filter((item) => item.isValued());
|
||||||
private _sumAmounts(selector: (item: ProformaItem) => ItemAmount): ItemAmount {
|
|
||||||
return this.getAll().reduce(
|
|
||||||
(acc, item) => acc.add(selector(item)),
|
|
||||||
ItemAmount.zero(this.currencyCode.code)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Helper puro para sumar impuestos individuales por tipo.
|
|
||||||
*/
|
|
||||||
private _calculateIndividualTaxes() {
|
|
||||||
let iva = ItemAmount.zero(this.currencyCode.code);
|
|
||||||
let rec = ItemAmount.zero(this.currencyCode.code);
|
|
||||||
let retention = ItemAmount.zero(this.currencyCode.code);
|
|
||||||
|
|
||||||
for (const item of this.getAll()) {
|
|
||||||
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
|
||||||
|
|
||||||
iva = iva.add(ivaAmount);
|
|
||||||
rec = rec.add(recAmount);
|
|
||||||
retention = retention.add(retentionAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { iva, rec, retention };
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Añade un nuevo ítem a la colección.
|
* @summary Añade un nuevo ítem a la colección.
|
||||||
* @param item - El ítem de factura a añadir.
|
* @param item - El ítem de factura a añadir.
|
||||||
@ -67,18 +56,13 @@ export class ProformaItems extends Collection<ProformaItem> {
|
|||||||
* 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 `false` sin modificar
|
||||||
* la colección.
|
* la colección.
|
||||||
*/
|
*/
|
||||||
add(item: ProformaItem): boolean {
|
public add(item: ProformaItem): boolean {
|
||||||
// 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 =
|
||||||
this.languageCode.equals(item.languageCode) &&
|
this.languageCode.equals(item.languageCode) &&
|
||||||
this.currencyCode.equals(item.currencyCode) &&
|
this.currencyCode.equals(item.currencyCode) &&
|
||||||
this.globalDiscountPercentage.equals(
|
this.globalDiscountPercentage.equals(item.globalDiscountPercentage);
|
||||||
item.globalDiscountPercentage.match(
|
|
||||||
(v) => v,
|
|
||||||
() => ItemDiscountPercentage.zero()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!same) return false;
|
if (!same) return false;
|
||||||
|
|
||||||
@ -92,7 +76,7 @@ export class ProformaItems extends Collection<ProformaItem> {
|
|||||||
* @remarks
|
* @remarks
|
||||||
* Delega en los ítems individuales (DDD correcto) pero evita múltiples recorridos.
|
* Delega en los ítems individuales (DDD correcto) pero evita múltiples recorridos.
|
||||||
*/
|
*/
|
||||||
public calculateAllAmounts() {
|
public totals(): IProformaItemTotals {
|
||||||
let subtotalAmount = ItemAmount.zero(this.currencyCode.code);
|
let subtotalAmount = ItemAmount.zero(this.currencyCode.code);
|
||||||
|
|
||||||
let itemDiscountAmount = ItemAmount.zero(this.currencyCode.code);
|
let itemDiscountAmount = ItemAmount.zero(this.currencyCode.code);
|
||||||
@ -109,7 +93,7 @@ export class ProformaItems extends Collection<ProformaItem> {
|
|||||||
let totalAmount = ItemAmount.zero(this.currencyCode.code);
|
let totalAmount = ItemAmount.zero(this.currencyCode.code);
|
||||||
|
|
||||||
for (const item of this.getAll()) {
|
for (const item of this.getAll()) {
|
||||||
const amounts = item.calculateAllAmounts();
|
const amounts = item.totals();
|
||||||
|
|
||||||
// Subtotales
|
// Subtotales
|
||||||
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount);
|
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount);
|
||||||
@ -152,114 +136,11 @@ export class ProformaItems extends Collection<ProformaItem> {
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSubtotalAmount(): ItemAmount {
|
private ensureSameCurrencyAndLanguage(items: IProformaItem[]): void {
|
||||||
return this.calculateAllAmounts().subtotalAmount;
|
for (const item of items) {
|
||||||
}
|
if (!item.currencyCode.equals(this.currencyCode)) {
|
||||||
|
throw new Error("[ProformaItems] All items must share the same currency.");
|
||||||
public getItemDiscountAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().itemDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getGlobalDiscountAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().globalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalDiscountAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().totalDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxableAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTaxesAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().taxesAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalAmount(): ItemAmount {
|
|
||||||
return this.calculateAllAmounts().totalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Recalcula totales agrupando por el trío iva, rec y retención.
|
|
||||||
*/
|
|
||||||
public groupTaxesByCode() {
|
|
||||||
const map = new Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
taxes: ItemTaxGroup;
|
|
||||||
taxable: ItemAmount;
|
|
||||||
ivaAmount: ItemAmount;
|
|
||||||
recAmount: ItemAmount;
|
|
||||||
retentionAmount: ItemAmount;
|
|
||||||
taxesAmount: ItemAmount;
|
|
||||||
}
|
}
|
||||||
>();
|
|
||||||
|
|
||||||
for (const item of this.getAll()) {
|
|
||||||
const amounts = item.calculateAllAmounts();
|
|
||||||
const taxable = amounts.taxableAmount;
|
|
||||||
const { ivaAmount, recAmount, retentionAmount, taxesAmount } = amounts;
|
|
||||||
|
|
||||||
const taxes = item.taxes;
|
|
||||||
|
|
||||||
const ivaCode = taxes.iva.match(
|
|
||||||
(t) => t.code,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
const recCode = taxes.rec.match(
|
|
||||||
(t) => t.code,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
const retCode = taxes.retention.match(
|
|
||||||
(t) => t.code,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clave del grupo: combinación IVA|REC|RET
|
|
||||||
const key = `${ivaCode}|${recCode}|${retCode}`;
|
|
||||||
|
|
||||||
const prev = map.get(key) ?? {
|
|
||||||
taxes,
|
|
||||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
|
||||||
ivaAmount: ItemAmount.zero(taxable.currencyCode),
|
|
||||||
recAmount: ItemAmount.zero(taxable.currencyCode),
|
|
||||||
retentionAmount: ItemAmount.zero(taxable.currencyCode),
|
|
||||||
taxesAmount: ItemAmount.zero(taxable.currencyCode),
|
|
||||||
};
|
|
||||||
|
|
||||||
map.set(key, {
|
|
||||||
taxes,
|
|
||||||
taxable: prev.taxable.add(taxable),
|
|
||||||
ivaAmount: prev.ivaAmount.add(ivaAmount),
|
|
||||||
recAmount: prev.recAmount.add(recAmount),
|
|
||||||
retentionAmount: prev.retentionAmount.add(retentionAmount),
|
|
||||||
taxesAmount: prev.taxesAmount.add(taxesAmount),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
|
||||||
|
|
||||||
// Devuelve grupos dinámicos del VO existente (InvoiceTaxGroup)
|
|
||||||
// Nota: necesitas construir InvoiceTaxGroup aquí o en Proforma.getTaxes().
|
|
||||||
// Para mantener el ejemplo acotado, se devuelve el map y Proforma lo transforma,
|
|
||||||
// pero puedes construir aquí directamente si prefieres.
|
|
||||||
/*return new Collection(
|
|
||||||
[...map.values()].map((entry) => {
|
|
||||||
const iva = entry.taxes.iva.unwrap();
|
|
||||||
const rec = entry.taxes.rec;
|
|
||||||
const retention = entry.taxes.retention;
|
|
||||||
|
|
||||||
// Convertimos a InvoiceAmount en el agregado (o aquí si tienes acceso)
|
|
||||||
// Aquí asumimos que InvoiceTaxGroup acepta ItemAmount/InvoiceAmount según tu implementación.
|
|
||||||
// Ajusta según tu VO real.
|
|
||||||
return InvoiceTaxGroup.create({
|
|
||||||
iva,
|
|
||||||
rec,
|
|
||||||
retention,
|
|
||||||
taxableAmount: entry.taxable.toInvoiceAmount(), // si existe helper; si no, lo haces en Proforma
|
|
||||||
}).data;
|
|
||||||
})
|
|
||||||
);*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./proforma-tax.entity";
|
|
||||||
export * from "./proforma-taxes.collection";
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoiceAmount } from "../../../common";
|
|
||||||
|
|
||||||
export type ProformaTaxProps = {
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
ivaCode: string;
|
|
||||||
ivaPercentage: Percentage;
|
|
||||||
ivaAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
recCode: Maybe<string>;
|
|
||||||
recPercentage: Maybe<Percentage>;
|
|
||||||
recAmount: Maybe<InvoiceAmount>;
|
|
||||||
|
|
||||||
retentionCode: Maybe<string>;
|
|
||||||
retentionPercentage: Maybe<Percentage>;
|
|
||||||
retentionAmount: Maybe<InvoiceAmount>;
|
|
||||||
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProformaTax extends DomainEntity<ProformaTaxProps> {
|
|
||||||
public static create(props: ProformaTaxProps, id?: UniqueID): Result<ProformaTax, Error> {
|
|
||||||
return Result.ok(new ProformaTax(props, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get taxableAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get ivaCode(): string {
|
|
||||||
return this.props.ivaCode;
|
|
||||||
}
|
|
||||||
public get ivaPercentage(): Percentage {
|
|
||||||
return this.props.ivaPercentage;
|
|
||||||
}
|
|
||||||
public get ivaAmount(): InvoiceAmount {
|
|
||||||
return this.props.ivaAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get recCode(): Maybe<string> {
|
|
||||||
return this.props.recCode;
|
|
||||||
}
|
|
||||||
public get recPercentage(): Maybe<Percentage> {
|
|
||||||
return this.props.recPercentage;
|
|
||||||
}
|
|
||||||
public get recAmount(): Maybe<InvoiceAmount> {
|
|
||||||
return this.props.recAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get retentionCode(): Maybe<string> {
|
|
||||||
return this.props.retentionCode;
|
|
||||||
}
|
|
||||||
public get retentionPercentage(): Maybe<Percentage> {
|
|
||||||
return this.props.retentionPercentage;
|
|
||||||
}
|
|
||||||
public get retentionAmount(): Maybe<InvoiceAmount> {
|
|
||||||
return this.props.retentionAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get taxesAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxesAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getProps(): ProformaTaxProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
|
||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ProformaTax } from "./proforma-tax.entity";
|
|
||||||
|
|
||||||
export type ProformaTaxesProps = {
|
|
||||||
taxes?: ProformaTax[];
|
|
||||||
languageCode: LanguageCode;
|
|
||||||
currencyCode: CurrencyCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProformaTaxes extends Collection<ProformaTax> {
|
|
||||||
private _languageCode!: LanguageCode;
|
|
||||||
private _currencyCode!: CurrencyCode;
|
|
||||||
|
|
||||||
constructor(props: ProformaTaxesProps) {
|
|
||||||
super(props.taxes ?? []);
|
|
||||||
this._languageCode = props.languageCode;
|
|
||||||
this._currencyCode = props.currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static create(props: ProformaTaxesProps): ProformaTaxes {
|
|
||||||
return new ProformaTaxes(props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./proforma-tax-calculator";
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { TaxPercentage } from "@erp/core/api";
|
||||||
|
import type { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IProformaTaxTotals } from "./proforma-tax-calculator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orden determinista:
|
||||||
|
* 1) IVA (code, %)
|
||||||
|
* 2) REC (None primero) (code, %)
|
||||||
|
* 3) RET (None primero) (code, %)
|
||||||
|
*/
|
||||||
|
export function proformaCompareTaxTotals(a: IProformaTaxTotals, b: IProformaTaxTotals): number {
|
||||||
|
const byIvaCode = compareCode(a.ivaCode, b.ivaCode);
|
||||||
|
if (byIvaCode !== 0) return byIvaCode;
|
||||||
|
|
||||||
|
const byIvaPct = comparePct(a.ivaPercentage, b.ivaPercentage);
|
||||||
|
if (byIvaPct !== 0) return byIvaPct;
|
||||||
|
|
||||||
|
const byRecCode = compareMaybeCodeNoneFirst(a.recCode, b.recCode);
|
||||||
|
if (byRecCode !== 0) return byRecCode;
|
||||||
|
|
||||||
|
const byRecPct = compareMaybePctNoneFirst(a.recPercentage, b.recPercentage);
|
||||||
|
if (byRecPct !== 0) return byRecPct;
|
||||||
|
|
||||||
|
const byRetCode = compareMaybeCodeNoneFirst(a.retentionCode, b.retentionCode);
|
||||||
|
if (byRetCode !== 0) return byRetCode;
|
||||||
|
|
||||||
|
return compareMaybePctNoneFirst(a.retentionPercentage, b.retentionPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareCode(a: string, b: string): number {
|
||||||
|
// Ajusta a tu VO: .value / .code / .toString()
|
||||||
|
return a.localeCompare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function comparePct(a: TaxPercentage, b: TaxPercentage): number {
|
||||||
|
// Ajusta a tu VO: a.value puede ser number/bigint/string
|
||||||
|
return a.value - b.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareMaybeCodeNoneFirst(a: Maybe<string>, b: Maybe<string>): number {
|
||||||
|
if (a.isNone() && b.isNone()) return 0;
|
||||||
|
if (a.isNone() && b.isSome()) return -1; // None primero
|
||||||
|
if (a.isSome() && b.isNone()) return 1;
|
||||||
|
|
||||||
|
return compareCode(a.unwrap(), b.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareMaybePctNoneFirst(a: Maybe<TaxPercentage>, b: Maybe<TaxPercentage>): number {
|
||||||
|
if (a.isNone() && b.isNone()) return 0;
|
||||||
|
if (a.isNone() && b.isSome()) return -1; // None primero
|
||||||
|
if (a.isSome() && b.isNone()) return 1;
|
||||||
|
|
||||||
|
return comparePct(a.unwrap(), b.unwrap());
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import type { TaxPercentage } from "@erp/core/api";
|
||||||
|
import { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { type InvoiceAmount, ItemAmount } from "../../common";
|
||||||
|
import type { IProformaItems } from "../entities";
|
||||||
|
|
||||||
|
type TaxGroupState = {
|
||||||
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
|
ivaCode: string;
|
||||||
|
ivaPercentage: TaxPercentage;
|
||||||
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
|
recCode: Maybe<string>;
|
||||||
|
recPercentage: Maybe<TaxPercentage>;
|
||||||
|
recAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
retentionCode: Maybe<string>;
|
||||||
|
retentionPercentage: Maybe<TaxPercentage>;
|
||||||
|
retentionAmount: InvoiceAmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agrupa líneas valoradas por trío (IVA/REC/RET) y acumula importes en scale 4.
|
||||||
|
*
|
||||||
|
* Reglas:
|
||||||
|
* - IVA siempre existe en líneas valoradas (incluye IVA EXENTO 0%).
|
||||||
|
* - REC y RETENTION pueden ser None.
|
||||||
|
* - No se recalculan porcentajes (se suma lo ya calculado por línea).
|
||||||
|
*/
|
||||||
|
export function proformaComputeTaxGroups(items: IProformaItems): Map<string, TaxGroupState> {
|
||||||
|
const map = new Map<string, TaxGroupState>();
|
||||||
|
const currency = items.currencyCode;
|
||||||
|
|
||||||
|
for (const item of items.valued()) {
|
||||||
|
const iva = item.taxes.iva.unwrap(); // siempre existe
|
||||||
|
const rec = item.taxes.rec;
|
||||||
|
const retention = item.taxes.retention;
|
||||||
|
|
||||||
|
const key = buildTaxGroupKey(iva, rec, retention);
|
||||||
|
|
||||||
|
if (!map.has(key)) {
|
||||||
|
map.set(key, {
|
||||||
|
taxableAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
|
ivaCode: iva.code,
|
||||||
|
ivaPercentage: iva.percentage,
|
||||||
|
ivaAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
|
recCode: rec.isSome() ? Maybe.some(rec.unwrap().code) : Maybe.none(),
|
||||||
|
recPercentage: rec.isSome() ? Maybe.some(rec.unwrap().percentage) : Maybe.none(),
|
||||||
|
recAmount: ItemAmount.zero(currency.code),
|
||||||
|
|
||||||
|
retentionCode: retention.isSome() ? Maybe.some(retention.unwrap().code) : Maybe.none(),
|
||||||
|
retentionPercentage: retention.isSome()
|
||||||
|
? Maybe.some(retention.unwrap().percentage)
|
||||||
|
: Maybe.none(),
|
||||||
|
retentionAmount: ItemAmount.zero(currency.code),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = map.get(key)!;
|
||||||
|
|
||||||
|
const itemTotals = item.totals();
|
||||||
|
|
||||||
|
g.taxableAmount = g.taxableAmount.add(itemTotals.taxableAmount);
|
||||||
|
g.ivaAmount = g.ivaAmount.add(itemTotals.ivaAmount);
|
||||||
|
g.recAmount = g.recAmount.add(itemTotals.recAmount);
|
||||||
|
g.retentionAmount = g.retentionAmount.add(itemTotals.retentionAmount);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTaxGroupKey(iva: any, rec: any, retention: any): string {
|
||||||
|
const recPart = rec.isSome() ? `${rec.unwrap().code}-${rec.unwrap().percentage.value}` : "NULL";
|
||||||
|
|
||||||
|
const retentionPart = retention.isSome()
|
||||||
|
? `${retention.unwrap().code}-${retention.unwrap().percentage.value}`
|
||||||
|
: "NULL";
|
||||||
|
|
||||||
|
return `${iva.code}-${iva.percentage.value}|${recPart}|${retentionPart}`;
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import type { TaxPercentage } from "@erp/core/api";
|
||||||
|
import { Collection, type Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvoiceAmount, type ItemAmount } from "../../common";
|
||||||
|
import type { IProformaItems } from "../entities";
|
||||||
|
|
||||||
|
import { proformaCompareTaxTotals } from "./proforma-compare-tax-totals";
|
||||||
|
import { proformaComputeTaxGroups } from "./proforma-compute-tax-groups";
|
||||||
|
|
||||||
|
export interface IProformaTaxTotals {
|
||||||
|
taxableAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
ivaCode: string;
|
||||||
|
ivaPercentage: TaxPercentage;
|
||||||
|
ivaAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
recCode: Maybe<string>;
|
||||||
|
recPercentage: Maybe<TaxPercentage>;
|
||||||
|
recAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
retentionCode: Maybe<string>;
|
||||||
|
retentionPercentage: Maybe<TaxPercentage>;
|
||||||
|
retentionAmount: InvoiceAmount;
|
||||||
|
|
||||||
|
taxesAmount: InvoiceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProformaTaxCalculator {
|
||||||
|
constructor(private readonly items: IProformaItems) {}
|
||||||
|
|
||||||
|
public calculate(): Collection<IProformaTaxTotals> {
|
||||||
|
const groups = proformaComputeTaxGroups(this.items);
|
||||||
|
const currencyCode = this.items.currencyCode;
|
||||||
|
|
||||||
|
const rows = Array.from(groups.values()).map((g) => {
|
||||||
|
const taxableAmount = this.toInvoiceAmount(g.taxableAmount);
|
||||||
|
const ivaAmount = this.toInvoiceAmount(g.ivaAmount);
|
||||||
|
const recAmount = this.toInvoiceAmount(g.recAmount);
|
||||||
|
const retentionAmount = this.toInvoiceAmount(g.retentionAmount);
|
||||||
|
|
||||||
|
const taxesAmount = ivaAmount.add(recAmount).subtract(retentionAmount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
taxableAmount,
|
||||||
|
|
||||||
|
ivaCode: g.ivaCode,
|
||||||
|
ivaPercentage: g.ivaPercentage,
|
||||||
|
ivaAmount,
|
||||||
|
|
||||||
|
recCode: g.recCode,
|
||||||
|
recPercentage: g.recPercentage,
|
||||||
|
recAmount,
|
||||||
|
|
||||||
|
retentionCode: g.retentionCode,
|
||||||
|
retentionPercentage: g.retentionPercentage,
|
||||||
|
retentionAmount,
|
||||||
|
|
||||||
|
taxesAmount,
|
||||||
|
} as const;
|
||||||
|
});
|
||||||
|
|
||||||
|
rows.sort(proformaCompareTaxTotals);
|
||||||
|
return new Collection(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toInvoiceAmount(amount: ItemAmount): InvoiceAmount {
|
||||||
|
return InvoiceAmount.create({
|
||||||
|
value: amount.convertScale(InvoiceAmount.DEFAULT_SCALE).value,
|
||||||
|
currency_code: this.items.currencyCode.code,
|
||||||
|
}).data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1 @@
|
|||||||
export * from "./proforma-item-tax-group.vo";
|
export * from "./proforma-item-taxes.vo";
|
||||||
export * from "./proforma-tax-group.vo";
|
|
||||||
|
|||||||
@ -4,15 +4,45 @@ import { type Maybe, Result } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import { ItemAmount } from "../../common/value-objects";
|
import { ItemAmount } from "../../common/value-objects";
|
||||||
|
|
||||||
export interface ProformaItemTaxGroupProps {
|
export type ProformaItemTaxesProps = {
|
||||||
iva: Maybe<Tax>; // si existe
|
iva: Maybe<Tax>; // si existe
|
||||||
rec: Maybe<Tax>; // si existe
|
rec: Maybe<Tax>; // si existe
|
||||||
retention: Maybe<Tax>; // si existe
|
retention: Maybe<Tax>; // si existe
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IProformaItemTaxes {
|
||||||
|
iva: Maybe<Tax>; // si existe
|
||||||
|
rec: Maybe<Tax>; // si existe
|
||||||
|
retention: Maybe<Tax>; // si existe
|
||||||
|
|
||||||
|
toKey(): string; // Clave para representar un trío.
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProformaItemTaxGroup extends ValueObject<ProformaItemTaxGroupProps> {
|
export class ProformaItemTaxes
|
||||||
static create(props: ProformaItemTaxGroupProps) {
|
extends ValueObject<ProformaItemTaxesProps>
|
||||||
return Result.ok(new ProformaItemTaxGroup(props));
|
implements IProformaItemTaxes
|
||||||
|
{
|
||||||
|
static create(props: ProformaItemTaxesProps) {
|
||||||
|
return Result.ok(new ProformaItemTaxes(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
toKey(): string {
|
||||||
|
const ivaCode = this.props.iva.match(
|
||||||
|
(iva) => iva.code,
|
||||||
|
() => "#"
|
||||||
|
);
|
||||||
|
|
||||||
|
const recCode = this.props.rec.match(
|
||||||
|
(rec) => rec.code,
|
||||||
|
() => "#"
|
||||||
|
);
|
||||||
|
|
||||||
|
const retentionCode = this.props.retention.match(
|
||||||
|
(retention) => retention.code,
|
||||||
|
() => "#"
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${ivaCode};${recCode};${retentionCode}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateAmounts(taxableAmount: ItemAmount) {
|
calculateAmounts(taxableAmount: ItemAmount) {
|
||||||
@ -46,37 +76,6 @@ export class ProformaItemTaxGroup extends ValueObject<ProformaItemTaxGroupProps>
|
|||||||
return this.props.retention;
|
return this.props.retention;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCodesArray(): string[] {
|
|
||||||
const codes: string[] = [];
|
|
||||||
|
|
||||||
this.props.iva.match(
|
|
||||||
(iva) => codes.push(iva.code),
|
|
||||||
() => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.props.rec.match(
|
|
||||||
(rec) => codes.push(rec.code),
|
|
||||||
() => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.props.retention.match(
|
|
||||||
(retention) => codes.push(retention.code),
|
|
||||||
() => {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return codes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCodesToString(): string {
|
|
||||||
return this.getCodesArray().join(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps() {
|
getProps() {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import type { Tax } from "@erp/core/api";
|
|
||||||
import { ValueObject } from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { InvoiceAmount } from "../../common";
|
|
||||||
|
|
||||||
export type ProformaTaxGroupProps = {
|
|
||||||
taxableAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
iva: Tax;
|
|
||||||
ivaAmount: InvoiceAmount;
|
|
||||||
|
|
||||||
rec: Maybe<Tax>; // si existe
|
|
||||||
recAmount: Maybe<InvoiceAmount>;
|
|
||||||
|
|
||||||
retention: Maybe<Tax>; // si existe
|
|
||||||
retentionAmount: Maybe<InvoiceAmount>;
|
|
||||||
|
|
||||||
taxesAmount: InvoiceAmount;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProformaTaxGroup extends ValueObject<ProformaTaxGroupProps> {
|
|
||||||
static create(props: ProformaTaxGroupProps) {
|
|
||||||
return Result.ok(new ProformaTaxGroup(props));
|
|
||||||
}
|
|
||||||
|
|
||||||
get taxableAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxableAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get iva(): Tax {
|
|
||||||
return this.props.iva;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ivaAmount(): InvoiceAmount {
|
|
||||||
return this.props.ivaAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get rec(): Maybe<Tax> {
|
|
||||||
return this.props.rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
get recAmount(): Maybe<InvoiceAmount> {
|
|
||||||
return this.props.recAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get retention(): Maybe<Tax> {
|
|
||||||
return this.props.retention;
|
|
||||||
}
|
|
||||||
|
|
||||||
get retentionAmount(): Maybe<InvoiceAmount> {
|
|
||||||
return this.props.retentionAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
get taxesAmount(): InvoiceAmount {
|
|
||||||
return this.props.taxesAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps() {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
toPrimitive() {
|
|
||||||
return this.getProps();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -37,12 +37,12 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
declare subtotal_amount_scale: number;
|
declare subtotal_amount_scale: number;
|
||||||
|
|
||||||
// Discount percentage
|
// Discount percentage
|
||||||
declare discount_percentage_value: CreationOptional<number | null>;
|
declare item_discount_percentage_value: CreationOptional<number | null>;
|
||||||
declare discount_percentage_scale: number;
|
declare item_discount_percentage_scale: number;
|
||||||
|
|
||||||
// Discount amount
|
// Discount amount
|
||||||
declare discount_amount_value: number;
|
declare item_discount_amount_value: number;
|
||||||
declare discount_amount_scale: number;
|
declare item_discount_amount_scale: number;
|
||||||
|
|
||||||
// Porcentaje de descuento global proporcional a esta línea.
|
// Porcentaje de descuento global proporcional a esta línea.
|
||||||
declare global_discount_percentage_value: CreationOptional<number | null>;
|
declare global_discount_percentage_value: CreationOptional<number | null>;
|
||||||
@ -86,7 +86,6 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
declare retention_percentage_value: CreationOptional<number | null>;
|
declare retention_percentage_value: CreationOptional<number | null>;
|
||||||
declare retention_percentage_scale: number;
|
declare retention_percentage_scale: number;
|
||||||
|
|
||||||
// Retention amount
|
|
||||||
declare retention_amount_value: number;
|
declare retention_amount_value: number;
|
||||||
declare retention_amount_scale: number;
|
declare retention_amount_scale: number;
|
||||||
|
|
||||||
@ -185,25 +184,25 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
discount_percentage_value: {
|
item_discount_percentage_value: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
discount_percentage_scale: {
|
item_discount_percentage_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
discount_amount_value: {
|
item_discount_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
discount_amount_scale: {
|
item_discount_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 4,
|
defaultValue: 4,
|
||||||
|
|||||||
@ -35,7 +35,6 @@ export class CustomerInvoiceTaxModel extends Model<
|
|||||||
declare iva_percentage_scale: number;
|
declare iva_percentage_scale: number;
|
||||||
|
|
||||||
// IVA amount
|
// IVA amount
|
||||||
|
|
||||||
declare iva_amount_value: number;
|
declare iva_amount_value: number;
|
||||||
declare iva_amount_scale: number;
|
declare iva_amount_scale: number;
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
declare items_discount_amount_scale: number;
|
declare items_discount_amount_scale: number;
|
||||||
|
|
||||||
// Global/header discount percentage
|
// Global/header discount percentage
|
||||||
declare global_discount_percentage_value: number;
|
declare global_discount_percentage_value: CreationOptional<number | null>;
|
||||||
declare global_discount_percentage_scale: number;
|
declare global_discount_percentage_scale: number;
|
||||||
|
|
||||||
// Global/header discount amount
|
// Global/header discount amount
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
|||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
LanguageCode,
|
LanguageCode,
|
||||||
Percentage,
|
|
||||||
TextValue,
|
TextValue,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
UtcDate,
|
UtcDate,
|
||||||
@ -16,6 +15,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import type { IIssuedInvoiceDomainMapper } from "../../../../../../application";
|
import type { IIssuedInvoiceDomainMapper } from "../../../../../../application";
|
||||||
import {
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
@ -68,8 +68,6 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
|
|
||||||
const isIssuedInvoice = Boolean(raw.is_proforma);
|
|
||||||
|
|
||||||
const proformaId = extractOrPushError(
|
const proformaId = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)),
|
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)),
|
||||||
"proforma_id",
|
"proforma_id",
|
||||||
@ -181,9 +179,8 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
|
|
||||||
// % descuento global (VO)
|
// % descuento global (VO)
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
Percentage.create({
|
DiscountPercentage.create({
|
||||||
value: Number(raw.global_discount_percentage_value ?? 0),
|
value: Number(raw.global_discount_percentage_value ?? 0),
|
||||||
scale: Number(raw.global_discount_percentage_scale ?? 2),
|
|
||||||
}),
|
}),
|
||||||
"global_discount_percentage_value",
|
"global_discount_percentage_value",
|
||||||
errors
|
errors
|
||||||
@ -265,7 +262,6 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
companyId,
|
companyId,
|
||||||
customerId,
|
customerId,
|
||||||
isIssuedInvoice,
|
|
||||||
proformaId,
|
proformaId,
|
||||||
status,
|
status,
|
||||||
series,
|
series,
|
||||||
@ -294,50 +290,42 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
source: CustomerInvoiceModel,
|
raw: CustomerInvoiceModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<IssuedInvoice, Error> {
|
): Result<IssuedInvoice, Error> {
|
||||||
try {
|
try {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
const attributes = this._mapAttributesToDomain(source, { errors, ...params });
|
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
||||||
|
|
||||||
// 2) Recipient (snapshot en la factura o include)
|
// 2) Recipient (snapshot en la factura o include)
|
||||||
const recipientResult = this._recipientMapper.mapToDomain(source, {
|
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
||||||
errors,
|
errors,
|
||||||
attributes,
|
attributes,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3) Verifactu (snapshot en la factura o include)
|
// 3) Verifactu (snapshot en la factura o include)
|
||||||
const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, {
|
const verifactuResult = this._verifactuMapper.mapToDomain(raw.verifactu, {
|
||||||
errors,
|
errors,
|
||||||
attributes,
|
attributes,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4) Items (colección)
|
// 4) Items (colección)
|
||||||
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, {
|
||||||
source.items,
|
errors,
|
||||||
source.items.length,
|
attributes,
|
||||||
{
|
...params,
|
||||||
errors,
|
});
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 5) Taxes (colección)
|
// 5) Taxes (colección)
|
||||||
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
const taxesResults = this._taxesMapper.mapToDomainCollection(raw.taxes, raw.taxes.length, {
|
||||||
source.taxes,
|
errors,
|
||||||
source.taxes.length,
|
attributes,
|
||||||
{
|
...params,
|
||||||
errors,
|
});
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6) Si hubo errores de mapeo, devolvemos colección de validación
|
// 6) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
@ -499,7 +487,6 @@ export class SequelizeIssuedInvoiceDomainMapper
|
|||||||
|
|
||||||
reference: maybeToNullable(source.reference, (reference) => reference),
|
reference: maybeToNullable(source.reference, (reference) => reference),
|
||||||
description: maybeToNullable(source.description, (description) => description),
|
description: maybeToNullable(source.description, (description) => description),
|
||||||
|
|
||||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
||||||
|
|
||||||
payment_method_id: maybeToNullable(
|
payment_method_id: maybeToNullable(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
IssuedInvoiceItem,
|
IssuedInvoiceItem,
|
||||||
type IssuedInvoiceItemProps,
|
type IssuedInvoiceItemProps,
|
||||||
@ -94,25 +95,25 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
);
|
);
|
||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
const itemDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.discount_percentage_value, (v) =>
|
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
||||||
ItemDiscountPercentage.create({ value: v })
|
ItemDiscountPercentage.create({ value: v })
|
||||||
),
|
),
|
||||||
`items[${index}].discount_percentage_value`,
|
`items[${index}].item_discount_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemDiscountAmount = extractOrPushError(
|
const itemDiscountAmount = extractOrPushError(
|
||||||
ItemAmount.create({
|
ItemAmount.create({
|
||||||
value: raw.discount_amount_value,
|
value: raw.item_discount_amount_value,
|
||||||
currency_code: attributes.currencyCode?.code,
|
currency_code: attributes.currencyCode?.code,
|
||||||
}),
|
}),
|
||||||
`items[${index}].discount_amount_value`,
|
`items[${index}].item_discount_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.global_discount_percentage_value, (v) =>
|
maybeFromNullableResult(raw.global_discount_percentage_value, (v) =>
|
||||||
ItemDiscountPercentage.create({ value: v })
|
DiscountPercentage.create({ value: v })
|
||||||
),
|
),
|
||||||
`items[${index}].global_discount_percentage_value`,
|
`items[${index}].global_discount_percentage_value`,
|
||||||
errors
|
errors
|
||||||
@ -357,16 +358,16 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
||||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
||||||
|
|
||||||
discount_percentage_value: maybeToNullable(
|
item_discount_percentage_value: maybeToNullable(
|
||||||
source.itemDiscountPercentage,
|
source.itemDiscountPercentage,
|
||||||
(v) => v.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
),
|
),
|
||||||
discount_percentage_scale:
|
item_discount_percentage_scale:
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
ItemDiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
||||||
discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
||||||
|
|
||||||
global_discount_percentage_value: maybeToNullable(
|
global_discount_percentage_value: maybeToNullable(
|
||||||
source.globalDiscountPercentage,
|
source.globalDiscountPercentage,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
|
Percentage,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
type ValidationErrorDetail,
|
type ValidationErrorDetail,
|
||||||
@ -14,10 +15,12 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
InvoiceTaxPercentage,
|
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
type IssuedInvoiceProps,
|
type IssuedInvoiceProps,
|
||||||
IssuedInvoiceTax,
|
IssuedInvoiceTax,
|
||||||
|
ItemAmount,
|
||||||
|
ItemDiscountPercentage,
|
||||||
|
TaxPercentage,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
@ -57,7 +60,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
}
|
}
|
||||||
|
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
source: CustomerInvoiceTaxModel,
|
raw: CustomerInvoiceTaxModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<IssuedInvoiceTax, Error> {
|
): Result<IssuedInvoiceTax, Error> {
|
||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
@ -68,18 +71,18 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
const taxableAmount = extractOrPushError(
|
||||||
InvoiceAmount.create({
|
InvoiceAmount.create({
|
||||||
value: source.taxable_amount_value,
|
value: raw.taxable_amount_value,
|
||||||
currency_code: attributes.currencyCode?.code,
|
currency_code: attributes.currencyCode?.code,
|
||||||
}),
|
}),
|
||||||
`taxes[${index}].taxable_amount_value`,
|
`taxes[${index}].taxable_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const ivaCode = source.iva_code;
|
const ivaCode = raw.iva_code;
|
||||||
|
|
||||||
const ivaPercentage = extractOrPushError(
|
const ivaPercentage = extractOrPushError(
|
||||||
InvoiceTaxPercentage.create({
|
TaxPercentage.create({
|
||||||
value: source.iva_percentage_value,
|
value: raw.iva_percentage_value,
|
||||||
}),
|
}),
|
||||||
`taxes[${index}].iva_percentage_value`,
|
`taxes[${index}].iva_percentage_value`,
|
||||||
errors
|
errors
|
||||||
@ -87,52 +90,52 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
|
|
||||||
const ivaAmount = extractOrPushError(
|
const ivaAmount = extractOrPushError(
|
||||||
InvoiceAmount.create({
|
InvoiceAmount.create({
|
||||||
value: source.iva_amount_value,
|
value: raw.iva_amount_value,
|
||||||
currency_code: attributes.currencyCode?.code,
|
currency_code: attributes.currencyCode?.code,
|
||||||
}),
|
}),
|
||||||
`taxes[${index}].iva_amount_value`,
|
`taxes[${index}].iva_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const recCode = maybeFromNullableOrEmptyString(source.rec_code);
|
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
||||||
|
|
||||||
const recPercentage = extractOrPushError(
|
const recPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(source.rec_percentage_value, (value) =>
|
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
InvoiceTaxPercentage.create({ value })
|
|
||||||
),
|
|
||||||
`taxes[${index}].rec_percentage_value`,
|
`taxes[${index}].rec_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const recAmount = extractOrPushError(
|
const recAmount = extractOrPushError(
|
||||||
maybeFromNullableResult(source.rec_amount_value, (value) =>
|
InvoiceAmount.create({
|
||||||
InvoiceAmount.create({ value, currency_code: attributes.currencyCode?.code })
|
value: raw.rec_amount_value,
|
||||||
),
|
currency_code: attributes.currencyCode?.code,
|
||||||
|
}),
|
||||||
`taxes[${index}].rec_amount_value`,
|
`taxes[${index}].rec_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const retentionCode = maybeFromNullableOrEmptyString(source.retention_code);
|
const retentionCode = maybeFromNullableOrEmptyString(raw.retention_code);
|
||||||
|
|
||||||
const retentionPercentage = extractOrPushError(
|
const retentionPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(source.retention_percentage_value, (value) =>
|
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
||||||
InvoiceTaxPercentage.create({ value })
|
TaxPercentage.create({ value })
|
||||||
),
|
),
|
||||||
`taxes[${index}].retention_percentage_value`,
|
`taxes[${index}].retention_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const retentionAmount = extractOrPushError(
|
const retentionAmount = extractOrPushError(
|
||||||
maybeFromNullableResult(source.retention_amount_value, (value) =>
|
InvoiceAmount.create({
|
||||||
InvoiceAmount.create({ value, currency_code: attributes.currencyCode?.code })
|
value: raw.retention_amount_value,
|
||||||
),
|
currency_code: attributes.currencyCode?.code,
|
||||||
|
}),
|
||||||
`taxes[${index}].retention_amount_value`,
|
`taxes[${index}].retention_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const taxesAmount = extractOrPushError(
|
const taxesAmount = extractOrPushError(
|
||||||
InvoiceAmount.create({
|
InvoiceAmount.create({
|
||||||
value: source.taxes_amount_value,
|
value: raw.taxes_amount_value,
|
||||||
currency_code: attributes.currencyCode?.code,
|
currency_code: attributes.currencyCode?.code,
|
||||||
}),
|
}),
|
||||||
`taxes[${index}].taxes_amount_value`,
|
`taxes[${index}].taxes_amount_value`,
|
||||||
@ -208,10 +211,11 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
|
|
||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
||||||
rec_percentage_scale:
|
rec_percentage_scale:
|
||||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
|
ItemDiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
rec_amount_value: maybeToNullable(source.recAmount, (v) => v.toPrimitive().value),
|
rec_amount_value: source.recAmount.toPrimitive().value,
|
||||||
rec_amount_scale: maybeToNullable(source.recAmount, (v) => v.toPrimitive().scale) ?? 4,
|
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
||||||
|
|
||||||
// RET
|
// RET
|
||||||
retention_code: maybeToNullableString(source.retentionCode),
|
retention_code: maybeToNullableString(source.retentionCode),
|
||||||
@ -221,14 +225,12 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
(v) => v.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
),
|
),
|
||||||
retention_percentage_scale:
|
retention_percentage_scale:
|
||||||
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2,
|
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
|
Percentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
retention_amount_value: maybeToNullable(
|
retention_amount_value: source.retentionAmount.toPrimitive().value,
|
||||||
source.retentionAmount,
|
|
||||||
(v) => v.toPrimitive().value
|
|
||||||
),
|
|
||||||
retention_amount_scale:
|
retention_amount_scale:
|
||||||
maybeToNullable(source.retentionAmount, (v) => v.toPrimitive().scale) ?? 4,
|
source.retentionAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
||||||
|
|
||||||
// TOTAL
|
// TOTAL
|
||||||
taxes_amount_value: source.taxesAmount.value,
|
taxes_amount_value: source.taxesAmount.value,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
|||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
LanguageCode,
|
LanguageCode,
|
||||||
Percentage,
|
|
||||||
TextValue,
|
TextValue,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
UtcDate,
|
UtcDate,
|
||||||
@ -16,6 +15,7 @@ import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
|||||||
|
|
||||||
import type { IProformaDomainMapper } from "../../../../../../application";
|
import type { IProformaDomainMapper } from "../../../../../../application";
|
||||||
import {
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
@ -46,86 +46,80 @@ export class SequelizeProformaDomainMapper
|
|||||||
|
|
||||||
this._itemsMapper = new SequelizeProformaItemDomainMapper(params);
|
this._itemsMapper = new SequelizeProformaItemDomainMapper(params);
|
||||||
this._recipientMapper = new SequelizeProformaRecipientDomainMapper();
|
this._recipientMapper = new SequelizeProformaRecipientDomainMapper();
|
||||||
this._taxesMapper = new SequelizeProformaTaxesDomainMapper();
|
this._taxesMapper = new SequelizeProformaTaxesDomainMapper(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
|
private _mapAttributesToDomain(raw: CustomerInvoiceModel, params?: MapperParamsType) {
|
||||||
const { errors } = params as {
|
const { errors } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
||||||
const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors);
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
|
|
||||||
const customerId = extractOrPushError(
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
UniqueID.create(source.customer_id),
|
|
||||||
"customer_id",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const isProforma = Boolean(source.is_proforma);
|
|
||||||
|
|
||||||
const proformaId = extractOrPushError(
|
const proformaId = extractOrPushError(
|
||||||
maybeFromNullableResult(source.proforma_id, (v) => UniqueID.create(v)),
|
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)),
|
||||||
"proforma_id",
|
"proforma_id",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = extractOrPushError(InvoiceStatus.create(source.status), "status", errors);
|
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
||||||
|
|
||||||
const series = extractOrPushError(
|
const series = extractOrPushError(
|
||||||
maybeFromNullableResult(source.series, (v) => InvoiceSerie.create(v)),
|
maybeFromNullableResult(raw.series, (v) => InvoiceSerie.create(v)),
|
||||||
"series",
|
"series",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoiceNumber = extractOrPushError(
|
const invoiceNumber = extractOrPushError(
|
||||||
InvoiceNumber.create(source.invoice_number),
|
InvoiceNumber.create(raw.invoice_number),
|
||||||
"invoice_number",
|
"invoice_number",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fechas
|
// Fechas
|
||||||
const invoiceDate = extractOrPushError(
|
const invoiceDate = extractOrPushError(
|
||||||
UtcDate.createFromISO(source.invoice_date),
|
UtcDate.createFromISO(raw.invoice_date),
|
||||||
"invoice_date",
|
"invoice_date",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const operationDate = extractOrPushError(
|
const operationDate = extractOrPushError(
|
||||||
maybeFromNullableResult(source.operation_date, (v) => UtcDate.createFromISO(v)),
|
maybeFromNullableResult(raw.operation_date, (v) => UtcDate.createFromISO(v)),
|
||||||
"operation_date",
|
"operation_date",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
// Idioma / divisa
|
// Idioma / divisa
|
||||||
const languageCode = extractOrPushError(
|
const languageCode = extractOrPushError(
|
||||||
LanguageCode.create(source.language_code),
|
LanguageCode.create(raw.language_code),
|
||||||
"language_code",
|
"language_code",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const currencyCode = extractOrPushError(
|
const currencyCode = extractOrPushError(
|
||||||
CurrencyCode.create(source.currency_code),
|
CurrencyCode.create(raw.currency_code),
|
||||||
"currency_code",
|
"currency_code",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
// Textos opcionales
|
// Textos opcionales
|
||||||
const reference = extractOrPushError(
|
const reference = extractOrPushError(
|
||||||
maybeFromNullableResult(source.reference, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(raw.reference, (value) => Result.ok(String(value))),
|
||||||
"reference",
|
"reference",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const description = extractOrPushError(
|
const description = extractOrPushError(
|
||||||
maybeFromNullableResult(source.description, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(raw.description, (value) => Result.ok(String(value))),
|
||||||
"description",
|
"description",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const notes = extractOrPushError(
|
const notes = extractOrPushError(
|
||||||
maybeFromNullableResult(source.notes, (value) => TextValue.create(value)),
|
maybeFromNullableResult(raw.notes, (value) => TextValue.create(value)),
|
||||||
"notes",
|
"notes",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -133,16 +127,16 @@ export class SequelizeProformaDomainMapper
|
|||||||
// Método de pago (VO opcional con id + descripción)
|
// Método de pago (VO opcional con id + descripción)
|
||||||
let paymentMethod = Maybe.none<InvoicePaymentMethod>();
|
let paymentMethod = Maybe.none<InvoicePaymentMethod>();
|
||||||
|
|
||||||
if (!isNullishOrEmpty(source.payment_method_id)) {
|
if (!isNullishOrEmpty(raw.payment_method_id)) {
|
||||||
const paymentId = extractOrPushError(
|
const paymentId = extractOrPushError(
|
||||||
UniqueID.create(String(source.payment_method_id)),
|
UniqueID.create(String(raw.payment_method_id)),
|
||||||
"paymentMethod.id",
|
"paymentMethod.id",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const paymentVO = extractOrPushError(
|
const paymentVO = extractOrPushError(
|
||||||
InvoicePaymentMethod.create(
|
InvoicePaymentMethod.create(
|
||||||
{ paymentDescription: String(source.payment_method_description ?? "") },
|
{ paymentDescription: String(raw.payment_method_description ?? "") },
|
||||||
paymentId ?? undefined
|
paymentId ?? undefined
|
||||||
),
|
),
|
||||||
"payment_method_description",
|
"payment_method_description",
|
||||||
@ -154,13 +148,12 @@ export class SequelizeProformaDomainMapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// % descuento (VO)
|
// % descuento global (VO)
|
||||||
const discountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
Percentage.create({
|
DiscountPercentage.create({
|
||||||
value: Number(source.discount_percentage_value ?? 0),
|
value: Number(raw.global_discount_percentage_value ?? 0),
|
||||||
scale: Number(source.discount_percentage_scale ?? 2),
|
|
||||||
}),
|
}),
|
||||||
"discount_percentage_value",
|
"global_discount_percentage_value",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -168,7 +161,6 @@ export class SequelizeProformaDomainMapper
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
companyId,
|
companyId,
|
||||||
customerId,
|
customerId,
|
||||||
isProforma,
|
|
||||||
proformaId,
|
proformaId,
|
||||||
status,
|
status,
|
||||||
series,
|
series,
|
||||||
@ -180,38 +172,35 @@ export class SequelizeProformaDomainMapper
|
|||||||
notes,
|
notes,
|
||||||
languageCode,
|
languageCode,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
discountPercentage,
|
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
|
|
||||||
|
globalDiscountPercentage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
source: CustomerInvoiceModel,
|
raw: CustomerInvoiceModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<Proforma, Error> {
|
): Result<Proforma, Error> {
|
||||||
try {
|
try {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
const attributes = this._mapAttributesToDomain(source, { errors, ...params });
|
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
||||||
|
|
||||||
// 2) Recipient (snapshot en la factura o include)
|
// 2) Recipient (snapshot en la factura o include)
|
||||||
const recipientResult = this._recipientMapper.mapToDomain(source, {
|
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
||||||
errors,
|
errors,
|
||||||
attributes,
|
attributes,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3) Items (colección)
|
// 3) Items (colección)
|
||||||
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, {
|
||||||
source.items,
|
errors,
|
||||||
source.items.length,
|
attributes,
|
||||||
{
|
...params,
|
||||||
errors,
|
});
|
||||||
attributes,
|
|
||||||
...params,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4) Si hubo errores de mapeo, devolvemos colección de validación
|
// 4) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
@ -225,14 +214,13 @@ export class SequelizeProformaDomainMapper
|
|||||||
const items = ProformaItems.create({
|
const items = ProformaItems.create({
|
||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
globalDiscountPercentage: attributes.discountPercentage!,
|
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||||
items: itemsResults.data.getAll(),
|
items: itemsResults.data.getAll(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceProps: ProformaProps = {
|
const invoiceProps: ProformaProps = {
|
||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
|
|
||||||
isProforma: attributes.isProforma,
|
|
||||||
status: attributes.status!,
|
status: attributes.status!,
|
||||||
series: attributes.series!,
|
series: attributes.series!,
|
||||||
invoiceNumber: attributes.invoiceNumber!,
|
invoiceNumber: attributes.invoiceNumber!,
|
||||||
@ -249,7 +237,7 @@ export class SequelizeProformaDomainMapper
|
|||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
|
|
||||||
globalDiscountPercentage: attributes.discountPercentage!,
|
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||||
|
|
||||||
paymentMethod: attributes.paymentMethod!,
|
paymentMethod: attributes.paymentMethod!,
|
||||||
|
|
||||||
@ -331,11 +319,12 @@ export class SequelizeProformaDomainMapper
|
|||||||
company_id: source.companyId.toPrimitive(),
|
company_id: source.companyId.toPrimitive(),
|
||||||
|
|
||||||
// Flags / estado / serie / número
|
// Flags / estado / serie / número
|
||||||
is_proforma: source.isProforma,
|
is_proforma: true,
|
||||||
status: source.status.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
|
proforma_id: null,
|
||||||
|
|
||||||
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
||||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||||
|
|
||||||
invoice_date: source.invoiceDate.toPrimitive(),
|
invoice_date: source.invoiceDate.toPrimitive(),
|
||||||
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
||||||
language_code: source.languageCode.toPrimitive(),
|
language_code: source.languageCode.toPrimitive(),
|
||||||
@ -345,6 +334,15 @@ export class SequelizeProformaDomainMapper
|
|||||||
description: maybeToNullable(source.description, (description) => description),
|
description: maybeToNullable(source.description, (description) => description),
|
||||||
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
notes: maybeToNullable(source.notes, (v) => v.toPrimitive()),
|
||||||
|
|
||||||
|
payment_method_id: maybeToNullable(
|
||||||
|
source.paymentMethod,
|
||||||
|
(payment) => payment.toObjectString().id
|
||||||
|
),
|
||||||
|
payment_method_description: maybeToNullable(
|
||||||
|
source.paymentMethod,
|
||||||
|
(payment) => payment.toObjectString().payment_description
|
||||||
|
),
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
@ -369,15 +367,6 @@ export class SequelizeProformaDomainMapper
|
|||||||
total_amount_value: allAmounts.totalAmount.value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: allAmounts.totalAmount.scale,
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
|
|
||||||
payment_method_id: maybeToNullable(
|
|
||||||
source.paymentMethod,
|
|
||||||
(payment) => payment.toObjectString().id
|
|
||||||
),
|
|
||||||
payment_method_description: maybeToNullable(
|
|
||||||
source.paymentMethod,
|
|
||||||
(payment) => payment.toObjectString().payment_description
|
|
||||||
),
|
|
||||||
|
|
||||||
customer_id: source.customerId.toPrimitive(),
|
customer_id: source.customerId.toPrimitive(),
|
||||||
...recipient,
|
...recipient,
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
import { UniqueID, type ValidationErrorDetail, maybeToNullable } from "@repo/rdx-ddd";
|
import { UniqueID, type ValidationErrorDetail, maybeToNullable } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
@ -25,6 +26,21 @@ export class SequelizeProformaTaxesDomainMapper extends SequelizeDomainMapper<
|
|||||||
CustomerInvoiceTaxCreationAttributes,
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
InvoiceTaxGroup
|
InvoiceTaxGroup
|
||||||
> {
|
> {
|
||||||
|
private taxCatalog!: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
|
constructor(params: MapperParamsType) {
|
||||||
|
super();
|
||||||
|
const { taxCatalog } = params as {
|
||||||
|
taxCatalog: JsonTaxCatalogProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!taxCatalog) {
|
||||||
|
throw new Error('taxCatalog not defined ("SequelizeProformaTaxesDomainMapper")');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.taxCatalog = taxCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
source: CustomerInvoiceTaxModel,
|
source: CustomerInvoiceTaxModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
|
|||||||
@ -28,6 +28,10 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
|
"src",
|
||||||
|
"../core/src/api/domain/value-objects/tax-percentage.vo.ts",
|
||||||
|
"../core/src/api/domain/value-objects/discount-percentage.vo.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { translateZodValidationError } from "../helpers";
|
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface TaxCodeProps {
|
interface TaxCodeProps {
|
||||||
@ -28,7 +27,8 @@ export class TaxCode extends ValueObject<TaxCodeProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string) {
|
static create(value: string) {
|
||||||
const valueIsValid = TaxCode.validate(value);
|
throw new Error("DEPRECATED -> ¿DEBERÍA USARSE STRING COMO EN LAS FACTURAS?");
|
||||||
|
/*const valueIsValid = TaxCode.validate(value);
|
||||||
|
|
||||||
if (!valueIsValid.success) {
|
if (!valueIsValid.success) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
@ -36,7 +36,7 @@ export class TaxCode extends ValueObject<TaxCodeProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||||
return Result.ok(new TaxCode({ value: valueIsValid.data! }));
|
return Result.ok(new TaxCode({ value: valueIsValid.data! }));*/
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): string {
|
getProps(): string {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export class Collection<T> {
|
|||||||
addCollection(collection: Collection<T>): boolean {
|
addCollection(collection: Collection<T>): boolean {
|
||||||
this.items.push(...collection.items);
|
this.items.push(...collection.items);
|
||||||
if (this.totalItems !== null) {
|
if (this.totalItems !== null) {
|
||||||
this.totalItems = this.totalItems + collection.totalItems;
|
this.totalItems += collection.totalItems;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
7
uecko-erp.code-workspace
Normal file
7
uecko-erp.code-workspace
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user