db = $db; $this->entity = $entity; } /** * Fetch declaration by ID * * @param int $id Declaration ID * @return int 1 if found, 0 if not found, -1 if error */ public function fetch($id) { $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if ($result) { $obj = $this->db->fetch_object($result); if ($obj) { $this->rowid = $obj->rowid; $this->period_id = $obj->period_id; $this->declaration_number = $obj->declaration_number; $this->declaration_name = $obj->declaration_name; $this->start_date = $obj->start_date; $this->end_date = $obj->end_date; $this->status = $obj->status; $this->created_date = $obj->created_date; return 1; } } return 0; } /** * Create a new declaration for a period * * @param int $period_id Period ID * @return int Declaration ID or -1 if error */ public function createDeclaration($period_id) { global $user; $this->db->begin(); // Get period information $period = $this->getPeriod($period_id); if (!$period) { $this->error = "Period not found"; $this->db->rollback(); return -1; } // Generate declaration number $declaration_number = $this->generateDeclarationNumber($period); // Create declaration record $sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_declarations (entity, period_id, declaration_number, status, created_date) VALUES (" . $this->entity . ", " . $period_id . ", '" . $declaration_number . "', 'draft', NOW())"; $result = $this->db->query($sql); if (!$result) { $this->error = "Error creating declaration: " . $this->db->lasterror(); $this->db->rollback(); return -1; } $declaration_id = $this->db->last_insert_id(MAIN_DB_PREFIX . "declarationtva_declarations"); // Calculate CA-3 amounts $this->calculateCA3Amounts($declaration_id, $period); $this->db->commit(); return $declaration_id; } /** * Create a new declaration with specific dates * * @param string $start_date Start date (YYYY-MM-DD) * @param string $end_date End date (YYYY-MM-DD) * @param string $declaration_name Declaration name * @return int Declaration ID or -1 if error */ public function createDeclarationWithDates($start_date, $end_date, $declaration_name = '') { global $user; $this->db->begin(); // Generate declaration number $declaration_number = $this->generateDeclarationNumberFromDates($start_date, $end_date); // Create declaration record $sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_declarations (entity, period_id, declaration_number, declaration_name, start_date, end_date, status, created_date) VALUES (" . $this->entity . ", 0, '" . $declaration_number . "', '" . $this->db->escape($declaration_name) . "', '" . $start_date . "', '" . $end_date . "', 'draft', NOW())"; $result = $this->db->query($sql); if (!$result) { $this->error = "Error creating declaration: " . $this->db->lasterror(); $this->db->rollback(); return -1; } $declaration_id = $this->db->last_insert_id(MAIN_DB_PREFIX . "declarationtva_declarations"); // Calculate CA-3 amounts $period = array('start_date' => $start_date, 'end_date' => $end_date); $this->calculateCA3Amounts($declaration_id, $period); $this->db->commit(); return $declaration_id; } /** * Calculate CA-3 amounts for a declaration * * @param int $declaration_id Declaration ID * @param array $period Period information * @return bool Success */ public function calculateCA3Amounts($declaration_id, $period) { // Get account mappings grouped by CA-3 line $mappings = $this->getAccountMappings(); // Group mappings by CA-3 line $grouped_mappings = array(); foreach ($mappings as $mapping) { $ca3_line = $mapping['ca3_line']; if (!isset($grouped_mappings[$ca3_line])) { $grouped_mappings[$ca3_line] = array(); } $grouped_mappings[$ca3_line][] = $mapping; } // Define all CA-3 lines that should be processed $all_ca3_lines = array('A1', 'A2', 'A3', 'A4', 'A5', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'F1', 'F2', 'F6', 'F7', 'F8', '08', '09', '9B', '17', '18', '19', '20', '21', '22', '25', '26', '28', '29'); $total_vat_collected = 0; $total_vat_deductible = 0; // Process ALL CA-3 lines (even those without mappings) foreach ($all_ca3_lines as $ca3_line) { $line_total_base = 0; $line_total_vat = 0; $line_total_amount = 0; $account_labels = array(); // Special handling for lines 08, 09, 9B (need separate base and VAT accounts) if (in_array($ca3_line, array('08', '09', '9B'))) { // Get base accounts (sales) $base_mappings = isset($grouped_mappings[$ca3_line . '_BASE']) ? $grouped_mappings[$ca3_line . '_BASE'] : array(); foreach ($base_mappings as $mapping) { $amounts = $this->getAccountAmounts($mapping['account_code'], $period['start_date'], $period['end_date']); $line_total_base += $amounts['base_amount']; $account_labels[] = $mapping['account_label'] . ' (base)'; } // Get VAT accounts $vat_mappings = isset($grouped_mappings[$ca3_line . '_VAT']) ? $grouped_mappings[$ca3_line . '_VAT'] : array(); foreach ($vat_mappings as $mapping) { $amounts = $this->getAccountAmounts($mapping['account_code'], $period['start_date'], $period['end_date']); $line_total_vat += $amounts['vat_amount']; $account_labels[] = $mapping['account_label'] . ' (VAT)'; } $line_total_amount = $line_total_base + $line_total_vat; } else { // Normal processing for other lines if (isset($grouped_mappings[$ca3_line])) { foreach ($grouped_mappings[$ca3_line] as $mapping) { $amounts = $this->getAccountAmounts($mapping['account_code'], $period['start_date'], $period['end_date']); $line_total_base += $amounts['base_amount']; $line_total_vat += $amounts['vat_amount']; $line_total_amount += $amounts['total_amount']; $account_labels[] = $mapping['account_label']; } } } // Create CA-3 line record with summed amounts (even if zero) $combined_amounts = array( 'base_amount' => $line_total_base, 'vat_amount' => $line_total_vat, 'total_amount' => $line_total_amount ); $combined_label = !empty($account_labels) ? implode(', ', $account_labels) : 'No accounts mapped'; // Truncate label if too long to prevent database issues (keep some buffer) if (strlen($combined_label) > 1000) { $combined_label = substr($combined_label, 0, 997) . '...'; } $this->createCA3Line($declaration_id, $ca3_line, $combined_label, $combined_amounts); // Update totals - only VAT amounts from sections B and C if (in_array($ca3_line, ['08', '09', '9B'])) { // Section B: VAT amounts only $total_vat_collected += $line_total_vat; } elseif (in_array($ca3_line, ['19', '20', '21', '22'])) { // Section B sub-section: VAT deductible amounts $total_vat_deductible += $line_total_vat; } } // Calculate line 16: Subtotal of lines 08, 09, 9B (for user reference) $this->calculateLine16($declaration_id); // Calculate line 23: Subtotal of lines 20, 21, 22 (for user reference) $this->calculateLine23($declaration_id); // Calculate D-section result lines (25, 26, 28, 29) $this->calculateDSectionLines($declaration_id, $total_vat_collected, $total_vat_deductible); // Calculate net amounts $net_vat_due = $total_vat_collected - $total_vat_deductible; $vat_credit = $net_vat_due < 0 ? abs($net_vat_due) : 0; $net_vat_due = $net_vat_due > 0 ? $net_vat_due : 0; // Update declaration totals $this->updateDeclarationTotals($declaration_id, $total_vat_collected, $total_vat_deductible, $net_vat_due, $vat_credit); return true; } /** * Calculate line 16: Subtotal of lines 08, 09, 9B (for user reference) * * @param int $declaration_id Declaration ID * @return bool Success */ private function calculateLine16($declaration_id) { // Get the amounts from lines 08, 09, 9B $sql = "SELECT ca3_line, vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . (int)$declaration_id . " AND ca3_line IN ('08', '09', '9B')"; $result = $this->db->query($sql); $line_16_total = 0; $account_labels = array(); if ($result) { while ($obj = $this->db->fetch_object($result)) { $line_16_total += $obj->vat_amount; $account_labels[] = "Line " . $obj->ca3_line . ": " . number_format($obj->vat_amount, 2); } } // Create line 16 record $this->createCA3Line($declaration_id, '16', 'Subtotal: ' . implode(', ', $account_labels), array( 'base_amount' => 0, 'vat_amount' => $line_16_total, 'total_amount' => $line_16_total )); return true; } /** * Round VAT amount to whole number and format with original amount in brackets * * @param float $amount Original amount * @return array Array with 'rounded' and 'formatted' values */ private function roundVATAmount($amount) { $rounded = round($amount); $formatted = $rounded; if ($rounded != $amount) { $formatted = $rounded . ' (' . number_format($amount, 2) . ')'; } return array( 'rounded' => $rounded, 'formatted' => $formatted ); } /** * Calculate line 23: Subtotal of lines 20, 21, 22 (for user reference) * * @param int $declaration_id Declaration ID * @return bool Success */ private function calculateLine23($declaration_id) { // Get the amounts from lines 20, 21, 22 $sql = "SELECT ca3_line, vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . (int)$declaration_id . " AND ca3_line IN ('20', '21', '22')"; $result = $this->db->query($sql); $line_23_total = 0; $account_labels = array(); if ($result) { while ($obj = $this->db->fetch_object($result)) { $line_23_total += $obj->vat_amount; $account_labels[] = "Line " . $obj->ca3_line . ": " . number_format($obj->vat_amount, 2); } } // Create line 23 record $this->createCA3Line($declaration_id, '23', 'Subtotal: ' . implode(', ', $account_labels), array( 'base_amount' => 0, 'vat_amount' => $line_23_total, 'total_amount' => $line_23_total )); return true; } /** * Get amount for a specific CA-3 line * * @param int $declaration_id Declaration ID * @param string $ca3_line CA-3 line code * @return float Line amount */ private function getLineAmount($declaration_id, $ca3_line) { $sql = "SELECT vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . (int)$declaration_id . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'"; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { $obj = $this->db->fetch_object($result); return (float)$obj->vat_amount; } return 0.0; } /** * Calculate D-section result lines (25, 26, 28, 29) * * @param int $declaration_id Declaration ID * @param float $total_vat_collected Total VAT collected * @param float $total_vat_deductible Total VAT deductible * @return bool Success */ private function calculateDSectionLines($declaration_id, $total_vat_collected, $total_vat_deductible) { // Get line 16 and 23 amounts for line 25 calculation $line_16_amount = $this->getLineAmount($declaration_id, '16'); $line_23_amount = $this->getLineAmount($declaration_id, '23'); // Calculate net VAT due $net_vat_due = $total_vat_collected - $total_vat_deductible; // Line 25: Crédit de TVA (ligne 23 - ligne16) - only if > 0 $line_25_amount = $line_23_amount - $line_16_amount; if ($line_25_amount > 0) { $this->createCA3Line($declaration_id, '25', 'Crédit de TVA (ligne 23 - ligne16)', array( 'base_amount' => 0, 'vat_amount' => $line_25_amount, 'total_amount' => $line_25_amount )); } else { $this->createCA3Line($declaration_id, '25', 'Crédit de TVA (ligne 23 - ligne16)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } // Get VAT refund threshold configuration require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $threshold_config = $config->getVATRefundThresholdConfiguration(); $refund_threshold = $threshold_config['refund_threshold']; $threshold_enabled = $threshold_config['refund_threshold_enabled']; // Line 26: Crédit de TVA à rembourser (≥ threshold) if ($line_25_amount > 0 && (!$threshold_enabled || $line_25_amount >= $refund_threshold)) { $this->createCA3Line($declaration_id, '26', 'Crédit de TVA à rembourser (ligne 25 – ligne 26)', array( 'base_amount' => 0, 'vat_amount' => $line_25_amount, 'total_amount' => $line_25_amount )); } else { $this->createCA3Line($declaration_id, '26', 'Crédit de TVA à rembourser (ligne 25 – ligne 26)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } // Line 27: Crédit de TVA à reporter (< threshold) $line_27_amount = $line_25_amount; // Same value as line 25 if ($line_25_amount > 0 && $threshold_enabled && $line_25_amount < $refund_threshold) { $this->createCA3Line($declaration_id, '27', 'Crédit de TVA à reporter (ligne 25 – ligne 26) (Cette somme est à reporter ligne 22 de la prochaine déclaration)', array( 'base_amount' => 0, 'vat_amount' => $line_27_amount, 'total_amount' => $line_27_amount )); } else { $this->createCA3Line($declaration_id, '27', 'Crédit de TVA à reporter (ligne 25 – ligne 26) (Cette somme est à reporter ligne 22 de la prochaine déclaration)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } // Line TD: TVA due (ligne 16 – ligne 23) - only if > 0 $line_td_amount = $line_16_amount - $line_23_amount; if ($line_td_amount > 0) { $this->createCA3Line($declaration_id, 'TD', 'TVA due (ligne 16 – ligne 23)', array( 'base_amount' => 0, 'vat_amount' => $line_td_amount, 'total_amount' => $line_td_amount )); } else { $this->createCA3Line($declaration_id, 'TD', 'TVA due (ligne 16 – ligne 23)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } // Line 28: TVA nette due (ligne TD – ligne X5) - same value as line TD, only if > 0 $line_28_amount = $line_td_amount; // Same value as line TD if ($line_28_amount > 0) { $this->createCA3Line($declaration_id, '28', 'TVA nette due (ligne TD – ligne X5)', array( 'base_amount' => 0, 'vat_amount' => $line_28_amount, 'total_amount' => $line_28_amount )); } else { $this->createCA3Line($declaration_id, '28', 'TVA nette due (ligne TD – ligne X5)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } // Line 32: Total à payer (lignes 28 + 29 + Z5 – AB) - same value as line 28, only if > 0 $line_32_amount = $line_28_amount; // Same value as line 28 if ($line_32_amount > 0) { $this->createCA3Line($declaration_id, '32', 'Total à payer (lignes 28 + 29 + Z5 – AB) (N\'oubliez pas d\'effectuer le règlement correspondant)', array( 'base_amount' => 0, 'vat_amount' => $line_32_amount, 'total_amount' => $line_32_amount )); } else { $this->createCA3Line($declaration_id, '32', 'Total à payer (lignes 28 + 29 + Z5 – AB) (N\'oubliez pas d\'effectuer le règlement correspondant)', array( 'base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0 )); } return true; } /** * Get account amounts for a period * * @param string $account_code Account code * @param string $start_date Start date * @param string $end_date End date * @return array Amounts (base_amount, vat_amount, total_amount) */ public function getAccountAmounts($account_code, $start_date, $end_date) { // Use the correct Dolibarr accounting table structure $possible_queries = array( // Correct table and column names based on your Dolibarr structure "SELECT SUM(debit) as total_debit, SUM(credit) as total_credit FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping WHERE numero_compte = '" . $this->db->escape($account_code) . "' AND doc_date >= '" . $this->db->escape($start_date) . "' AND doc_date <= '" . $this->db->escape($end_date) . "' AND entity = " . $this->entity, // Without entity filter (in case entity is not set correctly) "SELECT SUM(debit) as total_debit, SUM(credit) as total_credit FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping WHERE numero_compte = '" . $this->db->escape($account_code) . "' AND doc_date >= '" . $this->db->escape($start_date) . "' AND doc_date <= '" . $this->db->escape($end_date) . "'" ); foreach ($possible_queries as $sql) { $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { $obj = $this->db->fetch_object($result); // For VAT accounts, we need to use the credit side (VAT collected) or debit side (VAT paid) // If debit = credit, the account is balanced, so we use the credit amount (VAT collected) if ($obj->total_debit == $obj->total_credit) { // Account is balanced, use the credit amount (VAT collected) $total_amount = $obj->total_credit; } else { // Account has imbalance, use the absolute difference $total_amount = abs($obj->total_debit - $obj->total_credit); } return array( 'base_amount' => $total_amount, 'vat_amount' => $total_amount, 'total_amount' => $total_amount ); } } return array('base_amount' => 0, 'vat_amount' => 0, 'total_amount' => 0); } /** * Create CA-3 line record * * @param int $declaration_id Declaration ID * @param string $ca3_line CA-3 line code * @param string $line_label Line label * @param array $amounts Amounts array * @return bool Success */ public function createCA3Line($declaration_id, $ca3_line, $line_label, $amounts) { // Get amount calculation configuration require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $calc_config = $config->getAmountCalculationConfiguration(); // Apply calculation method (round or truncate) if ($calc_config['amount_calculation_method'] == 'truncate') { $rounded_base = floor($amounts['base_amount']); $rounded_vat = floor($amounts['vat_amount']); $rounded_total = floor($amounts['total_amount']); } else { // Default to rounding $rounded_base = round($amounts['base_amount']); $rounded_vat = round($amounts['vat_amount']); $rounded_total = round($amounts['total_amount']); } // Store original amounts in the line_label for now (we'll need to modify the database schema later) $original_base = $amounts['base_amount']; $original_vat = $amounts['vat_amount']; $original_total = $amounts['total_amount']; $sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_ca3_lines (declaration_id, ca3_line, line_label, base_amount, vat_amount, total_amount, created_date) VALUES (" . $declaration_id . ", '" . $this->db->escape($ca3_line) . "', '" . $this->db->escape($line_label) . "', " . $rounded_base . ", " . $rounded_vat . ", " . $rounded_total . ", NOW())"; $result = $this->db->query($sql); // Store original amounts in a separate way - we'll use the line_label to store them temporarily if ($result && ($rounded_vat != $original_vat || $rounded_base != $original_base)) { $original_info = "ORIGINAL_BASE:" . $original_base . "_ORIGINAL_VAT:" . $original_vat . "_ORIGINAL_TOTAL:" . $original_total; $update_sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_ca3_lines SET line_label = CONCAT(line_label, '|', '" . $this->db->escape($original_info) . "') WHERE declaration_id = " . $declaration_id . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'"; $this->db->query($update_sql); } return $result !== false; } /** * Update declaration totals * * @param int $declaration_id Declaration ID * @param float $total_vat_collected Total VAT collected * @param float $total_vat_deductible Total VAT deductible * @param float $net_vat_due Net VAT due * @param float $vat_credit VAT credit * @return bool Success */ public function updateDeclarationTotals($declaration_id, $total_vat_collected, $total_vat_deductible, $net_vat_due, $vat_credit) { // Round totals to whole numbers $rounded_collected = round($total_vat_collected); $rounded_deductible = round($total_vat_deductible); $rounded_net_due = round($net_vat_due); $rounded_credit = round($vat_credit); $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET total_vat_collected = " . $rounded_collected . ", total_vat_deductible = " . $rounded_deductible . ", net_vat_due = " . $rounded_net_due . ", vat_credit = " . $rounded_credit . " WHERE rowid = " . $declaration_id; $result = $this->db->query($sql); return $result !== false; } /** * Get period information * * @param int $period_id Period ID * @return array|false Period information or false if not found */ public function getPeriod($period_id) { $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_periods WHERE rowid = " . $period_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { return $this->db->fetch_array($result); } return false; } /** * Generate declaration number * * @param array $period Period information * @return string Declaration number */ public function generateDeclarationNumber($period) { $year = date('Y', strtotime($period['start_date'])); $start_month = date('m', strtotime($period['start_date'])); $end_month = date('m', strtotime($period['end_date'])); if ($start_month == $end_month) { $month_part = $start_month; } else { $month_part = $start_month . '-' . $end_month; } return 'CA3-' . $year . '-' . $month_part . '-' . date('YmdHis'); } /** * Generate declaration number from dates * * @param string $start_date Start date * @param string $end_date End date * @return string Declaration number */ public function generateDeclarationNumberFromDates($start_date, $end_date) { $year = date('Y', strtotime($start_date)); $start_month = date('m', strtotime($start_date)); $end_month = date('m', strtotime($end_date)); if ($start_month == $end_month) { $month_part = $start_month; } else { $month_part = $start_month . '-' . $end_month; } return 'CA3-' . $year . '-' . $month_part . '-' . date('YmdHis'); } /** * Get declaration information * * @param int $declaration_id Declaration ID * @return array|false Declaration information or false if not found */ public function getDeclaration($declaration_id) { $sql = "SELECT d.*, p.period_name, p.start_date, p.end_date FROM " . MAIN_DB_PREFIX . "declarationtva_declarations d LEFT JOIN " . MAIN_DB_PREFIX . "declarationtva_periods p ON d.period_id = p.rowid WHERE d.rowid = " . $declaration_id . " AND d.entity = " . $this->entity; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { return $this->db->fetch_array($result); } return false; } /** * Get CA-3 lines for a declaration * * @param int $declaration_id Declaration ID * @return array CA-3 lines */ public function getCA3Lines($declaration_id) { $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . $declaration_id . " ORDER BY ca3_line"; $result = $this->db->query($sql); $lines = array(); if ($result) { while ($obj = $this->db->fetch_object($result)) { $lines[] = array( 'ca3_line' => $obj->ca3_line, 'line_label' => $obj->line_label, 'base_amount' => $obj->base_amount, 'vat_amount' => $obj->vat_amount, 'total_amount' => $obj->total_amount ); } } return $lines; } /** * Delete a declaration * * @param int $declaration_id Declaration ID * @return bool Success */ public function deleteDeclaration($declaration_id) { $this->db->begin(); // Check if declaration exists and is in draft status $sql = "SELECT status FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if ($result) { $obj = $this->db->fetch_object($result); if ($obj && $obj->status == 'draft') { // Delete CA-3 lines first $sql_lines = "DELETE FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . (int)$declaration_id; $this->db->query($sql_lines); // Delete declaration $sql_declaration = "DELETE FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql_declaration); if ($result) { $this->db->commit(); return true; } } } $this->db->rollback(); return false; } /** * Recalculate CA-3 amounts for a declaration * * @param int $declaration_id Declaration ID * @return bool Success */ public function recalculateCA3Amounts($declaration_id) { // Get declaration info directly from database $sql = "SELECT start_date, end_date FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . $declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if (!$result || $this->db->num_rows($result) == 0) { return false; } $obj = $this->db->fetch_object($result); // Clear existing CA-3 lines $sql = "DELETE FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . $declaration_id; $this->db->query($sql); // Recalculate using declaration dates $period = array( 'start_date' => $obj->start_date, 'end_date' => $obj->end_date ); return $this->calculateCA3Amounts($declaration_id, $period); } /** * Get VAT credit carry-forward from previous periods */ public function getVATCreditCarryForward($declaration_start_date) { // Get journal configuration to use the correct account require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $journal_config = $config->getJournalConfiguration(); $vat_to_receive_account = $journal_config['vat_to_receive']; // Check VAT credit account balance at declaration start date $sql = "SELECT SUM(debit - credit) as balance FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping WHERE account_number = '" . $this->db->escape($vat_to_receive_account) . "' AND doc_date < '" . $declaration_start_date . "'"; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { $obj = $this->db->fetch_object($result); return max(0, $obj->balance); // Only positive balances (credits) } return 0; } /** * Create accounting entries for declaration submission * * @param int $declaration_id Declaration ID * @param object $user User object * @return bool Success */ public function createAccountingEntries($declaration_id, $user) { // Get declaration data $fetch_result = $this->fetch($declaration_id); if (!$fetch_result) { $this->error = 'Declaration not found'; return false; } // Create a declaration object with the fetched data $declaration = new stdClass(); $declaration->rowid = $this->rowid; $declaration->declaration_name = $this->declaration_name; $declaration->start_date = $this->start_date; $declaration->end_date = $this->end_date; // Get CA-3 lines data $ca3_lines = $this->getCA3Lines($declaration_id); if (empty($ca3_lines)) { $this->error = 'No CA-3 lines found for declaration'; return false; } // Debug: Log CA-3 lines found error_log("DEBUG: Found " . count($ca3_lines) . " CA-3 lines for declaration " . $declaration_id); // Create lookup array for CA-3 data $ca3_lookup = array(); foreach ($ca3_lines as $line) { $ca3_lookup[$line['ca3_line']] = $line; } // Get journal configuration require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $journal_config = $config->getJournalConfiguration(); // Create OD journal entries $od_entries = $this->createODJournalEntries($declaration, $ca3_lookup, $journal_config, $user); error_log("DEBUG: OD entries result: " . ($od_entries ? "SUCCESS" : "FAILED")); if (!$od_entries) { return false; } // Create bank journal entries $bank_entries = $this->createBankJournalEntries($declaration, $ca3_lookup, $journal_config, $user); if (!$bank_entries) { return false; } return true; } /** * Create OD journal entries for VAT accounts * * @param object $declaration Declaration object * @param array $ca3_lookup CA-3 data lookup array * @param array $journal_config Journal configuration * @param object $user User object * @return bool Success */ private function createODJournalEntries($declaration, $ca3_lookup, $journal_config, $user) { $entries = array(); // Get line 8 VAT accounts (debit side) $line8_entries = $this->getLine8VATAccountsForAccounting($declaration, $ca3_lookup); error_log("DEBUG: Line 8 entries: " . count($line8_entries)); $entries = array_merge($entries, $line8_entries); // Get line 20 accounts (credit side) $line20_entries = $this->getLine20AccountsForAccounting($declaration, $ca3_lookup); error_log("DEBUG: Line 20 entries: " . count($line20_entries)); $entries = array_merge($entries, $line20_entries); // Add balancing entry $balancing_entry = $this->getVATResultEntryForAccounting($declaration, $ca3_lookup, $journal_config); error_log("DEBUG: Balancing entry: " . ($balancing_entry ? "FOUND" : "NONE")); if ($balancing_entry) { $entries[] = $balancing_entry; } error_log("DEBUG: Total OD entries to create: " . count($entries)); // Create accounting entries in Dolibarr return $this->saveAccountingEntries($entries, $declaration, $user); } /** * Create bank journal entries for payments/refunds * * @param object $declaration Declaration object * @param array $ca3_lookup CA-3 data lookup array * @param array $journal_config Journal configuration * @param object $user User object * @return bool Success */ private function createBankJournalEntries($declaration, $ca3_lookup, $journal_config, $user) { // Get bank account configuration require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $bank_config = $config->getBankAccountConfiguration(); $bank_account_id = $bank_config['bank_account']; if (empty($bank_account_id) || $bank_account_id == 0) { // No bank account configured - skip bank entries return true; } // Get bank account details $bank_account = $this->getBankAccountDetails($bank_account_id); if (!$bank_account) { $this->error = 'Bank account not found'; return false; } // Calculate VAT amounts $line_16_amount = $this->getLineAmount($declaration->rowid, '16'); $line_23_amount = $this->getLineAmount($declaration->rowid, '23'); $td_amount = $line_16_amount - $line_23_amount; $entries = array(); if ($td_amount > 0) { // VAT payment case - money going out $entries[] = array( 'account_code' => $bank_account['account_code'], 'account_label' => $bank_account['account_label'], 'entry_label' => 'Paiement TVA - ' . $declaration->declaration_name, 'debit' => '', 'credit' => $td_amount ); $entries[] = array( 'account_code' => $journal_config['vat_to_pay'], 'account_label' => $this->getAccountLabel($journal_config['vat_to_pay']), 'entry_label' => 'Paiement TVA - ' . $declaration->declaration_name, 'debit' => $td_amount, 'credit' => '' ); } elseif ($td_amount < 0) { // VAT refund case - money coming in $vat_credit = abs($td_amount); // Determine which credit account to use based on threshold $threshold_config = $config->getVATRefundThresholdConfiguration(); $refund_threshold = $threshold_config['refund_threshold']; $threshold_enabled = $threshold_config['refund_threshold_enabled']; if ($threshold_enabled && $vat_credit < $refund_threshold) { // Use carry-forward account $vat_account = $journal_config['vat_to_receive']; } else { // Use immediate refund account $vat_account = $journal_config['vat_refund']; } $entries[] = array( 'account_code' => $bank_account['account_code'], 'account_label' => $bank_account['account_label'], 'entry_label' => 'Remboursement TVA - ' . $declaration->declaration_name, 'debit' => $vat_credit, 'credit' => '' ); $entries[] = array( 'account_code' => $vat_account, 'account_label' => $this->getAccountLabel($vat_account), 'entry_label' => 'Remboursement TVA - ' . $declaration->declaration_name, 'debit' => '', 'credit' => $vat_credit ); } // Create accounting entries in Dolibarr return $this->saveAccountingEntries($entries, $declaration, $user); } /** * Get line 8 VAT accounts for accounting entries * * @param object $declaration Declaration object * @param array $ca3_lookup CA-3 data lookup array * @return array Accounting entries */ private function getLine8VATAccountsForAccounting($declaration, $ca3_lookup) { $entries = array(); if (!isset($ca3_lookup['08'])) { return $entries; } error_log("DEBUG: Declaration object rowid: " . ($declaration->rowid ?? 'NULL')); $line8_details = $this->getCA3LineDetails($declaration->rowid, '08'); if (empty($line8_details) || empty($line8_details['account_details'])) { return $entries; } foreach ($line8_details['account_details'] as $account) { // Only include 445 accounts (VAT accounts) with non-zero amounts if (strpos($account['account_code'], '445') === 0 && $account['vat_amount'] > 0) { $entries[] = array( 'account_code' => $account['account_code'], 'account_label' => $account['account_label'], 'entry_label' => $declaration->declaration_name, 'debit' => $account['vat_amount'], 'credit' => 0 ); } } return $entries; } /** * Get line 20 accounts for accounting entries * * @param object $declaration Declaration object * @param array $ca3_lookup CA-3 data lookup array * @return array Accounting entries */ private function getLine20AccountsForAccounting($declaration, $ca3_lookup) { $entries = array(); if (!isset($ca3_lookup['20'])) { return $entries; } $line20_details = $this->getCA3LineDetails($declaration->rowid, '20'); if (empty($line20_details) || empty($line20_details['account_details'])) { return $entries; } foreach ($line20_details['account_details'] as $account) { // Only include 445 accounts (VAT accounts) with non-zero amounts if (strpos($account['account_code'], '445') === 0 && $account['vat_amount'] > 0) { $entries[] = array( 'account_code' => $account['account_code'], 'account_label' => $account['account_label'], 'entry_label' => $declaration->declaration_name, 'debit' => 0, 'credit' => $account['vat_amount'] ); } } return $entries; } /** * Get VAT result entry for accounting * * @param object $declaration Declaration object * @param array $ca3_lookup CA-3 data lookup array * @param array $journal_config Journal configuration * @return array|null Accounting entry */ private function getVATResultEntryForAccounting($declaration, $ca3_lookup, $journal_config) { // Get TD line amount (Line 16 - Line 23) $line_16_amount = $this->getLineAmount($declaration->rowid, '16'); $line_23_amount = $this->getLineAmount($declaration->rowid, '23'); $td_amount = $line_16_amount - $line_23_amount; if ($td_amount > 0) { // VAT due case return array( 'account_code' => $journal_config['vat_to_pay'], 'account_label' => $this->getAccountLabel($journal_config['vat_to_pay']), 'entry_label' => $declaration->declaration_name, 'debit' => 0, 'credit' => $td_amount ); } elseif ($td_amount < 0) { // VAT credit case $vat_credit = abs($td_amount); // Determine which credit account to use based on threshold require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $threshold_config = $config->getVATRefundThresholdConfiguration(); $refund_threshold = $threshold_config['refund_threshold']; $threshold_enabled = $threshold_config['refund_threshold_enabled']; if ($threshold_enabled && $vat_credit < $refund_threshold) { // Use carry-forward account $vat_account = $journal_config['vat_to_receive']; } else { // Use immediate refund account $vat_account = $journal_config['vat_refund']; } return array( 'account_code' => $vat_account, 'account_label' => $this->getAccountLabel($vat_account), 'entry_label' => $declaration->declaration_name, 'debit' => $vat_credit, 'credit' => 0 ); } return null; } /** * Save accounting entries to Dolibarr * * @param array $entries Accounting entries * @param object $declaration Declaration object * @param object $user User object * @return bool Success */ private function saveAccountingEntries($entries, $declaration, $user) { error_log("DEBUG: saveAccountingEntries called with user: " . ($user ? "VALID" : "NULL")); if (empty($entries)) { return true; // No entries to save } // Use Dolibarr's bookkeeping class require_once DOL_DOCUMENT_ROOT . '/accountancy/class/bookkeeping.class.php'; $bookkeeping = new Bookkeeping($this->db); foreach ($entries as $entry) { error_log("DEBUG: Creating accounting entry for account: " . $entry['account_code'] . ", debit: " . $entry['debit'] . ", credit: " . $entry['credit']); // Create accounting entry $bookkeeping->doc_type = 'declarationtva'; $bookkeeping->doc_ref = $declaration->declaration_name; $bookkeeping->doc_date = $declaration->end_date; // Use declaration end date for OD entries $bookkeeping->account_number = $entry['account_code']; $bookkeeping->account_label = $entry['account_label']; $bookkeeping->debit = $entry['debit']; $bookkeeping->credit = $entry['credit']; $bookkeeping->label = $entry['entry_label']; $bookkeeping->entity = $this->entity; $result = $bookkeeping->create($user); error_log("DEBUG: Bookkeeping create result: " . ($result ? "SUCCESS" : "FAILED - " . $bookkeeping->error)); if (!$result) { $this->error = 'Failed to create accounting entry: ' . $bookkeeping->error; return false; } } return true; } /** * Get bank account details * * @param int $bank_account_id Bank account ID * @return array|false Bank account details */ private function getBankAccountDetails($bank_account_id) { $sql = "SELECT ba.rowid, ba.label, ba.number, a.account_number as account_code, a.label as account_label FROM " . MAIN_DB_PREFIX . "bank_account ba LEFT JOIN " . MAIN_DB_PREFIX . "accounting_account a ON a.rowid = ba.account_number WHERE ba.rowid = " . (int)$bank_account_id . " AND ba.entity = " . $this->entity; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { return $this->db->fetch_array($result); } return false; } /** * Get account label from account code * * @param string $account_code Account code * @return string Account label */ private function getAccountLabel($account_code) { $sql = "SELECT label FROM " . MAIN_DB_PREFIX . "accounting_account WHERE account_number = '" . $this->db->escape($account_code) . "' AND entity = " . $this->entity; $result = $this->db->query($sql); if ($result && $this->db->num_rows($result) > 0) { $obj = $this->db->fetch_object($result); return $obj->label; } return $account_code; // Fallback to account code } /** * Submit declaration (create accounting entries and update status) * * @param int $declaration_id Declaration ID * @param object $user User object * @return bool Success */ public function submitDeclaration($declaration_id, $user) { error_log("DEBUG: submitDeclaration called with user: " . ($user ? "VALID" : "NULL")); // Get declaration data $declaration = $this->fetch($declaration_id); if (!$declaration) { $this->error = 'Declaration not found'; return false; } // Check if declaration is validated // TEMPORARILY DISABLED FOR TESTING - allows re-submission /* if ($declaration->status !== 'validated') { $this->error = 'Declaration must be validated before submission'; return false; } */ // Check if accounting entries are enabled require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; $config = new DeclarationTVA_Config($this->db, $this->entity); $auto_create_config = $config->getAutoCreateAccountingConfiguration(); if (!isset($auto_create_config['auto_create_accounting']) || !$auto_create_config['auto_create_accounting']) { // Accounting entries disabled - just update status return $this->updateDeclarationStatus($declaration_id, 'submitted'); } // Create accounting entries error_log("DEBUG: About to call createAccountingEntries with user: " . ($user ? "VALID" : "NULL")); $accounting_result = $this->createAccountingEntries($declaration_id, $user); if (!$accounting_result) { $this->error = 'Failed to create accounting entries: ' . $this->error; return false; } // Update declaration status to submitted $status_result = $this->updateDeclarationStatus($declaration_id, 'submitted'); if (!$status_result) { $this->error = 'Failed to update declaration status: ' . $this->error; return false; } return true; } /** * Reset declaration status to validated (for testing purposes) * * @param int $declaration_id Declaration ID * @return bool Success */ public function resetDeclarationStatus($declaration_id) { $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = 'validated' WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if (!$result) { $this->error = 'Database error: ' . $this->db->lasterror(); return false; } return true; } /** * Update declaration status * * @param int $declaration_id Declaration ID * @param string $status New status * @return bool Success */ private function updateDeclarationStatus($declaration_id, $status) { $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = '" . $this->db->escape($status) . "' WHERE rowid = " . (int)$declaration_id; $result = $this->db->query($sql); if (!$result) { $this->error = 'Database error: ' . $this->db->lasterror(); return false; } return true; } /** * Validate accounting entries balance * * @param array $entries Accounting entries * @return bool Is balanced */ private function validateAccountingBalance($entries) { $total_debit = 0; $total_credit = 0; foreach ($entries as $entry) { $total_debit += (float)$entry['debit']; $total_credit += (float)$entry['credit']; } // Allow for small rounding differences (0.01) return abs($total_debit - $total_credit) < 0.01; } /** * Get account mappings with labels from chart of accounts * * @return array Array of account mappings with labels */ public function getAccountMappings() { $sql = "SELECT m.rowid, m.ca3_line, m.account_code, m.account_label, m.vat_rate, m.is_active, a.label as account_label_pcg FROM " . MAIN_DB_PREFIX . "declarationtva_account_mappings m LEFT JOIN " . MAIN_DB_PREFIX . "accounting_account a ON a.account_number = m.account_code AND a.entity = m.entity WHERE m.entity = " . $this->entity . " AND m.is_active = 1 ORDER BY m.ca3_line, m.account_code"; $result = $this->db->query($sql); $mappings = array(); if ($result) { while ($obj = $this->db->fetch_object($result)) { // Use the label from chart of accounts if available, otherwise use the stored label $account_label = !empty($obj->account_label_pcg) ? $obj->account_label_pcg : $obj->account_label; $mappings[] = array( 'rowid' => $obj->rowid, 'ca3_line' => $obj->ca3_line, 'account_code' => $obj->account_code, 'account_label' => $account_label, 'vat_rate' => $obj->vat_rate, 'is_active' => $obj->is_active ); } } return $mappings; } /** * Get detailed account breakdown for a specific CA-3 line * * @param int $declaration_id Declaration ID * @param string $ca3_line CA-3 line code (e.g., 'A1', '08', '09', etc.) * @return array Detailed breakdown with accounts and amounts */ public function getCA3LineDetails($declaration_id, $ca3_line) { error_log("DEBUG: getCA3LineDetails called for declaration $declaration_id, line $ca3_line"); // Get declaration dates $sql = "SELECT start_date, end_date FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if (!$result || $this->db->num_rows($result) == 0) { error_log("DEBUG: No declaration dates found for declaration $declaration_id"); return array(); } $obj = $this->db->fetch_object($result); $start_date = $obj->start_date; $end_date = $obj->end_date; // Get account mappings for this CA-3 line $mappings = $this->getAccountMappings(); $line_mappings = array(); // Find mappings for this specific line foreach ($mappings as $mapping) { // Handle special cases for lines 08, 09, 9B which have _BASE and _VAT suffixes if (in_array($ca3_line, array('08', '09', '9B'))) { if ($mapping['ca3_line'] == $ca3_line . '_BASE' || $mapping['ca3_line'] == $ca3_line . '_VAT') { $line_mappings[] = $mapping; } } else { // Normal matching for other lines if ($mapping['ca3_line'] == $ca3_line) { $line_mappings[] = $mapping; } } } $details = array(); $total_base = 0; $total_vat = 0; $total_amount = 0; // Process each mapped account foreach ($line_mappings as $mapping) { $account_code = $mapping['account_code']; $account_label = $mapping['account_label']; // Get account amounts for this period $amounts = $this->getAccountAmounts($account_code, $start_date, $end_date); $account_detail = array( 'account_code' => $account_code, 'account_label' => $account_label, 'base_amount' => $amounts['base_amount'], 'vat_amount' => $amounts['vat_amount'], 'total_amount' => $amounts['total_amount'], 'mapping_type' => $mapping['ca3_line'] // Will be 'A1', '08_BASE', '08_VAT', etc. ); $details[] = $account_detail; // Accumulate totals $total_base += $amounts['base_amount']; $total_vat += $amounts['vat_amount']; $total_amount += $amounts['total_amount']; } // Get the calculated line total from CA-3 lines table $sql_line = "SELECT base_amount, vat_amount, total_amount, line_label FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines WHERE declaration_id = " . (int)$declaration_id . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'"; $result_line = $this->db->query($sql_line); $calculated_line = null; if ($result_line && $this->db->num_rows($result_line) > 0) { $calculated_line = $this->db->fetch_object($result_line); } $result = array( 'ca3_line' => $ca3_line, 'declaration_id' => $declaration_id, 'start_date' => $start_date, 'end_date' => $end_date, 'account_details' => $details, 'calculated_line' => $calculated_line, 'totals' => array( 'base_amount' => $total_base, 'vat_amount' => $total_vat, 'total_amount' => $total_amount ), 'account_count' => count($details) ); error_log("DEBUG: getCA3LineDetails returning " . count($details) . " account details for line $ca3_line"); return $result; } /** * Validate a declaration * * @param int $declaration_id Declaration ID * @return bool Success */ public function validateDeclaration($declaration_id) { // First, check if the validated_date and validated_by columns exist $sql_check_columns = "SHOW COLUMNS FROM " . MAIN_DB_PREFIX . "declarationtva_declarations LIKE 'validated_date'"; $result_check = $this->db->query($sql_check_columns); $has_validated_columns = ($result_check && $this->db->num_rows($result_check) > 0); if ($has_validated_columns) { // Use the enhanced version with validated_date and validated_by $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = 'validated', validated_date = NOW(), validated_by = " . $this->db->escape($GLOBALS['user']->id) . " WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; } else { // Use the basic version without the additional columns $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = 'validated' WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; } $result = $this->db->query($sql); if (!$result) { $this->error = "Database error updating declaration: " . $this->db->lasterror(); return false; } // Check if the update actually affected any rows if ($this->db->affected_rows($result) == 0) { $this->error = "No declaration found with ID " . $declaration_id . " or insufficient permissions"; return false; } return true; } /** * Unvalidate a declaration (for testing purposes) * * @param int $declaration_id Declaration ID * @return bool Success */ public function unvalidateDeclaration($declaration_id) { // Check if the validated_date and validated_by columns exist $sql_check_columns = "SHOW COLUMNS FROM " . MAIN_DB_PREFIX . "declarationtva_declarations LIKE 'validated_date'"; $result_check = $this->db->query($sql_check_columns); $has_validated_columns = ($result_check && $this->db->num_rows($result_check) > 0); if ($has_validated_columns) { // Use the enhanced version with validated_date and validated_by $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = 'draft', validated_date = NULL, validated_by = NULL WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; } else { // Use the basic version without the additional columns $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations SET status = 'draft' WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; } $result = $this->db->query($sql); if (!$result) { $this->error = "Database error updating declaration: " . $this->db->lasterror(); return false; } // Check if the update actually affected any rows if ($this->db->affected_rows($result) == 0) { $this->error = "No declaration found with ID " . $declaration_id . " or insufficient permissions"; return false; } return true; } /** * Save validated PDF to Dolibarr documents * * @param int $declaration_id Declaration ID * @param string $pdf_path Path to the PDF file * @return bool Success */ public function saveValidatedPDF($declaration_id, $pdf_path) { global $conf, $user; try { // Create VAT declarations validated directory structure $vat_declarations_dir = DOL_DATA_ROOT . '/declarationtva/validated/'; // Create directory if it doesn't exist if (!is_dir($vat_declarations_dir)) { if (!dol_mkdir($vat_declarations_dir)) { $this->error = "Failed to create VAT declarations directory: " . $vat_declarations_dir; return false; } } // Check if PDF is already in the correct location if (strpos($pdf_path, $vat_declarations_dir) === 0) { // PDF is already in the correct location, no need to copy $dest_path = $pdf_path; } else { // Generate filename with proper structure $filename = 'CA3_' . $this->declaration_number . '_' . date('Y-m-d') . '.pdf'; $dest_path = $vat_declarations_dir . $filename; // Copy PDF to validated directory if (!copy($pdf_path, $dest_path)) { $this->error = "Failed to copy PDF to validated directory"; return false; } } // Try to add document record to Dolibarr (optional feature) // Skip ECM integration if there are any issues to prevent fatal errors $ecm_integration_enabled = false; // Temporarily disabled to prevent fatal errors if ($ecm_integration_enabled) { // Try multiple possible paths for ecmfiles.class.php $ecmfiles_paths = array( DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php', dirname(__FILE__) . '/../../../ecm/class/ecmfiles.class.php', dirname(__FILE__) . '/../../../../ecm/class/ecmfiles.class.php', DOL_DOCUMENT_ROOT . '/core/class/ecmfiles.class.php', dirname(__FILE__) . '/../../../../core/class/ecmfiles.class.php' ); $ecmfiles_path = null; foreach ($ecmfiles_paths as $path) { if (file_exists($path)) { $ecmfiles_path = $path; break; } } if ($ecmfiles_path && file_exists($ecmfiles_path)) { try { // Log the path being used for debugging require_once $ecmfiles_path; $ecmfile = new EcmFiles($this->db); $ecmfile->filepath = 'declarationtva/' . date('Y') . '/' . date('m') . '/'; $ecmfile->filename = $filename; $ecmfile->label = 'CA-3 Déclaration TVA - ' . $this->declaration_number; $ecmfile->description = 'Déclaration TVA CA-3 validée avec détail des comptes pour la période ' . date('Y-m'); $ecmfile->entity = $this->entity; $ecmfile->fk_user_author = $user->id; $ecmfile->fk_user_modif = $user->id; $ecmfile->datec = dol_now(); $ecmfile->tms = dol_now(); $ecmfile->mimetype = 'application/pdf'; $ecmfile->filesize = filesize($dest_path); $ecmfile->checksum = md5_file($dest_path); $result = $ecmfile->create($user); if ($result > 0) { // Try to link document to declaration (this might fail if table doesn't exist) $link_result = $this->linkDocumentToDeclaration($declaration_id, $ecmfile->id); if (!$link_result) { // Log the error but don't fail the whole process } } else { // Log the error but don't fail the whole process } } catch (Exception $e) { // Log the error but don't fail the whole process } } else { // Log that ECM files class is not available } } // Return true even if ECM file creation failed - the PDF is still saved to disk return true; } catch (Exception $e) { $this->error = "Exception in saveValidatedPDF: " . $e->getMessage(); return false; } } /** * Link document to declaration * * @param int $declaration_id Declaration ID * @param int $document_id Document ID * @return bool Success */ private function linkDocumentToDeclaration($declaration_id, $document_id) { // First check if the table exists $sql_check = "SHOW TABLES LIKE '" . MAIN_DB_PREFIX . "declarationtva_documents'"; $result_check = $this->db->query($sql_check); if (!$result_check || $this->db->num_rows($result_check) == 0) { $this->error = "Documents table does not exist. Please run the database migration."; return false; } $sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_documents (declaration_id, document_id, created_date, entity) VALUES (" . (int)$declaration_id . ", " . (int)$document_id . ", NOW(), " . $this->entity . ")"; $result = $this->db->query($sql); if (!$result) { $this->error = "Database error linking document: " . $this->db->lasterror(); return false; } return true; } /** * Check if declaration has a validated document * * @param int $declaration_id Declaration ID * @return bool Has document */ public function hasValidatedDocument($declaration_id) { // Get declaration details $sql = "SELECT declaration_number, status FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if (!$result || $this->db->num_rows($result) == 0) { return false; } $obj = $this->db->fetch_object($result); // Only check for documents if declaration is validated if ($obj->status != 'validated') { return false; } // Check if PDF file exists in the VAT declarations validated directory $vat_declarations_dir = DOL_DATA_ROOT . '/declarationtva/validated/'; // Look for PDF files with the declaration ID (not declaration number) $pattern = $vat_declarations_dir . 'CA3_' . $declaration_id . '_*.pdf'; $files = glob($pattern); // If no files found with the exact pattern, try a more flexible search if (empty($files)) { $pattern = $vat_declarations_dir . 'CA3_*' . $declaration_id . '*.pdf'; $files = glob($pattern); } return !empty($files) && file_exists($files[0]); } /** * Get the PDF file path for a validated declaration * * @param int $declaration_id Declaration ID * @return string|false PDF file path or false if not found */ public function getValidatedPDFPath($declaration_id) { // Get declaration details $sql = "SELECT declaration_number, status FROM " . MAIN_DB_PREFIX . "declarationtva_declarations WHERE rowid = " . (int)$declaration_id . " AND entity = " . $this->entity; $result = $this->db->query($sql); if (!$result || $this->db->num_rows($result) == 0) { return false; } $obj = $this->db->fetch_object($result); // Only return path if declaration is validated if ($obj->status != 'validated') { return false; } // Check if PDF file exists in the VAT declarations validated directory $vat_declarations_dir = DOL_DATA_ROOT . '/declarationtva/validated/'; // Look for PDF files with the declaration ID (not declaration number) $pattern = $vat_declarations_dir . 'CA3_' . $declaration_id . '_*.pdf'; $files = glob($pattern); // If no files found with the exact pattern, try a more flexible search if (empty($files)) { $pattern = $vat_declarations_dir . 'CA3_*' . $declaration_id . '*.pdf'; $files = glob($pattern); } if (!empty($files) && file_exists($files[0])) { return $files[0]; } return false; } /** * Get the VAT declarations documents directory path * * @param string $year Year (optional, defaults to current year) * @param string $month Month (optional, defaults to current month) * @return string Directory path */ public function getVATDeclarationsDirectory($year = null, $month = null) { if ($year === null) { $year = date('Y'); } if ($month === null) { $month = date('m'); } return DOL_DATA_ROOT . '/documents/declarationtva/' . $year . '/' . $month . '/'; } /** * Get the relative path for ECM files * * @param string $year Year (optional, defaults to current year) * @param string $month Month (optional, defaults to current month) * @return string Relative path */ public function getVATDeclarationsRelativePath($year = null, $month = null) { if ($year === null) { $year = date('Y'); } if ($month === null) { $month = date('m'); } return 'declarationtva/' . $year . '/' . $month . '/'; } }