Tul xxx Tul
User / IP
:
216.73.216.183
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
siscaps
/
models
/
Viewing: Invoice.php
<?php class Invoice { private static bool $schemaEnsured = false; public int $id; public int $customer_id; public string $period; // YYYY-MM public float $amount; public string $status; // pending/paid public string $category; private static function ensureSchema(?PDO $pdo = null): void { if (self::$schemaEnsured) { return; } self::$schemaEnsured = true; $pdo = $pdo ?: (new Database())->getConnection(); try { $cols = $pdo->query("SHOW COLUMNS FROM invoices")->fetchAll(PDO::FETCH_COLUMN, 0); if (!in_array('late_fee_amount', $cols, true)) { $pdo->exec("ALTER TABLE invoices ADD COLUMN late_fee_amount DECIMAL(14,2) NOT NULL DEFAULT 0.00 AFTER tax"); } } catch (Throwable $e) { // noop } } public static function getInvoiceDueDays(): int { $data = class_exists('SystemData') ? SystemData::get() : []; $days = isset($data['invoice_due_days']) ? (int)$data['invoice_due_days'] : 10; return max(0, $days); } public static function getLateFeeDailyAmount(): float { $data = class_exists('SystemData') ? SystemData::get() : []; $amt = isset($data['late_fee_daily_amount']) ? (float)$data['late_fee_daily_amount'] : 0.0; return max(0.0, $amt); } public static function getLateFeeMethod(): string { $data = class_exists('SystemData') ? SystemData::get() : []; $method = isset($data['late_fee_method']) ? strtolower((string)$data['late_fee_method']) : 'days'; return in_array($method, ['days', 'percentage'], true) ? $method : 'days'; } public static function getLateFeePercentage(): float { $data = class_exists('SystemData') ? SystemData::get() : []; $pct = isset($data['late_fee_percentage']) ? (float)$data['late_fee_percentage'] : 0.0; $pct = max(0.0, $pct); return min(100.0, $pct); } public static function calculateDueDate(string $issueDate): string { $days = self::getInvoiceDueDays(); $ts = strtotime($issueDate); if ($ts === false) { $ts = time(); } return date('Y-m-d', strtotime('+' . $days . ' days', $ts)); } public static function refreshLateFeeForInvoice(int $invoiceId, ?PDO $pdo = null): void { if ($invoiceId <= 0) { return; } $method = self::getLateFeeMethod(); $daily = self::getLateFeeDailyAmount(); $pct = self::getLateFeePercentage(); $pdo = $pdo ?: (new Database())->getConnection(); self::ensureSchema($pdo); if ($method === 'percentage') { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE ROUND((subtotal + tax) * (? / 100), 2) END"; $bind = [$pct, $pct, $pct, $pct, $invoiceId]; } else { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE GREATEST(DATEDIFF(CURDATE(), due_date),0) * ? END"; $bind = [$daily, $daily, $daily, $daily, $invoiceId]; } $sql = "UPDATE invoices SET late_fee_amount = $feeExpr, total = (subtotal + tax + ($feeExpr)) WHERE id = ? AND status IN ('Pendiente','Parcial','Vencida')"; $stmt = $pdo->prepare($sql); $stmt->execute($bind); } public static function refreshLateFeesForCustomer(int $customer_id, ?PDO $pdo = null): void { if ($customer_id <= 0) { return; } $method = self::getLateFeeMethod(); $daily = self::getLateFeeDailyAmount(); $pct = self::getLateFeePercentage(); $pdo = $pdo ?: (new Database())->getConnection(); self::ensureSchema($pdo); if ($method === 'percentage') { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE ROUND((subtotal + tax) * (? / 100), 2) END"; $bind = [$pct, $pct, $pct, $pct, $customer_id]; } else { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE GREATEST(DATEDIFF(CURDATE(), due_date),0) * ? END"; $bind = [$daily, $daily, $daily, $daily, $customer_id]; } $sql = "UPDATE invoices SET late_fee_amount = $feeExpr, total = (subtotal + tax + ($feeExpr)) WHERE customer_id = ? AND status IN ('Pendiente','Parcial','Vencida')"; $stmt = $pdo->prepare($sql); $stmt->execute($bind); } public static function refreshLateFeesForOpenInvoices(?PDO $pdo = null): void { $method = self::getLateFeeMethod(); $daily = self::getLateFeeDailyAmount(); $pct = self::getLateFeePercentage(); $pdo = $pdo ?: (new Database())->getConnection(); self::ensureSchema($pdo); if ($method === 'percentage') { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE ROUND((subtotal + tax) * (? / 100), 2) END"; $bind = [$pct, $pct, $pct, $pct]; } else { $feeExpr = "CASE WHEN ? <= 0 OR due_date >= CURDATE() THEN 0 ELSE GREATEST(DATEDIFF(CURDATE(), due_date),0) * ? END"; $bind = [$daily, $daily, $daily, $daily]; } $sql = "UPDATE invoices SET late_fee_amount = $feeExpr, total = (subtotal + tax + ($feeExpr)) WHERE status IN ('Pendiente','Parcial','Vencida')"; $stmt = $pdo->prepare($sql); $stmt->execute($bind); } /** * Mark invoices as overdue when due_date has passed and status is Pending or Partial. */ public static function updateOverdueStatuses(): void { $pdo = (new Database())->getConnection(); self::ensureSchema($pdo); $pdo->prepare("UPDATE invoices SET status = 'Vencida' WHERE due_date < CURDATE() AND status IN ('Pendiente','Parcial')") ->execute(); } /** * Create manual invoice (non-service categories) with simplified data. */ public static function createManual(array $data, array $lines): int { $pdo = (new Database())->getConnection(); $pdo->beginTransaction(); try { $issueDate = $data['issue_date']; $invoiceNumber = self::generateInvoiceNumber($pdo, $issueDate); $ins = $pdo->prepare( "INSERT INTO invoices (invoice_number, customer_id, period_start, period_end, issue_date, due_date, consumption_m3, tariff_id, category, subtotal, tax, total, status, notes) VALUES (:invoice_number, :customer_id, NULL, NULL, :issue_date, :due_date, 0, NULL, :category, :subtotal, :tax, :total, :status, :notes)" ); $ins->execute([ ':invoice_number' => $invoiceNumber, ':customer_id' => (int)$data['customer_id'], ':issue_date' => $issueDate, ':due_date' => $data['due_date'], ':category' => $data['category'], ':subtotal' => (float)$data['subtotal'], ':tax' => (float)$data['tax'], ':total' => (float)$data['total'], ':status' => $data['status'] ?? 'Pendiente', ':notes' => $data['notes'] ?? null, ]); $invoiceId = (int)$pdo->lastInsertId(); $lineIns = $pdo->prepare("INSERT INTO invoice_lines (invoice_id, description, quantity, unit_price, type) VALUES (:invoice_id, :description, :quantity, :unit_price, :type)"); foreach ($lines as $ln) { $lineIns->execute([ ':invoice_id' => $invoiceId, ':description' => $ln['description'] ?? 'Cargo', ':quantity' => (float)$ln['quantity'], ':unit_price' => (float)$ln['unit_price'], ':type' => self::mapCategoryToLineType($ln['type'] ?? 'Cargo'), ]); } $pdo->commit(); return $invoiceId; } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } /** * Update manual invoice data and its single line. */ public static function updateManual(int $id, array $data, array $line): bool { $pdo = (new Database())->getConnection(); $pdo->beginTransaction(); try { $upd = $pdo->prepare( "UPDATE invoices SET customer_id = :customer_id, issue_date = :issue_date, due_date = :due_date, status = :status, notes = :notes, subtotal = :subtotal, tax = :tax, total = :total, category = :category, consumption_m3 = 0, tariff_id = NULL, period_start = NULL, period_end = NULL WHERE id = :id" ); $upd->execute([ ':customer_id' => (int)$data['customer_id'], ':issue_date' => $data['issue_date'], ':due_date' => $data['due_date'], ':status' => $data['status'] ?? 'Pendiente', ':notes' => $data['notes'] ?? null, ':subtotal' => (float)$data['subtotal'], ':tax' => (float)$data['tax'], ':total' => (float)$data['total'], ':category' => $data['category'], ':id' => $id, ]); // Remove existing lines and reinsert a single consolidated line $pdo->prepare('DELETE FROM invoice_lines WHERE invoice_id = :id')->execute([':id' => $id]); $ins = $pdo->prepare("INSERT INTO invoice_lines (invoice_id, description, quantity, unit_price, type) VALUES (:invoice_id, :description, :quantity, :unit_price, :type)"); $ins->execute([ ':invoice_id' => $id, ':description' => $line['description'] ?? 'Cargo', ':quantity' => (float)$line['quantity'], ':unit_price' => (float)$line['unit_price'], ':type' => self::mapCategoryToLineType($line['type'] ?? 'Cargo'), ]); $pdo->commit(); return true; } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } /** * Get paginated list of invoices with filters. * Filters: q (invoice_number/customer), customer_id, status, period_start, period_end, issue_from, issue_to */ public static function getAll(array $filters = [], int $page = 1, int $perPage = 15): array { self::updateOverdueStatuses(); self::refreshLateFeesForOpenInvoices(); $pdo = (new Database())->getConnection(); $where = []; $params = []; if (!empty($filters['q'])) { $where[] = "(i.invoice_number LIKE :q1 OR c.name LIKE :q2 OR c.customer_code LIKE :q3)"; $params[':q1'] = '%' . $filters['q'] . '%'; $params[':q2'] = '%' . $filters['q'] . '%'; $params[':q3'] = '%' . $filters['q'] . '%'; } if (!empty($filters['customer_id'])) { $where[] = 'i.customer_id = :customer_id'; $params[':customer_id'] = (int)$filters['customer_id']; } if (!empty($filters['status'])) { $where[] = 'i.status = :status'; $params[':status'] = $filters['status']; } if (!empty($filters['period_start'])) { $where[] = 'i.period_start >= :period_start'; $params[':period_start'] = $filters['period_start']; } if (!empty($filters['period_end'])) { $where[] = 'i.period_end <= :period_end'; $params[':period_end'] = $filters['period_end']; } if (!empty($filters['issue_from'])) { $where[] = 'i.issue_date >= :issue_from'; $params[':issue_from'] = $filters['issue_from']; } if (!empty($filters['issue_to'])) { $where[] = 'i.issue_date <= :issue_to'; $params[':issue_to'] = $filters['issue_to']; } if (!empty($filters['category'])) { $where[] = 'i.category = :category'; $params[':category'] = $filters['category']; } $whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : ''; // Count $stmt = $pdo->prepare("SELECT COUNT(*) FROM invoices i JOIN customers c ON c.id = i.customer_id $whereSql"); $stmt->execute($params); $total = (int)$stmt->fetchColumn(); $lastPage = max(1, (int)ceil($total / $perPage)); $page = max(1, min($page, $lastPage)); $offset = ($page - 1) * $perPage; // Data $sql = "SELECT i.*, c.name AS customer_name FROM invoices i JOIN customers c ON c.id = i.customer_id $whereSql ORDER BY i.issue_date DESC, i.id DESC LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); foreach ($params as $k => $v) { $stmt->bindValue($k, $v); } $stmt->bindValue(':limit', (int)$perPage, PDO::PARAM_INT); $stmt->bindValue(':offset', (int)$offset, PDO::PARAM_INT); $stmt->execute(); $items = $stmt->fetchAll(PDO::FETCH_ASSOC); return [ 'items' => $items, 'total' => $total, 'page' => $page, 'perPage' => $perPage, 'lastPage' => $lastPage, ]; } /** * Find invoice with customer and lines. */ public static function findById(int $id): ?array { self::refreshLateFeeForInvoice($id); $pdo = (new Database())->getConnection(); $stmt = $pdo->prepare("SELECT i.*, c.name AS customer_name, c.address AS customer_address FROM invoices i JOIN customers c ON c.id = i.customer_id WHERE i.id = :id LIMIT 1"); $stmt->execute([':id' => $id]); $invoice = $stmt->fetch(PDO::FETCH_ASSOC); if (!$invoice) return null; $stmt = $pdo->prepare("SELECT id, description, quantity, unit_price, line_total, type FROM invoice_lines WHERE invoice_id = :id ORDER BY id ASC"); $stmt->execute([':id' => $id]); $invoice['lines'] = $stmt->fetchAll(PDO::FETCH_ASSOC); return $invoice; } /** * Create invoice and associated lines. Returns new invoice ID. * $data keys: customer_id, period_start, period_end, issue_date, due_date, consumption_m3, tariff_id, subtotal, tax, total, status, notes * $lines: [ [description, quantity, unit_price, type] ... ] */ public static function create(array $data, array $lines): int { $pdo = (new Database())->getConnection(); $pdo->beginTransaction(); try { $invoiceNumber = self::generateInvoiceNumber($pdo, $data['issue_date']); $ins = $pdo->prepare( "INSERT INTO invoices (invoice_number, customer_id, period_start, period_end, issue_date, due_date, consumption_m3, tariff_id, category, subtotal, tax, total, status, notes) VALUES (:invoice_number, :customer_id, :period_start, :period_end, :issue_date, :due_date, :consumption_m3, :tariff_id, :category, :subtotal, :tax, :total, :status, :notes)" ); $ins->execute([ ':invoice_number' => $invoiceNumber, ':customer_id' => (int)$data['customer_id'], ':period_start' => $data['period_start'] ?? null, ':period_end' => $data['period_end'] ?? null, ':issue_date' => $data['issue_date'], ':due_date' => $data['due_date'], ':consumption_m3' => (float)$data['consumption_m3'], ':tariff_id' => $data['tariff_id'] ?? null, ':category' => $data['category'] ?? 'Servicio', ':subtotal' => (float)$data['subtotal'], ':tax' => (float)($data['tax'] ?? 0.0), ':total' => (float)$data['total'], ':status' => $data['status'] ?? 'Pendiente', ':notes' => $data['notes'] ?? null, ]); $invoiceId = (int)$pdo->lastInsertId(); $lineIns = $pdo->prepare("INSERT INTO invoice_lines (invoice_id, description, quantity, unit_price, type) VALUES (:invoice_id, :description, :quantity, :unit_price, :type)"); foreach ($lines as $ln) { $lineIns->execute([ ':invoice_id' => $invoiceId, ':description' => $ln['description'] ?? 'Consumo', ':quantity' => (float)$ln['quantity'], ':unit_price' => (float)$ln['unit_price'], ':type' => $ln['type'] ?? 'Consumo', ]); } $pdo->commit(); return $invoiceId; } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); throw $e; } } public static function updateStatus(int $id, string $status): bool { $pdo = (new Database())->getConnection(); $stmt = $pdo->prepare("UPDATE invoices SET status = :status WHERE id = :id"); return $stmt->execute([':status' => $status, ':id' => $id]); } /** * Delete an invoice and its related records (payments, invoice_lines) in a transaction. */ public static function delete(int $id): bool { $pdo = (new Database())->getConnection(); $pdo->beginTransaction(); try { // eliminar pagos relacionados si existen $pdo->prepare('DELETE FROM payments WHERE invoice_id = :id')->execute([':id' => $id]); // eliminar líneas $pdo->prepare('DELETE FROM invoice_lines WHERE invoice_id = :id')->execute([':id' => $id]); // eliminar factura $stmt = $pdo->prepare('DELETE FROM invoices WHERE id = :id'); $stmt->execute([':id' => $id]); $affected = $stmt->rowCount(); $pdo->commit(); return $affected > 0; } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } /** * Calculate amount based on tariff ranges. Returns ['lines'=>[], 'subtotal'=>x, 'tax'=>0, 'total'=>x] */ public static function calculateAmount(float $consumption_m3, int $tariff_id): array { $pdo = (new Database())->getConnection(); $stmt = $pdo->prepare("SELECT min_m3, max_m3, price_per_m3 FROM tariff_ranges WHERE tariff_id = :t ORDER BY min_m3 ASC"); $stmt->execute([':t' => $tariff_id]); $ranges = $stmt->fetchAll(PDO::FETCH_ASSOC); if (!$ranges) { throw new RuntimeException('La tarifa seleccionada no tiene rangos configurados.'); } $remaining = max(0.0, (float)$consumption_m3); $lines = []; $subtotal = 0.0; $prevCut = 0.0; foreach ($ranges as $r) { $min = (float)$r['min_m3']; $max = $r['max_m3'] !== null ? (float)$r['max_m3'] : null; $price = (float)$r['price_per_m3']; $lower = max($min, $prevCut); $upper = $max === null ? $consumption_m3 : min($consumption_m3, $max); $used = max(0.0, $upper - $lower); if ($used > 0) { $lineTotal = $used * $price; $subtotal += $lineTotal; $lines[] = [ 'description' => 'Consumo ' . rtrim(rtrim(number_format($lower, 3, '.', ''), '0'), '.') . ' - ' . ($max === null ? '+' : rtrim(rtrim(number_format($upper, 3, '.', ''), '0'), '.')) . ' m³', 'quantity' => $used, 'unit_price' => $price, 'type' => 'Consumo', ]; } $prevCut = $max ?? $consumption_m3; // stop after open-ended range } $tax = 0.0; // Impuestos no definidos en el esquema; mantener en 0 por defecto $total = $subtotal + $tax; return ['lines' => $lines, 'subtotal' => $subtotal, 'tax' => $tax, 'total' => $total]; } /** * Get active tariff id at a given date (latest effective_from <= date and status='Activa'). */ public static function getActiveTariffIdForDate(string $date): ?int { $pdo = (new Database())->getConnection(); $stmt = $pdo->prepare("SELECT id FROM tariffs WHERE status='Activa' AND effective_from <= :d ORDER BY effective_from DESC LIMIT 1"); $stmt->execute([':d' => $date]); $id = $stmt->fetchColumn(); return $id ? (int)$id : null; } /** * Customers that have open invoices (Pendiente/Parcial/Vencida) with a positive balance. * Returns: [id, name, customer_code, open_invoices, balance] */ public static function customersWithOpenBalance(): array { self::refreshLateFeesForOpenInvoices(); $pdo = (new Database())->getConnection(); $sql = "SELECT c.id, c.name, c.customer_code, COUNT(*) AS open_invoices, SUM(GREATEST(i.total - ( SELECT COALESCE(SUM(p.amount),0) FROM payments p WHERE p.invoice_id = i.id ), 0)) AS balance FROM invoices i JOIN customers c ON c.id = i.customer_id WHERE i.status IN ('Pendiente','Parcial','Vencida') GROUP BY c.id, c.name, c.customer_code HAVING SUM(GREATEST(i.total - ( SELECT COALESCE(SUM(p.amount),0) FROM payments p WHERE p.invoice_id = i.id ), 0)) > 0 ORDER BY c.name"; $stmt = $pdo->query($sql); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // Normalizar tipos foreach ($rows as &$r) { $r['id'] = (int)$r['id']; $r['open_invoices'] = (int)$r['open_invoices']; $r['balance'] = (float)$r['balance']; } return $rows; } /** * Count open invoices (statuses considered: Pendiente, Parcial, Vencida) for a customer. */ public static function countOpenInvoicesByCustomer(int $customer_id): int { if ($customer_id <= 0) return 0; self::refreshLateFeesForCustomer($customer_id); $pdo = (new Database())->getConnection(); $statuses = defined('SUSPENSION_OPEN_STATUSES') ? (array)SUSPENSION_OPEN_STATUSES : ['Pendiente','Parcial','Vencida']; $in = implode(',', array_fill(0, count($statuses), '?')); $sql = "SELECT COUNT(*) FROM invoices WHERE customer_id = ? AND status IN ($in)"; $stmt = $pdo->prepare($sql); $params = array_merge([$customer_id], $statuses); $stmt->execute($params); return (int)$stmt->fetchColumn(); } /** * Calculate open balance (sum of outstanding amounts) for a customer across open statuses. */ public static function openBalanceByCustomer(int $customer_id): float { if ($customer_id <= 0) return 0.0; self::refreshLateFeesForCustomer($customer_id); $pdo = (new Database())->getConnection(); $statuses = defined('SUSPENSION_OPEN_STATUSES') ? (array)SUSPENSION_OPEN_STATUSES : ['Pendiente','Parcial','Vencida']; $in = implode(',', array_fill(0, count($statuses), '?')); $sql = "SELECT COALESCE(SUM(GREATEST(i.total - ( SELECT COALESCE(SUM(p.amount),0) FROM payments p WHERE p.invoice_id = i.id ), 0)), 0) FROM invoices i WHERE i.customer_id = ? AND i.status IN ($in)"; $stmt = $pdo->prepare($sql); $params = array_merge([$customer_id], $statuses); $stmt->execute($params); return (float)$stmt->fetchColumn(); } private static function generateInvoiceNumber(PDO $pdo, string $issueDate): string { // Simple sequential per month: INV-YYYYMM-XXXXX $prefix = 'INV-' . date('Ym', strtotime($issueDate)) . '-'; $stmt = $pdo->prepare("SELECT COUNT(*) FROM invoices WHERE DATE_FORMAT(issue_date, '%Y%m') = :ym"); $stmt->execute([':ym' => date('Ym', strtotime($issueDate))]); $seq = (int)$stmt->fetchColumn() + 1; return $prefix . str_pad((string)$seq, 5, '0', STR_PAD_LEFT); } private static function mapCategoryToLineType(string $category): string { $map = [ 'Inscripcion' => 'Cargo', 'Reconexion' => 'Reconexión', 'Multa' => 'Multa', 'Servicio' => 'Consumo', ]; return $map[$category] ?? 'Cargo'; } }
Coded With 💗 by
0x6ick