.
This commit is contained in:
parent
3846199cbe
commit
26442edd60
@ -21,7 +21,7 @@ export class SequelizeTransactionManager extends TransactionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async _rollbackTransaction(): Promise<void> {
|
protected async _rollbackTransaction(): Promise<void> {
|
||||||
throw new InfrastructureError("Database not available");
|
throw new InfrastructureError("[SequelizeTransactionManager] Database not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(database: Sequelize, options?: { isolationLevel?: string }) {
|
constructor(database: Sequelize, options?: { isolationLevel?: string }) {
|
||||||
@ -32,7 +32,9 @@ export class SequelizeTransactionManager extends TransactionManager {
|
|||||||
|
|
||||||
get database(): Sequelize {
|
get database(): Sequelize {
|
||||||
if (!this._database) {
|
if (!this._database) {
|
||||||
throw new InfrastructureUnavailableError("Database not available");
|
throw new InfrastructureUnavailableError(
|
||||||
|
"[SequelizeTransactionManager] Database not available"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this._database;
|
return this._database;
|
||||||
}
|
}
|
||||||
@ -43,7 +45,9 @@ export class SequelizeTransactionManager extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
async complete<T>(work: (transaction: Transaction) => Promise<T>): Promise<T> {
|
async complete<T>(work: (transaction: Transaction) => Promise<T>): Promise<T> {
|
||||||
if (!this._database) {
|
if (!this._database) {
|
||||||
throw new InfrastructureUnavailableError("Database not available");
|
throw new InfrastructureUnavailableError(
|
||||||
|
"[SequelizeTransactionManager] Database not available"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evita transacciones anidadas según la política del TransactionManager base
|
// Evita transacciones anidadas según la política del TransactionManager base
|
||||||
@ -52,7 +56,9 @@ export class SequelizeTransactionManager extends TransactionManager {
|
|||||||
"❌ Cannot start a new transaction inside another. Nested transactions are not allowed.",
|
"❌ Cannot start a new transaction inside another. Nested transactions are not allowed.",
|
||||||
{ label: "SequelizeTransactionManager.complete" }
|
{ label: "SequelizeTransactionManager.complete" }
|
||||||
);
|
);
|
||||||
throw new Error("A transaction is already active. Nested transactions are not allowed.");
|
throw new InfrastructureError(
|
||||||
|
"[SequelizeTransactionManager] A transaction is already active. Nested transactions are not allowed."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
//export * from "./participantAddressFinder";
|
export * from "./status-invoice_is_approved.spec";
|
||||||
//export * from "./participantFinder";
|
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { CompositeSpecification } from "@repo/rdx-ddd";
|
||||||
|
import { CustomerInvoice } from "../../domain";
|
||||||
|
|
||||||
|
export class StatusInvoiceIsApprovedSpecification extends CompositeSpecification<CustomerInvoice> {
|
||||||
|
public async isSatisfiedBy(invoice: CustomerInvoice): Promise<boolean> {
|
||||||
|
return invoice.status.isApproved();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,3 +3,4 @@ export * from "./get-customer-invoice.use-case";
|
|||||||
export * from "./list-customer-invoices.use-case";
|
export * from "./list-customer-invoices.use-case";
|
||||||
export * from "./report";
|
export * from "./report";
|
||||||
//export * from "./update-customer-invoice.use-case";
|
//export * from "./update-customer-invoice.use-case";
|
||||||
|
export * from "./issue-customer-invoice.use-case";
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { CustomerInvoiceNumber, CustomerInvoiceService } from "../../domain";
|
||||||
|
import { StatusInvoiceIsApprovedSpecification } from "../specs";
|
||||||
|
|
||||||
|
type IssueCustomerInvoiceUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
invoice_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class IssueCustomerInvoiceUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly service: CustomerInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: IssueCustomerInvoiceUseCaseInput) {
|
||||||
|
const { invoice_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(invoice_id);
|
||||||
|
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return Result.fail(idOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceId = idOrError.data;
|
||||||
|
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const invoiceResult = await this.service.getInvoiceByIdInCompany(
|
||||||
|
companyId,
|
||||||
|
invoiceId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invoiceResult.isFailure) {
|
||||||
|
return Result.fail(invoiceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceProforma = invoiceResult.data;
|
||||||
|
|
||||||
|
const isOk = new StatusInvoiceIsApprovedSpecification().isSatisfiedBy(invoiceProforma);
|
||||||
|
|
||||||
|
if (!isOk) {
|
||||||
|
return Result.fail(
|
||||||
|
new EntityNotFoundError("Customer invoice", "id", invoiceId.toString())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// La factura se puede emitir.
|
||||||
|
// Pedir el número de factura
|
||||||
|
const newInvoiceNumber = CustomerInvoiceNumber.create("xxx/001").data;
|
||||||
|
|
||||||
|
// Asignamos el número de la factura
|
||||||
|
|
||||||
|
const issuedInvoiceResult = invoiceProforma.issueInvoice(newInvoiceNumber);
|
||||||
|
if (issuedInvoiceResult.isFailure) {
|
||||||
|
return Result.fail(new EntityNotFoundError("Customer invoice", "id", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuedInvoice = issuedInvoiceResult.data;
|
||||||
|
|
||||||
|
this.service.saveInvoice(issuedInvoice, transaction);
|
||||||
|
|
||||||
|
//return await this.service.IssueInvoiceByIdInCompany(companyId, invoiceId, transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,12 +38,15 @@ export interface CustomerInvoiceProps {
|
|||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
taxes: InvoiceTaxes;
|
|
||||||
items: CustomerInvoiceItems;
|
items: CustomerInvoiceItems;
|
||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
discountPercentage: Percentage;
|
discountPercentage: Percentage;
|
||||||
|
|
||||||
|
verifactu_qr: string;
|
||||||
|
verifactu_url: string;
|
||||||
|
verifactu_status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomerInvoice {
|
export interface ICustomerInvoice {
|
||||||
@ -56,6 +59,8 @@ export interface ICustomerInvoice {
|
|||||||
getTaxableAmount(): InvoiceAmount;
|
getTaxableAmount(): InvoiceAmount;
|
||||||
getTaxesAmount(): InvoiceAmount;
|
getTaxesAmount(): InvoiceAmount;
|
||||||
getTotalAmount(): InvoiceAmount;
|
getTotalAmount(): InvoiceAmount;
|
||||||
|
|
||||||
|
issueInvoice(newInvoiceNumber: CustomerInvoiceNumber): Result<CustomerInvoice, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
||||||
@ -65,7 +70,6 @@ export class CustomerInvoice
|
|||||||
implements ICustomerInvoice
|
implements ICustomerInvoice
|
||||||
{
|
{
|
||||||
private _items!: CustomerInvoiceItems;
|
private _items!: CustomerInvoiceItems;
|
||||||
private _taxes!: InvoiceTaxes;
|
|
||||||
|
|
||||||
protected constructor(props: CustomerInvoiceProps, id?: UniqueID) {
|
protected constructor(props: CustomerInvoiceProps, id?: UniqueID) {
|
||||||
super(props, id);
|
super(props, id);
|
||||||
@ -75,8 +79,6 @@ export class CustomerInvoice
|
|||||||
languageCode: props.languageCode,
|
languageCode: props.languageCode,
|
||||||
currencyCode: props.currencyCode,
|
currencyCode: props.currencyCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._taxes = props.taxes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||||
@ -167,7 +169,7 @@ export class CustomerInvoice
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get taxes(): InvoiceTaxes {
|
public get taxes(): InvoiceTaxes {
|
||||||
return this._taxes;
|
return this.items.getTaxesAmountByTaxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasRecipient() {
|
public get hasRecipient() {
|
||||||
@ -240,4 +242,16 @@ export class CustomerInvoice
|
|||||||
totalAmount,
|
totalAmount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public issueInvoice(newInvoiceNumber: CustomerInvoiceNumber) {
|
||||||
|
return CustomerInvoice.create(
|
||||||
|
{
|
||||||
|
...this.props,
|
||||||
|
status: CustomerInvoiceStatus.createEmitted(),
|
||||||
|
isProforma: false,
|
||||||
|
invoiceNumber: newInvoiceNumber,
|
||||||
|
},
|
||||||
|
this.id
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,6 +162,10 @@ export class CustomerInvoiceItem
|
|||||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
return this._getTotalAmount(taxableAmount, taxesAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTaxesAmountByTaxes() {
|
||||||
|
return this.taxes.getTaxesAmountByTaxes(this.getTaxableAmount());
|
||||||
|
}
|
||||||
|
|
||||||
public getAllAmounts() {
|
public getAllAmounts() {
|
||||||
const subtotalAmount = this.getSubtotalAmount();
|
const subtotalAmount = this.getSubtotalAmount();
|
||||||
const discountAmount = this._getDiscountAmount(subtotalAmount);
|
const discountAmount = this._getDiscountAmount(subtotalAmount);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import { Tax } from "@erp/core/api";
|
||||||
import { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
import { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
import { ItemAmount } from "../../value-objects";
|
import { ItemAmount } from "../../value-objects";
|
||||||
|
import { InvoiceTax, InvoiceTaxes } from "../invoice-taxes";
|
||||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
export interface CustomerInvoiceItemsProps {
|
export interface CustomerInvoiceItemsProps {
|
||||||
@ -70,4 +72,32 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
ItemAmount.zero(this._currencyCode.code)
|
ItemAmount.zero(this._currencyCode.code)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTaxesAmountByTaxes(): InvoiceTaxes {
|
||||||
|
InvoiceTaxes.create({});
|
||||||
|
|
||||||
|
const taxesMap = new Map<Tax, ItemAmount>();
|
||||||
|
const currencyCode = this._currencyCode.code;
|
||||||
|
|
||||||
|
for (const item of this.getAll()) {
|
||||||
|
for (const { tax, taxesAmount } of item.getTaxesAmountByTaxes()) {
|
||||||
|
const current = taxesMap.get(tax) ?? ItemAmount.zero(currencyCode);
|
||||||
|
taxesMap.set(tax, current.add(taxesAmount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: InvoiceTax[] = [];
|
||||||
|
for (const [tax, taxesAmount] of taxesMap) {
|
||||||
|
items.push(
|
||||||
|
InvoiceTax.create({
|
||||||
|
tax,
|
||||||
|
taxesAmount,
|
||||||
|
}).data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvoiceTaxes.create({
|
||||||
|
items: items,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Tax } from "@erp/core/api";
|
import { Tax } from "@erp/core/api";
|
||||||
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { ItemAmount } from "../../value-objects";
|
||||||
import { InvoiceAmount } from "../../value-objects/invoice-amount";
|
import { InvoiceAmount } from "../../value-objects/invoice-amount";
|
||||||
|
|
||||||
export interface InvoiceTaxProps {
|
export interface InvoiceTaxProps {
|
||||||
tax: Tax;
|
tax: Tax;
|
||||||
|
taxesAmount: ItemAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InvoiceTax extends DomainEntity<InvoiceTaxProps> {
|
export class InvoiceTax extends DomainEntity<InvoiceTaxProps> {
|
||||||
|
|||||||
@ -23,6 +23,21 @@ export class ItemTaxes extends Collection<ItemTax> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTaxesAmountByTaxCode(taxCode: string, taxableAmount: ItemAmount): ItemAmount {
|
||||||
|
const currencyCode = taxableAmount.currencyCode;
|
||||||
|
|
||||||
|
return this.filter((itemTax) => itemTax.tax.code === taxCode).reduce((totalAmount, itemTax) => {
|
||||||
|
return itemTax.getTaxAmount(taxableAmount).add(totalAmount);
|
||||||
|
}, ItemAmount.zero(currencyCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTaxesAmountByTaxes(taxableAmount: ItemAmount): {
|
||||||
|
return this.getAll().map((taxItem) => ({
|
||||||
|
tax: taxItem.tax,
|
||||||
|
taxesAmount: this.getTaxesAmountByTaxCode(taxItem.tax.code, taxableAmount),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public getCodesToString(): string {
|
public getCodesToString(): string {
|
||||||
return this.getAll()
|
return this.getAll()
|
||||||
.map((taxItem) => taxItem.tax.code)
|
.map((taxItem) => taxItem.tax.code)
|
||||||
|
|||||||
@ -74,6 +74,10 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
|
|||||||
return this.props.value === INVOICE_STATUS.DRAFT;
|
return this.props.value === INVOICE_STATUS.DRAFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isApproved(): boolean {
|
||||||
|
return this.props.value === INVOICE_STATUS.APPROVED;
|
||||||
|
}
|
||||||
|
|
||||||
getProps(): string {
|
getProps(): string {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,4 +3,5 @@ export * from "./delete-customer-invoice.controller";
|
|||||||
export * from "./get-customer-invoice.controller";
|
export * from "./get-customer-invoice.controller";
|
||||||
export * from "./list-customer-invoices.controller";
|
export * from "./list-customer-invoices.controller";
|
||||||
///export * from "./update-customer-invoice.controller";
|
///export * from "./update-customer-invoice.controller";
|
||||||
|
export * from "./issue-customer-invoice.controller";
|
||||||
export * from "./report-customer-invoice.controller";
|
export * from "./report-customer-invoice.controller";
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
import { IssueCustomerInvoiceUseCase } from "../../../application";
|
||||||
|
|
||||||
|
export class IssueCustomerInvoiceController extends ExpressController {
|
||||||
|
public constructor(
|
||||||
|
private readonly useCase: IssueCustomerInvoiceUseCase
|
||||||
|
/* private readonly presenter: any */
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl() {
|
||||||
|
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
|
const { id } = this.req.params;
|
||||||
|
|
||||||
|
const result = await this.useCase.execute({ id, tenantId });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -116,5 +116,17 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
"/:invoice_id/issue",
|
||||||
|
//checkTabContext,
|
||||||
|
validateRequest(XXX, "params"),
|
||||||
|
validateRequest(XXX, "body"),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const useCase = deps.build.issue();
|
||||||
|
const controller = new IssueCustomerInvoiceController(useCase);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
app.use(`${baseRoutePath}/customer-invoices`, router);
|
app.use(`${baseRoutePath}/customer-invoices`, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,12 +5,15 @@ import {
|
|||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
maybeFromNullableVO,
|
maybeFromNullableVO,
|
||||||
|
toNullable,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { InferCreationAttributes } from "sequelize";
|
import { InferCreationAttributes } from "sequelize";
|
||||||
import {
|
import {
|
||||||
|
CustomerInvoice,
|
||||||
CustomerInvoiceItem,
|
CustomerInvoiceItem,
|
||||||
CustomerInvoiceItemDescription,
|
CustomerInvoiceItemDescription,
|
||||||
|
CustomerInvoiceItemProps,
|
||||||
CustomerInvoiceProps,
|
CustomerInvoiceProps,
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDiscount,
|
ItemDiscount,
|
||||||
@ -42,7 +45,10 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
this._taxesMapper = new ItemTaxesDomainMapper(params);
|
this._taxesMapper = new ItemTaxesDomainMapper(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapAttributesToDomain(source: CustomerInvoiceItemModel, params?: MapperParamsType) {
|
private mapAttributesToDomain(
|
||||||
|
source: CustomerInvoiceItemModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Partial<CustomerInvoiceItemProps> & { itemId?: UniqueID } {
|
||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
@ -65,7 +71,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
|
|
||||||
const quantity = extractOrPushError(
|
const quantity = extractOrPushError(
|
||||||
maybeFromNullableVO(source.quantity_value, (value) => ItemQuantity.create({ value })),
|
maybeFromNullableVO(source.quantity_value, (value) => ItemQuantity.create({ value })),
|
||||||
`items[${index}].discount_percentage`,
|
`items[${index}].quantity`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -77,14 +83,23 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const discountPercentage = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.discount_percentage_value, (value) =>
|
||||||
|
ItemDiscount.create({ value })
|
||||||
|
),
|
||||||
|
`items[${index}].discount_percentage`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
itemId,
|
itemId,
|
||||||
|
|
||||||
languageCode: attributes.languageCode,
|
languageCode: attributes.languageCode,
|
||||||
currencyCode: attributes.currencyCode,
|
currencyCode: attributes.currencyCode,
|
||||||
description,
|
description,
|
||||||
quantity,
|
quantity,
|
||||||
|
|
||||||
unitAmount,
|
unitAmount,
|
||||||
|
discountPercentage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,16 +127,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Importes
|
// 3) Taxes (colección a nivel de item/línea)
|
||||||
const discountPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableVO(source.discount_percentage_value, (value) =>
|
|
||||||
ItemDiscount.create({ value })
|
|
||||||
),
|
|
||||||
`items[${index}].discount_percentage`,
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4) Taxes (colección a nivel de item/línea)
|
|
||||||
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
||||||
source.taxes,
|
source.taxes,
|
||||||
source.taxes.length,
|
source.taxes.length,
|
||||||
@ -133,7 +139,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
|
|
||||||
if (taxesResults.isFailure) {
|
if (taxesResults.isFailure) {
|
||||||
errors.push({
|
errors.push({
|
||||||
path: `taxes[${index}].discount_percentage`,
|
path: "taxes",
|
||||||
message: taxesResults.error.message,
|
message: taxesResults.error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -151,7 +157,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
quantity: attributes.quantity!,
|
quantity: attributes.quantity!,
|
||||||
unitAmount: attributes.unitAmount!,
|
unitAmount: attributes.unitAmount!,
|
||||||
discountPercentage: discountPercentage!,
|
discountPercentage: attributes.discountPercentage!,
|
||||||
taxes,
|
taxes,
|
||||||
},
|
},
|
||||||
attributes.itemId
|
attributes.itemId
|
||||||
@ -172,63 +178,59 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
source: CustomerInvoiceItem,
|
source: CustomerInvoiceItem,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<InferCreationAttributes<CustomerInvoiceItemModel, {}>, Error> {
|
): Result<InferCreationAttributes<CustomerInvoiceItemModel, {}>, Error> {
|
||||||
throw new Error("not implemented");
|
const { errors, index, parent } = params as {
|
||||||
/*
|
|
||||||
|
|
||||||
const { index, sourceParent } = params as {
|
|
||||||
index: number;
|
index: number;
|
||||||
sourceParent: CustomerInvoice;
|
parent: CustomerInvoice;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const taxesResults = this._taxesMapper.mapToPersistenceArray(source.taxes, params);
|
||||||
return {
|
|
||||||
|
if (taxesResults.isFailure) {
|
||||||
|
errors.push({
|
||||||
|
path: "taxes",
|
||||||
|
message: taxesResults.error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const allAmounts = source.getAllAmounts();
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
item_id: source.id.toPrimitive(),
|
item_id: source.id.toPrimitive(),
|
||||||
invoice_id: sourceParent.id.toPrimitive(),
|
invoice_id: parent.id.toPrimitive(),
|
||||||
position: index,
|
position: index,
|
||||||
|
|
||||||
description: toNullable(source.description, (description) => description.toPrimitive()),
|
description: toNullable(source.description, (v) => v.toPrimitive()),
|
||||||
|
|
||||||
quantity_value: toNullable(source.quantity, (value) => value.toPrimitive().value),
|
quantity_value: toNullable(source.quantity, (v) => v.toPrimitive().value),
|
||||||
quantity_scale: source.quantity.match(
|
quantity_scale: toNullable(source.quantity, (v) => v.toPrimitive().scale),
|
||||||
(value) => value.toPrimitive().scale,
|
|
||||||
() => ItemQuantity.DEFAULT_SCALE
|
|
||||||
),
|
|
||||||
|
|
||||||
unit_amount_value: toNullable(source.unitAmount, (value) => value.toPrimitive().value),
|
unit_amount_value: toNullable(source.unitAmount, (v) => v.toPrimitive().value),
|
||||||
unit_amount_scale: source.unitAmount.match(
|
unit_amount_scale: toNullable(source.unitAmount, (v) => v.toPrimitive().scale),
|
||||||
(value) => value.toPrimitive().scale,
|
|
||||||
() => ItemAmount.DEFAULT_SCALE
|
|
||||||
),
|
|
||||||
|
|
||||||
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
discount_percentage_value: toNullable(
|
discount_percentage_value: toNullable(
|
||||||
source.discountPercentage,
|
source.discountPercentage,
|
||||||
(value) => value.toPrimitive().value
|
(v) => v.toPrimitive().value
|
||||||
),
|
),
|
||||||
discount_percentage_scale: source.discountPercentage.match(
|
discount_percentage_scale: toNullable(
|
||||||
(value) => value.toPrimitive().scale,
|
source.discountPercentage,
|
||||||
() => ItemAmount.DEFAULT_SCALE
|
(v) => v.toPrimitive().scale
|
||||||
),
|
),
|
||||||
|
|
||||||
discount_amount_value: toNullable(
|
discount_amount_value: allAmounts.discountAmount.value,
|
||||||
source.discountAmount,
|
discount_amount_scale: allAmounts.discountAmount.scale,
|
||||||
(value) => value.toPrimitive().value
|
|
||||||
),
|
|
||||||
discount_amount_scale: source.discountAmount.match(
|
|
||||||
(value) => value.toPrimitive().scale,
|
|
||||||
() => ItemDiscount.DEFAULT_SCALE
|
|
||||||
),
|
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.toPrimitive().value,
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
taxable_amount_scale: source.taxableAmount.toPrimitive().value,
|
taxable_amount_scale: allAmounts.taxableAmount.value,
|
||||||
|
|
||||||
taxes_amount_value: source.taxesAmount.toPrimitive().value,
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
taxes_amount_scale: source.taxesAmount.toPrimitive().value,
|
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||||
|
|
||||||
total_amount_value: source.totalAmount.toPrimitive().value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: source.totalAmount.toPrimitive().scale,
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
};*/
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
import {
|
||||||
|
ISequelizeDomainMapper,
|
||||||
|
InfrastructureError,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeDomainMapper,
|
||||||
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
LanguageCode,
|
LanguageCode,
|
||||||
@ -10,6 +15,7 @@ import {
|
|||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
maybeFromNullableVO,
|
maybeFromNullableVO,
|
||||||
|
toNullable,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
import {
|
import {
|
||||||
@ -25,7 +31,7 @@ import { InvoiceTaxes } from "../../../domain/entities/invoice-taxes";
|
|||||||
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
||||||
import { CustomerInvoiceItemDomainMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.mapper";
|
import { CustomerInvoiceItemDomainMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.mapper";
|
||||||
import { InvoiceRecipientDomainMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.mapper";
|
import { InvoiceRecipientDomainMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.mapper";
|
||||||
import { TaxesDomainMapper as TaxesFullMapper } from "./taxes.mapper";
|
import { TaxesDomainMapper as TaxesFullMapper } from "./invoice-taxes.mapper";
|
||||||
|
|
||||||
export interface ICustomerInvoiceDomainMapper
|
export interface ICustomerInvoiceDomainMapper
|
||||||
extends ISequelizeDomainMapper<
|
extends ISequelizeDomainMapper<
|
||||||
@ -324,33 +330,86 @@ export class CustomerInvoiceDomainMapper
|
|||||||
source: CustomerInvoice,
|
source: CustomerInvoice,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<CustomerInvoiceCreationAttributes, Error> {
|
): Result<CustomerInvoiceCreationAttributes, Error> {
|
||||||
throw new Error("not implemented");
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
/*const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
|
// 1) Items
|
||||||
|
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
|
||||||
|
errors,
|
||||||
|
parent: source,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
if (itemsResult.isFailure) {
|
||||||
|
errors.push({
|
||||||
|
path: "items",
|
||||||
|
message: itemsResult.error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const customer = source.recipient.match(
|
// 1) Taxes
|
||||||
(recipient) =>
|
const taxesResult = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
||||||
({
|
errors,
|
||||||
customer_id: recipient.id.toPrimitive(),
|
parent: source,
|
||||||
customer_tin: recipient.tin.toPrimitive(),
|
...params,
|
||||||
customer_name: recipient.name.toPrimitive(),
|
});
|
||||||
customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()),
|
if (taxesResult.isFailure) {
|
||||||
customer_street2: toNullable(recipient.address.street2, (street2) =>
|
errors.push({
|
||||||
street2.toPrimitive()
|
path: "taxes",
|
||||||
),
|
message: taxesResult.error.message,
|
||||||
customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()),
|
});
|
||||||
customer_province: toNullable(recipient.address.province, (province) =>
|
}
|
||||||
province.toPrimitive()
|
|
||||||
),
|
// 3) Calcular totales
|
||||||
customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) =>
|
const allAmounts = source.getAllAmounts();
|
||||||
postalCode.toPrimitive()
|
|
||||||
),
|
// 4) Construir parte
|
||||||
customer_country: toNullable(recipient.address.country, (country) =>
|
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = {
|
||||||
country.toPrimitive()
|
id: source.id.toPrimitive(),
|
||||||
),
|
company_id: source.companyId.toPrimitive(),
|
||||||
}) as any,
|
|
||||||
() => ({
|
is_proforma: source.isProforma,
|
||||||
customer_id: source.customerId.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
|
series: toNullable(source.series, (series) => series.toPrimitive()),
|
||||||
|
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||||
|
invoice_date: source.invoiceDate.toPrimitive(),
|
||||||
|
operation_date: toNullable(source.operationDate, (operationDate) =>
|
||||||
|
operationDate.toPrimitive()
|
||||||
|
),
|
||||||
|
language_code: source.languageCode.toPrimitive(),
|
||||||
|
currency_code: source.currencyCode.toPrimitive(),
|
||||||
|
|
||||||
|
notes: toNullable(source.notes, (notes) => notes.toPrimitive()),
|
||||||
|
|
||||||
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
|
discount_percentage_value: source.discountPercentage.toPrimitive().value,
|
||||||
|
discount_percentage_scale: source.discountPercentage.toPrimitive().scale,
|
||||||
|
|
||||||
|
discount_amount_value: allAmounts.discountAmount.value,
|
||||||
|
discount_amount_scale: allAmounts.discountAmount.scale,
|
||||||
|
|
||||||
|
taxable_amount_value: allAmounts.taxableAmount.value,
|
||||||
|
taxable_amount_scale: allAmounts.taxableAmount.scale,
|
||||||
|
|
||||||
|
taxes_amount_value: allAmounts.taxesAmount.value,
|
||||||
|
taxes_amount_scale: allAmounts.taxesAmount.scale,
|
||||||
|
|
||||||
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
|
total_amount_scale: allAmounts.totalAmount.scale,
|
||||||
|
|
||||||
|
customer_id: source.customerId.toPrimitive(),
|
||||||
|
|
||||||
|
payment_method_id: toNullable(source.paymentMethod, (payment) => payment.toObjectString().id),
|
||||||
|
payment_method_description: toNullable(
|
||||||
|
source.paymentMethod,
|
||||||
|
(payment) => payment.toObjectString().payment_description
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5) Cliente / Recipient ??
|
||||||
|
// Si es proforma no guardamos los campos como históricos (snapshots)
|
||||||
|
if (source.isProforma) {
|
||||||
|
Object.assign(invoiceValues, {
|
||||||
customer_tin: null,
|
customer_tin: null,
|
||||||
customer_name: null,
|
customer_name: null,
|
||||||
customer_street: null,
|
customer_street: null,
|
||||||
@ -359,48 +418,40 @@ export class CustomerInvoiceDomainMapper
|
|||||||
customer_province: null,
|
customer_province: null,
|
||||||
customer_postal_code: null,
|
customer_postal_code: null,
|
||||||
customer_country: null,
|
customer_country: null,
|
||||||
})
|
});
|
||||||
) as any;
|
} else {
|
||||||
|
const recipient = source.recipient.getOrUndefined();
|
||||||
|
if (!recipient) {
|
||||||
|
return Result.fail(
|
||||||
|
new InfrastructureError(
|
||||||
|
"[CustomerInvoiceDomainMapper] Issued customer invoice w/o recipient data"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
Object.assign(invoiceValues, {
|
||||||
id: source.id.toPrimitive(),
|
customer_tin: recipient.tin.toPrimitive(),
|
||||||
company_id: source.companyId.toPrimitive(),
|
customer_name: recipient.name.toPrimitive(),
|
||||||
|
customer_street: toNullable(recipient.street, (v) => v.toPrimitive()),
|
||||||
|
customer_street2: toNullable(recipient.street2, (v) => v.toPrimitive()),
|
||||||
|
customer_city: toNullable(recipient.city, (v) => v.toPrimitive()),
|
||||||
|
customer_province: toNullable(recipient.province, (v) => v.toPrimitive()),
|
||||||
|
customer_postal_code: toNullable(recipient.postalCode, (v) => v.toPrimitive()),
|
||||||
|
customer_country: toNullable(recipient.country, (v) => v.toPrimitive()),
|
||||||
|
} as Partial<CustomerInvoiceCreationAttributes>);
|
||||||
|
}
|
||||||
|
|
||||||
status: source.status.toPrimitive(),
|
// 7) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
series: toNullable(source.series, (series) => series.toPrimitive()),
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
return Result.ok<CustomerInvoiceCreationAttributes>({
|
||||||
invoice_date: source.invoiceDate.toPrimitive(),
|
...invoiceValues,
|
||||||
|
items: itemsResult.data,
|
||||||
operation_date: toNullable(source.operationDate, (operationDate) =>
|
taxes: taxesResult.data,
|
||||||
operationDate.toPrimitive()
|
});
|
||||||
),
|
|
||||||
|
|
||||||
notes: toNullable(source.notes, (notes) => notes.toPrimitive()),
|
|
||||||
|
|
||||||
language_code: source.languageCode.code,
|
|
||||||
currency_code: source.currencyCode.code,
|
|
||||||
|
|
||||||
subtotal_amount_value: 0, //subtotal.amount,
|
|
||||||
subtotal_amount_scale: 2, //subtotal.scale,
|
|
||||||
|
|
||||||
discount_percentage_value: source.discountPercentage.value,
|
|
||||||
discount_percentage_scale: source.discountPercentage.scale,
|
|
||||||
|
|
||||||
discount_amount_value: source.discountAmount.value,
|
|
||||||
discount_amount_scale: source.discountAmount.scale,
|
|
||||||
|
|
||||||
taxable_amount_value: source.taxableAmount.value,
|
|
||||||
taxable_amount_scale: source.taxableAmount.scale,
|
|
||||||
|
|
||||||
taxes_amount_value: source.taxAmount.value,
|
|
||||||
taxes_amount_scale: source.taxAmount.scale,
|
|
||||||
|
|
||||||
total_amount_value: 0, //total.amount,
|
|
||||||
total_amount_scale: 2, //total.scale,
|
|
||||||
|
|
||||||
items,
|
|
||||||
...customer,
|
|
||||||
};*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import {
|
|||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceProps } from "../../../domain";
|
import { InferCreationAttributes } from "sequelize";
|
||||||
|
import { CustomerInvoice, CustomerInvoiceProps } from "../../../domain";
|
||||||
import { InvoiceTax } from "../../../domain/entities/invoice-taxes";
|
import { InvoiceTax } from "../../../domain/entities/invoice-taxes";
|
||||||
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../../sequelize";
|
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../../sequelize";
|
||||||
|
|
||||||
@ -63,10 +64,31 @@ export class TaxesDomainMapper extends SequelizeDomainMapper<
|
|||||||
return createResult;
|
return createResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: InvoiceTax,
|
source: InvoiceTax,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): CustomerInvoiceTaxCreationAttributes {
|
): Result<InferCreationAttributes<CustomerInvoiceTaxModel, {}>, Error> {
|
||||||
throw new Error("not implemented");
|
const { errors, parent } = params as {
|
||||||
}*/
|
index: number;
|
||||||
|
parent: CustomerInvoice;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const taxableAmount = parent.getTaxableAmount()
|
||||||
|
const taxesAmount =
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
|
tax_id: source.id.toPrimitive(),
|
||||||
|
invoice_id: parent.id.toPrimitive(),
|
||||||
|
|
||||||
|
tax_code: source.tax.code,
|
||||||
|
|
||||||
|
taxable_amount_value: taxableAmount.value,
|
||||||
|
taxable_amount_scale: taxableAmount.scale,
|
||||||
|
|
||||||
|
taxes_amount_value: taxesAmount.value,
|
||||||
|
taxes_amount_scale: taxesAmount.scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,22 +1,37 @@
|
|||||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
import {
|
||||||
|
ISequelizeDomainMapper,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeDomainMapper,
|
||||||
|
Tax,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import { CustomerInvoiceItem, ItemTax } from "@erp/customer-invoices/api/domain";
|
||||||
import {
|
import {
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { ItemTax } from "../../../domain";
|
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemTaxCreationAttributes,
|
||||||
CustomerInvoiceItemTaxModel,
|
CustomerInvoiceItemTaxModel,
|
||||||
} from "../../sequelize";
|
} from "../../sequelize";
|
||||||
|
|
||||||
export class ItemTaxesDomainMapper extends SequelizeDomainMapper<
|
export interface IItemTaxesDomainMapper
|
||||||
CustomerInvoiceItemTaxModel,
|
extends ISequelizeDomainMapper<
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemTaxModel,
|
||||||
ItemTax
|
CustomerInvoiceItemTaxCreationAttributes,
|
||||||
> {
|
ItemTax
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export class ItemTaxesDomainMapper
|
||||||
|
extends SequelizeDomainMapper<
|
||||||
|
CustomerInvoiceItemTaxModel,
|
||||||
|
CustomerInvoiceItemTaxCreationAttributes,
|
||||||
|
ItemTax
|
||||||
|
>
|
||||||
|
implements IItemTaxesDomainMapper
|
||||||
|
{
|
||||||
private _taxCatalog!: JsonTaxCatalogProvider;
|
private _taxCatalog!: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
@ -60,10 +75,29 @@ export class ItemTaxesDomainMapper extends SequelizeDomainMapper<
|
|||||||
return createResult;
|
return createResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: ItemTax,
|
source: ItemTax,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): CustomerInvoiceItemCreationAttributes {
|
): Result<CustomerInvoiceItemTaxCreationAttributes, Error> {
|
||||||
throw new Error("not implemented");
|
const { errors, parent } = params as {
|
||||||
}*/
|
parent: CustomerInvoiceItem;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxableAmount = parent.getTaxableAmount();
|
||||||
|
const taxAmount = source.getTaxAmount(taxableAmount);
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
|
tax_id: source.id.toPrimitive(),
|
||||||
|
item_id: parent.id.toPrimitive(),
|
||||||
|
|
||||||
|
tax_code: source.tax.code,
|
||||||
|
|
||||||
|
taxable_amount_value: taxableAmount.value,
|
||||||
|
taxable_amount_scale: taxableAmount.scale,
|
||||||
|
|
||||||
|
taxes_amount_value: taxAmount.value,
|
||||||
|
taxes_amount_scale: taxAmount.scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
declare operation_date: string;
|
declare operation_date: string;
|
||||||
declare language_code: string;
|
declare language_code: string;
|
||||||
declare currency_code: string;
|
declare currency_code: string;
|
||||||
|
//declare xxxxxx
|
||||||
|
|
||||||
declare notes: string;
|
declare notes: string;
|
||||||
|
|
||||||
|
|||||||
23
modules/doc-numbering/package.json
Normal file
23
modules/doc-numbering/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@erp/doc-numbering",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
"./api": "./src/api/index.ts"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"sequelize": "^6.37.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"zod": "^4.1.11"
|
||||||
|
},
|
||||||
|
"devDependencies": { "@types/express": "^4.17.21" },
|
||||||
|
"dependencies": {
|
||||||
|
"@erp/auth": "workspace:*",
|
||||||
|
"@erp/core": "workspace:*",
|
||||||
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
|
"@repo/rdx-utils": "workspace:*",
|
||||||
|
"@repo/rdx-logger": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { AggregateRoot, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { DocType } from "../value-objects";
|
||||||
|
|
||||||
|
export interface DocNumberProps {
|
||||||
|
docType: DocType; // INVOICE, QUOTATION, DELIVERY_NOTE, PAYMENT...
|
||||||
|
series: string; // opcional: "2025", "Sucursal-01"
|
||||||
|
currentValue: number;
|
||||||
|
formatPattern: string; // ej: "{year}/{number:000000}"
|
||||||
|
lastAssignedAt: UtcDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DocNumber extends AggregateRoot<DocNumberProps> {
|
||||||
|
static create(props: DocNumberProps, id?: UniqueID): Result<DocNumber, Error> {
|
||||||
|
const docNumber = new DocNumber(props, id);
|
||||||
|
|
||||||
|
// Reglas de negocio / validaciones
|
||||||
|
// ...
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent"
|
||||||
|
//const { contact } = props;
|
||||||
|
//user.addDomainEvent(new CustomerAuthenticatedEvent(id, contact.toString()));
|
||||||
|
|
||||||
|
return Result.ok(docNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get docType(): DocType {
|
||||||
|
return this.props.docType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get series(): string {
|
||||||
|
return this.props.series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get currentValue(): number {
|
||||||
|
return this.props.currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get formatPattern(): string {
|
||||||
|
return this.props.formatPattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
modules/doc-numbering/src/api/domain/aggregates/index.ts
Normal file
1
modules/doc-numbering/src/api/domain/aggregates/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./doc-number";
|
||||||
5
modules/doc-numbering/src/api/domain/index.ts
Normal file
5
modules/doc-numbering/src/api/domain/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./aggregates";
|
||||||
|
export * from "./entities";
|
||||||
|
export * from "./repositories";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./value-objects";
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { DocNumber } from "../aggregates/doc-number";
|
||||||
|
|
||||||
|
export interface IDocNumberingRepository {
|
||||||
|
getByReferenceInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
reference: string,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<DocNumber, Error>>;
|
||||||
|
save(reference: string, transaction?: any): Promise<Result<DocNumber, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./doc-number-repository.interface";
|
||||||
136
modules/doc-numbering/src/api/domain/value-objects/doc-type.ts
Normal file
136
modules/doc-numbering/src/api/domain/value-objects/doc-type.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { ValueObject, translateZodValidationError } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
// 🔹 Conjunto canónico de tipos admitidos en todo el ERP
|
||||||
|
export const DOCUMENT_TYPE_CODES = [
|
||||||
|
"INVOICE",
|
||||||
|
"QUOTATION",
|
||||||
|
"DELIVERY_NOTE",
|
||||||
|
"PAYMENT",
|
||||||
|
"CREDIT_NOTE",
|
||||||
|
"RECEIPT",
|
||||||
|
"PURCHASE_ORDER",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type DocTypeCode = (typeof DOCUMENT_TYPE_CODES)[number];
|
||||||
|
|
||||||
|
// 🔹 Alias comunes (entrada libre) → código canónico
|
||||||
|
const ALIASES: Record<string, DocTypeCode> = {
|
||||||
|
// facturas
|
||||||
|
INVOICE: "INVOICE",
|
||||||
|
FACTURA: "INVOICE",
|
||||||
|
"FACTURA-RECTIFICATIVA": "CREDIT_NOTE",
|
||||||
|
FACTURA_RECTIFICATIVA: "CREDIT_NOTE",
|
||||||
|
"CREDIT-NOTE": "CREDIT_NOTE",
|
||||||
|
CREDIT_NOTE: "CREDIT_NOTE",
|
||||||
|
ABONO: "CREDIT_NOTE",
|
||||||
|
|
||||||
|
// presupuestos
|
||||||
|
QUOTATION: "QUOTATION",
|
||||||
|
QUOTE: "QUOTATION",
|
||||||
|
PRESUPUESTO: "QUOTATION",
|
||||||
|
|
||||||
|
// albaranes
|
||||||
|
"DELIVERY-NOTE": "DELIVERY_NOTE",
|
||||||
|
DELIVERY_NOTE: "DELIVERY_NOTE",
|
||||||
|
ALBARAN: "DELIVERY_NOTE",
|
||||||
|
ALBARÁN: "DELIVERY_NOTE",
|
||||||
|
|
||||||
|
// pagos / cobros
|
||||||
|
PAYMENT: "PAYMENT",
|
||||||
|
PAGO: "PAYMENT",
|
||||||
|
RECEIPT: "RECEIPT",
|
||||||
|
RECIBO: "RECEIPT",
|
||||||
|
|
||||||
|
// pedidos
|
||||||
|
"PURCHASE-ORDER": "PURCHASE_ORDER",
|
||||||
|
PURCHASE_ORDER: "PURCHASE_ORDER",
|
||||||
|
"ORDEN-DE-COMPRA": "PURCHASE_ORDER",
|
||||||
|
ORDEN_DE_COMPRA: "PURCHASE_ORDER",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔹 Normaliza texto a forma comparable: mayúsculas, sin acentos, separadores como ""
|
||||||
|
function normalizeToken(input: string): string {
|
||||||
|
return (
|
||||||
|
input
|
||||||
|
.trim()
|
||||||
|
.toUpperCase()
|
||||||
|
// eliminar diacríticos (tildes)
|
||||||
|
.normalize("NFD")
|
||||||
|
.replace(/\p{Diacritic}/gu, "")
|
||||||
|
// espacios/guiones → guion bajo
|
||||||
|
.replace(/[\s-]+/g, "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocTypeProps {
|
||||||
|
value: DocTypeCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DocType extends ValueObject<DocTypeProps> {
|
||||||
|
// Validación de código canónico con zod
|
||||||
|
protected static validateCanonical(value: string) {
|
||||||
|
const schema = z.enum(DOCUMENT_TYPE_CODES);
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intenta mapear la entrada libre a un código canónico
|
||||||
|
protected static canonicalize(input: string): Result<DocTypeCode, Error> {
|
||||||
|
const token = normalizeToken(input);
|
||||||
|
const fromAlias = ALIASES[token];
|
||||||
|
const candidate = (fromAlias ?? token) as string;
|
||||||
|
|
||||||
|
const parsed = DocType.validateCanonical(candidate);
|
||||||
|
if (!parsed.success) {
|
||||||
|
return Result.fail(translateZodValidationError("DocType creation failed", parsed.error));
|
||||||
|
}
|
||||||
|
return Result.ok(parsed.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factoría desde entrada libre (admite alias).
|
||||||
|
* Devuelve Result con el VO o el error de validación.
|
||||||
|
*/
|
||||||
|
static create(input: string) {
|
||||||
|
const canon = DocType.canonicalize(input);
|
||||||
|
if (canon.isFailure) {
|
||||||
|
return Result.fail(canon.error);
|
||||||
|
}
|
||||||
|
return Result.ok(new DocType({ value: canon.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factoría directa para código canónico ya validado.
|
||||||
|
* Útil en mappers desde persistencia.
|
||||||
|
*/
|
||||||
|
static from(code: DocTypeCode): DocType {
|
||||||
|
return new DocType({ value: code });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista de tipos canónicos soportados (para UI/validadores externos).
|
||||||
|
*/
|
||||||
|
static list(): readonly DocTypeCode[] {
|
||||||
|
return DOCUMENT_TYPE_CODES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ¿Pertenece a alguno de los tipos indicados?
|
||||||
|
*/
|
||||||
|
isOneOf(...types: DocTypeCode[]): boolean {
|
||||||
|
return types.includes(this.props.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps(): DocTypeCode {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): DocTypeCode {
|
||||||
|
return this.getProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.getProps();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./doc-type";
|
||||||
29
modules/doc-numbering/src/api/index.ts
Normal file
29
modules/doc-numbering/src/api/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { IModuleServer, ModuleParams } from "@erp/core/api";
|
||||||
|
import { models } from "./infrastructure";
|
||||||
|
|
||||||
|
export * from "./infrastructure/sequelize";
|
||||||
|
|
||||||
|
export const docNumberingAPIModule: IModuleServer = {
|
||||||
|
name: "customers",
|
||||||
|
version: "1.0.0",
|
||||||
|
dependencies: [],
|
||||||
|
|
||||||
|
async init(params: ModuleParams) {
|
||||||
|
const { logger } = params;
|
||||||
|
logger.info("🚀 Document Numbering module initialized", { label: this.name });
|
||||||
|
},
|
||||||
|
async registerDependencies(params) {
|
||||||
|
const { logger } = params;
|
||||||
|
logger.info("🚀 Document Numbering module dependencies registered", {
|
||||||
|
label: this.name,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
models,
|
||||||
|
services: {
|
||||||
|
/*...*/
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default docNumberingAPIModule;
|
||||||
1
modules/doc-numbering/src/api/infrastructure/index.ts
Normal file
1
modules/doc-numbering/src/api/infrastructure/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "";
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
export type DocNumberCreationAttributes = InferCreationAttributes<DocNumberModel, {}> & {};
|
||||||
|
|
||||||
|
export class DocNumberModel extends Model<
|
||||||
|
InferAttributes<DocNumberModel>,
|
||||||
|
InferCreationAttributes<DocNumberModel>
|
||||||
|
> {
|
||||||
|
declare id: string; // UUID
|
||||||
|
declare docType: string; // ej. "INVOICE"
|
||||||
|
declare series: string | null; // ej. "2025", "Sucursal-01"
|
||||||
|
declare currentValue: number; // último número asignado
|
||||||
|
declare formatPattern: string; // ej. "{series}/{number:000000}"
|
||||||
|
|
||||||
|
static associate(database: Sequelize) {}
|
||||||
|
|
||||||
|
static hooks(database: Sequelize) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (database: Sequelize) => {
|
||||||
|
DocNumberModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
docType: {
|
||||||
|
type: DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
type: DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
currentValue: {
|
||||||
|
type: DataTypes.INTEGER.UNSIGNED,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
formatPattern: {
|
||||||
|
type: DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "{series}/{number:000000}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize: database,
|
||||||
|
tableName: "doc-numbers",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
|
paranoid: false, // no softs deletes
|
||||||
|
timestamps: true,
|
||||||
|
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: "deleted_at",
|
||||||
|
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ["docType", "series"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
|
||||||
|
defaultScope: {},
|
||||||
|
|
||||||
|
scopes: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return DocNumberModel;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./doc-number";
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
||||||
|
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { DocNumber, IDocNumberingRepository } from "../../../domain";
|
||||||
|
import { DocNumberModel } from "../models";
|
||||||
|
|
||||||
|
export class DocNumberRepository
|
||||||
|
extends SequelizeRepository<DocNumber>
|
||||||
|
implements IDocNumberingRepository
|
||||||
|
{
|
||||||
|
async getByReferenceInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
reference: string,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<DocNumber, Error>> {
|
||||||
|
try {
|
||||||
|
const mapper: IDocNumberDomainMapper = this._registry.getDomainMapper({
|
||||||
|
resource: "doc-number",
|
||||||
|
});
|
||||||
|
|
||||||
|
const row = await DocNumberModel.findOne({
|
||||||
|
where: { reference, company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const customer = mapper.mapToDomain(row);
|
||||||
|
return customer;
|
||||||
|
} catch (error: any) {
|
||||||
|
return Result.fail(translateSequelizeError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save(reference: string, transaction?: Transaction): Promise<Result<DocNumber, Error>> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
33
modules/doc-numbering/tsconfig.json
Normal file
33
modules/doc-numbering/tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@erp/customer-invoices/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@ -344,8 +344,8 @@ importers:
|
|||||||
specifier: ^6.37.5
|
specifier: ^6.37.5
|
||||||
version: 6.37.7(mysql2@3.14.1)
|
version: 6.37.7(mysql2@3.14.1)
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.1.11
|
specifier: ^3.25.67
|
||||||
version: 4.1.11
|
version: 3.25.67
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.9.4
|
specifier: 1.9.4
|
||||||
@ -663,6 +663,40 @@ importers:
|
|||||||
specifier: ^3.2.4
|
specifier: ^3.2.4
|
||||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
|
||||||
|
modules/document-numbering:
|
||||||
|
dependencies:
|
||||||
|
'@erp/auth':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../auth
|
||||||
|
'@erp/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
'@repo/rdx-criteria':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-criteria
|
||||||
|
'@repo/rdx-ddd':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-ddd
|
||||||
|
'@repo/rdx-logger':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-logger
|
||||||
|
'@repo/rdx-utils':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-utils
|
||||||
|
express:
|
||||||
|
specifier: ^4.18.2
|
||||||
|
version: 4.21.2
|
||||||
|
sequelize:
|
||||||
|
specifier: ^6.37.5
|
||||||
|
version: 6.37.7(mysql2@3.14.1)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.1.11
|
||||||
|
version: 4.1.11
|
||||||
|
devDependencies:
|
||||||
|
'@types/express':
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.23
|
||||||
|
|
||||||
modules/verifactu:
|
modules/verifactu:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@erp/auth':
|
'@erp/auth':
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user