michangarrito/apps/backend/dist/modules/invoices/invoices.service.js
rckrdmrd 97f407c661 [MIGRATION-V2] feat: Migrar michangarrito a estructura v2
- Prefijo v2: MCH
- TRACEABILITY-MASTER.yml creado
- Listo para integracion como submodulo

Workspace: v2.0.0 | SIMCO: v4.0.0
2026-01-10 11:28:54 -06:00

192 lines
8.6 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvoicesService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const tax_config_entity_1 = require("./entities/tax-config.entity");
const invoice_entity_1 = require("./entities/invoice.entity");
const invoice_item_entity_1 = require("./entities/invoice-item.entity");
let InvoicesService = class InvoicesService {
constructor(taxConfigRepo, invoiceRepo, itemRepo, dataSource) {
this.taxConfigRepo = taxConfigRepo;
this.invoiceRepo = invoiceRepo;
this.itemRepo = itemRepo;
this.dataSource = dataSource;
}
async getTaxConfig(tenantId) {
return this.taxConfigRepo.findOne({ where: { tenantId } });
}
async saveTaxConfig(tenantId, data) {
let config = await this.getTaxConfig(tenantId);
if (config) {
Object.assign(config, data);
}
else {
config = this.taxConfigRepo.create({ tenantId, ...data });
}
return this.taxConfigRepo.save(config);
}
async createInvoice(tenantId, dto) {
const taxConfig = await this.getTaxConfig(tenantId);
if (!taxConfig || taxConfig.status !== tax_config_entity_1.TaxConfigStatus.ACTIVE) {
throw new common_1.BadRequestException('Configuracion fiscal no activa');
}
let subtotal = 0;
let totalIva = 0;
for (const item of dto.items) {
const importe = item.cantidad * item.valorUnitario - (item.descuento || 0);
subtotal += importe;
totalIva += importe * 0.16;
}
const total = subtotal + totalIva;
const folioResult = await this.dataSource.query(`SELECT get_next_invoice_folio($1, $2) as folio`, [tenantId, taxConfig.serie]);
const folio = folioResult[0].folio;
const invoice = this.invoiceRepo.create({
tenantId,
saleId: dto.saleId,
tipoComprobante: invoice_entity_1.InvoiceType.INGRESO,
serie: taxConfig.serie,
folio,
receptorRfc: dto.receptorRfc.toUpperCase(),
receptorNombre: dto.receptorNombre,
receptorRegimenFiscal: dto.receptorRegimenFiscal,
receptorCodigoPostal: dto.receptorCodigoPostal,
receptorUsoCfdi: dto.receptorUsoCfdi,
receptorEmail: dto.receptorEmail,
subtotal,
totalImpuestosTrasladados: totalIva,
total,
formaPago: dto.formaPago,
metodoPago: dto.metodoPago,
condicionesPago: dto.condicionesPago,
status: invoice_entity_1.InvoiceStatus.DRAFT,
notes: dto.notes,
});
await this.invoiceRepo.save(invoice);
for (const itemDto of dto.items) {
const importe = itemDto.cantidad * itemDto.valorUnitario - (itemDto.descuento || 0);
const item = this.itemRepo.create({
invoiceId: invoice.id,
productId: itemDto.productId,
claveProdServ: itemDto.claveProdServ,
descripcion: itemDto.descripcion,
cantidad: itemDto.cantidad,
claveUnidad: itemDto.claveUnidad,
unidad: itemDto.unidad,
valorUnitario: itemDto.valorUnitario,
descuento: itemDto.descuento || 0,
importe,
});
await this.itemRepo.save(item);
}
return this.getInvoice(invoice.id);
}
async getInvoice(id) {
const invoice = await this.invoiceRepo.findOne({
where: { id },
relations: ['items'],
});
if (!invoice) {
throw new common_1.NotFoundException('Factura no encontrada');
}
return invoice;
}
async getInvoices(tenantId, options) {
const query = this.invoiceRepo.createQueryBuilder('invoice')
.where('invoice.tenant_id = :tenantId', { tenantId })
.leftJoinAndSelect('invoice.items', 'items')
.orderBy('invoice.created_at', 'DESC');
if (options?.status) {
query.andWhere('invoice.status = :status', { status: options.status });
}
if (options?.from) {
query.andWhere('invoice.created_at >= :from', { from: options.from });
}
if (options?.to) {
query.andWhere('invoice.created_at <= :to', { to: options.to });
}
if (options?.limit) {
query.limit(options.limit);
}
return query.getMany();
}
async stampInvoice(id) {
const invoice = await this.getInvoice(id);
if (invoice.status !== invoice_entity_1.InvoiceStatus.DRAFT && invoice.status !== invoice_entity_1.InvoiceStatus.PENDING) {
throw new common_1.BadRequestException(`No se puede timbrar factura con estado: ${invoice.status}`);
}
const taxConfig = await this.getTaxConfig(invoice.tenantId);
if (!taxConfig) {
throw new common_1.BadRequestException('Configuracion fiscal no encontrada');
}
const mockUuid = `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}`.toUpperCase();
invoice.uuid = mockUuid;
invoice.status = invoice_entity_1.InvoiceStatus.STAMPED;
invoice.stampedAt = new Date();
invoice.pacResponse = {
provider: taxConfig.pacProvider,
sandbox: taxConfig.pacSandbox,
timestamp: new Date().toISOString(),
};
return this.invoiceRepo.save(invoice);
}
async cancelInvoice(id, reason, uuidReplacement) {
const invoice = await this.getInvoice(id);
if (invoice.status !== invoice_entity_1.InvoiceStatus.STAMPED && invoice.status !== invoice_entity_1.InvoiceStatus.SENT) {
throw new common_1.BadRequestException(`No se puede cancelar factura con estado: ${invoice.status}`);
}
invoice.status = invoice_entity_1.InvoiceStatus.CANCELLED;
invoice.cancelledAt = new Date();
invoice.cancelReason = reason;
invoice.cancelUuidReplacement = uuidReplacement;
return this.invoiceRepo.save(invoice);
}
async sendInvoice(id, email) {
const invoice = await this.getInvoice(id);
if (invoice.status !== invoice_entity_1.InvoiceStatus.STAMPED) {
throw new common_1.BadRequestException('Solo se pueden enviar facturas timbradas');
}
const targetEmail = email || invoice.receptorEmail;
if (!targetEmail) {
throw new common_1.BadRequestException('No hay email de destino');
}
invoice.status = invoice_entity_1.InvoiceStatus.SENT;
return this.invoiceRepo.save(invoice);
}
async getSummary(tenantId, month) {
const targetMonth = month || new Date();
const monthStr = targetMonth.toISOString().split('T')[0];
const result = await this.dataSource.query(`SELECT * FROM get_invoice_summary($1, $2::date)`, [tenantId, monthStr]);
return result[0] || {
total_invoices: 0,
total_amount: 0,
total_cancelled: 0,
by_status: {},
};
}
};
exports.InvoicesService = InvoicesService;
exports.InvoicesService = InvoicesService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(tax_config_entity_1.TaxConfig)),
__param(1, (0, typeorm_1.InjectRepository)(invoice_entity_1.Invoice)),
__param(2, (0, typeorm_1.InjectRepository)(invoice_item_entity_1.InvoiceItem)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.DataSource])
], InvoicesService);
//# sourceMappingURL=invoices.service.js.map