.
This commit is contained in:
parent
d2e4c44474
commit
7def4f7dc5
@ -4,6 +4,9 @@ import { IAccountRepository } from "../repositories";
|
|||||||
import { AccountService } from "./account.service";
|
import { AccountService } from "./account.service";
|
||||||
|
|
||||||
const mockAccountRepository: IAccountRepository = {
|
const mockAccountRepository: IAccountRepository = {
|
||||||
|
accountExists: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
findByEmail: jest.fn(),
|
||||||
findById: jest.fn(),
|
findById: jest.fn(),
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const ListAccountsSchema = z.object({});
|
export const ListAccountsRequestSchema = z.object({});
|
||||||
|
|
||||||
export const IGetAcccountResponseDTOSchema = z.object({
|
export const IGetAccountRequestSchema = z.object({});
|
||||||
|
|
||||||
|
export const ICreateAccountRequestSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|
||||||
is_freelancer: z.boolean(),
|
is_freelancer: z.boolean(),
|
||||||
@ -30,7 +32,7 @@ export const IGetAcccountResponseDTOSchema = z.object({
|
|||||||
logo: z.string(),
|
logo: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ICreateAcccountResponseDTOSchema = z.object({
|
export const IUpdateAccountRequestSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|
||||||
is_freelancer: z.boolean(),
|
is_freelancer: z.boolean(),
|
||||||
@ -58,30 +60,4 @@ export const ICreateAcccountResponseDTOSchema = z.object({
|
|||||||
logo: z.string(),
|
logo: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const IUpdateAcccountResponseDTOSchema = z.object({
|
export const IDeleteAccountRequestSchema = z.object({});
|
||||||
id: z.string(),
|
|
||||||
|
|
||||||
is_freelancer: z.boolean(),
|
|
||||||
name: z.string(),
|
|
||||||
trade_name: z.string(),
|
|
||||||
tin: z.string(),
|
|
||||||
|
|
||||||
street: z.string(),
|
|
||||||
city: z.string(),
|
|
||||||
state: z.string(),
|
|
||||||
postal_code: z.string(),
|
|
||||||
country: z.string(),
|
|
||||||
|
|
||||||
email: z.string().email(), // Validación específica para email
|
|
||||||
phone: z.string(),
|
|
||||||
fax: z.string(),
|
|
||||||
website: z.string().url(), // Validación específica para URL
|
|
||||||
|
|
||||||
legal_record: z.string(),
|
|
||||||
|
|
||||||
default_tax: z.number(),
|
|
||||||
status: z.string(),
|
|
||||||
lang_code: z.string(),
|
|
||||||
currency_code: z.string(),
|
|
||||||
logo: z.string(),
|
|
||||||
});
|
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export * from "./accounts.request.dto";
|
export * from "./accounts.request.dto";
|
||||||
export * from "./accounts.response.dto";
|
export * from "./accounts.response.dto";
|
||||||
export * from "./accounts.validation.dto";
|
export * from "./accounts.schemas";
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
|
||||||
|
import { Result } from "@common/helpers";
|
||||||
|
import { ITransactionManager } from "@common/infrastructure/database";
|
||||||
|
import { logger } from "@common/infrastructure/logger";
|
||||||
|
import { IInvoiceProps, IInvoiceService, Invoice, InvoiceStatus } from "@contexts/invoices/domain";
|
||||||
|
import { ICreateInvoiceRequestDTO } from "../presentation";
|
||||||
|
|
||||||
|
export class CreateInvoiceUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly invoiceService: IInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(
|
||||||
|
invoiceID: UniqueID,
|
||||||
|
dto: ICreateInvoiceRequestDTO
|
||||||
|
): Promise<Result<Invoice, Error>> {
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const validOrErrors = this.validateInvoiceData(dto);
|
||||||
|
if (validOrErrors.isFailure) {
|
||||||
|
return Result.fail(validOrErrors.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = validOrErrors.data;
|
||||||
|
|
||||||
|
// Update invoice with dto
|
||||||
|
return await this.invoiceService.createInvoice(invoiceID, data, transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error(error as Error);
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateInvoiceData(dto: ICreateInvoiceRequestDTO): Result<IInvoiceProps, Error> {
|
||||||
|
const errors: Error[] = [];
|
||||||
|
|
||||||
|
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||||
|
if (invoice_status.isEmpty()) {
|
||||||
|
invoice_status = InvoiceStatus.createDraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
if (invoice_series.isEmpty()) {
|
||||||
|
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||||
|
if (issue_date.isEmpty()) {
|
||||||
|
issue_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||||
|
if (operation_date.isEmpty()) {
|
||||||
|
operation_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||||
|
|
||||||
|
if (invoiceCurrency.isEmpty()) {
|
||||||
|
invoiceCurrency = Currency.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||||
|
|
||||||
|
if (invoiceLanguage.isEmpty()) {
|
||||||
|
invoiceLanguage = Language.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = new Collection<InvoiceItem>(
|
||||||
|
invoiceDTO.items?.map(
|
||||||
|
(item) =>
|
||||||
|
InvoiceSimpleItem.create({
|
||||||
|
description: Description.create(item.description).object,
|
||||||
|
quantity: Quantity.create(item.quantity).object,
|
||||||
|
unitPrice: UnitPrice.create({
|
||||||
|
amount: item.unit_price.amount,
|
||||||
|
currencyCode: item.unit_price.currency,
|
||||||
|
precision: item.unit_price.precision,
|
||||||
|
}).object,
|
||||||
|
}).object
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!invoice_status.isDraft()) {
|
||||||
|
throw Error("Error al crear una factura que no es borrador");
|
||||||
|
}
|
||||||
|
|
||||||
|
return DraftInvoice.create(
|
||||||
|
{
|
||||||
|
invoiceSeries: invoice_series,
|
||||||
|
issueDate: issue_date,
|
||||||
|
operationDate: operation_date,
|
||||||
|
invoiceCurrency,
|
||||||
|
language: invoiceLanguage,
|
||||||
|
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||||
|
//notes: Note.create(invoiceDTO.notes).object,
|
||||||
|
|
||||||
|
//senderId: UniqueID.create(null).object,
|
||||||
|
recipient,
|
||||||
|
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Result } from "@common/helpers";
|
||||||
|
import { ITransactionManager } from "@common/infrastructure/database";
|
||||||
|
import { logger } from "@common/infrastructure/logger";
|
||||||
|
import { IInvoiceService, Invoice } from "../domain";
|
||||||
|
|
||||||
|
export class DeleteInvoiceUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly invoiceService: IInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(invoiceID: UniqueID): Promise<Result<Invoice, Error>> {
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
return await this.invoiceService.deleteInvoiceById(invoiceID, transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error(error as Error);
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Result } from "@common/helpers";
|
||||||
|
import { ITransactionManager } from "@common/infrastructure/database";
|
||||||
|
import { logger } from "@common/infrastructure/logger";
|
||||||
|
import { IInvoiceService, Invoice } from "../domain";
|
||||||
|
|
||||||
|
export class GetInvoiceUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly invoiceService: IInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(invoiceID: UniqueID): Promise<Result<Invoice, Error>> {
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
return await this.invoiceService.findInvoiceById(invoiceID, transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error(error as Error);
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
5
apps/server/src/contexts/invoicing/application/index.ts
Normal file
5
apps/server/src/contexts/invoicing/application/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./create-invoice.use-case";
|
||||||
|
export * from "./delete-invoice.use-case";
|
||||||
|
export * from "./get-invoice.use-case";
|
||||||
|
export * from "./list-invoices.use-case";
|
||||||
|
export * from "./update-invoice.use-case";
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { ITransactionManager } from "@common/infrastructure/database";
|
||||||
|
import { logger } from "@common/infrastructure/logger";
|
||||||
|
import { Invoice } from "../domain";
|
||||||
|
|
||||||
|
export class ListInvoicesUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly invoiceService: IInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(): Promise<Result<Collection<Invoice>, Error>> {
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
return await this.invoiceService.findInvoices(transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error(error as Error);
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./participantAddressFinder";
|
||||||
|
export * from "./participantFinder";
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
ApplicationServiceError,
|
||||||
|
IApplicationServiceError,
|
||||||
|
} from "@/contexts/common/application/services/ApplicationServiceError";
|
||||||
|
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||||
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
import { NullOr } from "@shared/utilities";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantAddress,
|
||||||
|
IInvoiceParticipantAddressRepository,
|
||||||
|
} from "../../domain";
|
||||||
|
|
||||||
|
export const participantAddressFinder = async (
|
||||||
|
addressId: UniqueID,
|
||||||
|
adapter: IAdapter,
|
||||||
|
repository: RepositoryBuilder<IInvoiceParticipantAddressRepository>,
|
||||||
|
) => {
|
||||||
|
if (addressId.isNull()) {
|
||||||
|
return Result.fail<IApplicationServiceError>(
|
||||||
|
ApplicationServiceError.create(
|
||||||
|
ApplicationServiceError.INVALID_REQUEST_PARAM,
|
||||||
|
`Participant address ID required`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transaction = adapter.startTransaction();
|
||||||
|
let address: NullOr<IInvoiceParticipantAddress> = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
address = await repository({ transaction: t }).getById(addressId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (address === null) {
|
||||||
|
return Result.fail<IApplicationServiceError>(
|
||||||
|
ApplicationServiceError.create(
|
||||||
|
ApplicationServiceError.NOT_FOUND_ERROR,
|
||||||
|
"",
|
||||||
|
{
|
||||||
|
id: addressId.toString(),
|
||||||
|
entity: "participant address",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<IInvoiceParticipantAddress>(address);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as Error;
|
||||||
|
|
||||||
|
if (repository().isRepositoryError(_error)) {
|
||||||
|
return Result.fail<IApplicationServiceError>(
|
||||||
|
ApplicationServiceError.create(
|
||||||
|
ApplicationServiceError.REPOSITORY_ERROR,
|
||||||
|
_error.message,
|
||||||
|
_error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.fail<IApplicationServiceError>(
|
||||||
|
ApplicationServiceError.create(
|
||||||
|
ApplicationServiceError.UNEXCEPTED_ERROR,
|
||||||
|
_error.message,
|
||||||
|
_error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { IInvoiceParticipantRepository } from "../../domain";
|
||||||
|
import { InvoiceParticipant } from "../../domain/InvoiceParticipant/InvoiceParticipant";
|
||||||
|
|
||||||
|
export const participantFinder = async (
|
||||||
|
participantId: UniqueID,
|
||||||
|
adapter: IAdapter,
|
||||||
|
repository: RepositoryBuilder<IInvoiceParticipantRepository>,
|
||||||
|
): Promise<InvoiceParticipant | undefined> => {
|
||||||
|
if (!participantId || (participantId && participantId.isNull())) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const participant = await adapter
|
||||||
|
.startTransaction()
|
||||||
|
.complete((t) => repository({ transaction: t }).getById(participantId));
|
||||||
|
|
||||||
|
return Promise.resolve(participant ? participant : undefined);
|
||||||
|
};
|
||||||
@ -0,0 +1,398 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
|
||||||
|
import { Result } from "@common/helpers";
|
||||||
|
import { ITransactionManager } from "@common/infrastructure/database";
|
||||||
|
import { logger } from "@common/infrastructure/logger";
|
||||||
|
import { IUpdateInvoiceRequestDTO } from "../presentation/dto";
|
||||||
|
|
||||||
|
export class CreateInvoiceUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly invoiceService: IInvoiceService,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(
|
||||||
|
invoiceID: UniqueID,
|
||||||
|
dto: Partial<IUpdateInvoiceRequestDTO>
|
||||||
|
): Promise<Result<Invoice, Error>> {
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const validOrErrors = this.validateInvoiceData(dto);
|
||||||
|
if (validOrErrors.isFailure) {
|
||||||
|
return Result.fail(validOrErrors.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = validOrErrors.data;
|
||||||
|
|
||||||
|
// Update invoice with dto
|
||||||
|
return await this.invoiceService.updateInvoiceById(invoiceID, data, transaction);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.error(error as Error);
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateInvoiceData(
|
||||||
|
dto: Partial<IUpdateInvoiceRequestDTO>
|
||||||
|
): Result<Partial<IInvoiceProps>, Error> {
|
||||||
|
const errors: Error[] = [];
|
||||||
|
const validatedData: Partial<IInvoiceProps> = {};
|
||||||
|
|
||||||
|
// Create invoice
|
||||||
|
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||||
|
if (invoice_status.isEmpty()) {
|
||||||
|
invoice_status = InvoiceStatus.createDraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
if (invoice_series.isEmpty()) {
|
||||||
|
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||||
|
if (issue_date.isEmpty()) {
|
||||||
|
issue_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||||
|
if (operation_date.isEmpty()) {
|
||||||
|
operation_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||||
|
|
||||||
|
if (invoiceCurrency.isEmpty()) {
|
||||||
|
invoiceCurrency = Currency.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||||
|
|
||||||
|
if (invoiceLanguage.isEmpty()) {
|
||||||
|
invoiceLanguage = Language.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = new Collection<InvoiceItem>(
|
||||||
|
invoiceDTO.items?.map(
|
||||||
|
(item) =>
|
||||||
|
InvoiceSimpleItem.create({
|
||||||
|
description: Description.create(item.description).object,
|
||||||
|
quantity: Quantity.create(item.quantity).object,
|
||||||
|
unitPrice: UnitPrice.create({
|
||||||
|
amount: item.unit_price.amount,
|
||||||
|
currencyCode: item.unit_price.currency,
|
||||||
|
precision: item.unit_price.precision,
|
||||||
|
}).object,
|
||||||
|
}).object
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!invoice_status.isDraft()) {
|
||||||
|
throw Error("Error al crear una factura que no es borrador");
|
||||||
|
}
|
||||||
|
|
||||||
|
return DraftInvoice.create(
|
||||||
|
{
|
||||||
|
invoiceSeries: invoice_series,
|
||||||
|
issueDate: issue_date,
|
||||||
|
operationDate: operation_date,
|
||||||
|
invoiceCurrency,
|
||||||
|
language: invoiceLanguage,
|
||||||
|
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||||
|
//notes: Note.create(invoiceDTO.notes).object,
|
||||||
|
|
||||||
|
//senderId: UniqueID.create(null).object,
|
||||||
|
recipient,
|
||||||
|
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateInvoiceResponseOrError =
|
||||||
|
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||||
|
| Result<Invoice, never>; // Success!
|
||||||
|
|
||||||
|
export class UpdateInvoiceUseCase2
|
||||||
|
implements
|
||||||
|
IUseCase<{ id: UniqueID; data: IUpdateInvoice_DTO }, Promise<UpdateInvoiceResponseOrError>>
|
||||||
|
{
|
||||||
|
private _context: IInvoicingContext;
|
||||||
|
private _adapter: ISequelizeAdapter;
|
||||||
|
private _repositoryManager: IRepositoryManager;
|
||||||
|
|
||||||
|
constructor(context: IInvoicingContext) {
|
||||||
|
this._context = context;
|
||||||
|
this._adapter = context.adapter;
|
||||||
|
this._repositoryManager = context.repositoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRepository<T>(name: string) {
|
||||||
|
return this._repositoryManager.getRepository<T>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleValidationFailure(
|
||||||
|
validationError: Error,
|
||||||
|
message?: string
|
||||||
|
): Result<never, IUseCaseError> {
|
||||||
|
return Result.fail<IUseCaseError>(
|
||||||
|
UseCaseError.create(
|
||||||
|
UseCaseError.INVALID_INPUT_DATA,
|
||||||
|
message ? message : validationError.message,
|
||||||
|
validationError
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(request: {
|
||||||
|
id: UniqueID;
|
||||||
|
data: IUpdateInvoice_DTO;
|
||||||
|
}): Promise<UpdateInvoiceResponseOrError> {
|
||||||
|
const { id, data: invoiceDTO } = request;
|
||||||
|
|
||||||
|
// Validaciones
|
||||||
|
const invoiceDTOOrError = ensureUpdateInvoice_DTOIsValid(invoiceDTO);
|
||||||
|
if (invoiceDTOOrError.isFailure) {
|
||||||
|
return this.handleValidationFailure(invoiceDTOOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
|
||||||
|
const invoiceRepoBuilder = this.getRepository<IInvoiceRepository>("Invoice");
|
||||||
|
|
||||||
|
let invoice: Invoice | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
invoice = await invoiceRepoBuilder({ transaction: t }).getById(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invoice === null) {
|
||||||
|
return Result.fail<IUseCaseError>(
|
||||||
|
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `Invoice not found`, {
|
||||||
|
id: request.id.toString(),
|
||||||
|
entity: "invoice",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<Invoice>(invoice);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as Error;
|
||||||
|
if (invoiceRepoBuilder().isRepositoryError(_error)) {
|
||||||
|
return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder());
|
||||||
|
} else {
|
||||||
|
return this.handleUnexceptedError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recipient validations
|
||||||
|
/*const recipientIdOrError = ensureParticipantIdIsValid(
|
||||||
|
invoiceDTO?.recipient?.id,
|
||||||
|
);
|
||||||
|
if (recipientIdOrError.isFailure) {
|
||||||
|
return this.handleValidationFailure(
|
||||||
|
recipientIdOrError.error,
|
||||||
|
"Recipient ID not valid",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const recipientId = recipientIdOrError.object;
|
||||||
|
|
||||||
|
const recipientBillingIdOrError = ensureParticipantAddressIdIsValid(
|
||||||
|
invoiceDTO?.recipient?.billing_address_id,
|
||||||
|
);
|
||||||
|
if (recipientBillingIdOrError.isFailure) {
|
||||||
|
return this.handleValidationFailure(
|
||||||
|
recipientBillingIdOrError.error,
|
||||||
|
"Recipient billing address ID not valid",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const recipientBillingId = recipientBillingIdOrError.object;
|
||||||
|
|
||||||
|
const recipientShippingIdOrError = ensureParticipantAddressIdIsValid(
|
||||||
|
invoiceDTO?.recipient?.shipping_address_id,
|
||||||
|
);
|
||||||
|
if (recipientShippingIdOrError.isFailure) {
|
||||||
|
return this.handleValidationFailure(
|
||||||
|
recipientShippingIdOrError.error,
|
||||||
|
"Recipient shipping address ID not valid",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const recipientShippingId = recipientShippingIdOrError.object;
|
||||||
|
|
||||||
|
const recipientContact = await this.findContact(
|
||||||
|
recipientId,
|
||||||
|
recipientBillingId,
|
||||||
|
recipientShippingId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!recipientContact) {
|
||||||
|
return this.handleValidationFailure(
|
||||||
|
new Error(`Recipient with ID ${recipientId.toString()} does not exist`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear invoice
|
||||||
|
const invoiceOrError = await this.tryUpdateInvoiceInstance(
|
||||||
|
invoiceDTO,
|
||||||
|
invoiceIdOrError.object,
|
||||||
|
//senderId,
|
||||||
|
//senderBillingId,
|
||||||
|
//senderShippingId,
|
||||||
|
recipientContact,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invoiceOrError.isFailure) {
|
||||||
|
const { error: domainError } = invoiceOrError;
|
||||||
|
let errorCode = "";
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
switch (domainError.code) {
|
||||||
|
case Invoice.ERROR_CUSTOMER_WITHOUT_NAME:
|
||||||
|
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||||
|
message =
|
||||||
|
"El cliente debe ser una compañía o tener nombre y apellidos.";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||||
|
message = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.fail<IUseCaseError>(
|
||||||
|
UseCaseError.create(errorCode, message, domainError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.saveInvoice(invoiceOrError.object);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tryUpdateInvoiceInstance(invoiceDTO, invoiceId, recipient) {
|
||||||
|
// Create invoice
|
||||||
|
let invoice_status = InvoiceStatus.create(invoiceDTO.status).object;
|
||||||
|
if (invoice_status.isEmpty()) {
|
||||||
|
invoice_status = InvoiceStatus.createDraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
if (invoice_series.isEmpty()) {
|
||||||
|
invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object;
|
||||||
|
if (issue_date.isEmpty()) {
|
||||||
|
issue_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object;
|
||||||
|
if (operation_date.isEmpty()) {
|
||||||
|
operation_date = InvoiceDate.createCurrentDate().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object;
|
||||||
|
|
||||||
|
if (invoiceCurrency.isEmpty()) {
|
||||||
|
invoiceCurrency = Currency.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object;
|
||||||
|
|
||||||
|
if (invoiceLanguage.isEmpty()) {
|
||||||
|
invoiceLanguage = Language.createDefaultCode().object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = new Collection<InvoiceItem>(
|
||||||
|
invoiceDTO.items?.map(
|
||||||
|
(item) =>
|
||||||
|
InvoiceSimpleItem.create({
|
||||||
|
description: Description.create(item.description).object,
|
||||||
|
quantity: Quantity.create(item.quantity).object,
|
||||||
|
unitPrice: UnitPrice.create({
|
||||||
|
amount: item.unit_price.amount,
|
||||||
|
currencyCode: item.unit_price.currency,
|
||||||
|
precision: item.unit_price.precision,
|
||||||
|
}).object,
|
||||||
|
}).object
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!invoice_status.isDraft()) {
|
||||||
|
throw Error("Error al crear una factura que no es borrador");
|
||||||
|
}
|
||||||
|
|
||||||
|
return DraftInvoice.create(
|
||||||
|
{
|
||||||
|
invoiceSeries: invoice_series,
|
||||||
|
issueDate: issue_date,
|
||||||
|
operationDate: operation_date,
|
||||||
|
invoiceCurrency,
|
||||||
|
language: invoiceLanguage,
|
||||||
|
invoiceNumber: InvoiceNumber.create(undefined).object,
|
||||||
|
//notes: Note.create(invoiceDTO.notes).object,
|
||||||
|
|
||||||
|
//senderId: UniqueID.create(null).object,
|
||||||
|
recipient,
|
||||||
|
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findContact(
|
||||||
|
contactId: UniqueID,
|
||||||
|
billingAddressId: UniqueID,
|
||||||
|
shippingAddressId: UniqueID
|
||||||
|
) {
|
||||||
|
const contactRepoBuilder = this.getRepository<IContactRepository>("Contact");
|
||||||
|
|
||||||
|
const contact = await contactRepoBuilder().getById2(
|
||||||
|
contactId,
|
||||||
|
billingAddressId,
|
||||||
|
shippingAddressId
|
||||||
|
);
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveInvoice(invoice: DraftInvoice) {
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
const invoiceRepoBuilder = this.getRepository<IInvoiceRepository>("Invoice");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
const invoiceRepo = invoiceRepoBuilder({ transaction: t });
|
||||||
|
await invoiceRepo.save(invoice);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.ok<DraftInvoice>(invoice);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as Error;
|
||||||
|
if (invoiceRepoBuilder().isRepositoryError(_error)) {
|
||||||
|
return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder());
|
||||||
|
} else {
|
||||||
|
return this.handleUnexceptedError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleUnexceptedError(error): Result<never, IUseCaseError> {
|
||||||
|
return Result.fail<IUseCaseError>(
|
||||||
|
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRepositoryError(
|
||||||
|
error: BaseError,
|
||||||
|
repository: IInvoiceRepository
|
||||||
|
): Result<never, IUseCaseError> {
|
||||||
|
const { message, details } = repository.handleRepositoryError(error);
|
||||||
|
return Result.fail<IUseCaseError>(
|
||||||
|
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
apps/server/src/contexts/invoicing/domain/Contact/Contact.ts
Normal file
64
apps/server/src/contexts/invoicing/domain/Contact/Contact.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { IDomainError } from "@/contexts/common/domain";
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
GenericAddress,
|
||||||
|
IGenericAddressProps,
|
||||||
|
Name,
|
||||||
|
Result,
|
||||||
|
TINNumber,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IContactProps {
|
||||||
|
tin: TINNumber;
|
||||||
|
companyName: Name;
|
||||||
|
firstName: Name;
|
||||||
|
lastName: Name;
|
||||||
|
|
||||||
|
billingAddress: GenericAddress<IGenericAddressProps>;
|
||||||
|
shippingAddress: GenericAddress<IGenericAddressProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContact {
|
||||||
|
id: UniqueID;
|
||||||
|
tin: TINNumber;
|
||||||
|
companyName: Name;
|
||||||
|
firstName: Name;
|
||||||
|
lastName: Name;
|
||||||
|
|
||||||
|
billingAddress: GenericAddress<IGenericAddressProps>;
|
||||||
|
shippingAddress: GenericAddress<IGenericAddressProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Contact extends Entity<IContactProps> implements IContact {
|
||||||
|
public static create(
|
||||||
|
props: IContactProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
): Result<Contact, IDomainError> {
|
||||||
|
const participant = new Contact(props, id);
|
||||||
|
return Result.ok<Contact>(participant);
|
||||||
|
}
|
||||||
|
get tin(): TINNumber {
|
||||||
|
return this.props.tin;
|
||||||
|
}
|
||||||
|
|
||||||
|
get companyName(): Name {
|
||||||
|
return this.props.companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get firstName(): Name {
|
||||||
|
return this.props.firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastName(): Name {
|
||||||
|
return this.props.lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get billingAddress() {
|
||||||
|
return this.props.billingAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shippingAddress() {
|
||||||
|
return this.props.shippingAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import { IRepository } from "@/contexts/common/domain/repositories";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { Contact } from ".";
|
||||||
|
|
||||||
|
export interface IContactRepository extends IRepository<Contact> {
|
||||||
|
getById(id: UniqueID): Promise<Contact | null>;
|
||||||
|
|
||||||
|
getById2(
|
||||||
|
id: UniqueID,
|
||||||
|
billingAddressId: UniqueID,
|
||||||
|
shippingAddressId: UniqueID,
|
||||||
|
): Promise<Contact | null>;
|
||||||
|
|
||||||
|
exists(id: UniqueID): Promise<boolean>;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./Contact";
|
||||||
|
export * from "./IContactRepository.interface";
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import {
|
||||||
|
GenericAddress,
|
||||||
|
IGenericAddress,
|
||||||
|
IGenericAddressProps,
|
||||||
|
Result,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export type ContactAddressType = "billing" | "shipping";
|
||||||
|
|
||||||
|
export interface IContactAddressProps extends IGenericAddressProps {}
|
||||||
|
|
||||||
|
export interface IContactAddress extends IGenericAddress {}
|
||||||
|
|
||||||
|
export class ContactAddress
|
||||||
|
extends GenericAddress<IContactAddressProps>
|
||||||
|
implements IContactAddress
|
||||||
|
{
|
||||||
|
public static create(props: IContactAddressProps, id?: UniqueID) {
|
||||||
|
return Result.ok(new this(props, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./ContactAddress";
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import {
|
||||||
|
Description,
|
||||||
|
Entity,
|
||||||
|
IEntityProps,
|
||||||
|
MoneyValue,
|
||||||
|
Quantity,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IInvoiceBaseItemProps extends IEntityProps {
|
||||||
|
description: Description; // Descripción del artículo o servicio
|
||||||
|
quantity: Quantity; // Cantidad de unidades
|
||||||
|
unitPrice: MoneyValue; // Precio unitario en la moneda de la factura
|
||||||
|
//tax: Tax; // Tasa de impuesto en decimal (por ejemplo, 0.15 para 15%)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInvoiceBaseItem {
|
||||||
|
description: Description;
|
||||||
|
quantity: Quantity;
|
||||||
|
unitPrice: MoneyValue;
|
||||||
|
//unitMeasure: string;
|
||||||
|
//tax: Tax;
|
||||||
|
//dto: Percentage | Number(10, 4) ???;
|
||||||
|
/*calculateSubtotal: () => number;
|
||||||
|
calculateTaxAmount: () => number;
|
||||||
|
calculateDtoAmount: () => number;
|
||||||
|
calculateTotal: () => number;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class InvoiceBaseItem<P extends IEntityProps>
|
||||||
|
extends Entity<P>
|
||||||
|
implements IInvoiceBaseItem
|
||||||
|
{
|
||||||
|
// Método para calcular el total antes de impuestos
|
||||||
|
calculateSubtotal(): MoneyValue {
|
||||||
|
return this.unitPrice.multiply(this.quantity.toNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para calcular el monto del impuesto
|
||||||
|
calculateTaxAmount(): MoneyValue {
|
||||||
|
return MoneyValue.create({ amount: 0, precision: 4 }).object;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para calcular el total incluyendo impuestos
|
||||||
|
calculateTotal(): MoneyValue {
|
||||||
|
return this.calculateSubtotal().add(this.calculateTaxAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters para acceder a los atributos privados
|
||||||
|
get description(): Description {
|
||||||
|
return this.props.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quantity(): Quantity {
|
||||||
|
return this.props.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
get unitPrice(): MoneyValue {
|
||||||
|
return this.props.unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
get taxRate(): number {
|
||||||
|
return this.props.taxRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
IInvoiceBaseItem,
|
||||||
|
IInvoiceBaseItemProps,
|
||||||
|
InvoiceBaseItem,
|
||||||
|
} from "./InvoiceBaseItem";
|
||||||
|
|
||||||
|
export interface IInvoiceBudgetItemProps extends IInvoiceBaseItemProps {}
|
||||||
|
|
||||||
|
export interface IInvoiceBudgetItem extends IInvoiceBaseItem {}
|
||||||
|
|
||||||
|
export class InvoiceBudgetItem
|
||||||
|
extends InvoiceBaseItem<IInvoiceBudgetItemProps>
|
||||||
|
implements IInvoiceBudgetItem {
|
||||||
|
//private contents: (InvoiceLineItem | InvoiceChapter)[] = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
export interface WithInvoiceItems {
|
||||||
|
items: InvoiceItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvoiceIems<T> {
|
||||||
|
private items: T[] = [];
|
||||||
|
|
||||||
|
public length(): number {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public lastPosition(): number {}
|
||||||
|
|
||||||
|
public positionIsValid(position: number): boolean {}
|
||||||
|
|
||||||
|
public delete(position: number): void {
|
||||||
|
if (position >= 0 && position < this.items.length) {
|
||||||
|
this.items.splice(position, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { IDomainError } from "@/contexts/common/domain";
|
||||||
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
IInvoiceBaseItem,
|
||||||
|
IInvoiceBaseItemProps,
|
||||||
|
InvoiceBaseItem,
|
||||||
|
} from "./InvoiceBaseItem";
|
||||||
|
|
||||||
|
export interface IInvoiceSimpleItemProps extends IInvoiceBaseItemProps {}
|
||||||
|
|
||||||
|
export interface IInvoiceSimpleItem extends IInvoiceBaseItem {}
|
||||||
|
|
||||||
|
export class InvoiceSimpleItem
|
||||||
|
extends InvoiceBaseItem<IInvoiceSimpleItemProps>
|
||||||
|
implements IInvoiceSimpleItem
|
||||||
|
{
|
||||||
|
public static create(
|
||||||
|
props: IInvoiceSimpleItemProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
): Result<InvoiceSimpleItem, IDomainError> {
|
||||||
|
return Result.ok(new InvoiceSimpleItem(props, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { InvoiceBudgetItem } from "./InvoiceBudgetItem";
|
||||||
|
import { InvoiceSimpleItem } from "./InvoiceSimpleItem";
|
||||||
|
|
||||||
|
export * from "./InvoiceBudgetItem";
|
||||||
|
export * from "./InvoiceSimpleItem";
|
||||||
|
|
||||||
|
export type InvoiceItem = InvoiceSimpleItem | InvoiceBudgetItem;
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import { IRepository } from "@/contexts/common/domain/repositories";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipant } from ".";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantRepository
|
||||||
|
extends IRepository<InvoiceParticipant> {
|
||||||
|
getById(id: UniqueID): Promise<InvoiceParticipant | null>;
|
||||||
|
exists(id: UniqueID): Promise<boolean>;
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { IDomainError } from "@/contexts/common/domain";
|
||||||
|
import { Entity, Name, Result, TINNumber, UniqueID } from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
InvoiceParticipantBillingAddress,
|
||||||
|
InvoiceParticipantShippingAddress,
|
||||||
|
} from "../InvoiceParticipantAddress";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantProps {
|
||||||
|
tin: TINNumber;
|
||||||
|
companyName: Name;
|
||||||
|
firstName: Name;
|
||||||
|
lastName: Name;
|
||||||
|
|
||||||
|
billingAddress?: InvoiceParticipantBillingAddress;
|
||||||
|
shippingAddress?: InvoiceParticipantShippingAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInvoiceParticipant {
|
||||||
|
id: UniqueID;
|
||||||
|
tin: TINNumber;
|
||||||
|
companyName: Name;
|
||||||
|
firstName: Name;
|
||||||
|
lastName: Name;
|
||||||
|
|
||||||
|
billingAddress?: InvoiceParticipantBillingAddress;
|
||||||
|
shippingAddress?: InvoiceParticipantShippingAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvoiceParticipant
|
||||||
|
extends Entity<IInvoiceParticipantProps>
|
||||||
|
implements IInvoiceParticipant
|
||||||
|
{
|
||||||
|
public static create(
|
||||||
|
props: IInvoiceParticipantProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
): Result<InvoiceParticipant, IDomainError> {
|
||||||
|
const participant = new InvoiceParticipant(props, id);
|
||||||
|
return Result.ok<InvoiceParticipant>(participant);
|
||||||
|
}
|
||||||
|
|
||||||
|
get tin(): TINNumber {
|
||||||
|
return this.props.tin;
|
||||||
|
}
|
||||||
|
|
||||||
|
get companyName(): Name {
|
||||||
|
return this.props.companyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get firstName(): Name {
|
||||||
|
return this.props.firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastName(): Name {
|
||||||
|
return this.props.lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get billingAddress() {
|
||||||
|
return this.props.billingAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shippingAddress() {
|
||||||
|
return this.props.shippingAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { InvoiceParticipant } from "./InvoiceParticipant";
|
||||||
|
|
||||||
|
export class Recipient extends InvoiceParticipant {}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { InvoiceParticipant } from "./InvoiceParticipant";
|
||||||
|
|
||||||
|
export class Supplier extends InvoiceParticipant {}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./IInvoiceParticipantRepository.interface";
|
||||||
|
export * from "./InvoiceParticipant";
|
||||||
|
export * from "./Recipient";
|
||||||
|
export * from "./Supplier";
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import { IRepository } from "@/contexts/common/domain/repositories";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipantAddress } from "./InvoiceParticipantAddress";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantAddressRepository
|
||||||
|
extends IRepository<InvoiceParticipantAddress> {
|
||||||
|
getById(id: UniqueID): Promise<InvoiceParticipantAddress | null>;
|
||||||
|
exists(id: UniqueID): Promise<boolean>;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import {
|
||||||
|
GenericAddress,
|
||||||
|
IGenericAddress,
|
||||||
|
IGenericAddressProps,
|
||||||
|
Result,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export type InvoiceParticipantAddressType = "billing" | "shipping";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantAddressProps extends IGenericAddressProps {}
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantAddress extends IGenericAddress {}
|
||||||
|
|
||||||
|
export class InvoiceParticipantAddress
|
||||||
|
extends GenericAddress<IInvoiceParticipantAddressProps>
|
||||||
|
implements IInvoiceParticipantAddress
|
||||||
|
{
|
||||||
|
public static create(props: IInvoiceParticipantAddressProps, id?: UniqueID) {
|
||||||
|
return Result.ok(new this(props, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantAddress,
|
||||||
|
IInvoiceParticipantAddressProps,
|
||||||
|
InvoiceParticipantAddress,
|
||||||
|
} from "./InvoiceParticipantAddress";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantBillingAddressProps
|
||||||
|
extends Omit<IInvoiceParticipantAddressProps, "type"> {}
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantBillingAddress
|
||||||
|
extends IInvoiceParticipantAddress {}
|
||||||
|
|
||||||
|
export class InvoiceParticipantBillingAddress
|
||||||
|
extends InvoiceParticipantAddress
|
||||||
|
implements IInvoiceParticipantBillingAddress
|
||||||
|
{
|
||||||
|
public static create(
|
||||||
|
props: IInvoiceParticipantBillingAddressProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
) {
|
||||||
|
const address = new InvoiceParticipantAddress(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
type: "billing",
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok<InvoiceParticipantBillingAddress>(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantAddress,
|
||||||
|
IInvoiceParticipantAddressProps,
|
||||||
|
InvoiceParticipantAddress,
|
||||||
|
} from "./InvoiceParticipantAddress";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantShippingAddressProps
|
||||||
|
extends Omit<IInvoiceParticipantAddressProps, "type"> {}
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantShippingAddress
|
||||||
|
extends IInvoiceParticipantAddress {}
|
||||||
|
|
||||||
|
export class InvoiceParticipantShippingAddress
|
||||||
|
extends InvoiceParticipantAddress
|
||||||
|
implements IInvoiceParticipantShippingAddress
|
||||||
|
{
|
||||||
|
public static create(
|
||||||
|
props: IInvoiceParticipantShippingAddressProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
) {
|
||||||
|
const address = new InvoiceParticipantAddress(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
type: "shipping",
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok<InvoiceParticipantShippingAddress>(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./InvoiceParticipantAddress";
|
||||||
|
export * from "./InvoiceParticipantAddress.repository.interface";
|
||||||
|
export * from "./InvoiceParticipantBillingAddress";
|
||||||
|
export * from "./InvoiceParticipantShippingAddress";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./invoice";
|
||||||
208
apps/server/src/contexts/invoicing/domain/aggregates/invoice.ts
Normal file
208
apps/server/src/contexts/invoicing/domain/aggregates/invoice.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@common/domain";
|
||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { Currency } from "dinero.js";
|
||||||
|
import { InvoiceStatus } from "../value-objects";
|
||||||
|
|
||||||
|
export interface IInvoiceProps {
|
||||||
|
invoiceNumber: InvoiceNumber;
|
||||||
|
invoiceSeries: InvoiceSeries;
|
||||||
|
|
||||||
|
issueDate: UtcDate;
|
||||||
|
operationDate: UtcDate;
|
||||||
|
|
||||||
|
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
||||||
|
|
||||||
|
//tax: Tax; // ? --> detalles?
|
||||||
|
invoiceCurrency: Currency;
|
||||||
|
|
||||||
|
language: Language;
|
||||||
|
|
||||||
|
//purchareOrderNumber: string;
|
||||||
|
//notes: Note;
|
||||||
|
|
||||||
|
//senderId: UniqueID;
|
||||||
|
|
||||||
|
recipient: InvoiceParticipant;
|
||||||
|
|
||||||
|
//paymentInstructions: Note;
|
||||||
|
//paymentTerms: string;
|
||||||
|
|
||||||
|
items: Collection<InvoiceItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInvoice {
|
||||||
|
id: UniqueID;
|
||||||
|
invoiceNumber: InvoiceNumber;
|
||||||
|
invoiceSeries: InvoiceSeries;
|
||||||
|
|
||||||
|
status: InvoiceStatus;
|
||||||
|
|
||||||
|
issueDate: UtcDate;
|
||||||
|
operationDate: UtcDate;
|
||||||
|
|
||||||
|
//senderId: UniqueID;
|
||||||
|
|
||||||
|
recipient: InvoiceParticipant;
|
||||||
|
|
||||||
|
//dueDate
|
||||||
|
|
||||||
|
//tax: Tax;
|
||||||
|
language: Language;
|
||||||
|
currency: Currency;
|
||||||
|
|
||||||
|
//purchareOrderNumber: string;
|
||||||
|
//notes: Note;
|
||||||
|
|
||||||
|
//paymentInstructions: Note;
|
||||||
|
//paymentTerms: string;
|
||||||
|
|
||||||
|
items: Collection<InvoiceItem>;
|
||||||
|
|
||||||
|
calculateSubtotal: () => MoneyValue;
|
||||||
|
calculateTaxTotal: () => MoneyValue;
|
||||||
|
calculateTotal: () => MoneyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
|
||||||
|
private _items: Collection<InvoiceItem>;
|
||||||
|
protected _status: InvoiceStatus;
|
||||||
|
|
||||||
|
static create(props: IInvoiceProps, id?: UniqueID): Result<Invoice, Error> {
|
||||||
|
const invoice = new Invoice(props, id);
|
||||||
|
|
||||||
|
// Reglas de negocio / validaciones
|
||||||
|
// ...
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// 🔹 Disparar evento de dominio "InvoiceAuthenticatedEvent"
|
||||||
|
//const { invoice } = props;
|
||||||
|
//user.addDomainEvent(new InvoiceAuthenticatedEvent(id, invoice.toString()));
|
||||||
|
|
||||||
|
return Result.ok(invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
get invoiceNumber() {
|
||||||
|
return this.props.invoiceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
get invoiceSeries() {
|
||||||
|
return this.props.invoiceSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
get issueDate() {
|
||||||
|
return this.props.issueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*get senderId(): UniqueID {
|
||||||
|
return this.props.senderId;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
get recipient(): InvoiceParticipant {
|
||||||
|
return this.props.recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
get operationDate() {
|
||||||
|
return this.props.operationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
get language() {
|
||||||
|
return this.props.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dueDate() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tax() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this._status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*get purchareOrderNumber() {
|
||||||
|
return this.props.purchareOrderNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
get paymentInstructions() {
|
||||||
|
return this.props.paymentInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get paymentTerms() {
|
||||||
|
return this.props.paymentTerms;
|
||||||
|
}
|
||||||
|
|
||||||
|
get billTo() {
|
||||||
|
return this.props.billTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shipTo() {
|
||||||
|
return this.props.shipTo;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
get currency() {
|
||||||
|
return this.props.invoiceCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*get notes() {
|
||||||
|
return this.props.notes;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Method to get the complete list of line items
|
||||||
|
/*get lineItems(): InvoiceLineItem[] {
|
||||||
|
return this._lineItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
addLineItem(lineItem: InvoiceLineItem, position?: number): void {
|
||||||
|
if (position === undefined) {
|
||||||
|
this._lineItems.push(lineItem);
|
||||||
|
} else {
|
||||||
|
this._lineItems.splice(position, 0, lineItem);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
calculateSubtotal(): MoneyValue {
|
||||||
|
let subtotal: MoneyValue | null = null;
|
||||||
|
|
||||||
|
for (const item of this._items.items) {
|
||||||
|
if (!subtotal) {
|
||||||
|
subtotal = item.calculateSubtotal();
|
||||||
|
} else {
|
||||||
|
subtotal = subtotal.add(item.calculateSubtotal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subtotal
|
||||||
|
? subtotal.convertPrecision(2)
|
||||||
|
: MoneyValue.create({
|
||||||
|
amount: 0,
|
||||||
|
currencyCode: this.props.invoiceCurrency.code,
|
||||||
|
precision: 2,
|
||||||
|
}).object;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to calculate the total tax in the invoice
|
||||||
|
calculateTaxTotal(): MoneyValue {
|
||||||
|
let taxTotal = MoneyValue.create({
|
||||||
|
amount: 0,
|
||||||
|
currencyCode: this.props.invoiceCurrency.code,
|
||||||
|
precision: 2,
|
||||||
|
}).object;
|
||||||
|
|
||||||
|
for (const item of this._items.items) {
|
||||||
|
taxTotal = taxTotal.add(item.calculateTaxAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxTotal.convertPrecision(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to calculate the total invoice amount, including taxes
|
||||||
|
calculateTotal(): MoneyValue {
|
||||||
|
return this.calculateSubtotal().add(this.calculateTaxTotal()).convertPrecision(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
apps/server/src/contexts/invoicing/domain/index.ts
Normal file
6
apps/server/src/contexts/invoicing/domain/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./aggregates";
|
||||||
|
export * from "./Contact";
|
||||||
|
export * from "./ContactAddress.ts";
|
||||||
|
export * from "./InvoiceItems";
|
||||||
|
export * from "./InvoiceParticipant";
|
||||||
|
export * from "./InvoiceParticipantAddress";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./invoice-repository.interface";
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { Invoice } from "../aggregates";
|
||||||
|
|
||||||
|
export interface IInvoiceRepository {
|
||||||
|
findAll(transaction?: any): Promise<Result<Collection<Invoice>, Error>>;
|
||||||
|
findById(id: UniqueID, transaction?: any): Promise<Result<Invoice, Error>>;
|
||||||
|
deleteById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
create(invoice: Invoice, transaction?: any): Promise<void>;
|
||||||
|
update(invoice: Invoice, transaction?: any): Promise<void>;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./invoice-service.interface";
|
||||||
|
export * from "./invoice.service";
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { IInvoiceProps, Invoice } from "../aggregates";
|
||||||
|
|
||||||
|
export interface IInvoiceService {
|
||||||
|
findInvoices(transaction?: any): Promise<Result<Collection<Invoice>, Error>>;
|
||||||
|
findInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<Invoice>>;
|
||||||
|
|
||||||
|
updateInvoiceById(
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
data: Partial<IInvoiceProps>,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<Invoice, Error>>;
|
||||||
|
|
||||||
|
createInvoice(
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
data: IInvoiceProps,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<Invoice, Error>>;
|
||||||
|
|
||||||
|
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<Invoice, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { IInvoiceProps, Invoice } from "../aggregates";
|
||||||
|
import { IInvoiceRepository } from "../repositories";
|
||||||
|
import { IInvoiceService } from "./invoice-service.interface";
|
||||||
|
|
||||||
|
export class InvoiceService implements IInvoiceService {
|
||||||
|
constructor(private readonly repo: IInvoiceRepository) {}
|
||||||
|
|
||||||
|
async findInvoices(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
|
||||||
|
const invoicesOrError = await this.repo.findAll(transaction);
|
||||||
|
if (invoicesOrError.isFailure) {
|
||||||
|
return Result.fail(invoicesOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solo devolver usuarios activos
|
||||||
|
//const allInvoices = invoicesOrError.data.filter((invoice) => invoice.isActive);
|
||||||
|
//return Result.ok(new Collection(allInvoices));
|
||||||
|
|
||||||
|
return invoicesOrError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findInvoiceById(invoiceId: UniqueID, transaction?: Transaction): Promise<Result<Invoice>> {
|
||||||
|
return await this.repo.findById(invoiceId, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateInvoiceById(
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
data: Partial<IInvoiceProps>,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Invoice, Error>> {
|
||||||
|
// Verificar si la cuenta existe
|
||||||
|
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||||
|
if (invoiceOrError.isFailure) {
|
||||||
|
return Result.fail(new Error("Invoice not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedInvoiceOrError = Invoice.update(invoiceOrError.data, data);
|
||||||
|
if (updatedInvoiceOrError.isFailure) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(`Error updating invoice: ${updatedInvoiceOrError.error.message}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateInvoice = updatedInvoiceOrError.data;
|
||||||
|
|
||||||
|
await this.repo.update(updateInvoice, transaction);
|
||||||
|
return Result.ok(updateInvoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvoice(
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
data: IInvoiceProps,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Invoice, Error>> {
|
||||||
|
// Verificar si la cuenta existe
|
||||||
|
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||||
|
if (invoiceOrError.isSuccess) {
|
||||||
|
return Result.fail(new Error("Invoice exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newInvoiceOrError = Invoice.create(data, invoiceId);
|
||||||
|
if (newInvoiceOrError.isFailure) {
|
||||||
|
return Result.fail(new Error(`Error creating invoice: ${newInvoiceOrError.error.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newInvoice = newInvoiceOrError.data;
|
||||||
|
|
||||||
|
await this.repo.create(newInvoice, transaction);
|
||||||
|
return Result.ok(newInvoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteInvoiceById(
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Invoice, Error>> {
|
||||||
|
// Verificar si la cuenta existe
|
||||||
|
const invoiceOrError = await this.repo.findById(invoiceId, transaction);
|
||||||
|
if (invoiceOrError.isFailure) {
|
||||||
|
return Result.fail(new Error("Invoice not exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newInvoiceOrError = Invoice.create(data, invoiceId);
|
||||||
|
if (newInvoiceOrError.isFailure) {
|
||||||
|
return Result.fail(new Error(`Error creating invoice: ${newInvoiceOrError.error.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newInvoice = newInvoiceOrError.data;
|
||||||
|
|
||||||
|
await this.repo.create(newInvoice, transaction);
|
||||||
|
return Result.ok(newInvoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./invoice-status";
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { UndefinedOr } from "../../../../utilities";
|
||||||
|
import {
|
||||||
|
IStringValueObjectOptions,
|
||||||
|
Result,
|
||||||
|
RuleValidator,
|
||||||
|
StringValueObject,
|
||||||
|
} from "../../../common";
|
||||||
|
|
||||||
|
export class InvoiceNumber extends StringValueObject {
|
||||||
|
protected static validate(
|
||||||
|
value: UndefinedOr<string>,
|
||||||
|
options: IStringValueObjectOptions,
|
||||||
|
) {
|
||||||
|
const rule = Joi.string()
|
||||||
|
.allow(null, "")
|
||||||
|
.default("")
|
||||||
|
.trim()
|
||||||
|
.label(options.label ? options.label : "value");
|
||||||
|
|
||||||
|
return RuleValidator.validate<string>(rule, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
value: UndefinedOr<string>,
|
||||||
|
options: IStringValueObjectOptions = {},
|
||||||
|
) {
|
||||||
|
const _options = {
|
||||||
|
label: "invoice_number",
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationResult = InvoiceNumber.validate(value, _options);
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new InvoiceNumber(validationResult.object));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { UndefinedOr } from "../../../../utilities";
|
||||||
|
import {
|
||||||
|
IStringValueObjectOptions,
|
||||||
|
Result,
|
||||||
|
RuleValidator,
|
||||||
|
StringValueObject,
|
||||||
|
} from "../../../common";
|
||||||
|
|
||||||
|
export class InvoiceSeries extends StringValueObject {
|
||||||
|
protected static validate(
|
||||||
|
value: UndefinedOr<string>,
|
||||||
|
options: IStringValueObjectOptions,
|
||||||
|
) {
|
||||||
|
const rule = Joi.string()
|
||||||
|
.allow(null, "")
|
||||||
|
.default("")
|
||||||
|
.trim()
|
||||||
|
.label(options.label ? options.label : "value");
|
||||||
|
|
||||||
|
return RuleValidator.validate<string>(rule, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
value: UndefinedOr<string>,
|
||||||
|
options: IStringValueObjectOptions = {},
|
||||||
|
) {
|
||||||
|
const _options = {
|
||||||
|
label: "invoice_series",
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationResult = InvoiceSeries.validate(value, _options);
|
||||||
|
InvoiceSeries;
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new InvoiceSeries(validationResult.object));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import { ValueObject } from "@common/domain";
|
||||||
|
import { Result } from "@common/helpers";
|
||||||
|
|
||||||
|
interface IInvoiceStatusProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum INVOICE_STATUS {
|
||||||
|
DRAFT = "draft",
|
||||||
|
EMITTED = "emitted",
|
||||||
|
SENT = "sent",
|
||||||
|
REJECTED = "rejected",
|
||||||
|
}
|
||||||
|
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
||||||
|
private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "rejected"];
|
||||||
|
|
||||||
|
private static readonly TRANSITIONS: Record<string, string[]> = {
|
||||||
|
draft: [INVOICE_STATUS.EMITTED],
|
||||||
|
emitted: [INVOICE_STATUS.SENT, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT],
|
||||||
|
sent: [INVOICE_STATUS.REJECTED],
|
||||||
|
rejected: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
static create(value: string): Result<InvoiceStatus, Error> {
|
||||||
|
if (!this.ALLOWED_STATUSES.includes(value)) {
|
||||||
|
return Result.fail(new Error(`Estado de la factura no válido: ${value}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
value === "rejected"
|
||||||
|
? InvoiceStatus.createRejected()
|
||||||
|
: value === "sent"
|
||||||
|
? InvoiceStatus.createSent()
|
||||||
|
: value === "emitted"
|
||||||
|
? InvoiceStatus.createSent()
|
||||||
|
: InvoiceStatus.createDraft()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createDraft(): InvoiceStatus {
|
||||||
|
return new InvoiceStatus({ value: INVOICE_STATUS.DRAFT });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createEmitted(): InvoiceStatus {
|
||||||
|
return new InvoiceStatus({ value: INVOICE_STATUS.EMITTED });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createSent(): InvoiceStatus {
|
||||||
|
return new InvoiceStatus({ value: INVOICE_STATUS.SENT });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createRejected(): InvoiceStatus {
|
||||||
|
return new InvoiceStatus({ value: INVOICE_STATUS.REJECTED });
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
canTransitionTo(nextStatus: string): boolean {
|
||||||
|
return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
transitionTo(nextStatus: string): Result<InvoiceStatus, Error> {
|
||||||
|
if (!this.canTransitionTo(nextStatus)) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return InvoiceStatus.create(nextStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
SequelizeRepository,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { Contact, IContactRepository } from "../domain/Contact";
|
||||||
|
import { IContactMapper } from "./mappers/contact.mapper";
|
||||||
|
|
||||||
|
export class ContactRepository
|
||||||
|
extends SequelizeRepository<Contact>
|
||||||
|
implements IContactRepository
|
||||||
|
{
|
||||||
|
protected mapper: IContactMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IContactMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById2(
|
||||||
|
id: UniqueID,
|
||||||
|
billingAddressId: UniqueID,
|
||||||
|
shippingAddressId: UniqueID,
|
||||||
|
) {
|
||||||
|
const Contact_Model = this.adapter.getModel("Contact_Model");
|
||||||
|
const ContactAddress_Model = this.adapter.getModel("ContactAddress_Model");
|
||||||
|
|
||||||
|
const rawContact: any = await Contact_Model.findOne({
|
||||||
|
where: { id: id.toString() },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: ContactAddress_Model,
|
||||||
|
as: "billingAddress",
|
||||||
|
where: {
|
||||||
|
id: billingAddressId.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ContactAddress_Model,
|
||||||
|
as: "shippingAddress",
|
||||||
|
where: {
|
||||||
|
id: shippingAddressId.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
transaction: this.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rawContact === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(id: UniqueID): Promise<Contact | null> {
|
||||||
|
const rawContact: any = await this._getById("Contact_Model", id, {
|
||||||
|
include: [{ all: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rawContact === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(id: UniqueID): Promise<boolean> {
|
||||||
|
return this._exists("Customer", "id", id.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import { SequelizeRepository } from "@/contexts/common/infrastructure/sequelize/SequelizeRepository";
|
||||||
|
|
||||||
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { IInvoiceRepository, Invoice } from "../domain";
|
||||||
|
import { IInvoiceMapper } from "./mappers";
|
||||||
|
|
||||||
|
export type QueryParams = {
|
||||||
|
pagination: Record<string, any>;
|
||||||
|
filters: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InvoiceRepository
|
||||||
|
extends SequelizeRepository<Invoice>
|
||||||
|
implements IInvoiceRepository
|
||||||
|
{
|
||||||
|
protected mapper: IInvoiceMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IInvoiceMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(id: UniqueID): Promise<Invoice | null> {
|
||||||
|
const rawContact: any = await this._getById("Invoice_Model", id, {
|
||||||
|
include: [
|
||||||
|
{ association: "items" },
|
||||||
|
{
|
||||||
|
association: "participants",
|
||||||
|
include: [
|
||||||
|
{ association: "shippingAddress" },
|
||||||
|
{ association: "billingAddress" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rawContact === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findAll(
|
||||||
|
queryCriteria?: IQueryCriteria
|
||||||
|
): Promise<ICollection<Invoice>> {
|
||||||
|
const { rows, count } = await this._findAll(
|
||||||
|
"Invoice_Model",
|
||||||
|
queryCriteria,
|
||||||
|
{
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
association: "participants",
|
||||||
|
separate: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(invoice: Invoice): Promise<void> {
|
||||||
|
const { items, participants, ...invoiceData } =
|
||||||
|
this.mapper.mapToPersistence(invoice);
|
||||||
|
|
||||||
|
await this.adapter
|
||||||
|
.getModel("Invoice_Model")
|
||||||
|
.create(invoiceData, { transaction: this.transaction });
|
||||||
|
|
||||||
|
await this.adapter
|
||||||
|
.getModel("InvoiceItem_Model")
|
||||||
|
.bulkCreate(items, { transaction: this.transaction });
|
||||||
|
|
||||||
|
await this.adapter
|
||||||
|
.getModel("InvoiceParticipant_Model")
|
||||||
|
.bulkCreate(participants, { transaction: this.transaction });
|
||||||
|
|
||||||
|
await this.adapter
|
||||||
|
.getModel("InvoiceParticipantAddress_Model")
|
||||||
|
.bulkCreate(
|
||||||
|
[participants[0].billingAddress, participants[0].shippingAddress],
|
||||||
|
{ transaction: this.transaction }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeById(id: UniqueID): Promise<void> {
|
||||||
|
return this._removeById("Invoice_Model", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(id: UniqueID): Promise<boolean> {
|
||||||
|
return this._exists("Invoice_Model", "id", id.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
SequelizeRepository,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { InvoiceParticipant } from "../domain";
|
||||||
|
import { IInvoiceParticipantMapper } from "./mappers";
|
||||||
|
|
||||||
|
export class InvoiceParticipantRepository extends SequelizeRepository<InvoiceParticipant> {
|
||||||
|
protected mapper: IInvoiceParticipantMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IInvoiceParticipantMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public async getParticipantById(
|
||||||
|
id: UniqueID,
|
||||||
|
): Promise<InvoiceParticipant | null> {
|
||||||
|
const rawParticipant: any = await this._getById(
|
||||||
|
"InvoiceParticipant_Model",
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
include: [{ all: true }],
|
||||||
|
raw: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rawParticipant === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getContactById(id: UniqueID): Promise<any | null> {
|
||||||
|
const rawContact: any = await this._getById("Customer", id, {
|
||||||
|
include: [{ all: true }],
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rawContact === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawContact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(id: UniqueID): Promise<boolean> {
|
||||||
|
return this._exists("Customer", "id", id.toString());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
SequelizeRepository,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { InvoiceParticipantAddress } from "../domain";
|
||||||
|
import { IInvoiceParticipantAddressMapper } from "./mappers";
|
||||||
|
|
||||||
|
export class InvoiceParticipantAddressRepository extends SequelizeRepository<InvoiceParticipantAddress> {
|
||||||
|
protected mapper: IInvoiceParticipantAddressMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IInvoiceParticipantAddressMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(
|
||||||
|
id: UniqueID,
|
||||||
|
): Promise<InvoiceParticipantAddress | null> {
|
||||||
|
const rawParticipant: any = await this._getById(
|
||||||
|
"InvoiceParticipantAddress_Model",
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
include: [{ all: true }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rawParticipant === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(id: UniqueID): Promise<boolean> {
|
||||||
|
return this._exists("CustomerAddress", "id", id.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
IRepositoryManager,
|
||||||
|
RepositoryManager,
|
||||||
|
} from "@/contexts/common/domain";
|
||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
createSequelizeAdapter,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { InvoicingServices, TInvoicingServices } from "../application";
|
||||||
|
|
||||||
|
export interface IInvoicingContext {
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
repositoryManager: IRepositoryManager;
|
||||||
|
services: TInvoicingServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvoicingContext {
|
||||||
|
private static instance: InvoicingContext | null = null;
|
||||||
|
public static getInstance(): InvoicingContext {
|
||||||
|
if (!InvoicingContext.instance) {
|
||||||
|
InvoicingContext.instance = new InvoicingContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvoicingContext.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private context: IInvoicingContext;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.context = {
|
||||||
|
adapter: createSequelizeAdapter(),
|
||||||
|
repositoryManager: RepositoryManager.getInstance(),
|
||||||
|
services: InvoicingServices,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContext(): IInvoicingContext {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedInvoicingContext = InvoicingContext.getInstance().getContext();
|
||||||
|
export { sharedInvoicingContext };
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./sequelize";
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import { Name, TINNumber, UniqueID } from "@shared/contexts";
|
||||||
|
|
||||||
|
import { Contact, IContactProps } from "../../domain";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import {
|
||||||
|
Contact_Model,
|
||||||
|
TCreationContact_Model,
|
||||||
|
} from "../sequelize/contact.model";
|
||||||
|
import {
|
||||||
|
IContactAddressMapper,
|
||||||
|
createContactAddressMapper,
|
||||||
|
} from "./contactAddress.mapper";
|
||||||
|
|
||||||
|
export interface IContactMapper
|
||||||
|
extends ISequelizeMapper<Contact_Model, TCreationContact_Model, Contact> {}
|
||||||
|
|
||||||
|
class ContactMapper
|
||||||
|
extends SequelizeMapper<Contact_Model, TCreationContact_Model, Contact>
|
||||||
|
implements IContactMapper
|
||||||
|
{
|
||||||
|
public constructor(props: {
|
||||||
|
addressMapper: IContactAddressMapper;
|
||||||
|
context: IInvoicingContext;
|
||||||
|
}) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toDomainMappingImpl(source: Contact_Model, params: any): Contact {
|
||||||
|
if (!source.billingAddress) {
|
||||||
|
this.handleRequiredFieldError(
|
||||||
|
"billingAddress",
|
||||||
|
new Error("Missing participant's billing address")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.shippingAddress) {
|
||||||
|
this.handleRequiredFieldError(
|
||||||
|
"shippingAddress",
|
||||||
|
new Error("Missing participant's shipping address")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const billingAddress = this.props.addressMapper.mapToDomain(
|
||||||
|
source.billingAddress!,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const shippingAddress = this.props.addressMapper.mapToDomain(
|
||||||
|
source.shippingAddress!,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const props: IContactProps = {
|
||||||
|
tin: this.mapsValue(source, "tin", TINNumber.create),
|
||||||
|
firstName: this.mapsValue(source, "first_name", Name.create),
|
||||||
|
lastName: this.mapsValue(source, "last_name", Name.create),
|
||||||
|
companyName: this.mapsValue(source, "company_name", Name.create),
|
||||||
|
billingAddress,
|
||||||
|
shippingAddress,
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
|
const contactOrError = Contact.create(props, id);
|
||||||
|
|
||||||
|
if (contactOrError.isFailure) {
|
||||||
|
throw contactOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contactOrError.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createContactMapper = (
|
||||||
|
context: IInvoicingContext
|
||||||
|
): IContactMapper =>
|
||||||
|
new ContactMapper({
|
||||||
|
addressMapper: createContactAddressMapper(context),
|
||||||
|
context,
|
||||||
|
});
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import {
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
Email,
|
||||||
|
Note,
|
||||||
|
Phone,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { ContactAddress, IContactAddressProps } from "../../domain";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import {
|
||||||
|
ContactAddress_Model,
|
||||||
|
TCreationContactAddress_Attributes,
|
||||||
|
} from "../sequelize";
|
||||||
|
|
||||||
|
export interface IContactAddressMapper
|
||||||
|
extends ISequelizeMapper<
|
||||||
|
ContactAddress_Model,
|
||||||
|
TCreationContactAddress_Attributes,
|
||||||
|
ContactAddress
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export const createContactAddressMapper = (
|
||||||
|
context: IInvoicingContext
|
||||||
|
): IContactAddressMapper => new ContactAddressMapper({ context });
|
||||||
|
|
||||||
|
class ContactAddressMapper
|
||||||
|
extends SequelizeMapper<
|
||||||
|
ContactAddress_Model,
|
||||||
|
TCreationContactAddress_Attributes,
|
||||||
|
ContactAddress
|
||||||
|
>
|
||||||
|
implements IContactAddressMapper
|
||||||
|
{
|
||||||
|
protected toDomainMappingImpl(source: ContactAddress_Model, params: any) {
|
||||||
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
|
|
||||||
|
const props: IContactAddressProps = {
|
||||||
|
type: source.type,
|
||||||
|
street: this.mapsValue(source, "street", Street.create),
|
||||||
|
city: this.mapsValue(source, "city", City.create),
|
||||||
|
province: this.mapsValue(source, "province", Province.create),
|
||||||
|
postalCode: this.mapsValue(source, "postal_code", PostalCode.create),
|
||||||
|
country: this.mapsValue(source, "country", Country.create),
|
||||||
|
email: this.mapsValue(source, "email", Email.create),
|
||||||
|
phone: this.mapsValue(source, "phone", Phone.create),
|
||||||
|
notes: this.mapsValue(source, "notes", Note.create),
|
||||||
|
};
|
||||||
|
|
||||||
|
const addressOrError = ContactAddress.create(props, id);
|
||||||
|
|
||||||
|
if (addressOrError.isFailure) {
|
||||||
|
throw addressOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addressOrError.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./contact.mapper";
|
||||||
|
export * from "./contactAddress.mapper";
|
||||||
|
export * from "./invoice.mapper";
|
||||||
|
export * from "./invoiceItem.mapper";
|
||||||
|
export * from "./invoiceParticipant.mapper";
|
||||||
|
export * from "./invoiceParticipantAddress.mapper";
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import {
|
||||||
|
Currency,
|
||||||
|
InvoiceDate,
|
||||||
|
InvoiceNumber,
|
||||||
|
InvoiceSeries,
|
||||||
|
Language,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||||
|
import { DraftInvoice, Invoice } from "../../domain";
|
||||||
|
import { IBaseInvoiceProps } from "../../domain/aggregates/invoice";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import { Invoice_Model, TCreationInvoice_Model } from "../sequelize";
|
||||||
|
import { IInvoiceItemMapper, createInvoiceItemMapper } from "./invoiceItem.mapper";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantMapper,
|
||||||
|
createInvoiceParticipantMapper,
|
||||||
|
} from "./invoiceParticipant.mapper";
|
||||||
|
|
||||||
|
export interface IInvoiceMapper
|
||||||
|
extends ISequelizeMapper<Invoice_Model, TCreationInvoice_Model, Invoice> {}
|
||||||
|
|
||||||
|
export const createInvoiceMapper = (context: IInvoicingContext): IInvoiceMapper =>
|
||||||
|
new InvoiceMapper({
|
||||||
|
context,
|
||||||
|
invoiceItemMapper: createInvoiceItemMapper(context),
|
||||||
|
participantMapper: createInvoiceParticipantMapper(context),
|
||||||
|
});
|
||||||
|
|
||||||
|
class InvoiceMapper
|
||||||
|
extends SequelizeMapper<Invoice_Model, TCreationInvoice_Model, Invoice>
|
||||||
|
implements IInvoiceMapper
|
||||||
|
{
|
||||||
|
public constructor(props: {
|
||||||
|
invoiceItemMapper: IInvoiceItemMapper;
|
||||||
|
participantMapper: IInvoiceParticipantMapper;
|
||||||
|
context: IInvoicingContext;
|
||||||
|
}) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toDomainMappingImpl(source: Invoice_Model): Invoice {
|
||||||
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
|
|
||||||
|
/*if (!source.items) {
|
||||||
|
this.handleRequiredFieldError(
|
||||||
|
"items",
|
||||||
|
new Error("Missing invoice items"),
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const items = (this.props.invoiceItemMapper as IInvoiceItemMapper).mapArrayToDomain(
|
||||||
|
source.items,
|
||||||
|
{
|
||||||
|
sourceParent: source,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const participants = (
|
||||||
|
this.props.participantMapper as IInvoiceParticipantMapper
|
||||||
|
).mapArrayToDomain(source.participants);
|
||||||
|
|
||||||
|
const props: IBaseInvoiceProps = {
|
||||||
|
invoiceNumber: this.mapsValue(source, "invoice_number", InvoiceNumber.create),
|
||||||
|
invoiceSeries: this.mapsValue(source, "invoice_series", InvoiceSeries.create),
|
||||||
|
issueDate: this.mapsValue(source, "issue_date", InvoiceDate.create),
|
||||||
|
operationDate: this.mapsValue(source, "operation_date", InvoiceDate.create),
|
||||||
|
invoiceCurrency: this.mapsValue(source, "invoice_currency", Currency.createFromCode),
|
||||||
|
language: this.mapsValue(source, "invoice_language", Language.createFromCode),
|
||||||
|
|
||||||
|
//recipientId: id,
|
||||||
|
//senderId: id,
|
||||||
|
items,
|
||||||
|
recipient: participants.items[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoiceOrError = DraftInvoice.create(props, id);
|
||||||
|
|
||||||
|
if (invoiceOrError.isFailure) {
|
||||||
|
throw invoiceOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(source: Invoice) {
|
||||||
|
const items = (this.props.invoiceItemMapper as IInvoiceItemMapper).mapCollectionToPersistence(
|
||||||
|
source.items,
|
||||||
|
{ sourceParent: source }
|
||||||
|
);
|
||||||
|
|
||||||
|
const recipientData = (
|
||||||
|
this.props.participantMapper as IInvoiceParticipantMapper
|
||||||
|
).mapToPersistence(source.recipient, { sourceParent: source });
|
||||||
|
|
||||||
|
const invoice: TCreationInvoice_Model = {
|
||||||
|
id: source.id.toPrimitive(),
|
||||||
|
invoice_status: source.status.toPrimitive(),
|
||||||
|
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||||
|
invoice_series: source.invoiceSeries.toPrimitive(),
|
||||||
|
invoice_currency: source.currency.toPrimitive(),
|
||||||
|
invoice_language: source.language.toPrimitive(),
|
||||||
|
issue_date: source.issueDate.toPrimitive(),
|
||||||
|
operation_date: source.operationDate.toPrimitive(),
|
||||||
|
subtotal: source.calculateSubtotal().toPrimitive(),
|
||||||
|
total: source.calculateTotal().toPrimitive(),
|
||||||
|
|
||||||
|
items,
|
||||||
|
participants: [recipientData],
|
||||||
|
};
|
||||||
|
|
||||||
|
return invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import { Description, Quantity, UniqueID, UnitPrice } from "@shared/contexts";
|
||||||
|
import { Invoice } from "../../domain";
|
||||||
|
import {
|
||||||
|
IInvoiceSimpleItemProps,
|
||||||
|
InvoiceItem,
|
||||||
|
InvoiceSimpleItem,
|
||||||
|
} from "../../domain/InvoiceItems";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import {
|
||||||
|
InvoiceItem_Model,
|
||||||
|
Invoice_Model,
|
||||||
|
TCreationInvoiceItem_Model,
|
||||||
|
} from "../sequelize";
|
||||||
|
|
||||||
|
export interface IInvoiceItemMapper
|
||||||
|
extends ISequelizeMapper<
|
||||||
|
InvoiceItem_Model,
|
||||||
|
TCreationInvoiceItem_Model,
|
||||||
|
InvoiceItem
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export const createInvoiceItemMapper = (
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): IInvoiceItemMapper => new InvoiceItemMapper({ context });
|
||||||
|
|
||||||
|
class InvoiceItemMapper
|
||||||
|
extends SequelizeMapper<
|
||||||
|
InvoiceItem_Model,
|
||||||
|
TCreationInvoiceItem_Model,
|
||||||
|
InvoiceItem
|
||||||
|
>
|
||||||
|
implements IInvoiceItemMapper
|
||||||
|
{
|
||||||
|
protected toDomainMappingImpl(
|
||||||
|
source: InvoiceItem_Model,
|
||||||
|
params: { sourceParent: Invoice_Model },
|
||||||
|
): InvoiceItem {
|
||||||
|
const { sourceParent } = params;
|
||||||
|
const id = this.mapsValue(source, "item_id", UniqueID.create);
|
||||||
|
|
||||||
|
const props: IInvoiceSimpleItemProps = {
|
||||||
|
description: this.mapsValue(source, "description", Description.create),
|
||||||
|
quantity: this.mapsValue(source, "quantity", Quantity.create),
|
||||||
|
unitPrice: this.mapsValue(source, "unit_price", (unit_price) =>
|
||||||
|
UnitPrice.create({
|
||||||
|
amount: unit_price,
|
||||||
|
currencyCode: sourceParent.invoice_currency,
|
||||||
|
precision: 4,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoiceItemOrError = InvoiceSimpleItem.create(props, id);
|
||||||
|
|
||||||
|
if (invoiceItemOrError.isFailure) {
|
||||||
|
throw invoiceItemOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceItemOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(
|
||||||
|
source: InvoiceItem,
|
||||||
|
params: { index: number; sourceParent: Invoice },
|
||||||
|
): TCreationInvoiceItem_Model {
|
||||||
|
const { index, sourceParent } = params;
|
||||||
|
|
||||||
|
const lineData = {
|
||||||
|
parent_id: undefined,
|
||||||
|
invoice_id: sourceParent.id.toPrimitive(),
|
||||||
|
item_type: "simple",
|
||||||
|
position: index,
|
||||||
|
|
||||||
|
item_id: source.id.toPrimitive(),
|
||||||
|
description: source.description.toPrimitive(),
|
||||||
|
quantity: source.quantity.toPrimitive(),
|
||||||
|
unit_price: source.unitPrice.toPrimitive(),
|
||||||
|
subtotal: source.calculateSubtotal().toPrimitive(),
|
||||||
|
total: source.calculateTotal().toPrimitive(),
|
||||||
|
};
|
||||||
|
return lineData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import { Name, TINNumber, UniqueID } from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantProps,
|
||||||
|
Invoice,
|
||||||
|
InvoiceParticipant,
|
||||||
|
InvoiceParticipantBillingAddress,
|
||||||
|
InvoiceParticipantShippingAddress,
|
||||||
|
} from "../../domain";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import {
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
TCreationInvoiceParticipant_Model,
|
||||||
|
} from "../sequelize";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantAddressMapper,
|
||||||
|
createInvoiceParticipantAddressMapper,
|
||||||
|
} from "./invoiceParticipantAddress.mapper";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantMapper
|
||||||
|
extends ISequelizeMapper<
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
TCreationInvoiceParticipant_Model,
|
||||||
|
InvoiceParticipant
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export const createInvoiceParticipantMapper = (
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): IInvoiceParticipantMapper =>
|
||||||
|
new InvoiceParticipantMapper({
|
||||||
|
context,
|
||||||
|
addressMapper: createInvoiceParticipantAddressMapper(context),
|
||||||
|
});
|
||||||
|
|
||||||
|
class InvoiceParticipantMapper
|
||||||
|
extends SequelizeMapper<
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
TCreationInvoiceParticipant_Model,
|
||||||
|
InvoiceParticipant
|
||||||
|
>
|
||||||
|
implements IInvoiceParticipantMapper
|
||||||
|
{
|
||||||
|
public constructor(props: {
|
||||||
|
addressMapper: IInvoiceParticipantAddressMapper;
|
||||||
|
context: IInvoicingContext;
|
||||||
|
}) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toDomainMappingImpl(source: InvoiceParticipant_Model, params: any) {
|
||||||
|
/*if (!source.billingAddress) {
|
||||||
|
this.handleRequiredFieldError(
|
||||||
|
"billingAddress",
|
||||||
|
new Error("Missing participant's billing address"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.shippingAddress) {
|
||||||
|
this.handleRequiredFieldError(
|
||||||
|
"shippingAddress",
|
||||||
|
new Error("Missing participant's shipping address"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const billingAddress = source.billingAddress
|
||||||
|
? ((
|
||||||
|
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||||
|
).mapToDomain(
|
||||||
|
source.billingAddress,
|
||||||
|
params,
|
||||||
|
) as InvoiceParticipantBillingAddress)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const shippingAddress = source.shippingAddress
|
||||||
|
? ((
|
||||||
|
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||||
|
).mapToDomain(
|
||||||
|
source.shippingAddress,
|
||||||
|
params,
|
||||||
|
) as InvoiceParticipantShippingAddress)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const props: IInvoiceParticipantProps = {
|
||||||
|
tin: this.mapsValue(source, "tin", TINNumber.create),
|
||||||
|
firstName: this.mapsValue(source, "first_name", Name.create),
|
||||||
|
lastName: this.mapsValue(source, "last_name", Name.create),
|
||||||
|
companyName: this.mapsValue(source, "company_name", Name.create),
|
||||||
|
billingAddress,
|
||||||
|
shippingAddress,
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = this.mapsValue(source, "participant_id", UniqueID.create);
|
||||||
|
const participantOrError = InvoiceParticipant.create(props, id);
|
||||||
|
|
||||||
|
if (participantOrError.isFailure) {
|
||||||
|
throw participantOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return participantOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(
|
||||||
|
source: InvoiceParticipant,
|
||||||
|
params: { sourceParent: Invoice },
|
||||||
|
): TCreationInvoiceParticipant_Model {
|
||||||
|
const { sourceParent } = params;
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoice_id: sourceParent.id.toPrimitive(),
|
||||||
|
|
||||||
|
participant_id: source.id.toPrimitive(),
|
||||||
|
tin: source.tin.toPrimitive(),
|
||||||
|
first_name: source.firstName.toPrimitive(),
|
||||||
|
last_name: source.lastName.toPrimitive(),
|
||||||
|
company_name: source.companyName.toPrimitive(),
|
||||||
|
|
||||||
|
billingAddress: (
|
||||||
|
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||||
|
).mapToPersistence(source.billingAddress!, { sourceParent: source }),
|
||||||
|
|
||||||
|
shippingAddress: (
|
||||||
|
this.props.addressMapper as IInvoiceParticipantAddressMapper
|
||||||
|
).mapToPersistence(source.shippingAddress!, { sourceParent: source }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import {
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
Email,
|
||||||
|
Note,
|
||||||
|
Phone,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import {
|
||||||
|
IInvoiceParticipantAddressProps,
|
||||||
|
InvoiceParticipant,
|
||||||
|
InvoiceParticipantAddress,
|
||||||
|
} from "../../domain";
|
||||||
|
import { IInvoicingContext } from "../InvoicingContext";
|
||||||
|
import {
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
TCreationInvoiceParticipantAddress_Model,
|
||||||
|
} from "../sequelize";
|
||||||
|
|
||||||
|
export interface IInvoiceParticipantAddressMapper
|
||||||
|
extends ISequelizeMapper<
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
TCreationInvoiceParticipantAddress_Model,
|
||||||
|
InvoiceParticipantAddress
|
||||||
|
> {}
|
||||||
|
|
||||||
|
export const createInvoiceParticipantAddressMapper = (
|
||||||
|
context: IInvoicingContext
|
||||||
|
): IInvoiceParticipantAddressMapper =>
|
||||||
|
new InvoiceParticipantAddressMapper({ context });
|
||||||
|
|
||||||
|
class InvoiceParticipantAddressMapper
|
||||||
|
extends SequelizeMapper<
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
TCreationInvoiceParticipantAddress_Model,
|
||||||
|
InvoiceParticipantAddress
|
||||||
|
>
|
||||||
|
implements IInvoiceParticipantAddressMapper
|
||||||
|
{
|
||||||
|
protected toDomainMappingImpl(
|
||||||
|
source: InvoiceParticipantAddress_Model,
|
||||||
|
params: any
|
||||||
|
) {
|
||||||
|
const id = this.mapsValue(source, "address_id", UniqueID.create);
|
||||||
|
|
||||||
|
const props: IInvoiceParticipantAddressProps = {
|
||||||
|
type: source.type,
|
||||||
|
street: this.mapsValue(source, "street", Street.create),
|
||||||
|
city: this.mapsValue(source, "city", City.create),
|
||||||
|
province: this.mapsValue(source, "province", Province.create),
|
||||||
|
postalCode: this.mapsValue(source, "postal_code", PostalCode.create),
|
||||||
|
country: this.mapsValue(source, "country", Country.create),
|
||||||
|
email: this.mapsValue(source, "email", Email.create),
|
||||||
|
phone: this.mapsValue(source, "phone", Phone.create),
|
||||||
|
notes: this.mapsValue(source, "notes", Note.create),
|
||||||
|
};
|
||||||
|
|
||||||
|
const addressOrError = InvoiceParticipantAddress.create(props, id);
|
||||||
|
|
||||||
|
if (addressOrError.isFailure) {
|
||||||
|
throw addressOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addressOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(
|
||||||
|
source: InvoiceParticipantAddress,
|
||||||
|
params: { sourceParent: InvoiceParticipant }
|
||||||
|
) {
|
||||||
|
const { sourceParent } = params;
|
||||||
|
|
||||||
|
return {
|
||||||
|
address_id: source.id.toPrimitive(),
|
||||||
|
participant_id: sourceParent.id.toPrimitive(),
|
||||||
|
type: String(source.type),
|
||||||
|
title: source.title,
|
||||||
|
street: source.street.toPrimitive(),
|
||||||
|
city: source.city.toPrimitive(),
|
||||||
|
postal_code: source.postalCode.toPrimitive(),
|
||||||
|
province: source.province.toPrimitive(),
|
||||||
|
country: source.country.toPrimitive(),
|
||||||
|
email: source.email.toPrimitive(),
|
||||||
|
phone: source.phone.toPrimitive(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ContactAddress_Model,
|
||||||
|
TCreationContactAddress_Attributes,
|
||||||
|
} from "./contactAddress.model";
|
||||||
|
|
||||||
|
export type TCreationContact_Model = InferCreationAttributes<
|
||||||
|
Contact_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" }
|
||||||
|
> & {
|
||||||
|
billingAddress: TCreationContactAddress_Attributes;
|
||||||
|
shippingAddress: TCreationContactAddress_Attributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Contact_Model extends Model<
|
||||||
|
InferAttributes<
|
||||||
|
Contact_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" }
|
||||||
|
>,
|
||||||
|
InferCreationAttributes<
|
||||||
|
Contact_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" }
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
// To avoid table creation
|
||||||
|
static async sync(): Promise<any> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const { Contact_Model, ContactAddress_Model } = connection.models;
|
||||||
|
|
||||||
|
Contact_Model.hasOne(ContactAddress_Model, {
|
||||||
|
as: "shippingAddress",
|
||||||
|
foreignKey: "customer_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
|
||||||
|
Contact_Model.hasOne(ContactAddress_Model, {
|
||||||
|
as: "billingAddress",
|
||||||
|
foreignKey: "customer_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare id: string;
|
||||||
|
declare tin: CreationOptional<string>;
|
||||||
|
declare company_name: CreationOptional<string>;
|
||||||
|
declare first_name: CreationOptional<string>;
|
||||||
|
declare last_name: CreationOptional<string>;
|
||||||
|
|
||||||
|
declare shippingAddress?: NonAttribute<ContactAddress_Model>;
|
||||||
|
declare billingAddress?: NonAttribute<ContactAddress_Model>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
Contact_Model.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
tin: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
},
|
||||||
|
company_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
},
|
||||||
|
first_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
},
|
||||||
|
last_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "customers",
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Contact_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
ForeignKey,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
import { Contact_Model } from "./contact.model";
|
||||||
|
|
||||||
|
export type TCreationContactAddress_Attributes = InferCreationAttributes<
|
||||||
|
ContactAddress_Model,
|
||||||
|
{ omit: "customer" }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class ContactAddress_Model extends Model<
|
||||||
|
InferAttributes<ContactAddress_Model, { omit: "customer" }>,
|
||||||
|
TCreationContactAddress_Attributes
|
||||||
|
> {
|
||||||
|
// To avoid table creation
|
||||||
|
static async sync(): Promise<any> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const { Contact_Model, ContactAddress_Model } = connection.models;
|
||||||
|
|
||||||
|
ContactAddress_Model.belongsTo(Contact_Model, {
|
||||||
|
as: "customer",
|
||||||
|
foreignKey: "customer_id",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare id: string;
|
||||||
|
declare customer_id: ForeignKey<Contact_Model["id"]>;
|
||||||
|
declare type: string;
|
||||||
|
declare street: CreationOptional<string>;
|
||||||
|
declare postal_code: CreationOptional<string>;
|
||||||
|
declare city: CreationOptional<string>;
|
||||||
|
declare province: CreationOptional<string>;
|
||||||
|
declare country: CreationOptional<string>;
|
||||||
|
declare phone: CreationOptional<string>;
|
||||||
|
declare email: CreationOptional<string>;
|
||||||
|
|
||||||
|
declare customer?: NonAttribute<Contact_Model>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
ContactAddress_Model.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
customer_id: new DataTypes.UUID(),
|
||||||
|
type: DataTypes.STRING(),
|
||||||
|
street: DataTypes.STRING(),
|
||||||
|
postal_code: DataTypes.STRING(),
|
||||||
|
city: DataTypes.STRING,
|
||||||
|
province: DataTypes.STRING,
|
||||||
|
country: DataTypes.STRING,
|
||||||
|
email: DataTypes.STRING,
|
||||||
|
phone: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "customer_addresses",
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return ContactAddress_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { IInvoiceRepository } from "@contexts/invoicing/domain";
|
||||||
|
import { invoiceRepository } from "./invoice.repository";
|
||||||
|
|
||||||
|
export * from "./invoice.model";
|
||||||
|
|
||||||
|
export * from "./invoice.repository";
|
||||||
|
|
||||||
|
export const createInvoiceRepository = (): IInvoiceRepository => {
|
||||||
|
return invoiceRepository;
|
||||||
|
};
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
import { SequelizeRevision } from "sequelize-revision";
|
||||||
|
import {
|
||||||
|
InvoiceItem_Model,
|
||||||
|
TCreationInvoiceItem_Model,
|
||||||
|
} from "./invoiceItem.model";
|
||||||
|
import {
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
TCreationInvoiceParticipant_Model,
|
||||||
|
} from "./invoiceParticipant.model";
|
||||||
|
|
||||||
|
export type TCreationInvoice_Model = InferCreationAttributes<
|
||||||
|
Invoice_Model,
|
||||||
|
{ omit: "items" | "participants" }
|
||||||
|
> & {
|
||||||
|
items: TCreationInvoiceItem_Model[];
|
||||||
|
participants: TCreationInvoiceParticipant_Model[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Invoice_Model extends Model<
|
||||||
|
InferAttributes<Invoice_Model, { omit: "items" | "participants" }>,
|
||||||
|
InferCreationAttributes<Invoice_Model, { omit: "items" | "participants" }>
|
||||||
|
> {
|
||||||
|
static async trackRevision(
|
||||||
|
connection: Sequelize,
|
||||||
|
sequelizeRevision: SequelizeRevision<any>
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
Invoice_Model,
|
||||||
|
InvoiceItem_Model,
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
} = connection.models;
|
||||||
|
sequelizeRevision.trackRevision(Invoice_Model);
|
||||||
|
sequelizeRevision.trackRevision(InvoiceItem_Model);
|
||||||
|
sequelizeRevision.trackRevision(InvoiceParticipant_Model);
|
||||||
|
sequelizeRevision.trackRevision(InvoiceParticipantAddress_Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const { Invoice_Model, InvoiceItem_Model, InvoiceParticipant_Model } =
|
||||||
|
connection.models;
|
||||||
|
|
||||||
|
Invoice_Model.hasMany(InvoiceItem_Model, {
|
||||||
|
as: "items",
|
||||||
|
foreignKey: "invoice_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
Invoice_Model.hasMany(InvoiceParticipant_Model, {
|
||||||
|
as: "participants",
|
||||||
|
foreignKey: "invoice_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare id: string;
|
||||||
|
declare invoice_status: string;
|
||||||
|
declare invoice_series: CreationOptional<string>;
|
||||||
|
declare invoice_number: CreationOptional<string>;
|
||||||
|
declare issue_date: CreationOptional<string>;
|
||||||
|
declare operation_date: CreationOptional<string>;
|
||||||
|
declare invoice_language: string;
|
||||||
|
declare invoice_currency: string;
|
||||||
|
declare subtotal: number;
|
||||||
|
declare total: number;
|
||||||
|
|
||||||
|
declare items: NonAttribute<InvoiceItem_Model[]>;
|
||||||
|
declare participants: NonAttribute<InvoiceParticipant_Model[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
Invoice_Model.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_status: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false, // Puede ser nulo
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_series: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true, // Puede ser nulo
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_number: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true, // Puede ser nulo
|
||||||
|
},
|
||||||
|
|
||||||
|
issue_date: {
|
||||||
|
type: new DataTypes.DATE(),
|
||||||
|
allowNull: true, // Puede ser nulo
|
||||||
|
},
|
||||||
|
|
||||||
|
operation_date: {
|
||||||
|
type: new DataTypes.DATE(),
|
||||||
|
allowNull: true, // Puede ser nulo
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_language: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_currency: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
subtotal: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "invoices",
|
||||||
|
|
||||||
|
paranoid: true, // softs deletes
|
||||||
|
timestamps: true,
|
||||||
|
//version: true,
|
||||||
|
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: "deleted_at",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Invoice_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import { UniqueID } from "@common/domain";
|
||||||
|
import { Collection, Result } from "@common/helpers";
|
||||||
|
import { SequelizeRepository } from "@common/infrastructure";
|
||||||
|
import { Invoice } from "@contexts/invoices/domain";
|
||||||
|
import { IInvoiceRepository } from "@contexts/invoices/domain/repositories/invoice-repository.interface";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { IInvoiceMapper, invoiceMapper } from "../mappers/invoice.mapper";
|
||||||
|
import { InvoiceModel } from "./invoice.model";
|
||||||
|
|
||||||
|
class InvoiceRepository extends SequelizeRepository<Invoice> implements IInvoiceRepository {
|
||||||
|
private readonly _mapper!: IInvoiceMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||||
|
*/
|
||||||
|
private _customErrorMapper(error: Error): string | null {
|
||||||
|
if (error.name === "SequelizeUniqueConstraintError") {
|
||||||
|
return "Invoice with this email already exists";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(mapper: IInvoiceMapper) {
|
||||||
|
super();
|
||||||
|
this._mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
async invoiceExists(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const _invoice = await this._getById(InvoiceModel, id, {}, transaction);
|
||||||
|
|
||||||
|
return Result.ok(Boolean(id.equals(_invoice.id)));
|
||||||
|
} catch (error: any) {
|
||||||
|
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
|
||||||
|
try {
|
||||||
|
const rawInvoices: any = await this._findAll(InvoiceModel, {}, transaction);
|
||||||
|
|
||||||
|
if (!rawInvoices === true) {
|
||||||
|
return Result.fail(new Error("Invoice with email not exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._mapper.mapArrayToDomain(rawInvoices);
|
||||||
|
} catch (error: any) {
|
||||||
|
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: UniqueID, transaction?: Transaction): Promise<Result<Invoice, Error>> {
|
||||||
|
try {
|
||||||
|
const rawInvoice: any = await this._getById(InvoiceModel, id, {}, transaction);
|
||||||
|
|
||||||
|
if (!rawInvoice === true) {
|
||||||
|
return Result.fail(new Error(`Invoice with id ${id.toString()} not exists`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._mapper.mapToDomain(rawInvoice);
|
||||||
|
} catch (error: any) {
|
||||||
|
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
this._deleteById(InvoiceModel, id);
|
||||||
|
return Result.ok<boolean>(true);
|
||||||
|
} catch (error: any) {
|
||||||
|
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(invoice: Invoice, transaction?: Transaction): Promise<void> {
|
||||||
|
const invoiceData = this._mapper.mapToPersistence(invoice);
|
||||||
|
await this._save(InvoiceModel, invoice.id, invoiceData, {}, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(invoice: Invoice, transaction?: Transaction): Promise<void> {
|
||||||
|
const invoiceData = this._mapper.mapToPersistence(invoice);
|
||||||
|
await this._save(InvoiceModel, invoice.id, invoiceData, {}, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceRepository = new InvoiceRepository(invoiceMapper);
|
||||||
|
export { invoiceRepository };
|
||||||
@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
import { Invoice_Model } from "./invoice.model";
|
||||||
|
|
||||||
|
export type TCreationInvoiceItem_Model = InferCreationAttributes<
|
||||||
|
InvoiceItem_Model,
|
||||||
|
{ omit: "invoice" }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class InvoiceItem_Model extends Model<
|
||||||
|
InferAttributes<InvoiceItem_Model, { omit: "invoice" }>,
|
||||||
|
InferCreationAttributes<InvoiceItem_Model, { omit: "invoice" }>
|
||||||
|
> {
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const { Invoice_Model, InvoiceItem_Model } = connection.models;
|
||||||
|
|
||||||
|
InvoiceItem_Model.belongsTo(Invoice_Model, {
|
||||||
|
as: "invoice",
|
||||||
|
foreignKey: "invoice_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare invoice_id: string;
|
||||||
|
declare item_id: string;
|
||||||
|
declare parent_id: CreationOptional<string>;
|
||||||
|
declare position: number;
|
||||||
|
declare item_type: string;
|
||||||
|
declare description: CreationOptional<string>;
|
||||||
|
declare quantity: CreationOptional<number>;
|
||||||
|
declare unit_price: CreationOptional<number>;
|
||||||
|
declare subtotal: CreationOptional<number>;
|
||||||
|
declare total: CreationOptional<number>;
|
||||||
|
|
||||||
|
declare invoice?: NonAttribute<Invoice_Model>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
InvoiceItem_Model.init(
|
||||||
|
{
|
||||||
|
item_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
invoice_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
parent_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: new DataTypes.MEDIUMINT(),
|
||||||
|
autoIncrement: false,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
item_type: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "simple",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: new DataTypes.TEXT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
type: DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
unit_price: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
/*tax_slug: {
|
||||||
|
type: new DataTypes.DECIMAL(3, 2),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
tax_rate: {
|
||||||
|
type: new DataTypes.DECIMAL(3, 2),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
tax_equalization: {
|
||||||
|
type: new DataTypes.DECIMAL(3, 2),
|
||||||
|
allowNull: true,
|
||||||
|
},*/
|
||||||
|
subtotal: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
/*tax_amount: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},*/
|
||||||
|
total: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "invoice_items",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return InvoiceItem_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
import { Invoice_Model } from "./invoice.model";
|
||||||
|
import {
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
TCreationInvoiceParticipantAddress_Model,
|
||||||
|
} from "./invoiceParticipantAddress.model";
|
||||||
|
|
||||||
|
export type TCreationInvoiceParticipant_Model = InferCreationAttributes<
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" | "invoice" }
|
||||||
|
> & {
|
||||||
|
billingAddress: TCreationInvoiceParticipantAddress_Model;
|
||||||
|
shippingAddress: TCreationInvoiceParticipantAddress_Model;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InvoiceParticipant_Model extends Model<
|
||||||
|
InferAttributes<
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" | "invoice" }
|
||||||
|
>,
|
||||||
|
InferCreationAttributes<
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
{ omit: "shippingAddress" | "billingAddress" | "invoice" }
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const {
|
||||||
|
Invoice_Model,
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
InvoiceParticipant_Model,
|
||||||
|
} = connection.models;
|
||||||
|
|
||||||
|
InvoiceParticipant_Model.belongsTo(Invoice_Model, {
|
||||||
|
as: "invoice",
|
||||||
|
foreignKey: "invoice_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
|
||||||
|
InvoiceParticipant_Model.hasOne(InvoiceParticipantAddress_Model, {
|
||||||
|
as: "shippingAddress",
|
||||||
|
foreignKey: "participant_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
|
||||||
|
InvoiceParticipant_Model.hasOne(InvoiceParticipantAddress_Model, {
|
||||||
|
as: "billingAddress",
|
||||||
|
foreignKey: "participant_id",
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare participant_id: string;
|
||||||
|
declare invoice_id: string;
|
||||||
|
declare tin: CreationOptional<string>;
|
||||||
|
declare company_name: CreationOptional<string>;
|
||||||
|
declare first_name: CreationOptional<string>;
|
||||||
|
declare last_name: CreationOptional<string>;
|
||||||
|
|
||||||
|
declare shippingAddress?: NonAttribute<InvoiceParticipantAddress_Model>;
|
||||||
|
declare billingAddress?: NonAttribute<InvoiceParticipantAddress_Model>;
|
||||||
|
|
||||||
|
declare invoice?: NonAttribute<Invoice_Model>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
InvoiceParticipant_Model.init(
|
||||||
|
{
|
||||||
|
participant_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
invoice_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
tin: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
company_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
first_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
last_name: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "invoice_participants",
|
||||||
|
timestamps: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return InvoiceParticipant_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
NonAttribute,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
import { InvoiceParticipant_Model } from "./invoiceParticipant.model";
|
||||||
|
|
||||||
|
export type TCreationInvoiceParticipantAddress_Model = InferCreationAttributes<
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
{ omit: "participant" }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class InvoiceParticipantAddress_Model extends Model<
|
||||||
|
InferAttributes<InvoiceParticipantAddress_Model, { omit: "participant" }>,
|
||||||
|
InferCreationAttributes<
|
||||||
|
InvoiceParticipantAddress_Model,
|
||||||
|
{ omit: "participant" }
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
static associate(connection: Sequelize) {
|
||||||
|
const { InvoiceParticipantAddress_Model, InvoiceParticipant_Model } =
|
||||||
|
connection.models;
|
||||||
|
InvoiceParticipantAddress_Model.belongsTo(InvoiceParticipant_Model, {
|
||||||
|
as: "participant",
|
||||||
|
foreignKey: "participant_id",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
declare address_id: string;
|
||||||
|
declare participant_id: string;
|
||||||
|
declare type: string;
|
||||||
|
declare street: CreationOptional<string>;
|
||||||
|
declare postal_code: CreationOptional<string>;
|
||||||
|
declare city: CreationOptional<string>;
|
||||||
|
declare province: CreationOptional<string>;
|
||||||
|
declare country: CreationOptional<string>;
|
||||||
|
declare phone: CreationOptional<string>;
|
||||||
|
declare email: CreationOptional<string>;
|
||||||
|
|
||||||
|
declare participant?: NonAttribute<InvoiceParticipant_Model>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
InvoiceParticipantAddress_Model.init(
|
||||||
|
{
|
||||||
|
address_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
participant_id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
street: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
postal_code: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
province: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "invoice_participant_addresses",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return InvoiceParticipantAddress_Model;
|
||||||
|
};
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
import { UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import {
|
||||||
|
CreateInvoiceResponseOrError,
|
||||||
|
CreateInvoiceUseCase,
|
||||||
|
} from "@/contexts/invoicing/application";
|
||||||
|
import {
|
||||||
|
ICreateInvoice_DTO,
|
||||||
|
ICreateInvoice_Response_DTO,
|
||||||
|
ensureCreateInvoice_DTOIsValid,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "../../../InvoicingContext";
|
||||||
|
import { ICreateInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class CreateInvoiceController extends ExpressController {
|
||||||
|
private useCase: CreateInvoiceUseCase;
|
||||||
|
private presenter: ICreateInvoicePresenter;
|
||||||
|
private context: IInvoicingContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: {
|
||||||
|
useCase: CreateInvoiceUseCase;
|
||||||
|
presenter: ICreateInvoicePresenter;
|
||||||
|
},
|
||||||
|
context: IInvoicingContext
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase, presenter } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl(): Promise<any> {
|
||||||
|
try {
|
||||||
|
const invoiceDTO: ICreateInvoice_DTO = this.req.body;
|
||||||
|
|
||||||
|
// Validaciones de DTO
|
||||||
|
const invoiceDTOOrError = ensureCreateInvoice_DTOIsValid(invoiceDTO);
|
||||||
|
if (invoiceDTOOrError.isFailure) {
|
||||||
|
return this.invalidInputError(invoiceDTOOrError.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llamar al caso de uso
|
||||||
|
const result: CreateInvoiceResponseOrError = await this.useCase.execute(
|
||||||
|
invoiceDTO
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
const { error } = result;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case UseCaseError.INVALID_REQUEST_PARAM:
|
||||||
|
return this.invalidInputError(error.message, error);
|
||||||
|
|
||||||
|
case UseCaseError.INVALID_INPUT_DATA:
|
||||||
|
return this.invalidInputError(error.message, error);
|
||||||
|
|
||||||
|
case UseCaseError.UNEXCEPTED_ERROR:
|
||||||
|
return this.internalServerError(error.message, error);
|
||||||
|
|
||||||
|
case UseCaseError.REPOSITORY_ERROR:
|
||||||
|
return this.conflictError(error, error.details);
|
||||||
|
|
||||||
|
case UseCaseError.NOT_FOUND_ERROR:
|
||||||
|
return this.notFoundError(error.message, error);
|
||||||
|
|
||||||
|
case UseCaseError.RESOURCE_ALREADY_EXITS:
|
||||||
|
return this.conflictError(error);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return this.clientError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = <Invoice>result.object;
|
||||||
|
|
||||||
|
return this.created<ICreateInvoice_Response_DTO>(
|
||||||
|
this.presenter.map(invoice, this.context)
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return this.fail(error as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { CreateInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import {
|
||||||
|
ContactRepository,
|
||||||
|
IInvoicingContext,
|
||||||
|
InvoiceParticipantAddressRepository,
|
||||||
|
InvoiceParticipantRepository,
|
||||||
|
InvoiceRepository,
|
||||||
|
} from "../../..";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createInvoiceMapper,
|
||||||
|
createInvoiceParticipantAddressMapper,
|
||||||
|
createInvoiceParticipantMapper,
|
||||||
|
} from "../../../mappers";
|
||||||
|
import { createContactMapper } from "../../../mappers/contact.mapper";
|
||||||
|
import { CreateInvoiceController } from "./CreateInvoiceController";
|
||||||
|
import { createInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const createInvoiceController = (context: IInvoicingContext) => {
|
||||||
|
const adapter = context.adapter;
|
||||||
|
const repoManager = context.repositoryManager;
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Invoice",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Participant",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceParticipantRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceParticipantMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"ParticipantAddress",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceParticipantAddressRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceParticipantAddressMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Contact",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new ContactRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createContactMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const createInvoiceUseCase = new CreateInvoiceUseCase(context);
|
||||||
|
|
||||||
|
return new CreateInvoiceController(
|
||||||
|
{
|
||||||
|
useCase: createInvoiceUseCase,
|
||||||
|
presenter: createInvoicePresenter,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICreateInvoice_Response_DTO } from "@shared/contexts";
|
||||||
|
import { invoiceItemPresenter } from "./InvoiceItem.presenter";
|
||||||
|
import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter";
|
||||||
|
|
||||||
|
export interface ICreateInvoicePresenter {
|
||||||
|
map: (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) => ICreateInvoice_Response_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createInvoicePresenter: ICreateInvoicePresenter = {
|
||||||
|
map: (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): ICreateInvoice_Response_DTO => {
|
||||||
|
return {
|
||||||
|
id: invoice.id.toString(),
|
||||||
|
|
||||||
|
invoice_status: invoice.status.toString(),
|
||||||
|
invoice_number: invoice.invoiceNumber.toString(),
|
||||||
|
invoice_series: invoice.invoiceSeries.toString(),
|
||||||
|
issue_date: invoice.issueDate.toISO8601(),
|
||||||
|
operation_date: invoice.operationDate.toISO8601(),
|
||||||
|
language_code: invoice.language.toString(),
|
||||||
|
currency: invoice.currency.toString(),
|
||||||
|
subtotal: invoice.calculateSubtotal().toObject(),
|
||||||
|
total: invoice.calculateTotal().toObject(),
|
||||||
|
|
||||||
|
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
|
||||||
|
|
||||||
|
recipient: InvoiceParticipantPresenter(invoice.recipient, context),
|
||||||
|
|
||||||
|
items: invoiceItemPresenter(invoice.items, context),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceItem } from "@/contexts/invoicing/domain/InvoiceItems";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const invoiceItemPresenter = (
|
||||||
|
items: ICollection<InvoiceItem>,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) =>
|
||||||
|
items.totalCount > 0
|
||||||
|
? items.items.map((item: InvoiceItem) => ({
|
||||||
|
description: item.description.toString(),
|
||||||
|
quantity: item.quantity.toString(),
|
||||||
|
unit_measure: "",
|
||||||
|
unit_price: item.unitPrice.toObject() as IMoney_Response_DTO,
|
||||||
|
subtotal: item.calculateSubtotal().toObject() as IMoney_Response_DTO,
|
||||||
|
tax_amount: item.calculateTaxAmount().toObject() as IMoney_Response_DTO,
|
||||||
|
total: item.calculateTotal().toObject() as IMoney_Response_DTO,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { IInvoiceParticipant } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICreateInvoice_Participant_Response_DTO } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipantAddressPresenter } from "./InvoiceParticipantAddress.presenter";
|
||||||
|
|
||||||
|
export const InvoiceParticipantPresenter = (
|
||||||
|
participant: IInvoiceParticipant,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): ICreateInvoice_Participant_Response_DTO | undefined => {
|
||||||
|
return {
|
||||||
|
id: participant.id.toString(),
|
||||||
|
tin: participant.tin.toString(),
|
||||||
|
first_name: participant.firstName.toString(),
|
||||||
|
last_name: participant.lastName.toString(),
|
||||||
|
company_name: participant.companyName.toString(),
|
||||||
|
|
||||||
|
billing_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant.billingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
shipping_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant.shippingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICreateInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const InvoiceParticipantAddressPresenter = (
|
||||||
|
address: InvoiceParticipantAddress,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): ICreateInvoice_AddressParticipant_Response_DTO => {
|
||||||
|
return {
|
||||||
|
id: address.id.toString(),
|
||||||
|
street: address.street.toString(),
|
||||||
|
city: address.city.toString(),
|
||||||
|
postal_code: address.postalCode.toString(),
|
||||||
|
province: address.province.toString(),
|
||||||
|
country: address.country.toString(),
|
||||||
|
email: address.email.toString(),
|
||||||
|
phone: address.phone.toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./CreateInvoice.presenter";
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import { UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
|
import { IServerError } from "@/contexts/common/domain";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import { DeleteInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import {
|
||||||
|
IDeleteInvoiceRequest_DTO,
|
||||||
|
RuleValidator,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export class DeleteInvoiceController extends ExpressController {
|
||||||
|
private useCase: DeleteInvoiceUseCase;
|
||||||
|
|
||||||
|
constructor(props: { useCase: DeleteInvoiceUseCase }) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl(): Promise<any> {
|
||||||
|
const { invoiceId } = this.req.params;
|
||||||
|
|
||||||
|
if (
|
||||||
|
RuleValidator.validate(
|
||||||
|
RuleValidator.RULE_NOT_NULL_OR_UNDEFINED,
|
||||||
|
invoiceId,
|
||||||
|
).isFailure
|
||||||
|
) {
|
||||||
|
return this.invalidInputError("Invoice Id param is required!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(invoiceId);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return this.invalidInputError("Invalid invoice Id param!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deleteInvoiceRequest: IDeleteInvoiceRequest_DTO = {
|
||||||
|
id: idOrError.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.useCase.execute(deleteInvoiceRequest);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
const { error } = result;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case UseCaseError.NOT_FOUND_ERROR:
|
||||||
|
return this.notFoundError("Invoice not found", error);
|
||||||
|
|
||||||
|
case UseCaseError.UNEXCEPTED_ERROR:
|
||||||
|
return this.internalServerError(result.error.message, result.error);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return this.clientError(result.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.noContent();
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return this.fail(e as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { RepositoryManager } from "@/contexts/common/domain";
|
||||||
|
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
|
||||||
|
import { DeleteInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import { IInvoicingContext } from "../../..";
|
||||||
|
import { InvoiceRepository } from "../../../Invoice.repository";
|
||||||
|
import { createInvoiceMapper } from "../../../mappers";
|
||||||
|
import { DeleteInvoiceController } from "./DeleteInvoiceController";
|
||||||
|
|
||||||
|
export const createDeleteInvoiceController = (context: IInvoicingContext) => {
|
||||||
|
const adapter = createSequelizeAdapter();
|
||||||
|
const repoManager = RepositoryManager.getInstance();
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Invoice",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteInvoiceUseCase = new DeleteInvoiceUseCase({
|
||||||
|
adapter,
|
||||||
|
repositoryManager: repoManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new DeleteInvoiceController({
|
||||||
|
useCase: deleteInvoiceUseCase,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import { UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import {
|
||||||
|
IGetInvoice_Request_DTO,
|
||||||
|
IGetInvoice_Response_DTO,
|
||||||
|
RuleValidator,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
import { GetInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "../../../InvoicingContext";
|
||||||
|
import { IGetInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class GetInvoiceController extends ExpressController {
|
||||||
|
private useCase: GetInvoiceUseCase;
|
||||||
|
private presenter: IGetInvoicePresenter;
|
||||||
|
private context: IInvoicingContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: {
|
||||||
|
useCase: GetInvoiceUseCase;
|
||||||
|
presenter: IGetInvoicePresenter;
|
||||||
|
},
|
||||||
|
context: IInvoicingContext
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase, presenter } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl(): Promise<any> {
|
||||||
|
const { invoiceId } = this.req.params;
|
||||||
|
if (
|
||||||
|
RuleValidator.validate(
|
||||||
|
RuleValidator.RULE_NOT_NULL_OR_UNDEFINED,
|
||||||
|
invoiceId
|
||||||
|
).isFailure
|
||||||
|
) {
|
||||||
|
return this.invalidInputError("Invoice Id param is required!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(invoiceId);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return this.invalidInputError("Invalid invoice Id param!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const getInvoiceRequest: IGetInvoice_Request_DTO = {
|
||||||
|
id: idOrError.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.useCase.execute(getInvoiceRequest);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
const { error } = result;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case UseCaseError.NOT_FOUND_ERROR:
|
||||||
|
return this.notFoundError(
|
||||||
|
`Invoice with id ${idOrError.object} not found`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
case UseCaseError.UNEXCEPTED_ERROR:
|
||||||
|
return this.internalServerError(result.error.message, result.error);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return this.clientError(result.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = <Invoice>result.object;
|
||||||
|
|
||||||
|
return this.ok<IGetInvoice_Response_DTO>(
|
||||||
|
await this.presenter.map(invoice, this.context)
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return this.fail(e as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { GetInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import { InvoiceRepository } from "../../../Invoice.repository";
|
||||||
|
import { IInvoicingContext } from "../../../InvoicingContext";
|
||||||
|
import { createInvoiceMapper } from "../../../mappers";
|
||||||
|
import { GetInvoiceController } from "./GetInvoiceController";
|
||||||
|
import { getInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const createGetInvoiceController = (context: IInvoicingContext) => {
|
||||||
|
const adapter = context.adapter;
|
||||||
|
const repoManager = context.repositoryManager;
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Invoice",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const getInvoiceUseCase = new GetInvoiceUseCase(context);
|
||||||
|
|
||||||
|
return new GetInvoiceController(
|
||||||
|
{
|
||||||
|
useCase: getInvoiceUseCase,
|
||||||
|
presenter: getInvoicePresenter,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { IGetInvoice_Response_DTO } from "@shared/contexts";
|
||||||
|
import { invoiceItemPresenter } from "./InvoiceItem.presenter";
|
||||||
|
import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter";
|
||||||
|
|
||||||
|
export interface IGetInvoicePresenter {
|
||||||
|
map: (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) => Promise<IGetInvoice_Response_DTO>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getInvoicePresenter: IGetInvoicePresenter = {
|
||||||
|
map: async (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): Promise<IGetInvoice_Response_DTO> => {
|
||||||
|
return {
|
||||||
|
id: invoice.id.toString(),
|
||||||
|
|
||||||
|
invoice_status: invoice.status.toString(),
|
||||||
|
invoice_number: invoice.invoiceNumber.toString(),
|
||||||
|
invoice_series: invoice.invoiceSeries.toString(),
|
||||||
|
issue_date: invoice.issueDate.toISO8601(),
|
||||||
|
operation_date: invoice.operationDate.toISO8601(),
|
||||||
|
language_code: invoice.language.toString(),
|
||||||
|
currency: invoice.currency.toString(),
|
||||||
|
subtotal: invoice.calculateSubtotal().toObject(),
|
||||||
|
total: invoice.calculateTotal().toObject(),
|
||||||
|
|
||||||
|
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
|
||||||
|
|
||||||
|
recipient: await InvoiceParticipantPresenter(invoice.recipient, context),
|
||||||
|
items: invoiceItemPresenter(invoice.items, context),
|
||||||
|
|
||||||
|
payment_term: {
|
||||||
|
payment_type: "",
|
||||||
|
due_date: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
due_amount: {
|
||||||
|
currency: invoice.currency.toString(),
|
||||||
|
precision: 2,
|
||||||
|
amount: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
custom_fields: [],
|
||||||
|
|
||||||
|
metadata: {
|
||||||
|
create_time: "",
|
||||||
|
last_updated_time: "",
|
||||||
|
delete_time: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceItem } from "@/contexts/invoicing/domain/InvoiceItems";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const invoiceItemPresenter = (
|
||||||
|
items: ICollection<InvoiceItem>,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) =>
|
||||||
|
items.totalCount > 0
|
||||||
|
? items.items.map((item: InvoiceItem) => ({
|
||||||
|
description: item.description.toString(),
|
||||||
|
quantity: item.quantity.toString(),
|
||||||
|
unit_measure: "",
|
||||||
|
unit_price: item.unitPrice.toObject() as IMoney_Response_DTO,
|
||||||
|
subtotal: item.calculateSubtotal().toObject() as IMoney_Response_DTO,
|
||||||
|
tax_amount: item.calculateTaxAmount().toObject() as IMoney_Response_DTO,
|
||||||
|
total: item.calculateTotal().toObject() as IMoney_Response_DTO,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { IInvoiceParticipant } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICreateInvoice_Participant_Response_DTO } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipantAddressPresenter } from "./InvoiceParticipantAddress.presenter";
|
||||||
|
|
||||||
|
export const InvoiceParticipantPresenter = async (
|
||||||
|
participant: IInvoiceParticipant,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): Promise<ICreateInvoice_Participant_Response_DTO | undefined> => {
|
||||||
|
return {
|
||||||
|
id: participant.id.toString(),
|
||||||
|
tin: participant.tin.toString(),
|
||||||
|
first_name: participant.firstName.toString(),
|
||||||
|
last_name: participant.lastName.toString(),
|
||||||
|
company_name: participant.companyName.toString(),
|
||||||
|
|
||||||
|
billing_address: await InvoiceParticipantAddressPresenter(
|
||||||
|
participant.billingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
shipping_address: await InvoiceParticipantAddressPresenter(
|
||||||
|
participant.shippingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICreateInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const InvoiceParticipantAddressPresenter = async (
|
||||||
|
address: InvoiceParticipantAddress,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): Promise<ICreateInvoice_AddressParticipant_Response_DTO> => {
|
||||||
|
return {
|
||||||
|
id: address.id.toString(),
|
||||||
|
street: address.street.toString(),
|
||||||
|
city: address.city.toString(),
|
||||||
|
postal_code: address.postalCode.toString(),
|
||||||
|
province: address.province.toString(),
|
||||||
|
country: address.country.toString(),
|
||||||
|
email: address.email.toString(),
|
||||||
|
phone: address.phone.toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./GetInvoice.presenter";
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./create-invoice";
|
||||||
|
export * from "./delete-invoice";
|
||||||
|
export * from "./get-invoice";
|
||||||
|
export * from "./list-invoices";
|
||||||
|
export * from "./update-invoice";
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { SequelizeTransactionManager } from "@common/infrastructure";
|
||||||
|
import { ListInvoicesController } from "./list-invoices.controller";
|
||||||
|
import { listInvoicesPresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const listInvoicesController = () => {
|
||||||
|
const transactionManager = new SequelizeTransactionManager();
|
||||||
|
const invoiceService = new InvoiceService(invoiceRepository);
|
||||||
|
|
||||||
|
const useCase = new ListInvoicesUseCase(invoiceService, transactionManager);
|
||||||
|
const presenter = listInvoicesPresenter;
|
||||||
|
|
||||||
|
return new ListInvoicesController(useCase, presenter);
|
||||||
|
};
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { ListInvoicesUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import { ExpressController } from "@common/presentation";
|
||||||
|
import { IListInvoicesPresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class ListInvoicesController extends ExpressController {
|
||||||
|
public constructor(
|
||||||
|
private readonly listInvoices: ListInvoicesUseCase,
|
||||||
|
private readonly presenter: IListInvoicesPresenter
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const { query } = this.req;
|
||||||
|
//const queryCriteria: IQueryCriteria = QueryCriteriaService.parse(query);
|
||||||
|
|
||||||
|
const invoicesOrError = await this.listInvoices.execute(/* queryCriteria */);
|
||||||
|
|
||||||
|
if (invoicesOrError.isFailure) {
|
||||||
|
return this.handleError(invoicesOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ok(
|
||||||
|
this.presenter.toDTO(
|
||||||
|
invoicesOrError.data /*, {
|
||||||
|
page: queryCriteria.pagination.offset,
|
||||||
|
limit: queryCriteria.pagination.limit,
|
||||||
|
}*/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: Error) {
|
||||||
|
const message = error.message;
|
||||||
|
|
||||||
|
if (
|
||||||
|
message.includes("Database connection lost") ||
|
||||||
|
message.includes("Database request timed out")
|
||||||
|
) {
|
||||||
|
return this.unavailableError(
|
||||||
|
"Database service is currently unavailable. Please try again later."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.conflictError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { IInvoiceParticipant } from "@/contexts/invoicing/domain";
|
||||||
|
import { IListInvoice_Participant_Response_DTO } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipantAddressPresenter } from "./InvoiceParticipantAddress.presenter";
|
||||||
|
|
||||||
|
export const InvoiceParticipantPresenter = (
|
||||||
|
participant: IInvoiceParticipant,
|
||||||
|
): IListInvoice_Participant_Response_DTO => {
|
||||||
|
return {
|
||||||
|
participant_id: participant?.id?.toString(),
|
||||||
|
tin: participant?.tin?.toString(),
|
||||||
|
first_name: participant?.firstName?.toString(),
|
||||||
|
last_name: participant?.lastName?.toString(),
|
||||||
|
company_name: participant?.companyName?.toString(),
|
||||||
|
|
||||||
|
billing_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant?.billingAddress!,
|
||||||
|
),
|
||||||
|
shipping_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant?.shippingAddress!,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain";
|
||||||
|
import { IListInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const InvoiceParticipantAddressPresenter = (
|
||||||
|
address: InvoiceParticipantAddress,
|
||||||
|
): IListInvoice_AddressParticipant_Response_DTO => {
|
||||||
|
return {
|
||||||
|
address_id: address?.id.toString(),
|
||||||
|
street: address?.street.toString(),
|
||||||
|
city: address?.city.toString(),
|
||||||
|
postal_code: address?.postalCode.toString(),
|
||||||
|
province: address?.province.toString(),
|
||||||
|
country: address?.country.toString(),
|
||||||
|
email: address?.email.toString(),
|
||||||
|
phone: address?.phone.toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./list-invoices.presenter";
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { Collection } from "@common/helpers";
|
||||||
|
import { Invoice } from "@contexts/invoicing/domain";
|
||||||
|
import { IListInvoicesResponseDTO } from "@contexts/invoicing/presentation/dto";
|
||||||
|
|
||||||
|
export interface IListInvoicesPresenter {
|
||||||
|
toDTO: (invoices: Collection<Invoice>) => IListInvoicesResponseDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listInvoicesPresenter: IListInvoicesPresenter = {
|
||||||
|
toDTO: (invoices: Collection<Invoice>): IListInvoicesResponseDTO[] => {
|
||||||
|
return invoices.map((invoice) => {
|
||||||
|
const result = {
|
||||||
|
id: invoice.id.toPrimitive(),
|
||||||
|
|
||||||
|
invoice_status: invoice.status.toPrimitive(),
|
||||||
|
invoice_number: invoice.invoiceNumber.toPrimitive(),
|
||||||
|
invoice_series: invoice.invoiceSeries.toPrimitive(),
|
||||||
|
issue_date: invoice.issueDate.toPrimitive()!,
|
||||||
|
operation_date: invoice.operationDate.toPrimitive()!,
|
||||||
|
language_code: invoice.language.toPrimitive(),
|
||||||
|
currency: invoice.currency.toPrimitive(),
|
||||||
|
subtotal: invoice.calculateSubtotal().toObject(),
|
||||||
|
total: invoice.calculateTotal().toObject(),
|
||||||
|
|
||||||
|
//recipient: InvoiceParticipantPresenter(invoice.recipient),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.timeEnd("listInvoicesPresenter.map");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
import { UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
|
import { IServerError } from "@/contexts/common/domain";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import {
|
||||||
|
IUpdateInvoice_DTO,
|
||||||
|
IUpdateInvoice_Response_DTO,
|
||||||
|
RuleValidator,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "../../../InvoicingContext";
|
||||||
|
|
||||||
|
import { UpdateInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import { IUpdateInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class UpdateInvoiceController extends ExpressController {
|
||||||
|
private useCase: UpdateInvoiceUseCase;
|
||||||
|
private presenter: IUpdateInvoicePresenter;
|
||||||
|
private context: IInvoicingContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: {
|
||||||
|
useCase: UpdateInvoiceUseCase;
|
||||||
|
presenter: IUpdateInvoicePresenter;
|
||||||
|
},
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase, presenter } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl(): Promise<any> {
|
||||||
|
const { invoiceId } = this.req.params;
|
||||||
|
const request: IUpdateInvoice_DTO = this.req.body;
|
||||||
|
|
||||||
|
if (
|
||||||
|
RuleValidator.validate(
|
||||||
|
RuleValidator.RULE_NOT_NULL_OR_UNDEFINED,
|
||||||
|
invoiceId,
|
||||||
|
).isFailure
|
||||||
|
) {
|
||||||
|
return this.invalidInputError("Invoice Id param is required!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(invoiceId);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return this.invalidInputError("Invalid invoice Id param!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.useCase.execute({
|
||||||
|
id: idOrError.object,
|
||||||
|
data: request,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
const { error } = result;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case UseCaseError.NOT_FOUND_ERROR:
|
||||||
|
return this.notFoundError("Invoice not found", error);
|
||||||
|
|
||||||
|
case UseCaseError.INVALID_INPUT_DATA:
|
||||||
|
return this.invalidInputError(error.message);
|
||||||
|
|
||||||
|
case UseCaseError.UNEXCEPTED_ERROR:
|
||||||
|
return this.internalServerError(result.error.message, result.error);
|
||||||
|
|
||||||
|
case UseCaseError.REPOSITORY_ERROR:
|
||||||
|
return this.conflictError(result.error, result.error.details);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return this.clientError(result.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = <Invoice>result.object;
|
||||||
|
|
||||||
|
return this.ok<IUpdateInvoice_Response_DTO>(
|
||||||
|
this.presenter.map(invoice, this.context),
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return this.fail(e as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import { UpdateInvoiceUseCase } from "@/contexts/invoicing/application";
|
||||||
|
import {
|
||||||
|
ContactRepository,
|
||||||
|
IInvoicingContext,
|
||||||
|
InvoiceParticipantAddressRepository,
|
||||||
|
InvoiceParticipantRepository,
|
||||||
|
} from "../../..";
|
||||||
|
import { InvoiceRepository } from "../../../Invoice.repository";
|
||||||
|
import {
|
||||||
|
createContactMapper,
|
||||||
|
createInvoiceMapper,
|
||||||
|
createInvoiceParticipantAddressMapper,
|
||||||
|
createInvoiceParticipantMapper,
|
||||||
|
} from "../../../mappers";
|
||||||
|
import { UpdateInvoiceController } from "./UpdateInvoiceController";
|
||||||
|
import { updateInvoicePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const updateInvoiceController = (context: IInvoicingContext) => {
|
||||||
|
const adapter = context.adapter;
|
||||||
|
const repoManager = context.repositoryManager;
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Invoice",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Participant",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceParticipantRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceParticipantMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"ParticipantAddress",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new InvoiceParticipantAddressRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createInvoiceParticipantAddressMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
repoManager.registerRepository(
|
||||||
|
"Contact",
|
||||||
|
(params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new ContactRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createContactMapper(context),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateInvoiceUseCase = new UpdateInvoiceUseCase(context);
|
||||||
|
|
||||||
|
return new UpdateInvoiceController(
|
||||||
|
{
|
||||||
|
useCase: updateInvoiceUseCase,
|
||||||
|
presenter: updateInvoicePresenter,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceItem } from "@/contexts/invoicing/domain/InvoiceItems";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const invoiceItemPresenter = (
|
||||||
|
items: ICollection<InvoiceItem>,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) =>
|
||||||
|
items.totalCount > 0
|
||||||
|
? items.items.map((item: InvoiceItem) => ({
|
||||||
|
description: item.description.toString(),
|
||||||
|
quantity: item.quantity.toString(),
|
||||||
|
unit_measure: "",
|
||||||
|
unit_price: item.unitPrice.toObject() as IMoney_Response_DTO,
|
||||||
|
subtotal: item.calculateSubtotal().toObject() as IMoney_Response_DTO,
|
||||||
|
tax_amount: item.calculateTaxAmount().toObject() as IMoney_Response_DTO,
|
||||||
|
total: item.calculateTotal().toObject() as IMoney_Response_DTO,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { IInvoiceParticipant } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { IUpdateInvoice_Participant_Response_DTO } from "@shared/contexts";
|
||||||
|
import { InvoiceParticipantAddressPresenter } from "./InvoiceParticipantAddress.presenter";
|
||||||
|
|
||||||
|
export const InvoiceParticipantPresenter = (
|
||||||
|
participant: IInvoiceParticipant,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): IUpdateInvoice_Participant_Response_DTO | undefined => {
|
||||||
|
return {
|
||||||
|
id: participant.id.toString(),
|
||||||
|
tin: participant.tin.toString(),
|
||||||
|
first_name: participant.firstName.toString(),
|
||||||
|
last_name: participant.lastName.toString(),
|
||||||
|
company_name: participant.companyName.toString(),
|
||||||
|
|
||||||
|
billing_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant.billingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
shipping_address: InvoiceParticipantAddressPresenter(
|
||||||
|
participant.shippingAddress!,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { IUpdateInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const InvoiceParticipantAddressPresenter = (
|
||||||
|
address: InvoiceParticipantAddress,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): IUpdateInvoice_AddressParticipant_Response_DTO => {
|
||||||
|
return {
|
||||||
|
id: address.id.toString(),
|
||||||
|
street: address.street.toString(),
|
||||||
|
city: address.city.toString(),
|
||||||
|
postal_code: address.postalCode.toString(),
|
||||||
|
province: address.province.toString(),
|
||||||
|
country: address.country.toString(),
|
||||||
|
email: address.email.toString(),
|
||||||
|
phone: address.phone.toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { Invoice } from "@/contexts/invoicing/domain";
|
||||||
|
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||||
|
import { IUpdateInvoice_Response_DTO } from "@shared/contexts";
|
||||||
|
import { invoiceItemPresenter } from "./InvoiceItem.presenter";
|
||||||
|
import { InvoiceParticipantPresenter } from "./InvoiceParticipant.presenter";
|
||||||
|
|
||||||
|
export interface IUpdateInvoicePresenter {
|
||||||
|
map: (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
) => IUpdateInvoice_Response_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateInvoicePresenter: IUpdateInvoicePresenter = {
|
||||||
|
map: (
|
||||||
|
invoice: Invoice,
|
||||||
|
context: IInvoicingContext,
|
||||||
|
): IUpdateInvoice_Response_DTO => {
|
||||||
|
return {
|
||||||
|
id: invoice.id.toString(),
|
||||||
|
|
||||||
|
invoice_status: invoice.status.toString(),
|
||||||
|
invoice_number: invoice.invoiceNumber.toString(),
|
||||||
|
invoice_series: invoice.invoiceSeries.toString(),
|
||||||
|
issue_date: invoice.issueDate.toISO8601(),
|
||||||
|
operation_date: invoice.operationDate.toISO8601(),
|
||||||
|
language_code: invoice.language.toString(),
|
||||||
|
currency: invoice.currency.toString(),
|
||||||
|
subtotal: invoice.calculateSubtotal().toObject(),
|
||||||
|
total: invoice.calculateTotal().toObject(),
|
||||||
|
|
||||||
|
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
|
||||||
|
|
||||||
|
recipient: InvoiceParticipantPresenter(invoice.recipient, context),
|
||||||
|
|
||||||
|
items: invoiceItemPresenter(invoice.items, context),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./UpdateInvoice.presenter";
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./invoices.request.dto";
|
||||||
|
export * from "./invoices.response.dto";
|
||||||
|
export * from "./invoices.schemas";
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
export interface IListInvoicesRequestDTO {}
|
||||||
|
|
||||||
|
export interface ICreateInvoiceRequestDTO {
|
||||||
|
id: string;
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUpdateInvoiceRequestDTO {
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,114 @@
|
|||||||
|
export interface IListInvoicesResponseDTO {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
status: string;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetInvoiceResponseDTO {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
status: string;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICreateInvoiceResponseDTO {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
status: string;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inferir el tipo en TypeScript desde el esquema Zod
|
||||||
|
//export type IUpdateAcccountResponseDTO = z.infer<typeof IUpdateAcccountResponseDTOSchema>;
|
||||||
|
|
||||||
|
export interface IUpdateInvoiceResponseDTO {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
is_freelancer: boolean;
|
||||||
|
name: string;
|
||||||
|
trade_name: string;
|
||||||
|
tin: string;
|
||||||
|
|
||||||
|
street: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postal_code: string;
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
fax: string;
|
||||||
|
website: string;
|
||||||
|
|
||||||
|
legal_record: string;
|
||||||
|
|
||||||
|
default_tax: number;
|
||||||
|
status: string;
|
||||||
|
lang_code: string;
|
||||||
|
currency_code: string;
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const ListInvoicesRequestSchema = z.object({});
|
||||||
|
|
||||||
|
export const IGetInvoiceRequestSchema = z.object({});
|
||||||
|
|
||||||
|
export const ICreateInvoiceRequestSchema = z.object({});
|
||||||
|
|
||||||
|
export const IUpdateInvoiceRequestSchema = z.object({});
|
||||||
|
|
||||||
|
export const IDeleteInvoiceRequestSchema = z.object({});
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { validateRequestDTO } from "@common/presentation";
|
import { validateRequestDTO } from "@common/presentation";
|
||||||
import {
|
import {
|
||||||
ICreateAcccountResponseDTOSchema,
|
ICreateAccountRequestSchema,
|
||||||
IGetAcccountResponseDTOSchema,
|
IGetAccountRequestSchema,
|
||||||
IUpdateAcccountResponseDTOSchema,
|
IUpdateAccountRequestSchema,
|
||||||
ListAccountsSchema,
|
ListAccountsRequestSchema,
|
||||||
} from "@contexts/accounts/presentation";
|
} from "@contexts/accounts/presentation";
|
||||||
import {
|
import {
|
||||||
createAccountController,
|
createAccountController,
|
||||||
@ -19,7 +19,7 @@ export const accountsRouter = (appRouter: Router) => {
|
|||||||
|
|
||||||
routes.get(
|
routes.get(
|
||||||
"/",
|
"/",
|
||||||
validateRequestDTO(ListAccountsSchema),
|
validateRequestDTO(ListAccountsRequestSchema),
|
||||||
checkTabContext,
|
checkTabContext,
|
||||||
//checkUser,
|
//checkUser,
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -28,8 +28,8 @@ export const accountsRouter = (appRouter: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
routes.get(
|
routes.get(
|
||||||
"/:accountId",
|
"/:invoiceId",
|
||||||
validateRequestDTO(IGetAcccountResponseDTOSchema),
|
validateRequestDTO(IGetAccountRequestSchema),
|
||||||
checkTabContext,
|
checkTabContext,
|
||||||
//checkUser,
|
//checkUser,
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -39,7 +39,7 @@ export const accountsRouter = (appRouter: Router) => {
|
|||||||
|
|
||||||
routes.post(
|
routes.post(
|
||||||
"/",
|
"/",
|
||||||
validateRequestDTO(ICreateAcccountResponseDTOSchema),
|
validateRequestDTO(ICreateAccountRequestSchema),
|
||||||
checkTabContext,
|
checkTabContext,
|
||||||
//checkUser,
|
//checkUser,
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
@ -48,8 +48,8 @@ export const accountsRouter = (appRouter: Router) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
routes.put(
|
routes.put(
|
||||||
"/:accountId",
|
"/:invoiceId",
|
||||||
validateRequestDTO(IUpdateAcccountResponseDTOSchema),
|
validateRequestDTO(IUpdateAccountRequestSchema),
|
||||||
checkTabContext,
|
checkTabContext,
|
||||||
//checkUser,
|
//checkUser,
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user