- 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>
87 lines
4.1 KiB
TypeScript
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);
|
|
}
|
|
}
|