[MMD-010] feat: Add FiadosService module for customer credit management
- Create fiados module with 3 entities: - CustomerCreditAccount: Credit accounts per customer - Fiado: Individual credit sales (fiados) - FiadoPayment: Payment/abono tracking - Implement FiadosService with full business logic: - Credit eligibility checking - Fiado creation with automatic due dates - Payment registration with FIFO allocation - Overdue tracking and account freezing - Connect FiadosToolsService to real FiadosService - Update MCP module registration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e26ab24aa5
commit
f74f713482
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Customer Credit Account Entity
|
||||||
|
* Mecánicas Diesel - ERP Suite
|
||||||
|
*
|
||||||
|
* Represents a customer's credit account for fiado tracking.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Customer } from '../../customers/entities/customer.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'customer_credit_accounts', schema: 'service_management' })
|
||||||
|
@Index('idx_credit_accounts_tenant', ['tenantId'])
|
||||||
|
@Index('idx_credit_accounts_customer', ['customerId'])
|
||||||
|
export class CustomerCreditAccount {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'customer_id', type: 'uuid' })
|
||||||
|
customerId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
|
// Credit limits
|
||||||
|
@Column({ name: 'credit_limit', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
creditLimit: number;
|
||||||
|
|
||||||
|
@Column({ name: 'credit_days', type: 'integer', default: 30 })
|
||||||
|
creditDays: number;
|
||||||
|
|
||||||
|
// Balances
|
||||||
|
@Column({ name: 'current_balance', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
currentBalance: number;
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
@Column({ name: 'total_credit_given', type: 'decimal', precision: 14, scale: 2, default: 0 })
|
||||||
|
totalCreditGiven: number;
|
||||||
|
|
||||||
|
@Column({ name: 'total_payments_received', type: 'decimal', precision: 14, scale: 2, default: 0 })
|
||||||
|
totalPaymentsReceived: number;
|
||||||
|
|
||||||
|
@Column({ name: 'overdue_amount', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
overdueAmount: number;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_frozen', type: 'boolean', default: false })
|
||||||
|
isFrozen: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'frozen_reason', type: 'text', nullable: true })
|
||||||
|
frozenReason?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'frozen_at', type: 'timestamptz', nullable: true })
|
||||||
|
frozenAt?: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'frozen_by', type: 'uuid', nullable: true })
|
||||||
|
frozenBy?: string;
|
||||||
|
|
||||||
|
// Audit
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
// Computed property
|
||||||
|
get availableCredit(): number {
|
||||||
|
return Number(this.creditLimit) - Number(this.currentBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/modules/fiados/entities/fiado-payment.entity.ts
Normal file
87
src/modules/fiados/entities/fiado-payment.entity.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Fiado Payment Entity
|
||||||
|
* Mecánicas Diesel - ERP Suite
|
||||||
|
*
|
||||||
|
* Represents a payment received for fiados.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Customer } from '../../customers/entities/customer.entity';
|
||||||
|
import { CustomerCreditAccount } from './customer-credit-account.entity';
|
||||||
|
import { Fiado } from './fiado.entity';
|
||||||
|
|
||||||
|
export enum PaymentMethod {
|
||||||
|
CASH = 'cash',
|
||||||
|
CARD = 'card',
|
||||||
|
TRANSFER = 'transfer',
|
||||||
|
CHECK = 'check',
|
||||||
|
OTHER = 'other',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ name: 'fiado_payments', schema: 'service_management' })
|
||||||
|
@Index('idx_fiado_payments_tenant', ['tenantId'])
|
||||||
|
@Index('idx_fiado_payments_customer', ['customerId'])
|
||||||
|
@Index('idx_fiado_payments_account', ['creditAccountId'])
|
||||||
|
@Index('idx_fiado_payments_fiado', ['fiadoId'])
|
||||||
|
export class FiadoPayment {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'customer_id', type: 'uuid' })
|
||||||
|
customerId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
|
@Column({ name: 'credit_account_id', type: 'uuid' })
|
||||||
|
creditAccountId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => CustomerCreditAccount)
|
||||||
|
@JoinColumn({ name: 'credit_account_id' })
|
||||||
|
creditAccount: CustomerCreditAccount;
|
||||||
|
|
||||||
|
@Column({ name: 'fiado_id', type: 'uuid', nullable: true })
|
||||||
|
fiadoId?: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Fiado, { nullable: true })
|
||||||
|
@JoinColumn({ name: 'fiado_id' })
|
||||||
|
fiado?: Fiado;
|
||||||
|
|
||||||
|
// Identification
|
||||||
|
@Column({ name: 'payment_number', type: 'varchar', length: 20 })
|
||||||
|
paymentNumber: string;
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
@Column({ type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
// Payment method
|
||||||
|
@Column({ name: 'payment_method', type: 'varchar', length: 20 })
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_reference', type: 'varchar', length: 100, nullable: true })
|
||||||
|
paymentReference?: string;
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
notes?: string;
|
||||||
|
|
||||||
|
// Audit
|
||||||
|
@Column({ name: 'received_by', type: 'uuid', nullable: true })
|
||||||
|
receivedBy?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
115
src/modules/fiados/entities/fiado.entity.ts
Normal file
115
src/modules/fiados/entities/fiado.entity.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Fiado Entity
|
||||||
|
* Mecánicas Diesel - ERP Suite
|
||||||
|
*
|
||||||
|
* Represents an individual credit sale (fiado).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Customer } from '../../customers/entities/customer.entity';
|
||||||
|
import { CustomerCreditAccount } from './customer-credit-account.entity';
|
||||||
|
|
||||||
|
export enum FiadoStatus {
|
||||||
|
PENDING = 'pending',
|
||||||
|
PARTIAL = 'partial',
|
||||||
|
PAID = 'paid',
|
||||||
|
OVERDUE = 'overdue',
|
||||||
|
CANCELLED = 'cancelled',
|
||||||
|
WRITTEN_OFF = 'written_off',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ name: 'fiados', schema: 'service_management' })
|
||||||
|
@Index('idx_fiados_tenant', ['tenantId'])
|
||||||
|
@Index('idx_fiados_customer', ['customerId'])
|
||||||
|
@Index('idx_fiados_account', ['creditAccountId'])
|
||||||
|
@Index('idx_fiados_status', ['tenantId', 'status'])
|
||||||
|
export class Fiado {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'customer_id', type: 'uuid' })
|
||||||
|
customerId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Customer)
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer: Customer;
|
||||||
|
|
||||||
|
@Column({ name: 'credit_account_id', type: 'uuid' })
|
||||||
|
creditAccountId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => CustomerCreditAccount)
|
||||||
|
@JoinColumn({ name: 'credit_account_id' })
|
||||||
|
creditAccount: CustomerCreditAccount;
|
||||||
|
|
||||||
|
@Column({ name: 'order_id', type: 'uuid', nullable: true })
|
||||||
|
orderId?: string;
|
||||||
|
|
||||||
|
// Identification
|
||||||
|
@Column({ name: 'fiado_number', type: 'varchar', length: 20 })
|
||||||
|
fiadoNumber: string;
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
@Column({ name: 'original_amount', type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
originalAmount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'remaining_amount', type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
remainingAmount: number;
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
@Column({ name: 'issued_at', type: 'timestamptz', default: () => 'NOW()' })
|
||||||
|
issuedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'due_at', type: 'timestamptz' })
|
||||||
|
dueAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'paid_at', type: 'timestamptz', nullable: true })
|
||||||
|
paidAt?: Date;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
@Column({
|
||||||
|
type: 'varchar',
|
||||||
|
length: 20,
|
||||||
|
default: FiadoStatus.PENDING,
|
||||||
|
})
|
||||||
|
status: FiadoStatus;
|
||||||
|
|
||||||
|
// Description
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
notes?: string;
|
||||||
|
|
||||||
|
// Audit
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
get isOverdue(): boolean {
|
||||||
|
return this.status !== FiadoStatus.PAID &&
|
||||||
|
this.status !== FiadoStatus.CANCELLED &&
|
||||||
|
new Date() > this.dueAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
get paidAmount(): number {
|
||||||
|
return Number(this.originalAmount) - Number(this.remainingAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/modules/fiados/entities/index.ts
Normal file
8
src/modules/fiados/entities/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Fiados Entities Index
|
||||||
|
* @module Fiados
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './customer-credit-account.entity';
|
||||||
|
export * from './fiado.entity';
|
||||||
|
export * from './fiado-payment.entity';
|
||||||
9
src/modules/fiados/index.ts
Normal file
9
src/modules/fiados/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Fiados Module
|
||||||
|
* Mecánicas Diesel - ERP Suite
|
||||||
|
*
|
||||||
|
* Credit (fiado) management for customers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './entities';
|
||||||
|
export * from './services';
|
||||||
640
src/modules/fiados/services/fiados.service.ts
Normal file
640
src/modules/fiados/services/fiados.service.ts
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
/**
|
||||||
|
* Fiados Service
|
||||||
|
* Mecánicas Diesel - ERP Suite
|
||||||
|
*
|
||||||
|
* Business logic for credit (fiado) management.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataSource, Repository, LessThan, In } from 'typeorm';
|
||||||
|
import { CustomerCreditAccount } from '../entities/customer-credit-account.entity';
|
||||||
|
import { Fiado, FiadoStatus } from '../entities/fiado.entity';
|
||||||
|
import { FiadoPayment, PaymentMethod } from '../entities/fiado-payment.entity';
|
||||||
|
import { Customer } from '../../customers/entities/customer.entity';
|
||||||
|
|
||||||
|
export interface FiadoBalance {
|
||||||
|
customerId: string;
|
||||||
|
customerName: string;
|
||||||
|
creditLimit: number;
|
||||||
|
currentBalance: number;
|
||||||
|
availableCredit: number;
|
||||||
|
overdueAmount: number;
|
||||||
|
isFrozen: boolean;
|
||||||
|
pendingFiados: Array<{
|
||||||
|
id: string;
|
||||||
|
fiadoNumber: string;
|
||||||
|
originalAmount: number;
|
||||||
|
remainingAmount: number;
|
||||||
|
dueAt: Date;
|
||||||
|
status: string;
|
||||||
|
isOverdue: boolean;
|
||||||
|
}>;
|
||||||
|
recentPayments: Array<{
|
||||||
|
id: string;
|
||||||
|
paymentNumber: string;
|
||||||
|
amount: number;
|
||||||
|
paymentMethod: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FiadoEligibility {
|
||||||
|
eligible: boolean;
|
||||||
|
reason: string;
|
||||||
|
currentBalance: number;
|
||||||
|
creditLimit: number;
|
||||||
|
availableCredit: number;
|
||||||
|
requestedAmount: number;
|
||||||
|
hasOverdue: boolean;
|
||||||
|
suggestions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateFiadoDto {
|
||||||
|
customerId: string;
|
||||||
|
amount: number;
|
||||||
|
orderId?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePaymentDto {
|
||||||
|
customerId: string;
|
||||||
|
amount: number;
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
fiadoId?: string;
|
||||||
|
paymentReference?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FiadosService {
|
||||||
|
private creditAccountRepository: Repository<CustomerCreditAccount>;
|
||||||
|
private fiadoRepository: Repository<Fiado>;
|
||||||
|
private paymentRepository: Repository<FiadoPayment>;
|
||||||
|
private customerRepository: Repository<Customer>;
|
||||||
|
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
this.creditAccountRepository = dataSource.getRepository(CustomerCreditAccount);
|
||||||
|
this.fiadoRepository = dataSource.getRepository(Fiado);
|
||||||
|
this.paymentRepository = dataSource.getRepository(FiadoPayment);
|
||||||
|
this.customerRepository = dataSource.getRepository(Customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create credit account for customer
|
||||||
|
*/
|
||||||
|
async getOrCreateCreditAccount(
|
||||||
|
tenantId: string,
|
||||||
|
customerId: string,
|
||||||
|
userId?: string
|
||||||
|
): Promise<CustomerCreditAccount> {
|
||||||
|
let account = await this.creditAccountRepository.findOne({
|
||||||
|
where: { tenantId, customerId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
// Get customer to copy credit settings
|
||||||
|
const customer = await this.customerRepository.findOne({
|
||||||
|
where: { id: customerId, tenantId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
throw new Error('Cliente no encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
account = this.creditAccountRepository.create({
|
||||||
|
tenantId,
|
||||||
|
customerId,
|
||||||
|
creditLimit: customer.creditLimit || 0,
|
||||||
|
creditDays: customer.creditDays || 30,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
account = await this.creditAccountRepository.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fiado balance for a customer
|
||||||
|
*/
|
||||||
|
async getFiadoBalance(tenantId: string, customerId: string): Promise<FiadoBalance> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, customerId);
|
||||||
|
|
||||||
|
// Get customer info
|
||||||
|
const customer = await this.customerRepository.findOne({
|
||||||
|
where: { id: customerId, tenantId },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get pending fiados
|
||||||
|
const pendingFiados = await this.fiadoRepository.find({
|
||||||
|
where: {
|
||||||
|
tenantId,
|
||||||
|
customerId,
|
||||||
|
status: In([FiadoStatus.PENDING, FiadoStatus.PARTIAL, FiadoStatus.OVERDUE]),
|
||||||
|
},
|
||||||
|
order: { dueAt: 'ASC' },
|
||||||
|
take: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get recent payments
|
||||||
|
const recentPayments = await this.paymentRepository.find({
|
||||||
|
where: { tenantId, customerId },
|
||||||
|
order: { createdAt: 'DESC' },
|
||||||
|
take: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
customerId,
|
||||||
|
customerName: customer?.name || 'Desconocido',
|
||||||
|
creditLimit: Number(account.creditLimit),
|
||||||
|
currentBalance: Number(account.currentBalance),
|
||||||
|
availableCredit: Number(account.creditLimit) - Number(account.currentBalance),
|
||||||
|
overdueAmount: Number(account.overdueAmount),
|
||||||
|
isFrozen: account.isFrozen,
|
||||||
|
pendingFiados: pendingFiados.map(f => ({
|
||||||
|
id: f.id,
|
||||||
|
fiadoNumber: f.fiadoNumber,
|
||||||
|
originalAmount: Number(f.originalAmount),
|
||||||
|
remainingAmount: Number(f.remainingAmount),
|
||||||
|
dueAt: f.dueAt,
|
||||||
|
status: f.status,
|
||||||
|
isOverdue: f.isOverdue,
|
||||||
|
})),
|
||||||
|
recentPayments: recentPayments.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
paymentNumber: p.paymentNumber,
|
||||||
|
amount: Number(p.amount),
|
||||||
|
paymentMethod: p.paymentMethod,
|
||||||
|
createdAt: p.createdAt,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if customer is eligible for credit
|
||||||
|
*/
|
||||||
|
async checkEligibility(
|
||||||
|
tenantId: string,
|
||||||
|
customerId: string,
|
||||||
|
amount: number
|
||||||
|
): Promise<FiadoEligibility> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, customerId);
|
||||||
|
const suggestions: string[] = [];
|
||||||
|
|
||||||
|
const currentBalance = Number(account.currentBalance);
|
||||||
|
const creditLimit = Number(account.creditLimit);
|
||||||
|
const availableCredit = creditLimit - currentBalance;
|
||||||
|
const overdueAmount = Number(account.overdueAmount);
|
||||||
|
const hasOverdue = overdueAmount > 0;
|
||||||
|
|
||||||
|
// Check if account is frozen
|
||||||
|
if (account.isFrozen) {
|
||||||
|
return {
|
||||||
|
eligible: false,
|
||||||
|
reason: `Crédito congelado: ${account.frozenReason || 'Contacte administración'}`,
|
||||||
|
currentBalance,
|
||||||
|
creditLimit,
|
||||||
|
availableCredit,
|
||||||
|
requestedAmount: amount,
|
||||||
|
hasOverdue,
|
||||||
|
suggestions: ['Contactar al administrador para revisar el estado de la cuenta'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for overdue fiados
|
||||||
|
if (hasOverdue) {
|
||||||
|
suggestions.push('Solicitar pago del saldo vencido antes de continuar');
|
||||||
|
return {
|
||||||
|
eligible: false,
|
||||||
|
reason: `Cliente tiene $${overdueAmount.toFixed(2)} vencido`,
|
||||||
|
currentBalance,
|
||||||
|
creditLimit,
|
||||||
|
availableCredit,
|
||||||
|
requestedAmount: amount,
|
||||||
|
hasOverdue,
|
||||||
|
suggestions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check credit limit
|
||||||
|
if (creditLimit === 0) {
|
||||||
|
suggestions.push('Solicitar apertura de línea de crédito');
|
||||||
|
return {
|
||||||
|
eligible: false,
|
||||||
|
reason: 'Cliente no tiene línea de crédito autorizada',
|
||||||
|
currentBalance,
|
||||||
|
creditLimit,
|
||||||
|
availableCredit,
|
||||||
|
requestedAmount: amount,
|
||||||
|
hasOverdue,
|
||||||
|
suggestions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check available credit
|
||||||
|
if (amount > availableCredit) {
|
||||||
|
suggestions.push(`Reducir el monto a $${availableCredit.toFixed(2)}`);
|
||||||
|
suggestions.push('Solicitar aumento de límite de crédito');
|
||||||
|
suggestions.push('Solicitar pago parcial antes de continuar');
|
||||||
|
return {
|
||||||
|
eligible: false,
|
||||||
|
reason: `Monto ($${amount.toFixed(2)}) excede crédito disponible ($${availableCredit.toFixed(2)})`,
|
||||||
|
currentBalance,
|
||||||
|
creditLimit,
|
||||||
|
availableCredit,
|
||||||
|
requestedAmount: amount,
|
||||||
|
hasOverdue,
|
||||||
|
suggestions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eligible: true,
|
||||||
|
reason: 'Cliente con crédito disponible',
|
||||||
|
currentBalance,
|
||||||
|
creditLimit,
|
||||||
|
availableCredit,
|
||||||
|
requestedAmount: amount,
|
||||||
|
hasOverdue,
|
||||||
|
suggestions: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new fiado (credit sale)
|
||||||
|
*/
|
||||||
|
async createFiado(
|
||||||
|
tenantId: string,
|
||||||
|
dto: CreateFiadoDto,
|
||||||
|
userId?: string
|
||||||
|
): Promise<Fiado> {
|
||||||
|
// Check eligibility first
|
||||||
|
const eligibility = await this.checkEligibility(tenantId, dto.customerId, dto.amount);
|
||||||
|
if (!eligibility.eligible) {
|
||||||
|
throw new Error(eligibility.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, dto.customerId, userId);
|
||||||
|
|
||||||
|
// Generate fiado number
|
||||||
|
const fiadoNumber = await this.generateFiadoNumber(tenantId);
|
||||||
|
|
||||||
|
// Calculate due date based on credit days
|
||||||
|
const dueAt = new Date();
|
||||||
|
dueAt.setDate(dueAt.getDate() + account.creditDays);
|
||||||
|
|
||||||
|
// Create fiado
|
||||||
|
const fiado = this.fiadoRepository.create({
|
||||||
|
tenantId,
|
||||||
|
customerId: dto.customerId,
|
||||||
|
creditAccountId: account.id,
|
||||||
|
orderId: dto.orderId,
|
||||||
|
fiadoNumber,
|
||||||
|
originalAmount: dto.amount,
|
||||||
|
remainingAmount: dto.amount,
|
||||||
|
dueAt,
|
||||||
|
status: FiadoStatus.PENDING,
|
||||||
|
description: dto.description,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedFiado = await this.fiadoRepository.save(fiado);
|
||||||
|
|
||||||
|
// Update account balance
|
||||||
|
await this.creditAccountRepository.update(
|
||||||
|
{ id: account.id },
|
||||||
|
{
|
||||||
|
currentBalance: () => `current_balance + ${dto.amount}`,
|
||||||
|
totalCreditGiven: () => `total_credit_given + ${dto.amount}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return savedFiado;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a payment for fiados
|
||||||
|
*/
|
||||||
|
async registerPayment(
|
||||||
|
tenantId: string,
|
||||||
|
dto: CreatePaymentDto,
|
||||||
|
userId?: string
|
||||||
|
): Promise<{ payment: FiadoPayment; fiadosPaid: Fiado[] }> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, dto.customerId, userId);
|
||||||
|
|
||||||
|
// Generate payment number
|
||||||
|
const paymentNumber = await this.generatePaymentNumber(tenantId);
|
||||||
|
|
||||||
|
// Create payment record
|
||||||
|
const payment = this.paymentRepository.create({
|
||||||
|
tenantId,
|
||||||
|
customerId: dto.customerId,
|
||||||
|
creditAccountId: account.id,
|
||||||
|
fiadoId: dto.fiadoId,
|
||||||
|
paymentNumber,
|
||||||
|
amount: dto.amount,
|
||||||
|
paymentMethod: dto.paymentMethod,
|
||||||
|
paymentReference: dto.paymentReference,
|
||||||
|
notes: dto.notes,
|
||||||
|
receivedBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedPayment = await this.paymentRepository.save(payment);
|
||||||
|
|
||||||
|
// Apply payment to fiados
|
||||||
|
const fiadosPaid = await this.applyPaymentToFiados(
|
||||||
|
tenantId,
|
||||||
|
dto.customerId,
|
||||||
|
dto.amount,
|
||||||
|
dto.fiadoId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update account balance
|
||||||
|
await this.creditAccountRepository.update(
|
||||||
|
{ id: account.id },
|
||||||
|
{
|
||||||
|
currentBalance: () => `current_balance - ${dto.amount}`,
|
||||||
|
totalPaymentsReceived: () => `total_payments_received + ${dto.amount}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Recalculate overdue amount
|
||||||
|
await this.recalculateOverdueAmount(tenantId, account.id);
|
||||||
|
|
||||||
|
return { payment: savedPayment, fiadosPaid };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply payment to fiados (FIFO - oldest first)
|
||||||
|
*/
|
||||||
|
private async applyPaymentToFiados(
|
||||||
|
tenantId: string,
|
||||||
|
customerId: string,
|
||||||
|
amount: number,
|
||||||
|
specificFiadoId?: string
|
||||||
|
): Promise<Fiado[]> {
|
||||||
|
let remainingPayment = amount;
|
||||||
|
const paidFiados: Fiado[] = [];
|
||||||
|
|
||||||
|
// If specific fiado, apply to that first
|
||||||
|
if (specificFiadoId) {
|
||||||
|
const specificFiado = await this.fiadoRepository.findOne({
|
||||||
|
where: { id: specificFiadoId, tenantId, customerId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (specificFiado && Number(specificFiado.remainingAmount) > 0) {
|
||||||
|
const toApply = Math.min(remainingPayment, Number(specificFiado.remainingAmount));
|
||||||
|
specificFiado.remainingAmount = Number(specificFiado.remainingAmount) - toApply;
|
||||||
|
remainingPayment -= toApply;
|
||||||
|
|
||||||
|
if (specificFiado.remainingAmount <= 0) {
|
||||||
|
specificFiado.status = FiadoStatus.PAID;
|
||||||
|
specificFiado.paidAt = new Date();
|
||||||
|
} else {
|
||||||
|
specificFiado.status = FiadoStatus.PARTIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.fiadoRepository.save(specificFiado);
|
||||||
|
paidFiados.push(specificFiado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply remaining to oldest fiados first
|
||||||
|
if (remainingPayment > 0) {
|
||||||
|
const pendingFiados = await this.fiadoRepository.find({
|
||||||
|
where: {
|
||||||
|
tenantId,
|
||||||
|
customerId,
|
||||||
|
status: In([FiadoStatus.PENDING, FiadoStatus.PARTIAL, FiadoStatus.OVERDUE]),
|
||||||
|
},
|
||||||
|
order: { dueAt: 'ASC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const fiado of pendingFiados) {
|
||||||
|
if (remainingPayment <= 0) break;
|
||||||
|
if (specificFiadoId && fiado.id === specificFiadoId) continue; // Already processed
|
||||||
|
|
||||||
|
const toApply = Math.min(remainingPayment, Number(fiado.remainingAmount));
|
||||||
|
fiado.remainingAmount = Number(fiado.remainingAmount) - toApply;
|
||||||
|
remainingPayment -= toApply;
|
||||||
|
|
||||||
|
if (fiado.remainingAmount <= 0) {
|
||||||
|
fiado.status = FiadoStatus.PAID;
|
||||||
|
fiado.paidAt = new Date();
|
||||||
|
} else {
|
||||||
|
fiado.status = FiadoStatus.PARTIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.fiadoRepository.save(fiado);
|
||||||
|
paidFiados.push(fiado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paidFiados;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update overdue fiados status
|
||||||
|
*/
|
||||||
|
async updateOverdueStatus(tenantId: string): Promise<number> {
|
||||||
|
const now = new Date();
|
||||||
|
const result = await this.fiadoRepository.update(
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
status: In([FiadoStatus.PENDING, FiadoStatus.PARTIAL]),
|
||||||
|
dueAt: LessThan(now),
|
||||||
|
},
|
||||||
|
{ status: FiadoStatus.OVERDUE }
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.affected || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculate overdue amount for an account
|
||||||
|
*/
|
||||||
|
private async recalculateOverdueAmount(tenantId: string, accountId: string): Promise<void> {
|
||||||
|
const result = await this.fiadoRepository
|
||||||
|
.createQueryBuilder('f')
|
||||||
|
.select('COALESCE(SUM(f.remaining_amount), 0)', 'total')
|
||||||
|
.where('f.credit_account_id = :accountId', { accountId })
|
||||||
|
.andWhere('f.status = :status', { status: FiadoStatus.OVERDUE })
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
await this.creditAccountRepository.update(
|
||||||
|
{ id: accountId },
|
||||||
|
{ overdueAmount: result?.total || 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get accounts receivable summary
|
||||||
|
*/
|
||||||
|
async getAccountsReceivable(
|
||||||
|
tenantId: string,
|
||||||
|
options: { status?: 'all' | 'current' | 'overdue'; minAmount?: number; limit?: number }
|
||||||
|
): Promise<{
|
||||||
|
total: number;
|
||||||
|
count: number;
|
||||||
|
overdueTotal: number;
|
||||||
|
overdueCount: number;
|
||||||
|
accounts: Array<{
|
||||||
|
customerId: string;
|
||||||
|
customerName: string;
|
||||||
|
balance: number;
|
||||||
|
overdueAmount: number;
|
||||||
|
oldestDueDate: Date | null;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
const limit = options.limit || 50;
|
||||||
|
|
||||||
|
const queryBuilder = this.creditAccountRepository
|
||||||
|
.createQueryBuilder('ca')
|
||||||
|
.leftJoin('ca.customer', 'c')
|
||||||
|
.select([
|
||||||
|
'ca.customer_id AS "customerId"',
|
||||||
|
'c.name AS "customerName"',
|
||||||
|
'ca.current_balance AS balance',
|
||||||
|
'ca.overdue_amount AS "overdueAmount"',
|
||||||
|
])
|
||||||
|
.where('ca.tenant_id = :tenantId', { tenantId })
|
||||||
|
.andWhere('ca.current_balance > 0');
|
||||||
|
|
||||||
|
if (options.status === 'overdue') {
|
||||||
|
queryBuilder.andWhere('ca.overdue_amount > 0');
|
||||||
|
} else if (options.status === 'current') {
|
||||||
|
queryBuilder.andWhere('ca.overdue_amount = 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.minAmount) {
|
||||||
|
queryBuilder.andWhere('ca.current_balance >= :minAmount', { minAmount: options.minAmount });
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = await queryBuilder
|
||||||
|
.orderBy('ca.current_balance', 'DESC')
|
||||||
|
.limit(limit)
|
||||||
|
.getRawMany();
|
||||||
|
|
||||||
|
// Get totals
|
||||||
|
const totals = await this.creditAccountRepository
|
||||||
|
.createQueryBuilder('ca')
|
||||||
|
.select([
|
||||||
|
'COALESCE(SUM(ca.current_balance), 0) AS total',
|
||||||
|
'COUNT(*) AS count',
|
||||||
|
'COALESCE(SUM(ca.overdue_amount), 0) AS "overdueTotal"',
|
||||||
|
'COUNT(CASE WHEN ca.overdue_amount > 0 THEN 1 END) AS "overdueCount"',
|
||||||
|
])
|
||||||
|
.where('ca.tenant_id = :tenantId', { tenantId })
|
||||||
|
.andWhere('ca.current_balance > 0')
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: Number(totals?.total || 0),
|
||||||
|
count: Number(totals?.count || 0),
|
||||||
|
overdueTotal: Number(totals?.overdueTotal || 0),
|
||||||
|
overdueCount: Number(totals?.overdueCount || 0),
|
||||||
|
accounts: accounts.map(a => ({
|
||||||
|
customerId: a.customerId,
|
||||||
|
customerName: a.customerName,
|
||||||
|
balance: Number(a.balance),
|
||||||
|
overdueAmount: Number(a.overdueAmount),
|
||||||
|
oldestDueDate: null, // Would need additional query
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique fiado number
|
||||||
|
*/
|
||||||
|
private async generateFiadoNumber(tenantId: string): Promise<string> {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const prefix = `F-${year}-`;
|
||||||
|
|
||||||
|
const lastFiado = await this.fiadoRepository
|
||||||
|
.createQueryBuilder('f')
|
||||||
|
.where('f.tenant_id = :tenantId', { tenantId })
|
||||||
|
.andWhere('f.fiado_number LIKE :prefix', { prefix: `${prefix}%` })
|
||||||
|
.orderBy('f.fiado_number', 'DESC')
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
let nextNumber = 1;
|
||||||
|
if (lastFiado) {
|
||||||
|
const match = lastFiado.fiadoNumber.match(/F-\d{4}-(\d+)/);
|
||||||
|
if (match) {
|
||||||
|
nextNumber = parseInt(match[1], 10) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${prefix}${nextNumber.toString().padStart(5, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique payment number
|
||||||
|
*/
|
||||||
|
private async generatePaymentNumber(tenantId: string): Promise<string> {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const prefix = `P-${year}-`;
|
||||||
|
|
||||||
|
const lastPayment = await this.paymentRepository
|
||||||
|
.createQueryBuilder('p')
|
||||||
|
.where('p.tenant_id = :tenantId', { tenantId })
|
||||||
|
.andWhere('p.payment_number LIKE :prefix', { prefix: `${prefix}%` })
|
||||||
|
.orderBy('p.payment_number', 'DESC')
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
let nextNumber = 1;
|
||||||
|
if (lastPayment) {
|
||||||
|
const match = lastPayment.paymentNumber.match(/P-\d{4}-(\d+)/);
|
||||||
|
if (match) {
|
||||||
|
nextNumber = parseInt(match[1], 10) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${prefix}${nextNumber.toString().padStart(5, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freeze a customer's credit account
|
||||||
|
*/
|
||||||
|
async freezeAccount(
|
||||||
|
tenantId: string,
|
||||||
|
customerId: string,
|
||||||
|
reason: string,
|
||||||
|
userId?: string
|
||||||
|
): Promise<CustomerCreditAccount> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, customerId);
|
||||||
|
|
||||||
|
account.isFrozen = true;
|
||||||
|
account.frozenReason = reason;
|
||||||
|
account.frozenAt = new Date();
|
||||||
|
account.frozenBy = userId;
|
||||||
|
|
||||||
|
return this.creditAccountRepository.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfreeze a customer's credit account
|
||||||
|
*/
|
||||||
|
async unfreezeAccount(tenantId: string, customerId: string): Promise<CustomerCreditAccount> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, customerId);
|
||||||
|
|
||||||
|
account.isFrozen = false;
|
||||||
|
account.frozenReason = undefined;
|
||||||
|
account.frozenAt = undefined;
|
||||||
|
account.frozenBy = undefined;
|
||||||
|
|
||||||
|
return this.creditAccountRepository.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update credit limit for a customer
|
||||||
|
*/
|
||||||
|
async updateCreditLimit(
|
||||||
|
tenantId: string,
|
||||||
|
customerId: string,
|
||||||
|
newLimit: number
|
||||||
|
): Promise<CustomerCreditAccount> {
|
||||||
|
const account = await this.getOrCreateCreditAccount(tenantId, customerId);
|
||||||
|
account.creditLimit = newLimit;
|
||||||
|
return this.creditAccountRepository.save(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/modules/fiados/services/index.ts
Normal file
6
src/modules/fiados/services/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Fiados Services Index
|
||||||
|
* @module Fiados
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './fiados.service';
|
||||||
@ -47,10 +47,10 @@ export class McpModule {
|
|||||||
|
|
||||||
// Register tool providers (connected to real services)
|
// Register tool providers (connected to real services)
|
||||||
this.toolRegistry.registerProvider(new ProductsToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new ProductsToolsService(this.dataSource));
|
||||||
this.toolRegistry.registerProvider(new InventoryToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new InventoryToolsService()); // TODO: Connect to PartService
|
||||||
this.toolRegistry.registerProvider(new OrdersToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new OrdersToolsService()); // TODO: Connect to ServiceOrderService
|
||||||
this.toolRegistry.registerProvider(new CustomersToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new CustomersToolsService()); // TODO: Connect to CustomersService
|
||||||
this.toolRegistry.registerProvider(new FiadosToolsService());
|
this.toolRegistry.registerProvider(new FiadosToolsService(this.dataSource));
|
||||||
this.toolRegistry.registerProvider(new SalesToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new SalesToolsService(this.dataSource));
|
||||||
this.toolRegistry.registerProvider(new FinancialToolsService(this.dataSource));
|
this.toolRegistry.registerProvider(new FinancialToolsService(this.dataSource));
|
||||||
this.toolRegistry.registerProvider(new BranchToolsService());
|
this.toolRegistry.registerProvider(new BranchToolsService());
|
||||||
|
|||||||
@ -1,17 +1,25 @@
|
|||||||
|
import { DataSource } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
McpToolProvider,
|
McpToolProvider,
|
||||||
McpToolDefinition,
|
McpToolDefinition,
|
||||||
McpToolHandler,
|
McpToolHandler,
|
||||||
McpContext,
|
McpContext,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
|
import { FiadosService, CreateFiadoDto, CreatePaymentDto } from '../../fiados/services/fiados.service';
|
||||||
|
import { PaymentMethod } from '../../fiados/entities/fiado-payment.entity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fiados (Credit) Tools Service
|
* Fiados (Credit) Tools Service
|
||||||
* Provides MCP tools for credit/fiado management.
|
* Provides MCP tools for credit/fiado management.
|
||||||
*
|
* Connected to FiadosService for real data.
|
||||||
* TODO: Connect to actual FiadosService when available.
|
|
||||||
*/
|
*/
|
||||||
export class FiadosToolsService implements McpToolProvider {
|
export class FiadosToolsService implements McpToolProvider {
|
||||||
|
private fiadosService: FiadosService;
|
||||||
|
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
this.fiadosService = new FiadosService(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
getTools(): McpToolDefinition[] {
|
getTools(): McpToolDefinition[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -54,7 +62,10 @@ export class FiadosToolsService implements McpToolProvider {
|
|||||||
properties: {
|
properties: {
|
||||||
customer_id: { type: 'string', format: 'uuid' },
|
customer_id: { type: 'string', format: 'uuid' },
|
||||||
amount: { type: 'number', minimum: 0.01 },
|
amount: { type: 'number', minimum: 0.01 },
|
||||||
payment_method: { type: 'string', enum: ['cash', 'card', 'transfer'] },
|
payment_method: { type: 'string', enum: ['cash', 'card', 'transfer', 'check', 'other'] },
|
||||||
|
fiado_id: { type: 'string', format: 'uuid' },
|
||||||
|
payment_reference: { type: 'string' },
|
||||||
|
notes: { type: 'string' },
|
||||||
},
|
},
|
||||||
required: ['customer_id', 'amount'],
|
required: ['customer_id', 'amount'],
|
||||||
},
|
},
|
||||||
@ -97,19 +108,35 @@ export class FiadosToolsService implements McpToolProvider {
|
|||||||
params: { customer_id: string },
|
params: { customer_id: string },
|
||||||
context: McpContext
|
context: McpContext
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// TODO: Connect to actual fiados service
|
const balance = await this.fiadosService.getFiadoBalance(
|
||||||
|
context.tenantId,
|
||||||
|
params.customer_id
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customer_id: params.customer_id,
|
customer_id: balance.customerId,
|
||||||
customer_name: 'Cliente ejemplo',
|
customer_name: balance.customerName,
|
||||||
balance: 1500.00,
|
balance: balance.currentBalance,
|
||||||
credit_limit: 5000.00,
|
credit_limit: balance.creditLimit,
|
||||||
available_credit: 3500.00,
|
available_credit: balance.availableCredit,
|
||||||
pending_fiados: [
|
overdue_amount: balance.overdueAmount,
|
||||||
{ id: 'fiado-1', amount: 500.00, date: '2026-01-10', status: 'pending' },
|
is_frozen: balance.isFrozen,
|
||||||
{ id: 'fiado-2', amount: 1000.00, date: '2026-01-05', status: 'pending' },
|
pending_fiados: balance.pendingFiados.map(f => ({
|
||||||
],
|
id: f.id,
|
||||||
recent_payments: [],
|
fiado_number: f.fiadoNumber,
|
||||||
message: 'Conectar a FiadosService real',
|
original_amount: f.originalAmount,
|
||||||
|
remaining_amount: f.remainingAmount,
|
||||||
|
due_at: f.dueAt,
|
||||||
|
status: f.status,
|
||||||
|
is_overdue: f.isOverdue,
|
||||||
|
})),
|
||||||
|
recent_payments: balance.recentPayments.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
payment_number: p.paymentNumber,
|
||||||
|
amount: p.amount,
|
||||||
|
payment_method: p.paymentMethod,
|
||||||
|
created_at: p.createdAt,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,48 +144,89 @@ export class FiadosToolsService implements McpToolProvider {
|
|||||||
params: { customer_id: string; amount: number; order_id?: string; description?: string },
|
params: { customer_id: string; amount: number; order_id?: string; description?: string },
|
||||||
context: McpContext
|
context: McpContext
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// TODO: Connect to actual fiados service
|
const dto: CreateFiadoDto = {
|
||||||
// First check eligibility
|
customerId: params.customer_id,
|
||||||
const eligibility = await this.checkFiadoEligibility(
|
amount: params.amount,
|
||||||
{ customer_id: params.customer_id, amount: params.amount },
|
orderId: params.order_id,
|
||||||
context
|
description: params.description,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fiado = await this.fiadosService.createFiado(
|
||||||
|
context.tenantId,
|
||||||
|
dto,
|
||||||
|
context.userId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!eligibility.eligible) {
|
// Get updated balance
|
||||||
throw new Error(eligibility.reason);
|
const balance = await this.fiadosService.getFiadoBalance(
|
||||||
}
|
context.tenantId,
|
||||||
|
params.customer_id
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fiado_id: 'fiado-' + Date.now(),
|
fiado_id: fiado.id,
|
||||||
customer_id: params.customer_id,
|
fiado_number: fiado.fiadoNumber,
|
||||||
amount: params.amount,
|
customer_id: fiado.customerId,
|
||||||
order_id: params.order_id,
|
amount: Number(fiado.originalAmount),
|
||||||
description: params.description,
|
order_id: fiado.orderId,
|
||||||
due_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days
|
description: fiado.description,
|
||||||
new_balance: 1500.00 + params.amount,
|
due_date: fiado.dueAt.toISOString(),
|
||||||
remaining_credit: 3500.00 - params.amount,
|
new_balance: balance.currentBalance,
|
||||||
created_by: context.userId,
|
remaining_credit: balance.availableCredit,
|
||||||
created_at: new Date().toISOString(),
|
created_by: fiado.createdBy,
|
||||||
message: 'Conectar a FiadosService real',
|
created_at: fiado.createdAt.toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async registerFiadoPayment(
|
private async registerFiadoPayment(
|
||||||
params: { customer_id: string; amount: number; payment_method?: string },
|
params: {
|
||||||
|
customer_id: string;
|
||||||
|
amount: number;
|
||||||
|
payment_method?: string;
|
||||||
|
fiado_id?: string;
|
||||||
|
payment_reference?: string;
|
||||||
|
notes?: string;
|
||||||
|
},
|
||||||
context: McpContext
|
context: McpContext
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// TODO: Connect to actual fiados service
|
const dto: CreatePaymentDto = {
|
||||||
return {
|
customerId: params.customer_id,
|
||||||
payment_id: 'payment-' + Date.now(),
|
|
||||||
customer_id: params.customer_id,
|
|
||||||
amount: params.amount,
|
amount: params.amount,
|
||||||
payment_method: params.payment_method || 'cash',
|
paymentMethod: (params.payment_method || 'cash') as PaymentMethod,
|
||||||
previous_balance: 1500.00,
|
fiadoId: params.fiado_id,
|
||||||
new_balance: 1500.00 - params.amount,
|
paymentReference: params.payment_reference,
|
||||||
fiados_paid: [],
|
notes: params.notes,
|
||||||
created_by: context.userId,
|
};
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
message: 'Conectar a FiadosService real',
|
const { payment, fiadosPaid } = await this.fiadosService.registerPayment(
|
||||||
|
context.tenantId,
|
||||||
|
dto,
|
||||||
|
context.userId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get updated balance
|
||||||
|
const balance = await this.fiadosService.getFiadoBalance(
|
||||||
|
context.tenantId,
|
||||||
|
params.customer_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
payment_id: payment.id,
|
||||||
|
payment_number: payment.paymentNumber,
|
||||||
|
customer_id: payment.customerId,
|
||||||
|
amount: Number(payment.amount),
|
||||||
|
payment_method: payment.paymentMethod,
|
||||||
|
payment_reference: payment.paymentReference,
|
||||||
|
previous_balance: balance.currentBalance + params.amount,
|
||||||
|
new_balance: balance.currentBalance,
|
||||||
|
fiados_paid: fiadosPaid.map(f => ({
|
||||||
|
fiado_id: f.id,
|
||||||
|
fiado_number: f.fiadoNumber,
|
||||||
|
status: f.status,
|
||||||
|
remaining_amount: Number(f.remainingAmount),
|
||||||
|
})),
|
||||||
|
created_by: payment.receivedBy,
|
||||||
|
created_at: payment.createdAt.toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,51 +234,21 @@ export class FiadosToolsService implements McpToolProvider {
|
|||||||
params: { customer_id: string; amount: number },
|
params: { customer_id: string; amount: number },
|
||||||
context: McpContext
|
context: McpContext
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// TODO: Connect to actual fiados service
|
const eligibility = await this.fiadosService.checkEligibility(
|
||||||
const mockBalance = 1500.00;
|
context.tenantId,
|
||||||
const mockCreditLimit = 5000.00;
|
params.customer_id,
|
||||||
const mockAvailableCredit = mockCreditLimit - mockBalance;
|
params.amount
|
||||||
const hasOverdue = false;
|
);
|
||||||
|
|
||||||
if (hasOverdue) {
|
|
||||||
return {
|
|
||||||
eligible: false,
|
|
||||||
reason: 'Cliente tiene saldo vencido',
|
|
||||||
current_balance: mockBalance,
|
|
||||||
credit_limit: mockCreditLimit,
|
|
||||||
available_credit: mockAvailableCredit,
|
|
||||||
requested_amount: params.amount,
|
|
||||||
has_overdue: true,
|
|
||||||
suggestions: ['Solicitar pago del saldo vencido antes de continuar'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.amount > mockAvailableCredit) {
|
|
||||||
return {
|
|
||||||
eligible: false,
|
|
||||||
reason: 'Monto excede credito disponible',
|
|
||||||
current_balance: mockBalance,
|
|
||||||
credit_limit: mockCreditLimit,
|
|
||||||
available_credit: mockAvailableCredit,
|
|
||||||
requested_amount: params.amount,
|
|
||||||
has_overdue: false,
|
|
||||||
suggestions: [
|
|
||||||
`Reducir el monto a $${mockAvailableCredit.toFixed(2)}`,
|
|
||||||
'Solicitar aumento de limite de credito',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
eligible: true,
|
eligible: eligibility.eligible,
|
||||||
reason: 'Cliente con credito disponible',
|
reason: eligibility.reason,
|
||||||
current_balance: mockBalance,
|
current_balance: eligibility.currentBalance,
|
||||||
credit_limit: mockCreditLimit,
|
credit_limit: eligibility.creditLimit,
|
||||||
available_credit: mockAvailableCredit,
|
available_credit: eligibility.availableCredit,
|
||||||
requested_amount: params.amount,
|
requested_amount: eligibility.requestedAmount,
|
||||||
has_overdue: false,
|
has_overdue: eligibility.hasOverdue,
|
||||||
suggestions: [],
|
suggestions: eligibility.suggestions,
|
||||||
message: 'Conectar a FiadosService real',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user