erp-construccion-backend-v2/src/modules/branches/services/branch-payment-terminal.service.ts
Adrian Flores Cortes 8f8843cd10 [ERP-CONSTRUCCION] feat: Implement 6 core business modules
Branches:
- 5 services: branch, schedule, inventory-settings, payment-terminal, user-assignment
- Hierarchical management, schedules, terminals

Products:
- 6 services: category, product, price, supplier, attribute, variant
- Hierarchical categories, multi-pricing, variants

Projects:
- 6 services: project, task, timesheet, milestone, member, stage
- Kanban support, timesheet approval workflow

Sales:
- 2 services: quotation, sales-order
- Full sales workflow with quotation-to-order conversion

Invoices:
- 2 services: invoice, payment
- Complete invoicing with payment allocation

Notifications:
- 5 services: notification, preference, template, channel, in-app
- Multi-channel support (email, sms, push, whatsapp, in-app)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 00:24:00 -06:00

241 lines
6.5 KiB
TypeScript

/**
* BranchPaymentTerminal Service
* Servicio para gestión de terminales de pago por sucursal
*
* @module Branches
*/
import { Repository, FindOptionsWhere } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import {
BranchPaymentTerminal,
TerminalProvider,
HealthStatus,
} from '../entities/branch-payment-terminal.entity';
import { ServiceContext } from './branch.service';
export interface CreateBranchPaymentTerminalDto {
branchId: string;
terminalId: string;
terminalName?: string;
terminalProvider: TerminalProvider;
provider: string;
credentials?: Record<string, any>;
isActive?: boolean;
isPrimary?: boolean;
dailyLimit?: number;
transactionLimit?: number;
config?: Record<string, any>;
}
export interface UpdateBranchPaymentTerminalDto {
terminalName?: string;
terminalProvider?: TerminalProvider;
provider?: string;
credentials?: Record<string, any>;
isActive?: boolean;
isPrimary?: boolean;
healthStatus?: HealthStatus;
dailyLimit?: number;
transactionLimit?: number;
config?: Record<string, any>;
}
export interface BranchPaymentTerminalFilters {
branchId?: string;
terminalProvider?: TerminalProvider;
isActive?: boolean;
healthStatus?: HealthStatus;
}
export class BranchPaymentTerminalService {
private repository: Repository<BranchPaymentTerminal>;
constructor() {
this.repository = AppDataSource.getRepository(BranchPaymentTerminal);
}
async findAll(
ctx: ServiceContext,
filters?: BranchPaymentTerminalFilters
): Promise<BranchPaymentTerminal[]> {
const where: FindOptionsWhere<BranchPaymentTerminal> = {
tenantId: ctx.tenantId,
};
if (filters?.branchId) {
where.branchId = filters.branchId;
}
if (filters?.terminalProvider) {
where.terminalProvider = filters.terminalProvider;
}
if (filters?.isActive !== undefined) {
where.isActive = filters.isActive;
}
if (filters?.healthStatus) {
where.healthStatus = filters.healthStatus;
}
return this.repository.find({
where,
order: { isPrimary: 'DESC', terminalName: 'ASC' },
});
}
async findById(ctx: ServiceContext, id: string): Promise<BranchPaymentTerminal | null> {
return this.repository.findOne({
where: { id, tenantId: ctx.tenantId },
});
}
async findByBranch(ctx: ServiceContext, branchId: string): Promise<BranchPaymentTerminal[]> {
return this.repository.find({
where: { branchId, tenantId: ctx.tenantId, isActive: true },
order: { isPrimary: 'DESC', terminalName: 'ASC' },
});
}
async findPrimaryTerminal(
ctx: ServiceContext,
branchId: string
): Promise<BranchPaymentTerminal | null> {
return this.repository.findOne({
where: { branchId, tenantId: ctx.tenantId, isPrimary: true, isActive: true },
});
}
async findByTerminalId(
ctx: ServiceContext,
terminalId: string
): Promise<BranchPaymentTerminal | null> {
return this.repository.findOne({
where: { terminalId, tenantId: ctx.tenantId },
});
}
async create(
ctx: ServiceContext,
data: CreateBranchPaymentTerminalDto
): Promise<BranchPaymentTerminal> {
// If this is the first terminal or marked as primary, ensure only one primary
if (data.isPrimary) {
await this.repository.update(
{ branchId: data.branchId, tenantId: ctx.tenantId, isPrimary: true },
{ isPrimary: false }
);
}
const terminal = this.repository.create({
...data,
tenantId: ctx.tenantId,
healthStatus: 'unknown',
});
return this.repository.save(terminal);
}
async update(
ctx: ServiceContext,
id: string,
data: UpdateBranchPaymentTerminalDto
): Promise<BranchPaymentTerminal | null> {
const terminal = await this.findById(ctx, id);
if (!terminal) {
return null;
}
// If setting as primary, unset other primaries for this branch
if (data.isPrimary && !terminal.isPrimary) {
await this.repository.update(
{ branchId: terminal.branchId, tenantId: ctx.tenantId, isPrimary: true },
{ isPrimary: false }
);
}
Object.assign(terminal, data);
return this.repository.save(terminal);
}
async delete(ctx: ServiceContext, id: string): Promise<boolean> {
const result = await this.repository.delete({ id, tenantId: ctx.tenantId });
return result.affected ? result.affected > 0 : false;
}
async setAsPrimary(ctx: ServiceContext, id: string): Promise<BranchPaymentTerminal | null> {
const terminal = await this.findById(ctx, id);
if (!terminal) {
return null;
}
// Unset other primaries
await this.repository.update(
{ branchId: terminal.branchId, tenantId: ctx.tenantId, isPrimary: true },
{ isPrimary: false }
);
terminal.isPrimary = true;
return this.repository.save(terminal);
}
async updateHealthStatus(
ctx: ServiceContext,
id: string,
healthStatus: HealthStatus
): Promise<BranchPaymentTerminal | null> {
const terminal = await this.findById(ctx, id);
if (!terminal) {
return null;
}
terminal.healthStatus = healthStatus;
terminal.lastHealthCheckAt = new Date();
return this.repository.save(terminal);
}
async recordTransaction(ctx: ServiceContext, id: string): Promise<BranchPaymentTerminal | null> {
const terminal = await this.findById(ctx, id);
if (!terminal) {
return null;
}
terminal.lastTransactionAt = new Date();
return this.repository.save(terminal);
}
async getHealthSummary(ctx: ServiceContext): Promise<{
total: number;
healthy: number;
degraded: number;
offline: number;
unknown: number;
}> {
const terminals = await this.repository.find({
where: { tenantId: ctx.tenantId, isActive: true },
});
return {
total: terminals.length,
healthy: terminals.filter(t => t.healthStatus === 'healthy').length,
degraded: terminals.filter(t => t.healthStatus === 'degraded').length,
offline: terminals.filter(t => t.healthStatus === 'offline').length,
unknown: terminals.filter(t => t.healthStatus === 'unknown').length,
};
}
async getTerminalsNeedingHealthCheck(
ctx: ServiceContext,
maxAgeMinutes: number = 30
): Promise<BranchPaymentTerminal[]> {
const cutoff = new Date(Date.now() - maxAgeMinutes * 60 * 1000);
const terminals = await this.repository.find({
where: { tenantId: ctx.tenantId, isActive: true },
});
return terminals.filter(t => !t.lastHealthCheckAt || t.lastHealthCheckAt < cutoff);
}
}