Facturas de cliente

This commit is contained in:
David Arranz 2025-09-08 19:24:38 +02:00
parent d17a22dc9f
commit 2e4bb56458
16 changed files with 496 additions and 186 deletions

View File

@ -1,3 +1,5 @@
export * from "./spain-tax-catalog-provider";
export * from "./tax-catalog-provider";
export * from "./json-tax-catalog.provider";
export * from "./spain-tax-catalog.provider";
export * from "./tax-catalog-types";
export * from "./tax-catalog.provider";

View File

@ -1,8 +1,8 @@
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
import { Maybe } from "@repo/rdx-utils";
import { TaxCatalogProvider } from "./tax-catalog-provider";
import { TaxCatalogType, TaxItemType } from "./tax-catalog-types";
import { TaxCatalogProvider } from "./tax-catalog.provider";
// Si quieres habilitar la carga desde fichero en Node:
// import * as fs from "node:fs";

View File

@ -1,4 +1,4 @@
import { JsonTaxCatalogProvider } from "./json-tax-catalog-provider";
import { JsonTaxCatalogProvider } from "./json-tax-catalog.provider";
import spainTaxCatalog from "./spain-tax-catalog.json";
export const spainTaxCatalogProvider = new JsonTaxCatalogProvider(spainTaxCatalog);

View File

@ -1,3 +1,4 @@
import { JsonTaxCatalogProvider } from "@erp/core";
import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
@ -5,7 +6,7 @@ import { Transaction } from "sequelize";
import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto";
import { CustomerInvoiceService } from "../../domain";
import { CreateCustomerInvoiceAssembler } from "./assembler";
import { mapDTOToCreateCustomerInvoiceProps } from "./map-dto-to-create-customer-invoice-props";
import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props";
type CreateCustomerInvoiceUseCaseInput = {
companyId: UniqueID;
@ -16,14 +17,17 @@ export class CreateCustomerInvoiceUseCase {
constructor(
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager,
private readonly assembler: CreateCustomerInvoiceAssembler
private readonly assembler: CreateCustomerInvoiceAssembler,
private readonly taxCatalog: JsonTaxCatalogProvider,
) {}
public execute(params: CreateCustomerInvoiceUseCaseInput) {
const { dto, companyId } = params;
const dtoMapper = new CreateCustomerInvoicePropsMapper({ taxCatalog: this.taxCatalog });
// 1) Mapear DTO → props de dominio
const dtoResult = mapDTOToCreateCustomerInvoiceProps(dto);
const dtoResult = dtoMapper.map(dto);
if (dtoResult.isFailure) {
return Result.fail(dtoResult.error);
}

View File

@ -1,4 +1,4 @@
import { spainTaxCatalogProvider } from "@erp/core";
import { JsonTaxCatalogProvider } from "@erp/core";
import {
DomainError,
Tax,
@ -45,181 +45,189 @@ import {
*
*/
export function mapDTOToCreateCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) {
try {
const errors: ValidationErrorDetail[] = [];
export class CreateCustomerInvoicePropsMapper {
private readonly taxCatalog: JsonTaxCatalogProvider;
private errors: ValidationErrorDetail[] = [];
private languageCode?: LanguageCode;
private currencyCode?: CurrencyCode;
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors);
constructor(params: {taxCatalog: JsonTaxCatalogProvider}) {
this.taxCatalog = params.taxCatalog;
this.errors = [];
}
const invoiceNumber = extractOrPushError(
CustomerInvoiceNumber.create(dto.invoice_number),
"invoice_number",
errors
);
const status = extractOrPushError(CustomerInvoiceStatus.create(dto.status), "status", errors);
public map(dto: CreateCustomerInvoiceRequestDTO) {
try {
this.errors = [];
const series = extractOrPushError(
maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)),
"series",
errors
);
const defaultStatus = CustomerInvoiceStatus.createDraft();
const invoiceDate = extractOrPushError(
UtcDate.createFromISO(dto.invoice_date),
"invoice_date",
errors
);
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", this.errors);
const customerId = extractOrPushError(UniqueID.create(dto.customer_id), "customer_id", this.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 = _mapDTOtoInvoiceItems(dto.items, languageCode!, currencyCode!, errors);
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
const invoiceNumber = extractOrPushError(
CustomerInvoiceNumber.create(dto.invoice_number),
"invoice_number",
this.errors
);
const series = extractOrPushError(
maybeFromNullableVO(dto.series, (value) => CustomerInvoiceSerie.create(value)),
"series",
this.errors
);
const invoiceDate = extractOrPushError(
UtcDate.createFromISO(dto.invoice_date),
"invoice_date",
this.errors
);
const operationDate = extractOrPushError(
maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)),
"operation_date",
this.errors
);
const notes = extractOrPushError(
maybeFromNullableVO(dto.notes, (value) => TextValue.create(value)),
"notes",
this.errors
);
this.languageCode = extractOrPushError(
LanguageCode.create(dto.language_code),
"language_code",
this.errors
);
this.currencyCode = extractOrPushError(
CurrencyCode.create(dto.currency_code),
"currency_code",
this.errors
);
const discountPercentage = extractOrPushError(
Percentage.create({
value: Number(dto.discount_percentage.value),
scale: Number(dto.discount_percentage.scale),
}),
"discount_percentage",
this.errors
);
const items = this.mapItems(dto.items);
if (this.errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice props mapping failed", this.errors)
);
}
const invoiceProps: CustomerInvoiceProps = {
companyId: companyId!,
status: defaultStatus!,
invoiceNumber: invoiceNumber!,
invoiceDate: invoiceDate!,
operationDate: operationDate!,
series: series!,
notes: notes!,
customerId: customerId!,
languageCode: this.languageCode!,
currencyCode: this.currencyCode!,
discountPercentage: discountPercentage!,
taxes: Taxes.create({ items: [] }),
items: items,
};
return Result.ok({ id: invoiceId!, props: invoiceProps });
} catch (err: unknown) {
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
}
}
const invoiceProps: CustomerInvoiceProps = {
companyId: companyId!,
status: status!,
private mapItems(items: CreateCustomerInvoiceItemRequestDTO[]) {
const invoiceItems = CustomerInvoiceItems.create({
currencyCode: this.currencyCode!,
languageCode: this.languageCode!,
items: [],
});
invoiceNumber: invoiceNumber!,
invoiceDate: invoiceDate!,
items.forEach((item, index) => {
const description = extractOrPushError(
maybeFromNullableVO(item.description, (value) =>
CustomerInvoiceItemDescription.create(value)
),
"description",
this.errors
);
operationDate: operationDate!,
series: series!,
const quantity = extractOrPushError(
maybeFromNullableVO(item.quantity, (value) => ItemQuantity.create(value)),
"quantity",
this.errors
);
notes: notes!,
const unitAmount = extractOrPushError(
maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)),
"unit_amount",
this.errors
);
languageCode: languageCode!,
currencyCode: currencyCode!,
const discountPercentage = extractOrPushError(
maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)),
"discount_percentage",
this.errors
);
discountPercentage: discountPercentage!,
const taxes = this.mapTaxes(item, index);
taxes: Taxes.create({ items: [] }),
items: items,
};
const itemProps: CustomerInvoiceItemProps = {
currencyCode: this.currencyCode!,
languageCode: this.languageCode!,
description: description!,
quantity: quantity!,
unitAmount: unitAmount!,
discountPercentage: discountPercentage!,
taxes,
};
return Result.ok({ id: customerId!, props: invoiceProps });
} catch (err: unknown) {
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
const itemResult = CustomerInvoiceItem.create(itemProps);
if (itemResult.isSuccess) {
invoiceItems.add(itemResult.data);
} else {
this.errors.push({
path: `items[${index}]`,
message: itemResult.error.message,
});
}
});
return invoiceItems;
}
private mapTaxes(item: CreateCustomerInvoiceItemRequestDTO, itemIndex: number) {
const taxes = Taxes.create({ items: [] });
item.taxes.split(",").every((tax_code, taxIndex) => {
const taxResult = Tax.createFromCode(tax_code, this.taxCatalog);
if (taxResult.isSuccess) {
taxes.add(taxResult.data);
} else {
this.errors.push({
path: `items[${itemIndex}].taxes[${taxIndex}]`,
message: taxResult.error.message,
});
}
});
return taxes;
}
}
function _mapDTOtoInvoiceItems(
items: CreateCustomerInvoiceItemRequestDTO[],
languageCode: LanguageCode,
currencyCode: CurrencyCode,
errors: ValidationErrorDetail[]
) {
const invoiceItems = CustomerInvoiceItems.create({
currencyCode,
languageCode,
items: [],
});
items.forEach((item, index) => {
const description = extractOrPushError(
maybeFromNullableVO(item.description, (value) =>
CustomerInvoiceItemDescription.create(value)
),
"description",
errors
);
const quantity = extractOrPushError(
maybeFromNullableVO(item.quantity, (value) => ItemQuantity.create(value)),
"quantity",
errors
);
const unitAmount = extractOrPushError(
maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)),
"unit_amount",
errors
);
const discountPercentage = extractOrPushError(
maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)),
"discount_percentage",
errors
);
const taxes = _mapDTOtoTaxes(item, index, errors);
const itemProps: CustomerInvoiceItemProps = {
currencyCode: currencyCode!,
languageCode: languageCode!,
description: description!,
quantity: quantity!,
unitAmount: unitAmount!,
discountPercentage: discountPercentage!,
taxes,
};
const itemResult = CustomerInvoiceItem.create(itemProps);
if (itemResult.isSuccess) {
invoiceItems.add(itemResult.data);
} else {
errors.push({
path: `items[${index}]`,
message: itemResult.error.message,
});
}
});
return invoiceItems;
}
function _mapDTOtoTaxes(
item: CreateCustomerInvoiceItemRequestDTO,
itemIndex: number,
errors: ValidationErrorDetail[]
) {
const taxes = Taxes.create({ items: [] });
item.taxes.split(",").every((tax_code, taxIndex) => {
const taxResult = Tax.createFromCode(tax_code, spainTaxCatalogProvider);
if (taxResult.isSuccess) {
taxes.add(taxResult.data);
} else {
errors.push({
path: `items[${itemIndex}].taxes[${taxIndex}]`,
message: taxResult.error.message,
});
}
});
return taxes;
}

View File

@ -13,7 +13,7 @@ export class GetCustomerInvoiceUseCase {
constructor(
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager,
private readonly assembler: GetCustomerInvoiceAssembler
private readonly assembler: GetCustomerInvoiceAssembler,
) {}
public execute(params: GetCustomerInvoiceUseCaseInput) {

View File

@ -1,4 +1,4 @@
import { Taxes } from "@erp/core/api";
import { DomainValidationError, Taxes } from "@erp/core/api";
import {
AggregateRoot,
CurrencyCode,
@ -10,7 +10,7 @@ import {
UtcDate,
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { CustomerInvoiceItems } from "../entities";
import { CustomerInvoiceItems, InvoiceRecipient } from "../entities";
import {
CustomerInvoiceNumber,
CustomerInvoiceSerie,
@ -27,6 +27,10 @@ export interface CustomerInvoiceProps {
invoiceDate: UtcDate;
operationDate: Maybe<UtcDate>;
customerId: UniqueID;
recipient: Maybe<InvoiceRecipient>;
notes: Maybe<TextValue>;
//dueDate: UtcDate; // ? --> depende de la forma de pago
@ -52,7 +56,7 @@ export interface CustomerInvoiceProps {
//totalAmount: MoneyValue;
//customer?: CustomerInvoiceCustomer;
items: CustomerInvoiceItems;
}
@ -75,9 +79,17 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
const customerInvoice = new CustomerInvoice(props, id);
// Reglas de negocio / validaciones
// ...
// ...
if (!customerInvoice.isDraft() && !customerInvoice.hasRecipient()) {
return Result.fail(
new DomainValidationError(
"MISSING_CUSTOMER_DATA",
"customerData",
"Customer data must be provided for non-draft invoices"
)
);
}
// 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent"
//const { customerInvoice } = props;
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
@ -93,6 +105,10 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
return this.props.companyId;
}
public get customerId(): UniqueID {
return this.props.customerId;
}
public get status(): CustomerInvoiceStatus {
return this.props.status;
}
@ -117,6 +133,10 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
return this.props.notes;
}
public get recipient(): Maybe<InvoiceRecipient> {
return this.props.recipient;
}
public get languageCode(): LanguageCode {
return this.props.languageCode;
}
@ -154,6 +174,14 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
return this._items;
}
public isDraft() {
return this.status.isDraft();
}
public hasRecipient() {
return this.recipient.isSome()
}
/*get senderId(): UniqueID {
return this.props.senderId;
}*/

View File

@ -1,2 +1,4 @@
export * from "./customer-invoice-items";
export * from "./invoice-customer";
export * from "./invoice-recipient";

View File

@ -0,0 +1,3 @@
export * from "./invoice-recipient";

View File

@ -0,0 +1,103 @@
import {
City,
Country,
Name,
PostalCode,
Province,
Street,
TINNumber,
ValueObject
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
export interface InvoiceRecipientProps {
name: Name;
tin: TINNumber;
street: Maybe<Street>;
street2: Maybe<Street>;
city: Maybe<City>;
postalCode: Maybe<PostalCode>;
province: Maybe<Province>;
country: Maybe<Country>;
}
export class InvoiceRecipient extends ValueObject<InvoiceRecipientProps> {
protected static validate(values: InvoiceRecipientProps) {
const schema = z.object({
name: z.string(),
tin: z.string(),
street: z.string().optional(),
street2: z.string().optional(),
city: z.string().optional(),
postalCode: z.string().optional(),
province: z.string().optional(),
country: z.string().optional(),
})
return schema.safeParse(values);
}
static create(values: InvoiceRecipientProps): Result<InvoiceRecipient, Error> {
const valueIsValid = InvoiceRecipient.validate(values)
if (!valueIsValid.success) {
return Result.fail(new Error(valueIsValid.error.issues[0].message));
}
return Result.ok(new InvoiceRecipient(values));
}
public update(partial: Partial<InvoiceRecipientProps>): Result<InvoiceRecipient, Error> {
const updatedProps = {
...this.props,
...partial,
} as InvoiceRecipientProps;
return InvoiceRecipient.create(updatedProps);
}
public get name(): Name {
return this.props.name;
}
public get tin(): TINNumber {
return this.props.tin;
}
get street(): Maybe<Street> {
return this.props.street;
}
get street2(): Maybe<Street> {
return this.props.street2;
}
get city(): Maybe<City> {
return this.props.city;
}
get postalCode(): Maybe<PostalCode> {
return this.props.postalCode;
}
get province(): Maybe<Province> {
return this.props.province;
}
get country(): Maybe<Country> {
return this.props.country;
}
getProps(): InvoiceRecipientProps {
return this.props;
}
toPrimitive() {
return this.getProps();
}
}

View File

@ -71,6 +71,10 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.REJECTED });
}
isDraft(): boolean {
return this.props.value === INVOICE_STATUS.DRAFT
}
getProps(): string {
return this.props.value;
}

View File

@ -1,3 +1,4 @@
import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core";
import type { ModuleParams } from "@erp/core/api";
import { SequelizeTransactionManager } from "@erp/core/api";
import {
@ -9,6 +10,7 @@ import {
ListCustomerInvoicesAssembler,
ListCustomerInvoicesUseCase,
UpdateCustomerInvoiceAssembler,
UpdateCustomerInvoiceUseCase,
} from "../application";
import { CustomerInvoiceService } from "../domain";
import { CustomerInvoiceMapper } from "./mappers";
@ -19,6 +21,9 @@ type InvoiceDeps = {
repo: CustomerInvoiceRepository;
mapper: CustomerInvoiceMapper;
service: CustomerInvoiceService;
catalogs: {
taxes: JsonTaxCatalogProvider;
};
assemblers: {
list: ListCustomerInvoicesAssembler;
get: GetCustomerInvoiceAssembler;
@ -41,6 +46,7 @@ let _repo: CustomerInvoiceRepository | null = null;
let _mapper: CustomerInvoiceMapper | null = null;
let _service: CustomerInvoiceService | null = null;
let _assemblers: InvoiceDeps["assemblers"] | null = null;
let _catalogs: InvoiceDeps["catalogs"] | null = null;
export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
const { database } = params;
@ -49,6 +55,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
if (!_mapper) _mapper = new CustomerInvoiceMapper();
if (!_repo) _repo = new CustomerInvoiceRepository(_mapper);
if (!_service) _service = new CustomerInvoiceService(_repo);
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
if (!_assemblers) {
_assemblers = {
@ -65,12 +72,18 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
mapper: _mapper,
service: _service,
assemblers: _assemblers,
catalogs: _catalogs,
build: {
list: () =>
new ListCustomerInvoicesUseCase(_service!, transactionManager!, _assemblers!.list),
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
create: () =>
new CreateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.create),
new CreateCustomerInvoiceUseCase(
_service!,
transactionManager!,
_assemblers!.create,
_catalogs!.taxes
),
update: () =>
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),

View File

@ -7,9 +7,16 @@ import {
extractOrPushError,
} from "@erp/core/api";
import {
City,
Country,
CurrencyCode,
LanguageCode,
Name,
Percentage,
PostalCode,
Province,
Street,
TINNumber,
TextValue,
UniqueID,
UtcDate,
@ -117,11 +124,60 @@ export class CustomerInvoiceMapper
errors
);
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
);
}
// Customer
const customerId = extractOrPushError(
UniqueID.create(source.customer_id),
"customer_id",
errors
);
const customerName = extractOrPushError(
maybeFromNullableVO(source.customer_name, (value) => Name.create(value)),
"customer_name",
errors
);
const customerTin = extractOrPushError(
maybeFromNullableVO(source.customer_tin, (value) => TINNumber.create(value)),
"customer_tin",
errors
);
const customerStreet = extractOrPushError(
maybeFromNullableVO(source.customer_street, (value) => Street.create(value)),
"customer_street",
errors
);
const customerStreet2 = extractOrPushError(
maybeFromNullableVO(source.customer_street2, (value) => Street.create(value)),
"customer_street2",
errors
);
const customerCity = extractOrPushError(
maybeFromNullableVO(source.customer_city, (value) => City.create(value)),
"customer_city",
errors
);
const customerProvince = extractOrPushError(
maybeFromNullableVO(source.customer_province, (value) => Province.create(value)),
"customer_province",
errors
);
const customerPostalCode = extractOrPushError(
maybeFromNullableVO(source.customer_postal_code, (value) => PostalCode.create(value)),
"customer_postal_code",
errors
);
const customerCountry = extractOrPushError(
maybeFromNullableVO(source.customer_country, (value) => Country.create(value)),
"customer_country",
errors
);
// Mapear los items de la factura
const items = this._itemsMapper.mapArrayToDomain(source.items, {
@ -130,6 +186,8 @@ export class CustomerInvoiceMapper
...params,
});
// Mapear los impuestos
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice item props mapping failed", errors)
@ -144,6 +202,8 @@ export class CustomerInvoiceMapper
invoiceDate: invoiceDate!,
operationDate: operationDate!,
customerId: customerId!,
notes: notes!,
languageCode: languageCode!,
@ -167,11 +227,30 @@ export class CustomerInvoiceMapper
source: CustomerInvoice,
params?: MapperParamsType
): CustomerInvoiceCreationAttributes {
//const subtotal = source.calculateSubtotal();
//const total = source.calculateTotal();
const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
const customer = source.recipient.match((recipient) => ({
customer_id: recipient.id.toPrimitive(),
customer_tin: recipient.tin.toPrimitive(),
customer_name: recipient.name.toPrimitive(),
customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()),
customer_street2: toNullable(recipient.address.street2, (street2) => street2.toPrimitive()),
customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()),
customer_province: toNullable(recipient.address.province, (province) => province.toPrimitive()),
customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => postalCode.toPrimitive()),
customer_country: toNullable(recipient.address.country, (country) => country.toPrimitive()),
}) as any, () => ({
customer_id: source.customerId.toPrimitive(),
customer_tin: null,
customer_name: null,
customer_street: null,
customer_street2: null,
customer_city: null,
customer_province: null,
customer_postal_code: null,
customer_country: null,
})) as any
return {
id: source.id.toPrimitive(),
company_id: source.companyId.toPrimitive(),
@ -194,16 +273,23 @@ export class CustomerInvoiceMapper
subtotal_amount_value: 0, //subtotal.amount,
subtotal_amount_scale: 2, //subtotal.scale,
/*discount_percentage_value: source.discountPercentage.value,
discount_percentage_value: source.discountPercentage.value,
discount_percentage_scale: source.discountPercentage.scale,
discount_amount_value: source.discountAmount.value,
discount_amount_scale: source.discountAmount.scale,*/
discount_amount_scale: source.discountAmount.scale,
taxable_amount_value: source.taxableAmount.value,
taxable_amount_scale: source.taxableAmount.value,
tax_amount_value: source.taxAmount.value,
tax_amount_scale: source.taxAmount.value,
total_amount_value: 0, //total.amount,
total_amount_scale: 2, //total.scale,
items,
...customer,
};
}
}

View File

@ -59,6 +59,17 @@ export class CustomerInvoiceModel extends Model<
declare total_amount_value: number;
declare total_amount_scale: number;
// Customer
declare customer_id: string;
declare customer_tin: string;
declare customer_name: string;
declare customer_street: string;
declare customer_street2: string;
declare customer_city: string;
declare customer_province: string;
declare customer_postal_code: string;
declare customer_country: string;
// Relaciones
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
//declare customer: NonAttribute<CustomerInvoiceParticipant_Model[]>;
@ -216,6 +227,51 @@ export default (database: Sequelize) => {
allowNull: false,
defaultValue: 2,
},
customer_id: {
type: DataTypes.UUID,
allowNull: false,
},
customer_tin: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_name: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_street: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_street2: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_city: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_province: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_postal_code: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
customer_country: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
},
},
{
sequelize: database,

View File

@ -16,12 +16,13 @@ export const CreateCustomerInvoiceRequestSchema = z.object({
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(""),
customer_id: z.uuid(),
notes: z.string().default(""),
language_code: z.string().toLowerCase().default("es"),