- Prefijo v2: MCH - TRACEABILITY-MASTER.yml creado - Listo para integracion como submodulo Workspace: v2.0.0 | SIMCO: v4.0.0
192 lines
8.6 KiB
JavaScript
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
|