Facturas de cliente
This commit is contained in:
parent
206d5bf9d3
commit
b7a58ebad5
@ -0,0 +1,45 @@
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { CreateCustomerInvoiceResponseDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
|
||||
type CreateCustomerInvoiceItemsByInvoiceIdResponseDTO = CreateCustomerInvoiceResponseDTO["items"];
|
||||
|
||||
export class CreateCustomerInvoiceItemsAssembler {
|
||||
toDTO(invoice: CustomerInvoice): CreateCustomerInvoiceItemsByInvoiceIdResponseDTO {
|
||||
const { items } = invoice;
|
||||
return items.map((item, index) => ({
|
||||
id: item.id.toString(),
|
||||
position: String(index),
|
||||
description: toEmptyString(item.description, (value) => value.toPrimitive()),
|
||||
|
||||
quantity: item.quantity.match(
|
||||
(quantity) => {
|
||||
const { value, scale } = quantity.toPrimitive();
|
||||
return { value: value.toString(), scale: scale.toString() };
|
||||
},
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
unit_amount: item.unitAmount.match(
|
||||
(unitAmount) => {
|
||||
const { value, scale } = unitAmount.toPrimitive();
|
||||
return { value: value.toString(), scale: scale.toString() };
|
||||
},
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
discount_percentage: item.discountPercentage.match(
|
||||
(discountPercentage) => {
|
||||
const { value, scale } = discountPercentage.toPrimitive();
|
||||
return { value: value.toString(), scale: scale.toString() };
|
||||
},
|
||||
() => ({ value: "", scale: "" })
|
||||
),
|
||||
|
||||
total_amount: {
|
||||
value: item.totalAmount.toPrimitive().value.toString(),
|
||||
scale: item.totalAmount.toPrimitive().scale.toString(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,68 @@
|
||||
import { CustomerInvoice } from "@erp/customer-invoices/api/domain";
|
||||
import { CustomerInvoicesCreationResponseDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
import { CreateCustomerInvoiceItemsAssembler } from "./create-customer-invoice-items.assembler";
|
||||
|
||||
export class CreateCustomerInvoiceAssembler {
|
||||
private _itemsAssembler!: CreateCustomerInvoiceItemsAssembler;
|
||||
|
||||
constructor() {
|
||||
this._itemsAssembler = new CreateCustomerInvoiceItemsAssembler();
|
||||
}
|
||||
|
||||
public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO {
|
||||
const items = this._itemsAssembler.toDTO(invoice);
|
||||
|
||||
export class CreateCustomerInvoicesAssembler {
|
||||
public toDTO(invoice: CustomerInvoice): CustomerInvoicesCreationResponseDTO {
|
||||
return {
|
||||
id: invoice.id.toPrimitive(),
|
||||
company_id: invoice.companyId.toPrimitive(),
|
||||
|
||||
invoice_status: invoice.status.toString(),
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
invoice_series: invoice.invoiceSeries.toString(),
|
||||
issue_date: invoice.issueDate.toISOString(),
|
||||
operation_date: invoice.operationDate.toISOString(),
|
||||
language_code: "ES",
|
||||
currency: "EUR",
|
||||
status: invoice.status.toPrimitive(),
|
||||
series: invoice.series.toString(),
|
||||
|
||||
//subtotal_price: invoice.calculateSubtotal().toPrimitive(),
|
||||
//total_price: invoice.calculateTotal().toPrimitive(),
|
||||
invoice_date: invoice.invoiceDate.toDateString(),
|
||||
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
|
||||
|
||||
//recipient: CustomerInvoiceParticipantAssembler(customerInvoice.recipient),
|
||||
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
|
||||
|
||||
language_code: invoice.languageCode.toPrimitive(),
|
||||
currency_code: invoice.currencyCode.toPrimitive(),
|
||||
|
||||
subtotal_amount: {
|
||||
value: invoice.subtotalAmount.value.toString(),
|
||||
scale: invoice.subtotalAmount.scale.toString(),
|
||||
},
|
||||
|
||||
discount_percentage: {
|
||||
value: invoice.discountPercentage.value.toString(),
|
||||
scale: invoice.discountPercentage.scale.toString(),
|
||||
},
|
||||
|
||||
discount_amount: {
|
||||
value: invoice.discountAmount.value.toString(),
|
||||
scale: invoice.discountAmount.scale.toString(),
|
||||
},
|
||||
|
||||
taxable_amount: {
|
||||
value: invoice.taxableAmount.value.toString(),
|
||||
scale: invoice.taxableAmount.scale.toString(),
|
||||
},
|
||||
|
||||
tax_amount: {
|
||||
value: invoice.taxAmount.value.toString(),
|
||||
scale: invoice.taxAmount.scale.toString(),
|
||||
},
|
||||
|
||||
total_amount: {
|
||||
value: invoice.totalAmount.value.toString(),
|
||||
scale: invoice.totalAmount.scale.toString(),
|
||||
},
|
||||
|
||||
items,
|
||||
|
||||
metadata: {
|
||||
entity: "customer-invoice",
|
||||
entity: "customer-invoices",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,62 +1,78 @@
|
||||
import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
|
||||
import { CreateCustomerInvoiceRequestDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { ICustomerInvoiceService } from "../../domain";
|
||||
import { mapDTOToCustomerInvoiceProps } from "../helpers";
|
||||
import { CreateCustomerInvoicesAssembler } from "./assembler";
|
||||
import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto";
|
||||
import { CustomerInvoiceService } from "../../domain";
|
||||
import { CreateCustomerInvoiceAssembler } from "./assembler";
|
||||
import { mapDTOToCreateCustomerInvoiceProps } from "./map-dto-to-create-customer-invoice-props";
|
||||
|
||||
type CreateCustomerInvoiceUseCaseInput = {
|
||||
tenantId: string;
|
||||
companyId: UniqueID;
|
||||
dto: CreateCustomerInvoiceRequestDTO;
|
||||
};
|
||||
|
||||
export class CreateCustomerInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly service: ICustomerInvoiceService,
|
||||
private readonly service: CustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: CreateCustomerInvoicesAssembler
|
||||
private readonly assembler: CreateCustomerInvoiceAssembler
|
||||
) {}
|
||||
|
||||
public execute(params: CreateCustomerInvoiceUseCaseInput) {
|
||||
const { dto, tenantId } = params;
|
||||
const invoicePropsOrError = mapDTOToCustomerInvoiceProps(dto);
|
||||
const { dto, companyId } = params;
|
||||
|
||||
if (invoicePropsOrError.isFailure) {
|
||||
return Result.fail(invoicePropsOrError.error);
|
||||
// 1) Mapear DTO → props de dominio
|
||||
const dtoResult = mapDTOToCreateCustomerInvoiceProps(dto);
|
||||
if (dtoResult.isFailure) {
|
||||
return Result.fail(dtoResult.error);
|
||||
}
|
||||
|
||||
const { props, id } = invoicePropsOrError.data;
|
||||
const invoiceOrError = this.service.build(props, id);
|
||||
const { props, id } = dtoResult.data;
|
||||
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(invoiceOrError.error);
|
||||
// 2) Construir entidad de dominio
|
||||
const buildResult = this.service.buildInvoiceInCompany(companyId, props, id);
|
||||
if (buildResult.isFailure) {
|
||||
return Result.fail(buildResult.error);
|
||||
}
|
||||
|
||||
const newInvoice = invoiceOrError.data;
|
||||
const newInvoice = buildResult.data;
|
||||
|
||||
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
try {
|
||||
const duplicateCheck = await this.service.existsById(id, transaction);
|
||||
|
||||
if (duplicateCheck.isFailure) {
|
||||
return Result.fail(duplicateCheck.error);
|
||||
const existsGuard = await this.ensureNotExists(companyId, id, transaction);
|
||||
if (existsGuard.isFailure) {
|
||||
return Result.fail(existsGuard.error);
|
||||
}
|
||||
|
||||
if (duplicateCheck.data) {
|
||||
return Result.fail(new DuplicateEntityError("CustomerInvoice", id.toString()));
|
||||
const saveResult = await this.service.saveInvoice(newInvoice, transaction);
|
||||
if (saveResult.isFailure) {
|
||||
return Result.fail(saveResult.error);
|
||||
}
|
||||
|
||||
const result = await this.service.save(newInvoice, transaction);
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
const viewDTO = this.assembler.toDTO(saveResult.data);
|
||||
|
||||
const viewDTO = this.assembler.toDTO(newInvoice);
|
||||
return Result.ok(viewDTO);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Verifica que no exista uana factura con el mismo id en la companyId.
|
||||
*/
|
||||
private async ensureNotExists(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: Transaction
|
||||
): Promise<Result<void, Error>> {
|
||||
const existsResult = await this.service.existsByIdInCompany(companyId, id, transaction);
|
||||
if (existsResult.isFailure) {
|
||||
return Result.fail(existsResult.error);
|
||||
}
|
||||
|
||||
if (existsResult.data) {
|
||||
return Result.fail(new DuplicateEntityError("Customer invoice", "id", String(id)));
|
||||
}
|
||||
|
||||
return Result.ok<void>(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,190 @@
|
||||
import {
|
||||
DomainError,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import {
|
||||
CurrencyCode,
|
||||
LanguageCode,
|
||||
Percentage,
|
||||
TextValue,
|
||||
UniqueID,
|
||||
UtcDate,
|
||||
maybeFromNullableVO,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto";
|
||||
import {
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemProps,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemUnitAmount,
|
||||
CustomerInvoiceNumber,
|
||||
CustomerInvoiceProps,
|
||||
CustomerInvoiceSerie,
|
||||
CustomerInvoiceStatus,
|
||||
} from "../../domain";
|
||||
|
||||
/**
|
||||
* Convierte el DTO a las props validadas (CustomerProps).
|
||||
* No construye directamente el agregado.
|
||||
*
|
||||
* @param dto - DTO con los datos de la factura de cliente
|
||||
* @returns
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
export function mapDTOToCreateCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) {
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(dto.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
const status = extractOrPushError(CustomerInvoiceStatus.create(dto.status), "status", errors);
|
||||
|
||||
const series = extractOrPushError(
|
||||
maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
"series",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(dto.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const operationDate = extractOrPushError(
|
||||
maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const notes = extractOrPushError(
|
||||
maybeFromNullableVO(dto.notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
errors
|
||||
);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(dto.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(dto.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
Percentage.create({
|
||||
value: Number(dto.discount_percentage.value),
|
||||
scale: Number(dto.discount_percentage.scale),
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
const items = mapDTOToCreateCustomerInvoiceItemsProps(dto, errors);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
|
||||
);
|
||||
}
|
||||
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
companyId: companyId!,
|
||||
status: status!,
|
||||
|
||||
invoiceNumber: invoiceNumber!,
|
||||
invoiceDate: invoiceDate!,
|
||||
|
||||
operationDate: operationDate!,
|
||||
series: series!,
|
||||
|
||||
notes: notes!,
|
||||
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
|
||||
discountPercentage: discountPercentage!,
|
||||
};
|
||||
|
||||
return Result.ok({ id: customerId!, props: invoiceProps });
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
|
||||
}
|
||||
}
|
||||
|
||||
function mapDTOToCreateCustomerInvoiceItemsProps(
|
||||
dto: CreateCustomerInvoiceRequestDTO,
|
||||
errors: ValidationErrorDetail[]
|
||||
): CustomerInvoiceItemProps[] | undefined {
|
||||
const items: CustomerInvoiceItemProps[] = [];
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(dto.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(dto.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
dto.items.forEach((item, index) => {
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableVO(item.description, (value) =>
|
||||
CustomerInvoiceItemDescription.create(value)
|
||||
),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableVO(item.quantity, (value) => CustomerInvoiceItemQuantity.create(value)),
|
||||
"quantity",
|
||||
errors
|
||||
);
|
||||
|
||||
const unitAmount = extractOrPushError(
|
||||
maybeFromNullableVO(item.unit_amount, (value) => CustomerInvoiceItemUnitAmount.create(value)),
|
||||
"unit_amount",
|
||||
errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
maybeFromNullableVO(item.discount_percentage, (value) =>
|
||||
CustomerInvoiceItemDiscount.create(value)
|
||||
),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
items.push({
|
||||
currencyCode: currencyCode!,
|
||||
languageCode: languageCode!,
|
||||
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
discountPercentage: discountPercentage!,
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
|
||||
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = UpdateCustomerInvoiceByIdResponseDTO["items"];
|
||||
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"];
|
||||
|
||||
export class GetCustomerInvoiceItemsAssembler {
|
||||
toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO {
|
||||
|
||||
@ -21,7 +21,7 @@ export class GetCustomerInvoiceAssembler {
|
||||
status: invoice.status.toPrimitive(),
|
||||
series: invoice.series.toString(),
|
||||
|
||||
issue_date: invoice.issueDate.toDateString(),
|
||||
invoice_date: invoice.invoiceDate.toDateString(),
|
||||
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
|
||||
|
||||
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
|
||||
@ -65,46 +65,6 @@ export class GetCustomerInvoiceAssembler {
|
||||
entity: "customer-invoices",
|
||||
},
|
||||
|
||||
//subtotal: customerInvoice.calculateSubtotal().toPrimitive(),
|
||||
|
||||
//total: customerInvoice.calculateTotal().toPrimitive(),
|
||||
|
||||
/*items:
|
||||
customerInvoice.items.size() > 0
|
||||
? customerInvoice.items.map((item: CustomerInvoiceItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toPrimitive(),
|
||||
unit_measure: "",
|
||||
unit_price: item.unitPrice.toPrimitive(),
|
||||
subtotal: item.calculateSubtotal().toPrimitive(),
|
||||
//tax_amount: item.calculateTaxAmount().toPrimitive(),
|
||||
total: item.calculateTotal().toPrimitive(),
|
||||
}))
|
||||
: [],*/
|
||||
|
||||
//sender: {}, //await CustomerInvoiceParticipantAssembler(customerInvoice.senderId, context),
|
||||
|
||||
/*recipient: await CustomerInvoiceParticipantAssembler(customerInvoice.recipient, context),
|
||||
items: customerInvoiceItemAssembler(customerInvoice.items, context),
|
||||
|
||||
payment_term: {
|
||||
payment_type: "",
|
||||
due_date: "",
|
||||
},
|
||||
|
||||
due_amount: {
|
||||
currency: customerInvoice.currency.toString(),
|
||||
precision: 2,
|
||||
amount: 0,
|
||||
},
|
||||
|
||||
custom_fields: [],
|
||||
|
||||
metadata: {
|
||||
create_time: "",
|
||||
last_updated_time: "",
|
||||
delete_time: "",
|
||||
},*/
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,11 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT
|
||||
"invoice_series",
|
||||
errors
|
||||
);
|
||||
const issueDate = extractOrPushError(UtcDate.createFromISO(dto.issue_date), "issue_date", errors);
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(dto.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
const operationDate = extractOrPushError(
|
||||
UtcDate.createFromISO(dto.operation_date),
|
||||
"operation_date",
|
||||
@ -59,7 +63,7 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
invoiceNumber: invoiceNumber!,
|
||||
invoiceSeries: invoiceSeries!,
|
||||
issueDate: issueDate!,
|
||||
invoiceDate: invoiceDate!,
|
||||
operationDate: operationDate!,
|
||||
status: CustomerInvoiceStatus.createDraft(),
|
||||
currency,
|
||||
|
||||
@ -15,7 +15,7 @@ export class ListCustomerInvoicesAssembler {
|
||||
invoice_status: invoice.status.toString(),
|
||||
invoice_number: invoice.invoiceNumber.toString(),
|
||||
invoice_series: invoice.invoiceSeries.toString(),
|
||||
issue_date: invoice.issueDate.toISOString(),
|
||||
invoice_date: invoice.invoiceDate.toISOString(),
|
||||
operation_date: invoice.operationDate.toISOString(),
|
||||
language_code: "ES",
|
||||
currency: "EUR",
|
||||
|
||||
@ -21,7 +21,7 @@ export class UpdateCustomerInvoiceAssembler {
|
||||
status: invoice.status.toPrimitive(),
|
||||
series: invoice.series.toString(),
|
||||
|
||||
issue_date: invoice.issueDate.toDateString(),
|
||||
invoice_date: invoice.invoiceDate.toDateString(),
|
||||
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
|
||||
|
||||
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
|
||||
|
||||
@ -52,9 +52,9 @@ export class CreateCustomerInvoiceUseCase {
|
||||
customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = CustomerInvoiceDate.create(customerInvoiceDTO.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = CustomerInvoiceDate.createCurrentDate().object;
|
||||
let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object;
|
||||
if (invoice_date.isEmpty()) {
|
||||
invoice_date = CustomerInvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object;
|
||||
@ -96,7 +96,7 @@ export class CreateCustomerInvoiceUseCase {
|
||||
return DraftCustomerInvoice.create(
|
||||
{
|
||||
customerInvoiceSeries: customerInvoice_series,
|
||||
issueDate: issue_date,
|
||||
invoiceDate: invoice_date,
|
||||
operationDate: operation_date,
|
||||
customerInvoiceCurrency,
|
||||
language: customerInvoiceLanguage,
|
||||
@ -285,9 +285,9 @@ export class UpdateCustomerInvoiceUseCase2
|
||||
customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object;
|
||||
}
|
||||
|
||||
let issue_date = CustomerInvoiceDate.create(customerInvoiceDTO.issue_date).object;
|
||||
if (issue_date.isEmpty()) {
|
||||
issue_date = CustomerInvoiceDate.createCurrentDate().object;
|
||||
let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object;
|
||||
if (invoice_date.isEmpty()) {
|
||||
invoice_date = CustomerInvoiceDate.createCurrentDate().object;
|
||||
}
|
||||
|
||||
let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object;
|
||||
@ -329,7 +329,7 @@ export class UpdateCustomerInvoiceUseCase2
|
||||
return DraftCustomerInvoice.create(
|
||||
{
|
||||
customerInvoiceSeries: customerInvoice_series,
|
||||
issueDate: issue_date,
|
||||
invoiceDate: invoice_date,
|
||||
operationDate: operation_date,
|
||||
customerInvoiceCurrency,
|
||||
language: customerInvoiceLanguage,
|
||||
|
||||
@ -23,7 +23,7 @@ export interface CustomerInvoiceProps {
|
||||
status: CustomerInvoiceStatus;
|
||||
series: Maybe<CustomerInvoiceSerie>;
|
||||
|
||||
issueDate: UtcDate;
|
||||
invoiceDate: UtcDate;
|
||||
operationDate: Maybe<UtcDate>;
|
||||
|
||||
notes: Maybe<TextValue>;
|
||||
@ -33,7 +33,6 @@ export interface CustomerInvoiceProps {
|
||||
//tax: Tax; // ? --> detalles?
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
@ -43,18 +42,18 @@ export interface CustomerInvoiceProps {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
subtotalAmount: MoneyValue;
|
||||
//subtotalAmount: MoneyValue;
|
||||
|
||||
discountPercentage: Percentage;
|
||||
//discountAmount: MoneyValue;
|
||||
|
||||
//taxableAmount: MoneyValue;
|
||||
taxAmount: MoneyValue;
|
||||
//taxAmount: MoneyValue;
|
||||
|
||||
totalAmount: MoneyValue;
|
||||
//totalAmount: MoneyValue;
|
||||
|
||||
//customer?: CustomerInvoiceCustomer;
|
||||
items?: CustomerInvoiceItems;
|
||||
items: CustomerInvoiceItems;
|
||||
}
|
||||
|
||||
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
||||
@ -107,8 +106,8 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
return this.props.invoiceNumber;
|
||||
}
|
||||
|
||||
public get issueDate(): UtcDate {
|
||||
return this.props.issueDate;
|
||||
public get invoiceDate(): UtcDate {
|
||||
return this.props.invoiceDate;
|
||||
}
|
||||
|
||||
public get operationDate(): Maybe<UtcDate> {
|
||||
@ -128,7 +127,7 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
}
|
||||
|
||||
public get subtotalAmount(): MoneyValue {
|
||||
return this.props.subtotalAmount;
|
||||
throw new Error("discountAmount not implemented");
|
||||
}
|
||||
|
||||
public get discountPercentage(): Percentage {
|
||||
@ -144,7 +143,7 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
}
|
||||
|
||||
public get taxAmount(): MoneyValue {
|
||||
return this.props.taxAmount;
|
||||
throw new Error("discountAmount not implemented");
|
||||
}
|
||||
|
||||
public get totalAmount(): MoneyValue {
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
import { CurrencyCode, DomainEntity, LanguageCode, UniqueID } from "@repo/rdx-ddd";
|
||||
import {
|
||||
CurrencyCode,
|
||||
DomainEntity,
|
||||
LanguageCode,
|
||||
MoneyValue,
|
||||
Percentage,
|
||||
UniqueID,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoiceItemDescription,
|
||||
@ -12,8 +19,8 @@ import {
|
||||
export interface CustomerInvoiceItemProps {
|
||||
description: Maybe<CustomerInvoiceItemDescription>;
|
||||
quantity: Maybe<CustomerInvoiceItemQuantity>; // Cantidad de unidades
|
||||
unitPrice: Maybe<CustomerInvoiceItemUnitAmount>; // Precio unitario en la moneda de la factura
|
||||
discount: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
||||
unitAmount: Maybe<CustomerInvoiceItemUnitAmount>; // Precio unitario en la moneda de la factura
|
||||
discountPercentage: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
@ -49,7 +56,7 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
||||
}
|
||||
|
||||
get unitAmount(): Maybe<CustomerInvoiceItemUnitAmount> {
|
||||
return this.props.unitPrice;
|
||||
return this.props.unitAmount;
|
||||
}
|
||||
|
||||
get subtotalAmount(): CustomerInvoiceItemSubtotalAmount {
|
||||
@ -59,8 +66,12 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
||||
return this._subtotalAmount;
|
||||
}
|
||||
|
||||
get discountPercentage(): Maybe<CustomerInvoiceItemDiscount> {
|
||||
return this.props.discount;
|
||||
get discountPercentage(): Maybe<Percentage> {
|
||||
return this.props.discountPercentage;
|
||||
}
|
||||
|
||||
get discountAmount(): Maybe<MoneyValue> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
get totalAmount(): CustomerInvoiceItemTotalAmount {
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
import { Quantity } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemQuantity extends Quantity {}
|
||||
@ -3,10 +3,10 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
export class CustomerInvoiceItemSubtotalAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ value: amount, currency_code, scale }: MoneyValueProps) {
|
||||
static create({ value, currency_code }: MoneyValueProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||
value: Number(value),
|
||||
scale: CustomerInvoiceItemSubtotalAmount.DEFAULT_SCALE,
|
||||
currency_code,
|
||||
};
|
||||
return MoneyValue.create(props);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemTotalAmount extends MoneyValue {
|
||||
export class ItemTotalAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ value, currency_code, scale }: MoneyValueProps) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
export * from "./customer-invoice-address-type";
|
||||
export * from "./customer-invoice-item-description";
|
||||
export * from "./customer-invoice-item-discount";
|
||||
export * from "./customer-invoice-item-quantity";
|
||||
export * from "./customer-invoice-item-subtotal-amount";
|
||||
export * from "./customer-invoice-item-total-amount";
|
||||
export * from "./customer-invoice-item-unit-amount";
|
||||
export * from "./customer-invoice-number";
|
||||
export * from "./customer-invoice-serie";
|
||||
export * from "./customer-invoice-status";
|
||||
export * from "./item-quantity";
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { Quantity, QuantityProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class ItemQuantity extends Quantity {
|
||||
public static DEFAULT_SCALE = 2;
|
||||
|
||||
static create({ value }: QuantityProps) {
|
||||
const props = {
|
||||
value: Number(value),
|
||||
scale: ItemQuantity.DEFAULT_SCALE,
|
||||
};
|
||||
return Quantity.create(props);
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,16 @@
|
||||
import type { ModuleParams } from "@erp/core/api";
|
||||
import { SequelizeTransactionManager } from "@erp/core/api";
|
||||
import {
|
||||
CreateCustomerInvoiceAssembler,
|
||||
CreateCustomerInvoiceUseCase,
|
||||
CreateCustomerInvoicesAssembler,
|
||||
DeleteCustomerInvoiceUseCase,
|
||||
GetCustomerInvoiceAssembler,
|
||||
GetCustomerInvoiceItemsAssembler,
|
||||
GetCustomerInvoiceUseCase,
|
||||
ListCustomerInvoicesAssembler,
|
||||
ListCustomerInvoicesUseCase,
|
||||
UpdateCustomerInvoiceAssembler,
|
||||
UpdateCustomerInvoiceUseCase,
|
||||
} from "../application";
|
||||
import { CustomerInvoiceService, ICustomerInvoiceService } from "../domain";
|
||||
import { CustomerInvoiceService } from "../domain";
|
||||
import { CustomerInvoiceMapper } from "./mappers";
|
||||
import { CustomerInvoiceRepository } from "./sequelize";
|
||||
|
||||
@ -20,11 +18,11 @@ type InvoiceDeps = {
|
||||
transactionManager: SequelizeTransactionManager;
|
||||
repo: CustomerInvoiceRepository;
|
||||
mapper: CustomerInvoiceMapper;
|
||||
service: ICustomerInvoiceService;
|
||||
service: CustomerInvoiceService;
|
||||
assemblers: {
|
||||
list: ListCustomerInvoicesAssembler;
|
||||
get: GetCustomerInvoiceItemsAssembler;
|
||||
create: CreateCustomerInvoicesAssembler;
|
||||
get: GetCustomerInvoiceAssembler;
|
||||
create: CreateCustomerInvoiceAssembler;
|
||||
update: UpdateCustomerInvoiceAssembler;
|
||||
};
|
||||
build: {
|
||||
@ -41,7 +39,7 @@ type InvoiceDeps = {
|
||||
|
||||
let _repo: CustomerInvoiceRepository | null = null;
|
||||
let _mapper: CustomerInvoiceMapper | null = null;
|
||||
let _service: ICustomerInvoiceService | null = null;
|
||||
let _service: CustomerInvoiceService | null = null;
|
||||
let _assemblers: InvoiceDeps["assemblers"] | null = null;
|
||||
|
||||
export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
@ -56,7 +54,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
_assemblers = {
|
||||
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
||||
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
||||
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
||||
create: new CreateCustomerInvoiceAssembler(), // transforma domain → CreatedDTO
|
||||
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,25 +4,17 @@ import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
|
||||
import { CreateCustomerInvoiceUseCase } from "../../../application";
|
||||
|
||||
export class CreateCustomerInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly useCase: CreateCustomerInvoiceUseCase
|
||||
/* private readonly presenter: any */
|
||||
) {
|
||||
public constructor(private readonly useCase: CreateCustomerInvoiceUseCase) {
|
||||
super();
|
||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const dto = this.req.body as CreateCustomerInvoiceRequestDTO;
|
||||
|
||||
/*
|
||||
// Inyectar empresa del usuario autenticado (ownership)
|
||||
dto.customerCompanyId = user.companyId;
|
||||
*/
|
||||
|
||||
const result = await this.useCase.execute({ tenantId, dto });
|
||||
const result = await this.useCase.execute({ dto, companyId });
|
||||
|
||||
return result.match(
|
||||
(data) => this.created(data),
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { CurrencyCode, LanguageCode, Quantity, UniqueID, maybeFromNullableVO, toNullable } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import {
|
||||
@ -35,70 +41,75 @@ export class CustomerInvoiceItemMapper
|
||||
source: CustomerInvoiceItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const { sourceParent } = params as { sourceParent: CustomerInvoiceModel };
|
||||
const { sourceParent, errors } = params as {
|
||||
sourceParent: CustomerInvoiceModel;
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
// Validación y creación de ID único
|
||||
const idOrError = UniqueID.create(source.item_id);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
const itemId = extractOrPushError(UniqueID.create(source.item_id), "item_id", errors);
|
||||
|
||||
// Validación y creación de descripción
|
||||
const descriptionOrError = CustomerInvoiceItemDescription.create(source.description || "");
|
||||
if (descriptionOrError.isFailure) {
|
||||
return Result.fail(descriptionOrError.error);
|
||||
}
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(sourceParent.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
// Validación y creación de cantidad
|
||||
const quantityOrError = CustomerInvoiceItemQuantity.create({
|
||||
amount: source.quantity_amount,
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(sourceParent.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const description = extractOrPushError(
|
||||
maybeFromNullableVO(source.description, (value) =>
|
||||
CustomerInvoiceItemDescription.create(value)
|
||||
),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
|
||||
const quantity = extractOrPushError(
|
||||
maybeFromNullableVO(source.quantity_value, (value) =>
|
||||
Quantity.create({ value, })
|
||||
|
||||
CustomerInvoiceItemQuantity.create({
|
||||
value: source.quantity_amouwnt,
|
||||
scale: source.quantity_scale,
|
||||
});
|
||||
if (quantityOrError.isFailure) {
|
||||
return Result.fail(quantityOrError.error);
|
||||
}
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
// Validación y creación de precio unitario
|
||||
const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({
|
||||
const unitAmount = extractOrPushError(
|
||||
CustomerInvoiceItemUnitAmount.create({
|
||||
value: source.unit_price_amount,
|
||||
scale: source.unit_price_scale,
|
||||
currency_code: sourceParent.invoice_currency,
|
||||
});
|
||||
if (unitPriceOrError.isFailure) {
|
||||
return Result.fail(unitPriceOrError.error);
|
||||
}
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
// Validación y creación de descuento
|
||||
const discountOrError = CustomerInvoiceItemDiscount.create({
|
||||
value: source.discount_amount || 0,
|
||||
scale: source.discount_scale || 0,
|
||||
});
|
||||
if (discountOrError.isFailure) {
|
||||
return Result.fail(discountOrError.error);
|
||||
}
|
||||
const discountPercentage = extractOrPushError(
|
||||
CustomerInvoiceItemDiscount.create({
|
||||
value: source.discount_amount,
|
||||
scale: source.discount_scale,
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
// Combinación de resultados
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
descriptionOrError,
|
||||
quantityOrError,
|
||||
unitPriceOrError,
|
||||
discountOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
// Creación del objeto de dominio
|
||||
return CustomerInvoiceItem.create(
|
||||
{
|
||||
description: descriptionOrError.data,
|
||||
quantity: quantityOrError.data,
|
||||
unitPrice: unitPriceOrError.data,
|
||||
discount: discountOrError.data,
|
||||
},
|
||||
idOrError.data
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
description: description!,
|
||||
quantity: quantity!,
|
||||
unitAmount: unitAmount!,
|
||||
discountPercentage: discountPercentage!,
|
||||
},º
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
@ -106,35 +117,44 @@ export class CustomerInvoiceItemMapper
|
||||
source: CustomerInvoiceItem,
|
||||
params?: MapperParamsType
|
||||
): InferCreationAttributes<CustomerInvoiceItemModel, {}> {
|
||||
1
|
||||
const { index, sourceParent } = params as {
|
||||
index: number;
|
||||
sourceParent: CustomerInvoice;
|
||||
};
|
||||
|
||||
const lineData = {
|
||||
parent_id: undefined,
|
||||
|
||||
|
||||
return {
|
||||
item_id: source.id.toPrimitive(),
|
||||
invoice_id: sourceParent.id.toPrimitive(),
|
||||
item_type: "simple",
|
||||
position: index,
|
||||
|
||||
item_id: source.id.toPrimitive(),
|
||||
description: source.description.toPrimitive(),
|
||||
description: toNullable(source.description, (description) => description.toPrimitive()),
|
||||
|
||||
quantity_amount: source.quantity.toPrimitive().amount,
|
||||
quantity_scale: source.quantity.toPrimitive().scale,
|
||||
quantity_value: toNullable(source.quantity, (value) => value.toPrimitive().value),
|
||||
quantity_scale: source.quantity.match(
|
||||
(value) => value.toPrimitive().scale,
|
||||
() => CustomerInvoiceItemQuantity.DEFAULT_SCALE),
|
||||
|
||||
unit_price_amount: source.unitAmount.toPrimitive().amount,
|
||||
unit_price_scale: source.unitAmount.toPrimitive().scale,
|
||||
unit_amount_value: toNullable(source.unitAmount, (value) => value.toPrimitive().value),
|
||||
unit_amount_scale: source.unitAmount.match(
|
||||
(value) => value.toPrimitive().scale,
|
||||
() => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE),
|
||||
|
||||
subtotal_amount: source.subtotalAmount.toPrimitive().amount,
|
||||
subtotal_scale: source.subtotalAmount.toPrimitive().scale,
|
||||
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
|
||||
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
||||
|
||||
discount_amount: source.discount.toPrimitive().amount,
|
||||
discount_scale: source.discount.toPrimitive().scale,
|
||||
discount_percentage_value: toNullable(source.discountPercentage, (value) => value.toPrimitive().value),
|
||||
discount_percentage_scale: source.discountPercentage.match(
|
||||
(value) => value.toPrimitive().scale,
|
||||
() => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE),
|
||||
|
||||
total_amount: source.totalAmount.toPrimitive().amount,
|
||||
total_scale: source.totalAmount.toPrimitive().scale,
|
||||
discount_amount_value: source.subtotalAmount.toPrimitive().value,
|
||||
discount_amount_scale: source.subtotalAmount.toPrimitive().scale,
|
||||
|
||||
total_amount_value: source.totalAmount.toPrimitive().value,
|
||||
total_amount_scale: source.totalAmount.toPrimitive().scale,
|
||||
};
|
||||
return lineData;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,15 @@ import {
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { CurrencyCode, LanguageCode, UniqueID, UtcDate, maybeFromNullableVO } from "@repo/rdx-ddd";
|
||||
import {
|
||||
CurrencyCode,
|
||||
LanguageCode,
|
||||
Percentage,
|
||||
TextValue,
|
||||
UniqueID,
|
||||
UtcDate,
|
||||
maybeFromNullableVO,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoice,
|
||||
@ -29,36 +37,48 @@ export class CustomerInvoiceMapper
|
||||
extends SequelizeMapper<CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice>
|
||||
implements ICustomerInvoiceMapper
|
||||
{
|
||||
private customerInvoiceItemMapper: CustomerInvoiceItemMapper;
|
||||
private _itemsMapper: CustomerInvoiceItemMapper;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.customerInvoiceItemMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items
|
||||
this._itemsMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items
|
||||
}
|
||||
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoice, Error> {
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||
const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors);
|
||||
const companyId = extractOrPushError(
|
||||
UniqueID.create(source.company_id),
|
||||
"company_id",
|
||||
errors
|
||||
);
|
||||
|
||||
const status = extractOrPushError(
|
||||
CustomerInvoiceStatus.create(source.status),
|
||||
"status",
|
||||
errors
|
||||
);
|
||||
const series = extractOrPushError(CustomerInvoiceSerie.create(source.series), "series", errors);
|
||||
|
||||
const series = extractOrPushError(
|
||||
maybeFromNullableVO(source.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||
"serie",
|
||||
errors
|
||||
);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(source.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
const issueDate = extractOrPushError(
|
||||
UtcDate.createFromISO(source.issue_date),
|
||||
"issue_date",
|
||||
|
||||
const invoiceDate = extractOrPushError(
|
||||
UtcDate.createFromISO(source.invoice_date),
|
||||
"invoice_date",
|
||||
errors
|
||||
);
|
||||
|
||||
@ -68,6 +88,12 @@ export class CustomerInvoiceMapper
|
||||
errors
|
||||
);
|
||||
|
||||
const notes = extractOrPushError(
|
||||
maybeFromNullableVO(source.notes, (value) => TextValue.create(value)),
|
||||
"notes",
|
||||
errors
|
||||
);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(source.language_code),
|
||||
"language_code",
|
||||
@ -80,6 +106,15 @@ export class CustomerInvoiceMapper
|
||||
errors
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
Percentage.create({
|
||||
value: source.discount_percentage_value,
|
||||
scale: source.discount_percentage_scale,
|
||||
}),
|
||||
"discount_percentage",
|
||||
errors
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
|
||||
@ -87,28 +122,40 @@ export class CustomerInvoiceMapper
|
||||
}
|
||||
|
||||
// Mapear los items de la factura
|
||||
/*const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, {
|
||||
const items = this._itemsMapper.mapArrayToDomain(source.items, {
|
||||
sourceParent: source,
|
||||
errors,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (itemsOrErrors.isFailure) {
|
||||
return Result.fail(itemsOrErrors.error);
|
||||
}*/
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice item props mapping failed", errors)
|
||||
);
|
||||
}
|
||||
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
companyId: companyId!,
|
||||
status: status!,
|
||||
series: series!,
|
||||
invoiceNumber: invoiceNumber!,
|
||||
issueDate: issueDate!,
|
||||
invoiceDate: invoiceDate!,
|
||||
operationDate: operationDate!,
|
||||
|
||||
notes: notes!,
|
||||
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
//items: itemsOrErrors.data,
|
||||
|
||||
discountPercentage: discountPercentage!,
|
||||
|
||||
items: items!,
|
||||
};
|
||||
|
||||
return CustomerInvoice.create(invoiceProps, invoiceId);
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(err as Error);
|
||||
}
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
@ -118,14 +165,14 @@ export class CustomerInvoiceMapper
|
||||
const subtotal = source.calculateSubtotal();
|
||||
const total = source.calculateTotal();
|
||||
|
||||
const items = this.customerInvoiceItemMapper.mapCollectionToPersistence(source.items, params);
|
||||
const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
|
||||
|
||||
return {
|
||||
id: source.id.toString(),
|
||||
invoice_status: source.status.toPrimitive(),
|
||||
invoice_series: source.invoiceSeries.toPrimitive(),
|
||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||
issue_date: source.issueDate.toPrimitive(),
|
||||
invoice_date: source.invoiceDate.toPrimitive(),
|
||||
operation_date: source.operationDate.toPrimitive(),
|
||||
invoice_language: "es",
|
||||
invoice_currency: source.currency || "EUR",
|
||||
|
||||
@ -7,7 +7,7 @@ const ALLOWED_FILTERS = {
|
||||
customerId: "customer_id",
|
||||
invoiceSeries: "invoice_series",
|
||||
invoiceNumber: "invoice_number",
|
||||
issueDate: "issue_date",
|
||||
invoiceDate: "invoice_date",
|
||||
status: "status",
|
||||
currencyCode: "currency_code",
|
||||
// Rango por total (en unidades menores)
|
||||
@ -15,8 +15,8 @@ const ALLOWED_FILTERS = {
|
||||
} as const;
|
||||
|
||||
const ALLOWED_SORT: Record<string, string | string[]> = {
|
||||
// Sort "issueDate" realmente ordena por (issue_date DESC, invoice_series ASC, invoice_number DESC, id DESC)
|
||||
issueDate: ["issue_date"],
|
||||
// Sort "invoiceDate" realmente ordena por (invoice_date DESC, invoice_series ASC, invoice_number DESC, id DESC)
|
||||
invoiceDate: ["invoice_date"],
|
||||
invoiceNumber: ["invoice_number"],
|
||||
invoiceSeries: ["invoice_series"],
|
||||
status: ["status"],
|
||||
@ -31,7 +31,7 @@ export const DEFAULT_LIST_ATTRIBUTES = [
|
||||
"customer_id",
|
||||
"invoice_series",
|
||||
"invoice_number",
|
||||
"issue_date",
|
||||
"invoice_date",
|
||||
"status",
|
||||
"total_amount_value",
|
||||
"total_amount_scale",
|
||||
@ -47,7 +47,7 @@ type Sanitized = {
|
||||
attributes: (string | any)[];
|
||||
// keyset opcional
|
||||
keyset?: {
|
||||
after?: { issueDate: string; invoiceSeries: string; invoiceNumber: number; id: string };
|
||||
after?: { invoiceDate: string; invoiceSeries: string; invoiceNumber: number; id: string };
|
||||
};
|
||||
};
|
||||
|
||||
@ -114,9 +114,9 @@ export function sanitizeListCriteria(criteria: Criteria): Sanitized {
|
||||
.split(",")
|
||||
.filter(Boolean);
|
||||
if (sortArray.length === 0) {
|
||||
// orden por defecto: issue_date desc, invoice_series asc, invoice_number desc, id desc
|
||||
// orden por defecto: invoice_date desc, invoice_series asc, invoice_number desc, id desc
|
||||
order.push(
|
||||
["issue_date", "DESC"],
|
||||
["invoice_date", "DESC"],
|
||||
["invoice_series", "ASC"],
|
||||
["invoice_number", "DESC"],
|
||||
["id", "DESC"]
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { CustomerInvoiceItem } from "../../domain";
|
||||
|
||||
export type CustomerInvoiceItemTaxesCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceItemTaxesModel,
|
||||
{}
|
||||
>;
|
||||
|
||||
export class CustomerInvoiceItemTaxesModel extends Model<
|
||||
InferAttributes<CustomerInvoiceItemTaxesModel>,
|
||||
InferCreationAttributes<CustomerInvoiceItemTaxesModel>
|
||||
> {
|
||||
declare id: string;
|
||||
declare item_id: string;
|
||||
|
||||
declare tax_code: string; //"iva_21"
|
||||
|
||||
// Taxable amount (base imponible) // 100,00 €
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare tax_amount_value: number;
|
||||
declare tax_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare item: NonAttribute<CustomerInvoiceItem>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const { CustomerInvoiceItemModel } = database.models;
|
||||
|
||||
CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, {
|
||||
as: "item",
|
||||
targetKey: "id",
|
||||
foreignKey: "item_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
static hooks(database: Sequelize) {}
|
||||
}
|
||||
|
||||
export default (database: Sequelize) => {
|
||||
CustomerInvoiceItemTaxesModel.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
item_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
tax_code: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
tax_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
tax_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
tableName: "customer_invoices_item_taxes",
|
||||
|
||||
underscored: true,
|
||||
|
||||
indexes: [{ name: "tax_code_idx", fields: ["tax_code"], unique: false }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
|
||||
return CustomerInvoiceItemTaxesModel;
|
||||
};
|
||||
@ -20,26 +20,39 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
declare item_id: string;
|
||||
declare invoice_id: string;
|
||||
|
||||
declare parent_id: string;
|
||||
declare position: number;
|
||||
declare item_type: string;
|
||||
|
||||
declare description: string;
|
||||
|
||||
declare quantity_amount: number;
|
||||
declare quantity_value: number;
|
||||
declare quantity_scale: number;
|
||||
|
||||
declare unit_price_amount: number;
|
||||
declare unit_price_scale: number;
|
||||
declare unit_amount_value: number;
|
||||
declare unit_amount_scale: number;
|
||||
|
||||
declare subtotal_amount: number;
|
||||
declare subtotal_scale: number;
|
||||
// Subtotal
|
||||
declare subtotal_amount_value: number;
|
||||
declare subtotal_amount_scale: number;
|
||||
|
||||
declare discount_amount: number;
|
||||
declare discount_scale: number;
|
||||
// Discount percentage
|
||||
declare discount_percentage_value: number;
|
||||
declare discount_percentage_scale: number;
|
||||
|
||||
declare total_amount: number;
|
||||
declare total_scale: number;
|
||||
// Discount amount
|
||||
declare discount_amount_value: number;
|
||||
declare discount_amount_scale: number;
|
||||
|
||||
// Taxable amount (base imponible)
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total taxes amount / taxes total
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Total
|
||||
declare total_amount_value: number;
|
||||
declare total_amount_scale: number;
|
||||
|
||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||
|
||||
@ -62,101 +75,119 @@ export default (database: Sequelize) => {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
invoice_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
allowNull: false,
|
||||
},
|
||||
parent_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
||||
},
|
||||
|
||||
position: {
|
||||
type: new DataTypes.MEDIUMINT().UNSIGNED,
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
},
|
||||
item_type: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
defaultValue: "simple",
|
||||
},
|
||||
|
||||
description: {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
quantity_amount: {
|
||||
quantity_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
quantity_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
unit_price_amount: {
|
||||
unit_amount_value: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
unit_price_scale: {
|
||||
|
||||
unit_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
/*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_amount: {
|
||||
subtotal_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_scale: {
|
||||
|
||||
subtotal_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
discount_percentage_value: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
discount_amount: {
|
||||
discount_percentage_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
discount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
/*tax_amount: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},*/
|
||||
total_amount: {
|
||||
discount_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
total_scale: {
|
||||
|
||||
discount_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxes_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
taxes_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
total_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
total_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { CustomerInvoice } from "../../domain";
|
||||
|
||||
export type CustomerInvoiceTaxesCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceTaxesModel,
|
||||
{}
|
||||
>;
|
||||
|
||||
export class CustomerInvoiceTaxesModel extends Model<
|
||||
InferAttributes<CustomerInvoiceTaxesModel>,
|
||||
InferCreationAttributes<CustomerInvoiceTaxesModel>
|
||||
> {
|
||||
declare id: string;
|
||||
declare invoice_id: string;
|
||||
|
||||
declare tax_code: string; //"iva_21"
|
||||
|
||||
// Taxable amount (base imponible) // 100,00 €
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare tax_amount_value: number;
|
||||
declare tax_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare invoice: NonAttribute<CustomerInvoice>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const { CustomerInvoiceModel } = database.models;
|
||||
|
||||
CustomerInvoiceTaxesModel.belongsTo(CustomerInvoiceModel, {
|
||||
as: "invoice",
|
||||
targetKey: "id",
|
||||
foreignKey: "invoice_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
static hooks(database: Sequelize) {}
|
||||
}
|
||||
|
||||
export default (database: Sequelize) => {
|
||||
CustomerInvoiceTaxesModel.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
invoice_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
tax_code: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
tax_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
tax_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
tableName: "customer_invoices_taxes",
|
||||
|
||||
underscored: true,
|
||||
|
||||
indexes: [{ name: "tax_code_idx", fields: ["tax_code"], unique: false }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
|
||||
return CustomerInvoiceTaxesModel;
|
||||
};
|
||||
@ -28,11 +28,13 @@ export class CustomerInvoiceModel extends Model<
|
||||
declare status: string;
|
||||
declare series: string;
|
||||
declare invoice_number: string;
|
||||
declare issue_date: string;
|
||||
declare invoice_date: string;
|
||||
declare operation_date: string;
|
||||
declare language_code: string;
|
||||
declare currency_code: string;
|
||||
|
||||
declare notes: string;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount_value: number;
|
||||
declare subtotal_amount_scale: number;
|
||||
@ -117,7 +119,7 @@ export default (database: Sequelize) => {
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
issue_date: {
|
||||
invoice_date: {
|
||||
type: new DataTypes.DATEONLY(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
@ -138,18 +140,23 @@ export default (database: Sequelize) => {
|
||||
currency_code: {
|
||||
type: new DataTypes.STRING(3),
|
||||
allowNull: false,
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
notes: {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
subtotal_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
subtotal_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
discount_percentage_value: {
|
||||
@ -160,8 +167,8 @@ export default (database: Sequelize) => {
|
||||
|
||||
discount_percentage_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
discount_amount_value: {
|
||||
@ -172,8 +179,8 @@ export default (database: Sequelize) => {
|
||||
|
||||
discount_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
@ -183,8 +190,8 @@ export default (database: Sequelize) => {
|
||||
},
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
tax_amount_value: {
|
||||
@ -194,8 +201,8 @@ export default (database: Sequelize) => {
|
||||
},
|
||||
tax_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
total_amount_value: {
|
||||
@ -206,8 +213,8 @@ export default (database: Sequelize) => {
|
||||
|
||||
total_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,36 +1,39 @@
|
||||
import { NumericStringSchema, PercentageSchema } from "@erp/core";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const CreateCustomerInvoiceRequestSchema = z.object({
|
||||
id: z.uuid(),
|
||||
invoice_status: z.string(),
|
||||
invoice_number: z.string().min(1, "Customer invoice number is required"),
|
||||
invoice_series: z.string().min(1, "Customer invoice series is required"),
|
||||
issue_date: z.string().datetime({ offset: true, message: "Invalid issue date format" }),
|
||||
operation_date: z.string().datetime({ offset: true, message: "Invalid operation date format" }),
|
||||
description: z.string(),
|
||||
language_code: z.string().min(2, "Language code must be at least 2 characters long"),
|
||||
currency_code: z.string().min(3, "Currency code must be at least 3 characters long"),
|
||||
notes: z.string().optional(),
|
||||
items: z.array(
|
||||
company_id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: z.string().default("draft"),
|
||||
series: z.string().default(""),
|
||||
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string().default(""),
|
||||
|
||||
notes: z.string().default(""),
|
||||
|
||||
language_code: z.string().toLowerCase().default("es"),
|
||||
currency_code: z.string().toUpperCase().default("EUR"),
|
||||
|
||||
discount_percentage: PercentageSchema.default({
|
||||
value: "0",
|
||||
scale: "2",
|
||||
}),
|
||||
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
description: z.string().min(1, "Item description is required"),
|
||||
quantity: z.object({
|
||||
value: z.number().positive("Quantity amount must be positive"),
|
||||
scale: z.number().int().nonnegative("Quantity scale must be a non-negative integer"),
|
||||
}),
|
||||
unit_amount: z.object({
|
||||
value: z.number().positive("Unit price amount must be positive"),
|
||||
scale: z.number().int().nonnegative("Unit price scale must be a non-negative integer"),
|
||||
currency_code: z
|
||||
.string()
|
||||
.min(3, "Unit price currency code must be at least 3 characters long"),
|
||||
}),
|
||||
discount_percentage: z.object({
|
||||
value: z.number().nonnegative("Discount amount cannot be negative"),
|
||||
scale: z.number().int().nonnegative("Discount scale must be a non-negative integer"),
|
||||
}),
|
||||
id: z.uuid(),
|
||||
position: z.string(),
|
||||
description: z.string().default(""),
|
||||
quantity: NumericStringSchema.default(""),
|
||||
unit_amount: NumericStringSchema.default(""),
|
||||
discount_percentage: NumericStringSchema.default(""),
|
||||
})
|
||||
),
|
||||
)
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export type CreateCustomerInvoiceRequestDTO = z.infer<typeof CreateCustomerInvoiceRequestSchema>;
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const CreateCustomerInvoiceResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
status: z.string(),
|
||||
series: z.string(),
|
||||
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string(),
|
||||
|
||||
notes: z.string(),
|
||||
|
||||
language_code: z.string(),
|
||||
currency_code: z.string(),
|
||||
|
||||
subtotal_amount: AmountSchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
discount_amount: AmountSchema,
|
||||
taxable_amount: AmountSchema,
|
||||
tax_amount: AmountSchema,
|
||||
total_amount: AmountSchema,
|
||||
|
||||
items: z.array(
|
||||
z.object({
|
||||
id: z.uuid(),
|
||||
position: z.string(),
|
||||
description: z.string(),
|
||||
quantity: QuantitySchema,
|
||||
unit_amount: AmountSchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
total_amount: AmountSchema,
|
||||
})
|
||||
),
|
||||
|
||||
metadata: MetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export type CreateCustomerInvoiceResponseDTO = z.infer<typeof CreateCustomerInvoiceResponseSchema>;
|
||||
@ -1,19 +0,0 @@
|
||||
import { MetadataSchema } from "@erp/core";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const CustomerInvoicesCreationResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
invoice_status: z.string(),
|
||||
invoice_number: z.string(),
|
||||
invoice_series: z.string(),
|
||||
issue_date: z.iso.datetime({ offset: true }),
|
||||
operation_date: z.iso.datetime({ offset: true }),
|
||||
language_code: z.string(),
|
||||
currency: z.string(),
|
||||
|
||||
metadata: MetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export type CustomerInvoicesCreationResponseDTO = z.infer<
|
||||
typeof CustomerInvoicesCreationResponseSchema
|
||||
>;
|
||||
@ -7,7 +7,7 @@ export const CustomerInvoiceListResponseSchema = createListViewResponseSchema(
|
||||
invoice_status: z.string(),
|
||||
invoice_number: z.string(),
|
||||
invoice_series: z.string(),
|
||||
issue_date: z.iso.datetime({ offset: true }),
|
||||
invoice_date: z.iso.datetime({ offset: true }),
|
||||
operation_date: z.iso.datetime({ offset: true }),
|
||||
language_code: z.string(),
|
||||
currency: z.string(),
|
||||
|
||||
@ -9,7 +9,7 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({
|
||||
status: z.string(),
|
||||
series: z.string(),
|
||||
|
||||
issue_date: z.string(),
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string(),
|
||||
|
||||
notes: z.string(),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export * from "./customer-invoice-creation.response.dto";
|
||||
export * from "./create-customer-invoice.response.dto";
|
||||
export * from "./customer-invoices-list.response.dto";
|
||||
export * from "./get-customer-invoice-by-id.response.dto";
|
||||
export * from "./update-customer-invoice-by-id.response.dto";
|
||||
|
||||
@ -9,7 +9,7 @@ export const UpdateCustomerInvoiceByIdResponseSchema = z.object({
|
||||
status: z.string(),
|
||||
series: z.string(),
|
||||
|
||||
issue_date: z.string(),
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string(),
|
||||
|
||||
notes: z.string(),
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"invoice_number": "Inv. number",
|
||||
"invoice_series": "Serie",
|
||||
"invoice_status": "Status",
|
||||
"issue_date": "Date",
|
||||
"invoice_date": "Date",
|
||||
"total_price": "Total price"
|
||||
}
|
||||
},
|
||||
@ -52,7 +52,7 @@
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"issue_date": {
|
||||
"invoice_date": {
|
||||
"label": "Date",
|
||||
"placeholder": "Select a date",
|
||||
"description": "Invoice issue date"
|
||||
|
||||
@ -35,8 +35,8 @@ export const CustomerInvoicesListGrid = () => {
|
||||
{ field: "invoice_series", headerName: t("pages.list.grid_columns.invoice_series") },
|
||||
|
||||
{
|
||||
field: "issue_date",
|
||||
headerName: t("pages.list.grid_columns.issue_date"),
|
||||
field: "invoice_date",
|
||||
headerName: t("pages.list.grid_columns.invoice_date"),
|
||||
valueFormatter: (params: ValueFormatterParams) => {
|
||||
return formatDate(params.value);
|
||||
},
|
||||
|
||||
@ -49,7 +49,7 @@ const invoiceFormSchema = z.object({
|
||||
invoice_status: z.string(),
|
||||
invoice_number: z.string().min(1, "Número de factura requerido"),
|
||||
invoice_series: z.string().min(1, "Serie requerida"),
|
||||
issue_date: z.string(),
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string(),
|
||||
language_code: z.string(),
|
||||
currency: z.string(),
|
||||
@ -144,7 +144,7 @@ const defaultInvoiceData = {
|
||||
invoice_status: "draft",
|
||||
invoice_number: "1",
|
||||
invoice_series: "A",
|
||||
issue_date: "2025-04-30T00:00:00.000Z",
|
||||
invoice_date: "2025-04-30T00:00:00.000Z",
|
||||
operation_date: "2025-04-30T00:00:00.000Z",
|
||||
description: "",
|
||||
language_code: "ES",
|
||||
@ -332,11 +332,11 @@ export const CustomerInvoiceEditForm = ({
|
||||
|
||||
<DatePickerInputField
|
||||
control={form.control}
|
||||
name='issue_date'
|
||||
name='invoice_date'
|
||||
required
|
||||
label={t("form_fields.issue_date.label")}
|
||||
placeholder={t("form_fields.issue_date.placeholder")}
|
||||
description={t("form_fields.issue_date.description")}
|
||||
label={t("form_fields.invoice_date.label")}
|
||||
placeholder={t("form_fields.invoice_date.placeholder")}
|
||||
description={t("form_fields.invoice_date.description")}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@ -429,11 +429,11 @@ export const CustomerInvoiceEditForm = ({
|
||||
|
||||
<DatePickerInputField
|
||||
control={form.control}
|
||||
name='issue_date'
|
||||
name='invoice_date'
|
||||
required
|
||||
label={t("form_fields.issue_date.label")}
|
||||
placeholder={t("form_fields.issue_date.placeholder")}
|
||||
description={t("form_fields.issue_date.description")}
|
||||
label={t("form_fields.invoice_date.label")}
|
||||
placeholder={t("form_fields.invoice_date.placeholder")}
|
||||
description={t("form_fields.invoice_date.description")}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@ -815,14 +815,14 @@ export const CustomerInvoiceEditForm = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant='outline' className='w-full justify-start text-left font-normal'>
|
||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||
{format(issueDate, "PPP", { locale: es })}
|
||||
{format(invoiceDate, "PPP", { locale: es })}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-auto p-0' align='start'>
|
||||
<Calendar
|
||||
mode='single'
|
||||
selected={issueDate}
|
||||
onSelect={(date) => handleDateChange("issue_date", date)}
|
||||
selected={invoiceDate}
|
||||
onSelect={(date) => handleDateChange("invoice_date", date)}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
@ -8,8 +8,8 @@ import { CreateCustomersAssembler } from "./assembler";
|
||||
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
|
||||
|
||||
type CreateCustomerUseCaseInput = {
|
||||
dto: CreateCustomerRequestDTO;
|
||||
companyId: UniqueID;
|
||||
dto: CreateCustomerRequestDTO;
|
||||
};
|
||||
|
||||
export class CreateCustomerUseCase {
|
||||
@ -30,7 +30,7 @@ export class CreateCustomerUseCase {
|
||||
|
||||
const { props, id } = dtoResult.data;
|
||||
|
||||
// 3) Construir entidad de dominio
|
||||
// 2) Construir entidad de dominio
|
||||
const buildResult = this.service.buildCustomerInCompany(companyId, props, id);
|
||||
if (buildResult.isFailure) {
|
||||
return Result.fail(buildResult.error);
|
||||
@ -38,7 +38,7 @@ export class CreateCustomerUseCase {
|
||||
|
||||
const newCustomer = buildResult.data;
|
||||
|
||||
// 4) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
||||
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
const existsGuard = await this.ensureNotExists(companyId, id, transaction);
|
||||
if (existsGuard.isFailure) {
|
||||
|
||||
@ -16,7 +16,7 @@ export const updateCustomerPresenter: IUpdateCustomerPresenter = {
|
||||
customer_status: customer.status.toString(),
|
||||
customer_number: customer.customerNumber.toString(),
|
||||
customer_series: customer.customerSeries.toString(),
|
||||
issue_date: customer.issueDate.toISO8601(),
|
||||
invoice_date: customer.invoiceDate.toISO8601(),
|
||||
operation_date: customer.operationDate.toISO8601(),
|
||||
language_code: customer.language.toString(),
|
||||
currency: customer.currency.toString(),
|
||||
|
||||
@ -182,7 +182,6 @@ export default (database: Sequelize) => {
|
||||
indexes: [
|
||||
{ name: "company_idx", fields: ["company_id"], unique: false },
|
||||
{ name: "idx_company_idx", fields: ["id", "company_id"], unique: true },
|
||||
{ name: "email_idx", fields: ["email"], unique: true },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
Loading…
Reference in New Issue
Block a user