erp-core-backend-v2/src/modules/invoices/services/index.ts
rckrdmrd d9778eb632 feat(invoices): Unify billing and commercial Invoice entities
- Create unified Invoice entity in invoices module with all fields:
  - Commercial fields: salesOrderId, purchaseOrderId, partnerId
  - SaaS fields: subscriptionId, periodStart, periodEnd
  - Context discriminator: invoiceContext ('commercial' | 'saas')
- Mark billing-usage/invoice.entity.ts as deprecated, re-export from invoices
- Update billing-usage/services/invoices.service.ts for unified entity
- Add missing InvoiceStatus values (validated, cancelled, voided)
- Export types from both modules for backward compatibility

Note: financial/invoice.entity.ts remains separate (different schema)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 02:36:06 -06:00

87 lines
4.1 KiB
TypeScript

import { Repository, FindOptionsWhere } from 'typeorm';
import { Invoice, Payment } from '../entities';
import { CreateInvoiceDto, UpdateInvoiceDto, CreatePaymentDto } from '../dto';
export interface InvoiceSearchParams {
tenantId: string;
invoiceType?: string;
partnerId?: string;
status?: string;
limit?: number;
offset?: number;
}
export class InvoicesService {
constructor(
private readonly invoiceRepository: Repository<Invoice>,
private readonly paymentRepository: Repository<Payment>
) {}
async findAllInvoices(params: InvoiceSearchParams): Promise<{ data: Invoice[]; total: number }> {
const { tenantId, invoiceType, partnerId, status, limit = 50, offset = 0 } = params;
const where: FindOptionsWhere<Invoice> = { tenantId };
if (invoiceType) where.invoiceType = invoiceType as any;
if (partnerId) where.partnerId = partnerId;
if (status) where.status = status as any;
const [data, total] = await this.invoiceRepository.findAndCount({ where, take: limit, skip: offset, order: { createdAt: 'DESC' } });
return { data, total };
}
async findInvoice(id: string, tenantId: string): Promise<Invoice | null> {
return this.invoiceRepository.findOne({ where: { id, tenantId } });
}
async createInvoice(tenantId: string, dto: CreateInvoiceDto, createdBy?: string): Promise<Invoice> {
const count = await this.invoiceRepository.count({ where: { tenantId, invoiceType: dto.invoiceType } });
const prefix = dto.invoiceType === 'sale' ? 'FAC' : dto.invoiceType === 'purchase' ? 'FP' : dto.invoiceType === 'credit_note' ? 'NC' : 'ND';
const invoiceNumber = `${prefix}-${String(count + 1).padStart(6, '0')}`;
const invoice = this.invoiceRepository.create({ ...dto, tenantId, invoiceNumber, createdBy, invoiceDate: dto.invoiceDate ? new Date(dto.invoiceDate) : new Date(), dueDate: dto.dueDate ? new Date(dto.dueDate) : undefined });
return this.invoiceRepository.save(invoice);
}
async updateInvoice(id: string, tenantId: string, dto: UpdateInvoiceDto, updatedBy?: string): Promise<Invoice | null> {
const invoice = await this.findInvoice(id, tenantId);
if (!invoice) return null;
Object.assign(invoice, { ...dto, updatedBy });
return this.invoiceRepository.save(invoice);
}
async deleteInvoice(id: string, tenantId: string): Promise<boolean> {
const result = await this.invoiceRepository.softDelete({ id, tenantId });
return (result.affected ?? 0) > 0;
}
async validateInvoice(id: string, tenantId: string, userId?: string): Promise<Invoice | null> {
const invoice = await this.findInvoice(id, tenantId);
if (!invoice || invoice.status !== 'draft') return null;
invoice.status = 'validated';
invoice.updatedBy = userId ?? null;
return this.invoiceRepository.save(invoice);
}
async findAllPayments(tenantId: string, partnerId?: string, limit = 50, offset = 0): Promise<{ data: Payment[]; total: number }> {
const where: FindOptionsWhere<Payment> = { tenantId };
if (partnerId) where.partnerId = partnerId;
const [data, total] = await this.paymentRepository.findAndCount({ where, take: limit, skip: offset, order: { createdAt: 'DESC' } });
return { data, total };
}
async findPayment(id: string, tenantId: string): Promise<Payment | null> {
return this.paymentRepository.findOne({ where: { id, tenantId } });
}
async createPayment(tenantId: string, dto: CreatePaymentDto, createdBy?: string): Promise<Payment> {
const count = await this.paymentRepository.count({ where: { tenantId } });
const paymentNumber = `PAG-${String(count + 1).padStart(6, '0')}`;
const payment = this.paymentRepository.create({ ...dto, tenantId, paymentNumber, createdBy, paymentDate: dto.paymentDate ? new Date(dto.paymentDate) : new Date() });
return this.paymentRepository.save(payment);
}
async confirmPayment(id: string, tenantId: string, userId?: string): Promise<Payment | null> {
const payment = await this.findPayment(id, tenantId);
if (!payment || payment.status !== 'draft') return null;
payment.status = 'confirmed';
return this.paymentRepository.save(payment);
}
}