DeclarationTVA/core/class/declarationtva_pdf.class.php
Frank Cools 5ea9b31a7d Fix template path for reset functionality
- Update template_path to use relative path from class file
- Fix REVENIR AU MODELE OFFICIEL button functionality
- Ensure template detection works correctly
- Resolve template path mismatch issue
2025-10-08 01:13:45 +02:00

2327 lines
88 KiB
PHP

<?php
/* Copyright (C) 2025 Frank Cools
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file core/class/declarationtva_pdf.class.php
* \ingroup declarationtva
* \brief PDF generation for CA-3 declarations
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
// Load TCPDF for PDF support (Dolibarr's standard)
require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf.php';
// Load FPDI-TCPDF for fillable PDF support
require_once DOL_DOCUMENT_ROOT.'/custom/declarationtva/vendor/autoload.php';
/**
* Class to generate CA-3 declaration PDF
*/
class DeclarationTVA_PDF
{
/**
* @var DoliDB Database handler
*/
public $db;
/**
* @var string Error code (or message)
*/
public $error = '';
/**
* @var string[] Several error codes (or messages)
*/
public $errors = array();
/**
* @var int Entity
*/
public $entity;
/**
* @var string Template path
*/
public $template_path;
/**
* @var string Template version
*/
public $template_version = '30';
/**
* @var string Template document number
*/
public $template_document = '10963';
/**
* @var string Gitea repository URL for templates
*/
public $gitea_repo_url = 'https://git.covago.com/frank/DeclarationTVA';
/**
* @var string Manifest file URL
*/
public $manifest_url = 'https://git.covago.com/frank/DeclarationTVA/raw/branch/main/templates/manifest.json';
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
$this->entity = (int) $conf->entity;
$this->template_path = dirname(__FILE__) . '/../../templates/declarationtva/';
}
/**
* Generate CA-3 declaration PDF
*
* @param int $declaration_id Declaration ID
* @param string $outputlangs Output language
* @return string|false PDF file path or false on error
*/
public function generateCA3PDF($declaration_id, $outputlangs = '')
{
global $conf, $langs, $user;
// Load declaration data
$declaration = new DeclarationTVA($this->db);
$result = $declaration->fetch($declaration_id);
if ($result <= 0) {
$this->error = 'Declaration not found';
return false;
}
// Get CA-3 line data
$ca3_data = $declaration->getCA3Lines($declaration_id);
if (empty($ca3_data)) {
$this->error = 'No CA-3 data found';
return false;
}
// Get company information
$company = new Societe($this->db);
$company->fetch($conf->entity);
// Generate PDF filename
$filename = 'CA3_' . $declaration->declaration_number . '_' . date('Y-m-d') . '.pdf';
$filepath = DOL_DATA_ROOT . '/declarationtva/' . $filename;
// Ensure directory exists
if (!is_dir(DOL_DATA_ROOT . '/declarationtva/')) {
dol_mkdir(DOL_DATA_ROOT . '/declarationtva/');
}
// Check if we have a custom template
$template_file = $this->getTemplatePath();
if (!$template_file) {
$this->error = 'CA-3 template not found';
return false;
}
// Generate PDF using FPDI or similar library
$result = $this->fillPDFTemplate($template_file, $filepath, $declaration, $ca3_data, $company);
if ($result) {
return $filepath;
} else {
return false;
}
}
/**
* Generate detailed CA-3 declaration PDF with breakdown pages
*
* @param int $declaration_id Declaration ID
* @param string $outputlangs Output language
* @return string|false PDF file path or false on error
*/
public function generateDetailedCA3PDF($declaration_id, $outputlangs = '')
{
global $conf, $langs, $user;
// Load declaration data
$declaration = new DeclarationTVA($this->db);
$result = $declaration->fetch($declaration_id);
if ($result <= 0) {
$this->error = 'Declaration not found';
return false;
}
// Get CA-3 line data
$ca3_data = $declaration->getCA3Lines($declaration_id);
if (empty($ca3_data)) {
$this->error = 'No CA-3 data found';
return false;
}
// Get company information
$company = new Societe($this->db);
$company->fetch($conf->entity);
// Generate PDF filename
$filename = 'CA3_' . $declaration->declaration_number . '_' . date('Y-m-d') . '.pdf';
// Create VAT declarations documents directory structure
$vat_declarations_dir = DOL_DATA_ROOT . '/declarationtva/validated/';
// Create directory if it doesn't exist
if (!is_dir($vat_declarations_dir)) {
dol_mkdir($vat_declarations_dir);
}
$filepath = $vat_declarations_dir . $filename;
// Generate complete PDF with CA-3 form and detailed breakdown pages
$result = $this->generateCA3PDF($declaration_id);
if ($result && file_exists($result)) {
// Copy the generated PDF to our target location
if (copy($result, $filepath)) {
// Clean up the temporary file
unlink($result);
$result = true;
} else {
$this->error = "Failed to copy PDF to target location";
$result = false;
}
}
if ($result) {
// Verify file was actually created
if (file_exists($filepath)) {
return $filepath;
} else {
$this->error = "PDF file was not created at expected path: " . $filepath;
return false;
}
} else {
$this->error = "PDF generation failed: " . $this->error;
return false;
}
}
/**
* Get the template file path (custom or default)
*
* @return string|false Template file path or false if not found
*/
private function getTemplatePath()
{
error_log("DeclarationTVA: Template path: " . $this->template_path);
// Check for custom template first
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
error_log("DeclarationTVA: Looking for custom template: " . $custom_template);
if (file_exists($custom_template)) {
error_log("DeclarationTVA: Custom template found");
return $custom_template;
}
// Fall back to default template
$default_template = $this->template_path . 'ca3_official_template.pdf';
error_log("DeclarationTVA: Looking for default template: " . $default_template);
if (file_exists($default_template)) {
error_log("DeclarationTVA: Default template found");
return $default_template;
}
error_log("DeclarationTVA: No template found");
return false;
}
/**
* Fill PDF template with declaration data
*
* @param string $template_path Template file path
* @param string $output_path Output file path
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function fillPDFTemplate($template_path, $output_path, $declaration, $ca3_data, $company)
{
try {
// Check if we have a custom fillable PDF template
if (file_exists($template_path) && $this->isFillablePDF($template_path)) {
return $this->fillFillablePDF($template_path, $output_path, $declaration, $ca3_data, $company);
} else {
// Fall back to basic PDF generation
return $this->generateBasicPDF($output_path, $declaration, $ca3_data, $company);
}
} catch (Exception $e) {
$this->error = 'PDF generation failed: ' . $e->getMessage();
return false;
}
}
/**
* Check if PDF is fillable (has form fields)
*
* @param string $pdf_path PDF file path
* @return bool True if fillable
*/
private function isFillablePDF($pdf_path)
{
// Simple check - if it's our custom template, assume it's fillable
return strpos($pdf_path, 'ca3_custom_template.pdf') !== false;
}
/**
* Fill a fillable PDF template using pdftk
*
* @param string $template_path Template file path
* @param string $output_path Output file path
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function fillFillablePDF($template_path, $output_path, $declaration, $ca3_data, $company)
{
try {
// Check if pdftk is available
if (!$this->isPdftkAvailable()) {
// Fallback to manual approach if pdftk is not available
return $this->fillFillablePDFManual($template_path, $output_path, $declaration, $ca3_data, $company);
}
// Prepare field data mapping
$field_data = $this->prepareFieldData($declaration, $ca3_data, $company);
// Create FDF data file for pdftk
$fdf_file = $this->createFDFFile($field_data, $declaration);
// Use pdftk to fill the form
$pdftk_path = $this->getPdftkPath();
$command = "\"$pdftk_path\" \"$template_path\" fill_form \"$fdf_file\" output \"$output_path\"";
$result = shell_exec($command . ' 2>&1');
// Clean up temporary FDF file
if (file_exists($fdf_file)) {
unlink($fdf_file);
}
// Check if output file was created successfully
if (file_exists($output_path) && filesize($output_path) > 0) {
// Add detailed pages to the filled PDF
$this->addDetailedPagesToPDF($output_path, $declaration, $ca3_data, $company);
return true;
} else {
$this->error = 'pdftk failed to generate output: ' . $result;
return false;
}
} catch (Exception $e) {
$this->error = 'Failed to fill PDF template: ' . $e->getMessage();
return false;
}
}
/**
* Prepare field data for PDF form filling
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return array Field data mapping
*/
private function prepareFieldData($declaration, $ca3_data, $company)
{
$field_data = array();
// Get the actual company information from Dolibarr configuration
global $conf, $mysoc;
// Use Dolibarr's company configuration
$field_data['company_name'] = $mysoc->name;
$field_data['company_address'] = $mysoc->address;
$field_data['company_city'] = $mysoc->town;
$field_data['company_postal_code'] = $mysoc->zip;
// Try different fields for SIRET - it could be in different idprof fields
$siret_value = !empty($mysoc->idprof2) ? $mysoc->idprof2 :
(!empty($mysoc->idprof1) ? $mysoc->idprof1 :
(!empty($mysoc->idprof3) ? $mysoc->idprof3 :
(!empty($mysoc->idprof4) ? $mysoc->idprof4 : '')));
// If no SIRET found, use a placeholder
if (empty($siret_value)) {
$siret_value = 'SIRET_NOT_CONFIGURED';
}
$field_data['company_siret'] = $this->formatSiret($siret_value);
$field_data['company_vat_number'] = $this->formatVatNumber($mysoc->tva_intra); // VAT number
$field_data['declaration_period_start'] = dol_print_date($declaration->start_date, 'day');
$field_data['declaration_period_end'] = dol_print_date($declaration->end_date, 'day');
$field_data['declaration_number'] = $declaration->declaration_number;
// Section A: Opérations imposables
$field_data['A1_amount'] = $this->getCA3LineAmount($ca3_data, 'A1');
$field_data['A2_amount'] = $this->getCA3LineAmount($ca3_data, 'A2');
$field_data['A3_amount'] = $this->getCA3LineAmount($ca3_data, 'A3');
$field_data['A4_amount'] = $this->getCA3LineAmount($ca3_data, 'A4');
$field_data['A5_amount'] = $this->getCA3LineAmount($ca3_data, 'A5');
// Section A: Additional reference fields (E1-E6, F1-F2, F6-F8)
$field_data['E1_amount'] = $this->getCA3LineAmount($ca3_data, 'E1');
$field_data['E2_amount'] = $this->getCA3LineAmount($ca3_data, 'E2');
$field_data['E3_amount'] = $this->getCA3LineAmount($ca3_data, 'E3');
$field_data['E4_amount'] = $this->getCA3LineAmount($ca3_data, 'E4');
$field_data['E5_amount'] = $this->getCA3LineAmount($ca3_data, 'E5');
$field_data['E6_amount'] = $this->getCA3LineAmount($ca3_data, 'E6');
$field_data['F1_amount'] = $this->getCA3LineAmount($ca3_data, 'F1');
$field_data['F2_amount'] = $this->getCA3LineAmount($ca3_data, 'F2');
$field_data['F6_amount'] = $this->getCA3LineAmount($ca3_data, 'F6');
$field_data['F7_amount'] = $this->getCA3LineAmount($ca3_data, 'F7');
$field_data['F8_amount'] = $this->getCA3LineAmount($ca3_data, 'F8');
// Debug: Log the CA-3 data structure for troubleshooting
if (empty($ca3_data)) {
// Use test data for debugging
$ca3_data = array(
'A1' => array('vat_amount' => 1000.00),
'A2' => array('vat_amount' => 2000.00),
'08' => array('base_amount' => 5000.00, 'vat_amount' => 1000.00),
'09' => array('base_amount' => 10000.00, 'vat_amount' => 2000.00),
'20' => array('vat_amount' => 500.00),
'F1' => array('vat_amount' => 300.00),
'F2' => array('vat_amount' => 400.00)
);
}
// Section B: TVA due (Base + VAT columns)
$field_data['B08_base_amount'] = $this->getCA3LineAmount($ca3_data, '08', 'base_amount');
$field_data['B08_vat_amount'] = $this->getCA3LineAmount($ca3_data, '08', 'vat_amount');
$field_data['B09_base_amount'] = $this->getCA3LineAmount($ca3_data, '09', 'base_amount');
$field_data['B09_vat_amount'] = $this->getCA3LineAmount($ca3_data, '09', 'vat_amount');
$field_data['B9B_base_amount'] = $this->getCA3LineAmount($ca3_data, '9B', 'base_amount');
$field_data['B9B_vat_amount'] = $this->getCA3LineAmount($ca3_data, '9B', 'vat_amount');
$field_data['B17_amount'] = $this->getCA3LineAmount($ca3_data, '17');
// Section C: TVA déductible
$field_data['C20_amount'] = $this->getCA3LineAmount($ca3_data, '20');
$field_data['C21_amount'] = $this->getCA3LineAmount($ca3_data, '21');
$field_data['C22_amount'] = $this->getCA3LineAmount($ca3_data, '22');
// Section F: Intracom Acquisitions (already mapped above in Section A)
// Section D: Résultat (Calculated)
$field_data['D25_amount'] = $this->getCA3LineAmount($ca3_data, '25');
$field_data['D26_amount'] = $this->getCA3LineAmount($ca3_data, '26');
$field_data['D27_amount'] = $this->getCA3LineAmount($ca3_data, '27');
$field_data['DTD_amount'] = $this->getCA3LineAmount($ca3_data, 'TD');
$field_data['D28_amount'] = $this->getCA3LineAmount($ca3_data, '28');
$field_data['D32_amount'] = $this->getCA3LineAmount($ca3_data, '32');
// Subtotals
$field_data['subtotal_B16_amount'] = $this->getCA3LineAmount($ca3_data, '16');
$field_data['subtotal_C23_amount'] = $this->getCA3LineAmount($ca3_data, '23');
// Grand totals
$field_data['total_vat_collected'] = $this->formatAmount($declaration->total_vat_collected);
$field_data['total_vat_deductible'] = $this->formatAmount($declaration->total_vat_deductible);
$field_data['net_vat_due'] = $this->formatAmount($declaration->net_vat_due);
$field_data['vat_credit'] = $this->formatAmount($declaration->vat_credit);
return $field_data;
}
/**
* Format amount for PDF display
*
* @param float $amount Amount to format
* @return string Formatted amount
*/
private function formatAmount($amount)
{
return number_format($amount, 0, ',', ' ');
}
/**
* Format amount for display with real values (with decimals)
*
* @param float $amount Amount to format
* @return string Formatted amount with decimals
*/
private function formatAmountReal($amount)
{
return number_format($amount, 2, ',', ' ');
}
/**
* Fill a PDF form field by modifying PDF content
*
* @param string $pdf_content PDF content
* @param string $field_name Field name
* @param string $value Field value
* @return string Modified PDF content
*/
private function fillPDFFormField($pdf_content, $field_name, $value)
{
// Escape special characters in the value
$escaped_value = $this->escapePDFString($value);
// Look for the field in the PDF content and replace its value
// This is a simplified approach that works for many PDF types
// Pattern 1: Look for /T(field_name) and add /V(value) after it
$pattern1 = '/\/T\s*\(\s*' . preg_quote($field_name, '/') . '\s*\)/';
if (preg_match($pattern1, $pdf_content)) {
$pdf_content = preg_replace(
$pattern1,
'/T(' . $field_name . ') /V(' . $escaped_value . ')',
$pdf_content
);
}
// Pattern 2: Look for existing /V values and replace them
$pattern2 = '/\/T\s*\(\s*' . preg_quote($field_name, '/') . '\s*\).*?\/V\s*\([^)]*\)/';
if (preg_match($pattern2, $pdf_content)) {
$pdf_content = preg_replace(
$pattern2,
'/T(' . $field_name . ') /V(' . $escaped_value . ')',
$pdf_content
);
}
return $pdf_content;
}
/**
* Check if pdftk is available on the system
*
* @return bool True if pdftk is available
*/
private function isPdftkAvailable()
{
// Try multiple common locations for pdftk
$possible_paths = array(
'/usr/bin/pdftk',
'/usr/local/bin/pdftk',
'/bin/pdftk',
'/opt/pdftk/bin/pdftk',
'/usr/share/pdftk/bin/pdftk',
'/home/*/bin/pdftk',
'/home/*/.local/bin/pdftk'
);
foreach ($possible_paths as $path) {
if (strpos($path, '*') !== false) {
// Handle glob patterns
$glob_paths = glob($path);
foreach ($glob_paths as $glob_path) {
if (file_exists($glob_path) && is_executable($glob_path)) {
$test_result = shell_exec("$glob_path --version 2>&1");
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return true;
}
}
}
} else {
if (file_exists($path) && is_executable($path)) {
$test_result = shell_exec("$path --version 2>&1");
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return true;
}
}
}
}
// Fallback: try 'which pdftk' in case it's in PATH
$result = shell_exec('which pdftk 2>/dev/null');
if (!empty(trim($result))) {
$test_result = shell_exec(trim($result) . ' --version 2>&1');
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return true;
}
}
return false;
}
/**
* Get the full path to pdftk executable
*
* @return string Path to pdftk executable
*/
private function getPdftkPath()
{
// Try multiple common locations for pdftk
$possible_paths = array(
'/usr/bin/pdftk',
'/usr/local/bin/pdftk',
'/bin/pdftk',
'/opt/pdftk/bin/pdftk',
'/usr/share/pdftk/bin/pdftk',
'/home/*/bin/pdftk',
'/home/*/.local/bin/pdftk'
);
foreach ($possible_paths as $path) {
if (strpos($path, '*') !== false) {
// Handle glob patterns
$glob_paths = glob($path);
foreach ($glob_paths as $glob_path) {
if (file_exists($glob_path) && is_executable($glob_path)) {
$test_result = shell_exec("$glob_path --version 2>&1");
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return $glob_path;
}
}
}
} else {
if (file_exists($path) && is_executable($path)) {
$test_result = shell_exec("$path --version 2>&1");
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return $path;
}
}
}
}
// Fallback: try 'which pdftk' in case it's in PATH
$result = shell_exec('which pdftk 2>/dev/null');
if (!empty(trim($result))) {
$test_result = shell_exec(trim($result) . ' --version 2>&1');
if (!empty($test_result) && strpos($test_result, 'pdftk') !== false) {
return trim($result);
}
}
// Default fallback
return 'pdftk';
}
/**
* Create FDF file for pdftk form filling
*
* @param array $field_data Field data
* @param DeclarationTVA $declaration Declaration object
* @return string FDF file path
*/
private function createFDFFile($field_data, $declaration)
{
$fdf_file = sys_get_temp_dir() . '/ca3_fdf_' . $declaration->declaration_number . '_' . uniqid() . '.fdf';
$fdf_content = "%FDF-1.2\n";
$fdf_content .= "1 0 obj\n";
$fdf_content .= "<<\n";
$fdf_content .= "/FDF\n";
$fdf_content .= "<<\n";
$fdf_content .= "/Fields [\n";
foreach ($field_data as $field_name => $value) {
if (!empty($value)) {
$fdf_content .= "<<\n";
$fdf_content .= "/T (" . $field_name . ")\n";
$fdf_content .= "/V (" . $this->escapeFDFString($value) . ")\n";
// Force Courier New 9pt for official documents
$fdf_content .= "/Ff 0\n"; // Field flags (0 = no special flags)
$fdf_content .= "/F 9\n"; // Font size 9pt
$fdf_content .= "/DA (/Cour 9 Tf 0 g)\n"; // Courier 9pt, black
$fdf_content .= "/Q 0\n"; // Left alignment
$fdf_content .= ">>\n";
}
}
$fdf_content .= "]\n";
$fdf_content .= ">>\n";
$fdf_content .= ">>\n";
$fdf_content .= "endobj\n";
$fdf_content .= "trailer\n";
$fdf_content .= "<<\n";
$fdf_content .= "/Root 1 0 R\n";
$fdf_content .= ">>\n";
$fdf_content .= "%%EOF\n";
file_put_contents($fdf_file, $fdf_content);
return $fdf_file;
}
/**
* Escape string for FDF format
*
* @param string $value Value to escape
* @return string Escaped value
*/
private function escapeFDFString($value)
{
// Escape special FDF characters
$value = str_replace('\\', '\\\\', $value);
$value = str_replace('(', '\\(', $value);
$value = str_replace(')', '\\)', $value);
return $value;
}
/**
* Manual PDF filling fallback (when pdftk is not available)
*
* @param string $template_path Template file path
* @param string $output_path Output file path
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function fillFillablePDFManual($template_path, $output_path, $declaration, $ca3_data, $company)
{
try {
// Simple approach: Just copy the template and create a data file
if (copy($template_path, $output_path)) {
// Create a data file with all the values for manual filling
$data_file = dirname($output_path) . '/ca3_data_' . $declaration->declaration_number . '.txt';
$field_data = $this->prepareFieldData($declaration, $ca3_data, $company);
$data_content = "CA-3 Declaration Data (Manual Filling Required)\n";
$data_content .= "============================================\n\n";
$data_content .= "Note: pdftk is not installed. Please install pdftk for automatic form filling.\n";
$data_content .= "See installation guide: /custom/declarationtva/docs/PDFTK_INSTALLATION.md\n\n";
$data_content .= "Declaration: " . $declaration->declaration_number . "\n";
$data_content .= "Period: " . dol_print_date($declaration->start_date, 'day') . " - " . dol_print_date($declaration->end_date, 'day') . "\n\n";
$data_content .= "Company Information:\n";
$data_content .= "-------------------\n";
$data_content .= "company_name: " . $field_data['company_name'] . "\n";
$data_content .= "company_address: " . $field_data['company_address'] . "\n";
$data_content .= "company_city: " . $field_data['company_city'] . "\n";
$data_content .= "company_postal_code: " . $field_data['company_postal_code'] . "\n";
$data_content .= "company_siret: " . $field_data['company_siret'] . "\n\n";
$data_content .= "Declaration Information:\n";
$data_content .= "------------------------\n";
$data_content .= "declaration_period_start: " . $field_data['declaration_period_start'] . "\n";
$data_content .= "declaration_period_end: " . $field_data['declaration_period_end'] . "\n";
$data_content .= "declaration_number: " . $field_data['declaration_number'] . "\n\n";
$data_content .= "Section A - Opérations imposables:\n";
$data_content .= "----------------------------------\n";
$data_content .= "A1_amount: " . $field_data['A1_amount'] . "\n";
$data_content .= "A2_amount: " . $field_data['A2_amount'] . "\n";
$data_content .= "A3_amount: " . $field_data['A3_amount'] . "\n";
$data_content .= "A4_amount: " . $field_data['A4_amount'] . "\n";
$data_content .= "A5_amount: " . $field_data['A5_amount'] . "\n";
$data_content .= "E1_amount: " . $field_data['E1_amount'] . "\n";
$data_content .= "E2_amount: " . $field_data['E2_amount'] . "\n";
$data_content .= "E3_amount: " . $field_data['E3_amount'] . "\n";
$data_content .= "E4_amount: " . $field_data['E4_amount'] . "\n";
$data_content .= "E5_amount: " . $field_data['E5_amount'] . "\n";
$data_content .= "E6_amount: " . $field_data['E6_amount'] . "\n";
$data_content .= "F1_amount: " . $field_data['F1_amount'] . "\n";
$data_content .= "F2_amount: " . $field_data['F2_amount'] . "\n";
$data_content .= "F6_amount: " . $field_data['F6_amount'] . "\n";
$data_content .= "F7_amount: " . $field_data['F7_amount'] . "\n";
$data_content .= "F8_amount: " . $field_data['F8_amount'] . "\n\n";
$data_content .= "Section B - TVA due:\n";
$data_content .= "--------------------\n";
$data_content .= "B08_base_amount: " . $field_data['B08_base_amount'] . "\n";
$data_content .= "B08_vat_amount: " . $field_data['B08_vat_amount'] . "\n";
$data_content .= "B09_base_amount: " . $field_data['B09_base_amount'] . "\n";
$data_content .= "B09_vat_amount: " . $field_data['B09_vat_amount'] . "\n";
$data_content .= "B9B_base_amount: " . $field_data['B9B_base_amount'] . "\n";
$data_content .= "B9B_vat_amount: " . $field_data['B9B_vat_amount'] . "\n";
$data_content .= "B17_amount: " . $field_data['B17_amount'] . "\n\n";
$data_content .= "Section C - TVA déductible:\n";
$data_content .= "----------------------------\n";
$data_content .= "C20_amount: " . $field_data['C20_amount'] . "\n";
$data_content .= "C21_amount: " . $field_data['C21_amount'] . "\n";
$data_content .= "C22_amount: " . $field_data['C22_amount'] . "\n\n";
$data_content .= "Section D - Résultat:\n";
$data_content .= "---------------------\n";
$data_content .= "D25_amount: " . $field_data['D25_amount'] . "\n";
$data_content .= "D26_amount: " . $field_data['D26_amount'] . "\n";
$data_content .= "DTD_amount: " . $field_data['DTD_amount'] . "\n";
$data_content .= "D28_amount: " . $field_data['D28_amount'] . "\n";
$data_content .= "D29_amount: " . $field_data['D29_amount'] . "\n\n";
$data_content .= "Totals:\n";
$data_content .= "-------\n";
$data_content .= "total_vat_collected: " . $field_data['total_vat_collected'] . "\n";
$data_content .= "total_vat_deductible: " . $field_data['total_vat_deductible'] . "\n";
$data_content .= "net_vat_due: " . $field_data['net_vat_due'] . "\n";
$data_content .= "vat_credit: " . $field_data['vat_credit'] . "\n";
file_put_contents($data_file, $data_content);
// Add detailed pages to the filled PDF
$this->addDetailedPagesToPDF($output_path, $declaration, $ca3_data, $company);
return true;
} else {
$this->error = 'Failed to copy template file';
return false;
}
} catch (Exception $e) {
$this->error = 'Failed to fill PDF template: ' . $e->getMessage();
return false;
}
}
/**
* Generate basic PDF (fallback method)
*
* @param string $output_path Output file path
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function generateBasicPDF($output_path, $declaration, $ca3_data, $company)
{
try {
// Create a new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// Set document information
$pdf->SetCreator('DeclarationTVA Module');
$pdf->SetAuthor($company->name);
$pdf->SetTitle('CA-3 Declaration ' . $declaration->declaration_number);
$pdf->SetSubject('French VAT Declaration');
// Set margins
$pdf->SetMargins(15, 15, 15);
$pdf->SetHeaderMargin(5);
$pdf->SetFooterMargin(10);
// Add a page
$pdf->AddPage();
// Add title
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'Déclaration TVA CA-3', 0, 1, 'C');
$pdf->Ln(10);
// Add declaration information
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 8, 'Numéro de déclaration: ' . $declaration->declaration_number, 0, 1);
$pdf->Cell(0, 8, 'Période: ' . dol_print_date($declaration->start_date, 'day') . ' - ' . dol_print_date($declaration->end_date, 'day'), 0, 1);
$pdf->Cell(0, 8, 'Statut: ' . $this->translateStatus($declaration->status), 0, 1);
$pdf->Ln(10);
// Add CA-3 sections
$this->addCA3Section($pdf, 'A. Opérations imposables', $ca3_data, array('A1', 'A2', 'A3', 'A4', 'A5'));
$this->addCA3Section($pdf, 'B. TVA due', $ca3_data, array('08', '09', '9B', '17'));
$this->addCA3Section($pdf, 'C. TVA déductible', $ca3_data, array('20', '21', '22'));
$this->addCA3Section($pdf, 'D. Résultat', $ca3_data, array('25', '26', '27', 'TD', '28', '32'));
// Add totals
$pdf->Ln(10);
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, 'TOTAL TVA COLLECTÉE: ' . price($declaration->total_vat_collected, 0, '', 1, 0), 0, 1);
$pdf->Cell(0, 8, 'TOTAL TVA DÉDUCTIBLE: ' . price($declaration->total_vat_deductible, 0, '', 1, 0), 0, 1);
$pdf->Cell(0, 8, 'TVA NETTE DUE: ' . price($declaration->net_vat_due, 0, '', 1, 0), 0, 1);
if ($declaration->vat_credit > 0) {
$pdf->Cell(0, 8, 'CRÉDIT DE TVA: ' . price($declaration->vat_credit, 0, '', 1, 0), 0, 1);
}
// Add detailed breakdown pages
$this->addDetailPages($pdf, $declaration, $ca3_data);
// Output PDF
$pdf->Output($output_path, 'F');
return true;
} catch (Exception $e) {
$this->error = 'Basic PDF generation failed: ' . $e->getMessage();
return false;
}
}
/**
* Add CA-3 section to PDF
*
* @param TCPDF $pdf PDF object
* @param string $section_title Section title
* @param array $ca3_data CA-3 data
* @param array $lines Lines to include
*/
private function addCA3Section($pdf, $section_title, $ca3_data, $lines)
{
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, $section_title, 0, 1);
$pdf->SetFont('helvetica', '', 10);
foreach ($lines as $line) {
if (isset($ca3_data[$line])) {
$data = $ca3_data[$line];
$amount = isset($data['vat_amount']) ? $data['vat_amount'] : 0;
// Add link to detail page for this line
$link = $pdf->AddLink();
$pdf->SetLink($link, 0, $this->getDetailPageNumber($line));
$pdf->Cell(20, 6, $line, 1, 0, 'C', false, $link);
$pdf->Cell(100, 6, $data['line_label'], 1, 0);
$pdf->Cell(30, 6, price($amount, 0, '', 1, 0), 1, 1, 'R');
}
}
$pdf->Ln(5);
}
/**
* Add detailed breakdown pages to PDF
*
* @param TCPDF $pdf PDF object
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
*/
private function addDetailPages($pdf, $declaration, $ca3_data)
{
// Define calculated lines that should be excluded from detailed breakdown
$calculated_lines = array('25', '26', '27', 'TD', '28', '32', '16', '23');
// Define the order of lines as they appear in the view page
$ordered_lines = array(
// Section A: Opérations imposables
'A1', 'A2', 'A3', 'A4', 'A5', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'F1', 'F2', 'F6', 'F7', 'F8',
// Section B: TVA due
'08', '09', '9B', '17',
// Section C: TVA déductible
'19', '20', '21', '22'
);
// Create a lookup array for quick access to line data
$ca3_lookup = array();
foreach ($ca3_data as $line_data) {
$ca3_lookup[$line_data['ca3_line']] = $line_data;
}
// Get lines that have actual values and are not calculated lines, in the correct order
$lines_with_data = array();
foreach ($ordered_lines as $line_code) {
// Skip if line doesn't exist in data
if (!isset($ca3_lookup[$line_code])) {
continue;
}
// Skip calculated lines - they don't have account mappings
if (in_array($line_code, $calculated_lines)) {
continue;
}
$line_data = $ca3_lookup[$line_code];
$has_vat_amount = isset($line_data['vat_amount']) && $line_data['vat_amount'] > 0;
$has_base_amount = isset($line_data['base_amount']) && $line_data['base_amount'] > 0;
$has_total_amount = isset($line_data['total_amount']) && $line_data['total_amount'] > 0;
// Only include lines that have meaningful values and are not calculated
if ($has_vat_amount || $has_base_amount || $has_total_amount) {
$lines_with_data[] = $line_code;
}
}
// If no lines have data, add a message page
if (empty($lines_with_data)) {
$pdf->AddPage();
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'Aucune donnée détaillée disponible', 0, 1, 'C');
$pdf->Ln(10);
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 8, 'Aucune ligne de la déclaration ne contient de données comptables.', 0, 1);
$pdf->Cell(0, 8, 'Veuillez configurer les mappings de comptes dans la section Administration.', 0, 1);
return;
}
// Start line details on page 2
$pdf->AddPage();
// Add a detail section for each line with data in the correct order
foreach ($lines_with_data as $line_code) {
$this->addLineDetailPage($pdf, $declaration, $line_code);
}
}
/**
* Add a detail section for a specific CA-3 line
*
* @param TCPDF $pdf PDF object
* @param DeclarationTVA $declaration Declaration object
* @param string $line_code CA-3 line code
*/
private function addLineDetailPage($pdf, $declaration, $line_code)
{
// Check if we need a new page (if current position is too low)
$current_y = $pdf->GetY();
$page_height = $pdf->getPageHeight() - $pdf->getMargins()['bottom'];
// If we're too close to the bottom, start a new page
if ($current_y > $page_height - 100) {
$pdf->AddPage();
} else {
// Add some space between sections
$pdf->Ln(10);
}
// Set section title with separator
$pdf->SetFont('helvetica', 'B', 16);
$pdf->SetTextColor(0, 0, 0);
$pdf->Cell(0, 8, 'Détail de la ligne ' . $line_code, 0, 1, 'C');
// Add separator line
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line($pdf->getMargins()['left'], $pdf->GetY(), $pdf->getPageWidth() - $pdf->getMargins()['right'], $pdf->GetY());
$pdf->Ln(5);
// Get line details
$line_details = $declaration->getCA3LineDetails($declaration->rowid, $line_code);
if (empty($line_details) || empty($line_details['account_details'])) {
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 8, 'Aucun détail comptable disponible pour cette ligne.', 0, 1);
$pdf->Cell(0, 8, 'Cette ligne peut être calculée automatiquement ou ne pas avoir de mapping de comptes.', 0, 1);
// Still show the calculated value if available
if (!empty($line_details['calculated_line'])) {
$calc = $line_details['calculated_line'];
$pdf->Ln(5);
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, 'Valeur calculée:', 0, 1);
$pdf->SetFont('helvetica', '', 10);
if ($calc->base_amount > 0) {
$pdf->Cell(0, 6, 'Montant de base: ' . price($calc->base_amount, 0, '', 1, 0), 0, 1);
}
if ($calc->vat_amount > 0) {
$pdf->Cell(0, 6, 'Montant de TVA: ' . price($calc->vat_amount, 0, '', 1, 0), 0, 1);
}
if ($calc->total_amount > 0) {
$pdf->Cell(0, 6, 'Total: ' . price($calc->total_amount, 0, '', 1, 0), 0, 1);
}
}
return;
}
// Add line summary
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, 'Résumé de la ligne ' . $line_code, 0, 1);
$pdf->SetFont('helvetica', '', 10);
$pdf->Cell(0, 6, 'Période: ' . dol_print_date($line_details['start_date'], 'day') . ' - ' . dol_print_date($line_details['end_date'], 'day'), 0, 1);
$pdf->Cell(0, 6, 'Nombre de comptes: ' . $line_details['account_count'], 0, 1);
if (!empty($line_details['calculated_line'])) {
$calc = $line_details['calculated_line'];
$pdf->Cell(0, 6, 'Montant calculé: ' . price($calc->vat_amount, 0, '', 1, 0), 0, 1);
}
$pdf->Ln(10);
// Add account details table
$this->addAccountDetailsTable($pdf, $line_details['account_details'], $line_code);
}
/**
* Add account details table to PDF
*
* @param TCPDF $pdf PDF object
* @param array $account_details Account details array
* @param string $line_code CA-3 line code
*/
private function addAccountDetailsTable($pdf, $account_details, $line_code)
{
// Group accounts by type for lines 08, 09, 9B (same as view page)
$base_accounts = array();
$vat_accounts = array();
$other_accounts = array();
foreach ($account_details as $account) {
if (strpos($account['mapping_type'], '_BASE') !== false) {
$base_accounts[] = $account;
} elseif (strpos($account['mapping_type'], '_VAT') !== false) {
$vat_accounts[] = $account;
} else {
$other_accounts[] = $account;
}
}
// Calculate table width and center position
$table_width = 150; // 35 + 80 + 35
$page_width = $pdf->getPageWidth() - $pdf->getMargins()['left'] - $pdf->getMargins()['right'];
$start_x = ($page_width - $table_width) / 2 + $pdf->getMargins()['left'];
// Set X position to center the table
$pdf->SetX($start_x);
// Table header - same as view page: Code compte, Libellé compte, Montant
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell(35, 8, 'Code compte', 1, 0, 'C');
$pdf->Cell(80, 8, 'Libellé compte', 1, 0, 'C');
$pdf->Cell(35, 8, 'Montant', 1, 1, 'C');
$total_base_base = 0;
$total_vat_base = 0;
$total_base_vat = 0;
$total_vat_vat = 0;
$total_base_other = 0;
$total_vat_other = 0;
// Display BASE accounts first (if any)
if (!empty($base_accounts)) {
$pdf->SetFont('helvetica', 'B', 9);
$pdf->Cell(0, 6, 'Comptes de base (ventes)', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 9);
foreach ($base_accounts as $account) {
$total_base_base += $account['base_amount'];
$total_vat_base += $account['vat_amount'];
$pdf->SetX($start_x);
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
$pdf->Cell(35, 6, price($account['base_amount'], 0, '', 1, 0), 1, 1, 'R');
}
// Subtotal for BASE accounts
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetX($start_x);
$pdf->Cell(115, 6, 'Sous-total comptes de base', 1, 0, 'L');
$pdf->Cell(35, 6, price($total_base_base, 0, '', 1, 0), 1, 1, 'R');
$pdf->Ln(2);
}
// Display VAT accounts second (if any)
if (!empty($vat_accounts)) {
$pdf->SetFont('helvetica', 'B', 9);
$pdf->Cell(0, 6, 'Comptes de TVA', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 9);
foreach ($vat_accounts as $account) {
$total_base_vat += $account['base_amount'];
$total_vat_vat += $account['vat_amount'];
$pdf->SetX($start_x);
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
$pdf->Cell(35, 6, price($account['vat_amount'], 0, '', 1, 0), 1, 1, 'R');
}
// Subtotal for VAT accounts
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetX($start_x);
$pdf->Cell(115, 6, 'Sous-total comptes de TVA', 1, 0, 'L');
$pdf->Cell(35, 6, price($total_vat_vat, 0, '', 1, 0), 1, 1, 'R');
$pdf->Ln(2);
}
// Display other accounts (normal lines)
if (!empty($other_accounts)) {
$pdf->SetFont('helvetica', '', 9);
foreach ($other_accounts as $account) {
$total_base_other += $account['base_amount'];
$total_vat_other += $account['vat_amount'];
$pdf->SetX($start_x);
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
$pdf->Cell(35, 6, price($account['vat_amount'], 0, '', 1, 0), 1, 1, 'R');
}
// Total for other accounts
if (!empty($other_accounts)) {
$pdf->SetFont('helvetica', 'B', 9);
$pdf->SetX($start_x);
$pdf->Cell(115, 6, 'Total', 1, 0, 'L');
$pdf->Cell(35, 6, price($total_vat_other, 0, '', 1, 0), 1, 1, 'R');
}
}
}
/**
* Add detailed pages to an existing PDF
*
* @param string $pdf_path Path to the existing PDF
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function addDetailedPagesToPDF($pdf_path, $declaration, $ca3_data, $company)
{
try {
// Create a temporary detailed PDF
$temp_detailed_path = tempnam(sys_get_temp_dir(), 'ca3_detailed_') . '.pdf';
$result = $this->generateDetailedPDF($temp_detailed_path, $declaration, $ca3_data, $company);
if (!$result) {
return false;
}
// Check if detailed PDF was created and has content
if (!file_exists($temp_detailed_path) || filesize($temp_detailed_path) == 0) {
return false;
}
// Use a different approach: use pdftk to merge PDFs (preserves form fields)
$merge_result = $this->mergePDFsWithPdftk($pdf_path, $temp_detailed_path, $pdf_path);
if (!$merge_result) {
// Fallback: try FPDI merge
$merge_result = $this->mergePDFs($pdf_path, $temp_detailed_path, $pdf_path);
if (!$merge_result) {
return false;
}
}
// Clean up temporary file
if (file_exists($temp_detailed_path)) {
unlink($temp_detailed_path);
}
return true;
} catch (Exception $e) {
$this->error = 'Failed to add detailed pages: ' . $e->getMessage();
return false;
}
}
/**
* Merge two PDFs using pdftk (preserves form fields)
*
* @param string $pdf1_path First PDF path
* @param string $pdf2_path Second PDF path
* @param string $output_path Output PDF path
* @return bool Success
*/
private function mergePDFsWithPdftk($pdf1_path, $pdf2_path, $output_path)
{
try {
// Check if pdftk is available
if (!$this->isPdftkAvailable()) {
$this->error = 'pdftk is not available for PDF merging';
return false;
}
// Use pdftk to merge PDFs (preserves form fields)
// Create a temporary output file to avoid pdftk input/output filename conflict
$temp_output_path = tempnam(sys_get_temp_dir(), 'ca3_merged_') . '.pdf';
$pdftk_path = $this->getPdftkPath();
$command = "\"$pdftk_path\" \"$pdf1_path\" \"$pdf2_path\" cat output \"$temp_output_path\"";
$result = shell_exec($command . ' 2>&1');
// If pdftk succeeded, copy the merged file to the final output path
if (file_exists($temp_output_path) && filesize($temp_output_path) > 0) {
copy($temp_output_path, $output_path);
unlink($temp_output_path); // Clean up temporary file
}
// Check if output file was created successfully
if (file_exists($output_path) && filesize($output_path) > 0) {
return true;
} else {
$this->error = 'pdftk failed to merge PDFs: ' . $result;
return false;
}
} catch (Exception $e) {
$this->error = 'Failed to merge PDFs with pdftk: ' . $e->getMessage();
return false;
}
}
/**
* Merge two PDFs using FPDI with TCPDF
*
* @param string $pdf1_path First PDF path
* @param string $pdf2_path Second PDF path
* @param string $output_path Output PDF path
* @return bool Success
*/
private function mergePDFs($pdf1_path, $pdf2_path, $output_path)
{
try {
// Use TCPDF-based FPDI
$pdf = new \setasign\Fpdi\Tcpdf\Fpdi();
// Import pages from first PDF
$page_count1 = $pdf->setSourceFile($pdf1_path);
for ($i = 1; $i <= $page_count1; $i++) {
$pdf->AddPage();
$pdf->useTemplate($pdf->importPage($i));
}
// Import pages from second PDF
$page_count2 = $pdf->setSourceFile($pdf2_path);
for ($i = 1; $i <= $page_count2; $i++) {
$pdf->AddPage();
$pdf->useTemplate($pdf->importPage($i));
}
// Output merged PDF
$pdf->Output($output_path, 'F');
return true;
} catch (Exception $e) {
$this->error = 'Failed to merge PDFs: ' . $e->getMessage();
return false;
}
}
/**
* Translate status to French
*
* @param string $status Status in English
* @return string Status in French
*/
private function translateStatus($status)
{
$translations = array(
'draft' => 'Brouillon',
'validated' => 'Validé',
'submitted' => 'Soumis',
'approved' => 'Approuvé',
'rejected' => 'Rejeté'
);
return isset($translations[$status]) ? $translations[$status] : ucfirst($status);
}
/**
* Get mapping type label for display
*
* @param string $mapping_type Mapping type (e.g., '08_BASE', '08_VAT')
* @return string Display label
*/
private function getMappingTypeLabel($mapping_type)
{
if (strpos($mapping_type, '_BASE') !== false) {
return 'Base';
} elseif (strpos($mapping_type, '_VAT') !== false) {
return 'TVA';
} else {
return 'Standard';
}
}
/**
* Truncate text to fit in table cell
*
* @param string $text Text to truncate
* @param int $max_length Maximum length
* @return string Truncated text
*/
private function truncateText($text, $max_length)
{
if (strlen($text) <= $max_length) {
return $text;
}
return substr($text, 0, $max_length - 3) . '...';
}
/**
* Get detail page number for a line (for linking)
*
* @param string $line_code CA-3 line code
* @return int Page number
*/
private function getDetailPageNumber($line_code)
{
// This is a simplified approach - in a real implementation,
// you'd track page numbers as you create them
return 2; // Start from page 2 (after main form)
}
/**
* Generate detailed PDF with breakdown pages
*
* @param string $output_path Output file path
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
* @param Societe $company Company object
* @return bool Success
*/
private function generateDetailedPDF($output_path, $declaration, $ca3_data, $company)
{
try {
// Create a new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
// Set document information
$pdf->SetCreator('DeclarationTVA Module');
$pdf->SetAuthor($company->name);
$pdf->SetTitle('CA-3 Declaration Details ' . $declaration->declaration_number);
$pdf->SetSubject('French VAT Declaration - Detailed Breakdown');
// Set margins
$pdf->SetMargins(15, 15, 15);
$pdf->SetHeaderMargin(5);
$pdf->SetFooterMargin(10);
// Add title page
$pdf->AddPage();
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'Détails de la Déclaration TVA CA-3', 0, 1, 'C');
$pdf->Ln(10);
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 8, 'Numéro de déclaration: ' . $declaration->declaration_number, 0, 1);
$pdf->Cell(0, 8, 'Période: ' . dol_print_date($declaration->start_date, 'day') . ' - ' . dol_print_date($declaration->end_date, 'day'), 0, 1);
$pdf->Cell(0, 8, 'Statut: ' . $this->translateStatus($declaration->status), 0, 1);
$pdf->Ln(10);
// Add journal entry table on page 1
$this->addJournalEntryTable($pdf, $declaration, $ca3_data);
// Add detailed breakdown pages starting on page 2
$this->addDetailPages($pdf, $declaration, $ca3_data);
// Output PDF
$pdf->Output($output_path, 'F');
return true;
} catch (Exception $e) {
$this->error = 'Detailed PDF generation failed: ' . $e->getMessage();
return false;
}
}
/**
* Upload custom template
*
* @param array $file Uploaded file array
* @return bool Success
*/
public function uploadCustomTemplate($file)
{
if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
$this->error = 'No file uploaded';
return false;
}
// Validate file type
$file_info = pathinfo($file['name']);
if (strtolower($file_info['extension']) !== 'pdf') {
$this->error = 'Only PDF files are allowed';
return false;
}
// Validate file size (max 10MB)
if ($file['size'] > 10 * 1024 * 1024) {
$this->error = 'File too large (max 10MB)';
return false;
}
// Ensure template directory exists
if (!is_dir($this->template_path)) {
if (!dol_mkdir($this->template_path)) {
$this->error = 'Failed to create template directory';
return false;
}
}
// Check directory permissions
if (!is_writable($this->template_path)) {
$this->error = 'Template directory is not writable';
return false;
}
// Move uploaded file
$target_path = $this->template_path . 'ca3_custom_template.pdf';
// Remove existing custom template if it exists
if (file_exists($target_path)) {
unlink($target_path);
}
if (move_uploaded_file($file['tmp_name'], $target_path)) {
// Verify the file was saved correctly
if (file_exists($target_path) && filesize($target_path) > 0) {
return true;
} else {
$this->error = 'Template file was not saved correctly';
return false;
}
} else {
$last_error = error_get_last();
$this->error = 'Failed to save template: ' . ($last_error ? $last_error['message'] : 'Unknown error');
return false;
}
}
/**
* Get template information
*
* @return array Template information
*/
public function getTemplateInfo()
{
$info = array(
'version' => $this->template_version,
'document' => $this->template_document,
'official_number' => $this->template_document . '*' . $this->template_version,
'custom_template' => false,
'template_path' => ''
);
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
if (file_exists($custom_template)) {
$info['custom_template'] = true;
$info['template_path'] = $custom_template;
}
return $info;
}
/**
* Reset to default template
*
* @return bool Success
*/
public function resetToDefaultTemplate()
{
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
if (file_exists($custom_template)) {
return unlink($custom_template);
}
return true;
}
/**
* Check for template updates from Gitea
*
* @return array Update information
*/
public function checkTemplateUpdates()
{
$update_info = array(
'update_available' => false,
'current_version' => $this->template_version,
'latest_version' => $this->template_version,
'download_url' => '',
'release_date' => '',
'error' => ''
);
try {
// Try to fetch manifest from Gitea, fallback to local if not accessible
$manifest_content = @file_get_contents($this->manifest_url);
if ($manifest_content === false) {
// Fallback to local manifest file
$local_manifest = DOL_DOCUMENT_ROOT.'/custom/declarationtva/templates/manifest.json';
if (file_exists($local_manifest)) {
$manifest_content = file_get_contents($local_manifest);
// Set a note that we're using local manifest
$update_info['error'] = 'Using local manifest (Gitea server not accessible - repository may be private or server configuration issue)';
}
if ($manifest_content === false) {
$update_info['error'] = 'Failed to fetch manifest from Gitea and local file not found';
return $update_info;
}
}
$manifest = json_decode($manifest_content, true);
if (!$manifest || !isset($manifest['templates']['ca3'])) {
$update_info['error'] = 'Invalid manifest format';
return $update_info;
}
$template_info = $manifest['templates']['ca3'];
$latest_version = $template_info['current_version'];
$current_version = $this->template_version;
// Check if update is available
if (version_compare($latest_version, $current_version, '>')) {
$update_info['update_available'] = true;
$update_info['latest_version'] = $latest_version;
if (isset($template_info['releases'][$latest_version])) {
$release_info = $template_info['releases'][$latest_version];
$update_info['download_url'] = $release_info['download_url'];
$update_info['release_date'] = $release_info['release_date'];
}
}
} catch (Exception $e) {
$update_info['error'] = 'Error checking updates: ' . $e->getMessage();
}
return $update_info;
}
/**
* Download and install template update
*
* @param string $version Version to download
* @param string $download_url Download URL
* @return bool Success
*/
public function downloadTemplateUpdate($version, $download_url)
{
try {
// Ensure template directory exists
if (!is_dir($this->template_path)) {
dol_mkdir($this->template_path);
}
// Try to download template from Gitea, fallback to local if not accessible
$template_content = @file_get_contents($download_url);
if ($template_content === false) {
// Fallback to local template file
$local_template = DOL_DOCUMENT_ROOT.'/custom/declarationtva/templates/declarationtva/ca3_official_template.pdf';
if (file_exists($local_template)) {
$template_content = file_get_contents($local_template);
// Note that we're using local template
$this->error = 'Using local template (Gitea server not accessible - repository may be private or server configuration issue)';
}
if ($template_content === false) {
$this->error = 'Failed to download template from Gitea and local file not found';
return false;
}
}
// Save as official template
$template_file = $this->template_path . 'ca3_official_template.pdf';
if (file_put_contents($template_file, $template_content) === false) {
$this->error = 'Failed to save downloaded template';
return false;
}
// Update version info
$this->template_version = $version;
return true;
} catch (Exception $e) {
$this->error = 'Error downloading template: ' . $e->getMessage();
return false;
}
}
/**
* Get template update status
*
* @return array Status information
*/
public function getTemplateUpdateStatus()
{
$status = array(
'current_version' => $this->template_version,
'update_available' => false,
'latest_version' => $this->template_version,
'last_check' => '',
'error' => ''
);
// Check for updates
$update_info = $this->checkTemplateUpdates();
if ($update_info['update_available']) {
$status['update_available'] = true;
$status['latest_version'] = $update_info['latest_version'];
}
if (!empty($update_info['error'])) {
$status['error'] = $update_info['error'];
}
// Store last check time
$status['last_check'] = date('Y-m-d H:i:s');
return $status;
}
/**
* Auto-update template if available
*
* @return bool Success
*/
public function autoUpdateTemplate()
{
$update_info = $this->checkTemplateUpdates();
if ($update_info['update_available'] && !empty($update_info['download_url'])) {
return $this->downloadTemplateUpdate(
$update_info['latest_version'],
$update_info['download_url']
);
}
return true; // No update needed
}
/**
* Get CA-3 line amount from the data array
*
* @param array $ca3_data CA-3 data array
* @param string $ca3_line Line identifier (e.g., 'A1', '08', '17')
* @param string $amount_type Amount type ('vat_amount' or 'base_amount')
* @return string Formatted amount
*/
private function getCA3LineAmount($ca3_data, $ca3_line, $amount_type = 'vat_amount')
{
if (empty($ca3_data) || !is_array($ca3_data)) {
return '0,00';
}
$calculated_amount = null;
$mapped_amount = null;
// Search through the array for the matching ca3_line
foreach ($ca3_data as $line_data) {
if (isset($line_data['ca3_line']) && $line_data['ca3_line'] == $ca3_line) {
$amount = isset($line_data[$amount_type]) ? $line_data[$amount_type] : 0;
// Prioritize calculated entries over "No accounts mapped" entries
if (isset($line_data['line_label']) && strpos($line_data['line_label'], 'Calculated') !== false) {
$calculated_amount = $amount;
} elseif (isset($line_data['line_label']) && strpos($line_data['line_label'], 'No accounts mapped') === false) {
$mapped_amount = $amount;
} else {
// This is a "No accounts mapped" entry, only use if no other option
if ($calculated_amount === null && $mapped_amount === null) {
$mapped_amount = $amount;
}
}
}
}
// Return calculated amount if available, otherwise mapped amount, otherwise 0
$final_amount = $calculated_amount !== null ? $calculated_amount :
($mapped_amount !== null ? $mapped_amount : 0);
return $this->formatAmount($final_amount);
}
/**
* Format SIRET number with special spacing
* Adds space between every character, with 3 extra spaces before last 5 characters
*
* @param string $siret SIRET number
* @return string Formatted SIRET
*/
private function formatSiret($siret)
{
// Remove any existing spaces or special characters
$siret = preg_replace('/[^0-9]/', '', $siret);
// If SIRET is not configured or too short, return as is
if (empty($siret) || $siret === 'SIRET_NOT_CONFIGURED' || strlen($siret) < 5) {
return $siret;
}
// Split into parts: main part and last 5 characters
$main_part = substr($siret, 0, -5);
$last_5 = substr($siret, -5);
// Add space between every character in main part
$formatted_main = implode(' ', str_split($main_part));
// Add space between every character in last 5, with 3 extra spaces before
$formatted_last = ' ' . implode(' ', str_split($last_5));
return $formatted_main . $formatted_last;
}
/**
* Format VAT number with special spacing
* Adds space between every character, with 2 extra spaces after first 2 chars and 2 extra spaces after first 4 chars
*
* @param string $vat_number VAT number
* @return string Formatted VAT number
*/
private function formatVatNumber($vat_number)
{
// Remove any existing spaces or special characters
$vat_number = preg_replace('/[^A-Z0-9]/', '', strtoupper($vat_number));
// If VAT number is not configured or too short, return as is
if (empty($vat_number) || strlen($vat_number) < 4) {
return $vat_number;
}
$result = '';
$length = strlen($vat_number);
for ($i = 0; $i < $length; $i++) {
$result .= $vat_number[$i];
// Add space between every character
if ($i < $length - 1) {
$result .= ' ';
}
// After first 2 characters, add 2 extra spaces
if ($i == 1) {
$result .= ' ';
}
// After first 4 characters, add 2 extra spaces
if ($i == 3) {
$result .= ' ';
}
}
return $result;
}
/**
* Add journal entry table to PDF
*
* @param TCPDF $pdf PDF object
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_data CA-3 line data
*/
private function addJournalEntryTable($pdf, $declaration, $ca3_data)
{
// Add some space before the journal table
$pdf->Ln(10);
// Title
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'Écritures du Journal OD', 0, 1, 'C');
$pdf->Ln(10);
// Create lookup array for CA-3 data
$ca3_lookup = array();
foreach ($ca3_data as $line_data) {
$ca3_lookup[$line_data['ca3_line']] = $line_data;
}
// Get journal entries
$journal_entries = $this->generateJournalEntries($declaration, $ca3_lookup);
if (empty($journal_entries)) {
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 8, 'Aucune écriture à générer.', 0, 1);
return;
}
// Table header
$pdf->SetFont('helvetica', 'B', 8);
$pdf->SetFillColor(240, 240, 240);
// Column widths - reduced and centered
$col_widths = array(20, 60, 40, 20, 20);
$col_headers = array('Code compte', 'Libellé compte', 'Libellé écriture', 'Débit', 'Crédit');
// Calculate table width and center position
$table_width = array_sum($col_widths);
$page_width = $pdf->getPageWidth() - $pdf->getMargins()['left'] - $pdf->getMargins()['right'];
$start_x = ($page_width - $table_width) / 2 + $pdf->getMargins()['left'];
// Set X position to center the table
$pdf->SetX($start_x);
// Draw header
for ($i = 0; $i < count($col_headers); $i++) {
$pdf->Cell($col_widths[$i], 8, $col_headers[$i], 1, 0, 'C', true);
}
$pdf->Ln();
// Table content
$pdf->SetFont('helvetica', '', 7);
$pdf->SetFillColor(255, 255, 255);
foreach ($journal_entries as $entry) {
// Set X position to center the table for each row
$pdf->SetX($start_x);
$pdf->Cell($col_widths[0], 6, $entry['account_code'], 1, 0, 'C');
$pdf->Cell($col_widths[1], 6, $entry['account_label'], 1, 0, 'L');
$pdf->Cell($col_widths[2], 6, $entry['entry_label'], 1, 0, 'L');
$pdf->Cell($col_widths[3], 6, $entry['debit'], 1, 0, 'R');
$pdf->Cell($col_widths[4], 6, $entry['credit'], 1, 0, 'R');
$pdf->Ln();
}
}
/**
* Generate journal entries based on CA-3 data
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_lookup CA-3 data lookup array
* @return array Journal entries
*/
private function generateJournalEntries($declaration, $ca3_lookup)
{
$entries = array();
// Get line 8 VAT accounts (debit side, non-zero only)
$line8_entries = $this->getLine8VATAccounts($declaration, $ca3_lookup);
$entries = array_merge($entries, $line8_entries);
// Get line 20 accounts (credit side, non-zero only)
$line20_entries = $this->getLine20Accounts($declaration, $ca3_lookup);
$entries = array_merge($entries, $line20_entries);
// Add balancing entry to ensure debits equal credits
$balancing_entries = $this->getBalancingEntries($declaration, $entries);
foreach ($balancing_entries as $entry) {
if ($entry) {
$entries[] = $entry;
}
}
return $entries;
}
/**
* Get line 8 VAT accounts for debit side
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_lookup CA-3 data lookup array
* @return array Journal entries
*/
private function getLine8VATAccounts($declaration, $ca3_lookup)
{
$entries = array();
if (!isset($ca3_lookup['08'])) {
return $entries;
}
$line8_data = $ca3_lookup['08'];
$line8_details = $declaration->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' => $this->formatAmountReal($account['vat_amount']),
'credit' => ''
);
}
}
return $entries;
}
/**
* Get line 20 accounts for credit side
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_lookup CA-3 data lookup array
* @return array Journal entries
*/
private function getLine20Accounts($declaration, $ca3_lookup)
{
$entries = array();
if (!isset($ca3_lookup['20'])) {
return $entries;
}
$line20_data = $ca3_lookup['20'];
$line20_details = $declaration->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' => '',
'credit' => $this->formatAmountReal($account['vat_amount'])
);
}
}
return $entries;
}
/**
* Get VAT result entry on account 4455100 or 4456700
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_lookup CA-3 data lookup array
* @return array|null Journal entry
*/
private function getVATResultEntry($declaration, $ca3_lookup)
{
// Get TD line amount (Line 16 - Line 23)
$line_16_amount = $this->getLineAmount($declaration, '16');
$line_23_amount = $this->getLineAmount($declaration, '23');
$td_amount = $line_16_amount - $line_23_amount;
error_log("DeclarationTVA: TD amount: " . $td_amount);
// If TD = 0, use line 27 value and account 4456700
if (abs($td_amount) < 0.01) {
$line27_amount = $this->getLineAmount($declaration, '27');
error_log("DeclarationTVA: TD is 0, using line 27 amount: " . $line27_amount);
if (abs($line27_amount) < 0.01) {
return null;
}
$entry = array(
'account_code' => '4456700',
'account_label' => $this->getAccountLabel('4456700'),
'entry_label' => $declaration->declaration_name,
'debit' => '',
'credit' => ''
);
// Put line 23 amount on debit side
$line23_amount = $this->getLineAmount($declaration, '23');
if ($line23_amount > 0) {
$entry['debit'] = $this->formatAmount($line23_amount);
}
return $entry;
}
// If TD > 0, use TD value and account 4455100 (existing logic)
$entry = array(
'account_code' => '4455100',
'account_label' => $this->getAccountLabel('4455100'),
'entry_label' => $declaration->declaration_name,
'debit' => '',
'credit' => ''
);
if ($td_amount < 0) {
$entry['debit'] = $this->formatAmount(abs($td_amount));
} else {
$entry['credit'] = $this->formatAmount($td_amount);
}
return $entry;
}
/**
* Get rounding difference entry
*
* @param DeclarationTVA $declaration Declaration object
* @param array $ca3_lookup CA-3 data lookup array
* @return array|null Journal entry
*/
private function getRoundingEntry($declaration, $ca3_lookup)
{
// Calculate rounding difference
$total_vat_due = 0;
$total_vat_deductible = 0;
// Sum all VAT due amounts
foreach ($ca3_lookup as $line_data) {
if (in_array($line_data['ca3_line'], array('08', '09', '9B', '17'))) {
$total_vat_due += $line_data['vat_amount'];
}
}
// Sum all VAT deductible amounts
foreach ($ca3_lookup as $line_data) {
if (in_array($line_data['ca3_line'], array('19', '20', '21', '22'))) {
$total_vat_deductible += $line_data['vat_amount'];
}
}
$vat_result = $total_vat_due - $total_vat_deductible;
$rounded_result = round($vat_result);
$rounding_diff = $vat_result - $rounded_result;
if (abs($rounding_diff) < 0.01) {
return null; // No rounding difference
}
$entry = array(
'account_code' => '',
'account_label' => '',
'entry_label' => $declaration->declaration_name,
'debit' => '',
'credit' => ''
);
if ($rounding_diff < 0) {
$entry['account_code'] = '658000';
$entry['account_label'] = 'Charges exceptionnelles';
$entry['debit'] = $this->formatAmountReal(abs($rounding_diff));
} else {
$entry['account_code'] = '758000';
$entry['account_label'] = 'Produits exceptionnels';
$entry['credit'] = $this->formatAmountReal($rounding_diff);
}
return $entry;
}
/**
* Get balancing entries (main balancing + rounding)
*
* @param DeclarationTVA $declaration Declaration object
* @param array $entries Current journal entries
* @return array Array of balancing entries
*/
private function getBalancingEntries($declaration, $entries)
{
$total_debits = 0;
$total_credits = 0;
// Calculate totals from existing entries
foreach ($entries as $entry) {
if (!empty($entry['debit'])) {
$total_debits += $this->parseAmount($entry['debit']);
}
if (!empty($entry['credit'])) {
$total_credits += $this->parseAmount($entry['credit']);
}
}
$difference = $total_debits - $total_credits;
// If difference is very small (less than 0.01), no balancing entry needed
if (abs($difference) < 0.01) {
return array();
}
$balancing_entries = array();
// Use TD line calculation (Line 16 - Line 23) as main balancing amount
$line_16_amount = $this->getLineAmount($declaration, '16');
$line_23_amount = $this->getLineAmount($declaration, '23');
$td_amount = $line_16_amount - $line_23_amount;
// If TD <= 0, use line 26 or 27 (whichever is not 0) and account 4456700 on debit side
if ($td_amount <= 0) {
$line26_amount = $this->getLineAmount($declaration, '26');
$line27_amount = $this->getLineAmount($declaration, '27');
// Use whichever line is not 0
$selected_amount = 0;
if (abs($line26_amount) >= 0.01) {
$selected_amount = $line26_amount;
} elseif (abs($line27_amount) >= 0.01) {
$selected_amount = $line27_amount;
}
if (abs($selected_amount) >= 0.01) {
$balancing_entries[] = array(
'account_code' => '4456700',
'account_label' => $this->getAccountLabel('4456700'),
'entry_label' => $declaration->declaration_name,
'debit' => $this->formatAmount($selected_amount),
'credit' => ''
);
}
} else {
// If TD > 0, use TD value and account 4455100
$balancing_entries[] = array(
'account_code' => '4455100',
'account_label' => $this->getAccountLabel('4455100'),
'entry_label' => $declaration->declaration_name,
'debit' => '',
'credit' => $this->formatAmount(abs($td_amount))
);
}
// Add final balancing entry to ensure debits equal credits
$final_debits = 0;
$final_credits = 0;
// Calculate totals including the main balancing entries we just added
foreach ($balancing_entries as $entry) {
if (!empty($entry['debit'])) {
$final_debits += $this->parseAmount($entry['debit']);
}
if (!empty($entry['credit'])) {
$final_credits += $this->parseAmount($entry['credit']);
}
}
// Add the existing entries to the calculation
foreach ($entries as $entry) {
if (!empty($entry['debit'])) {
$final_debits += $this->parseAmount($entry['debit']);
}
if (!empty($entry['credit'])) {
$final_credits += $this->parseAmount($entry['credit']);
}
}
$final_difference = $final_debits - $final_credits;
// If there's still a difference, add final balancing entry
if (abs($final_difference) >= 0.01) {
if ($final_difference > 0) {
// More debits, need credit entry on 758000
$balancing_entries[] = array(
'account_code' => '758000',
'account_label' => $this->getAccountLabel('758000'),
'entry_label' => $declaration->declaration_name,
'debit' => '',
'credit' => $this->formatAmountReal($final_difference)
);
} else {
// More credits, need debit entry on 658000
$balancing_entries[] = array(
'account_code' => '658000',
'account_label' => $this->getAccountLabel('658000'),
'entry_label' => $declaration->declaration_name,
'debit' => $this->formatAmountReal(abs($final_difference)),
'credit' => ''
);
}
}
return $balancing_entries;
}
/**
* Get line amount for a specific CA-3 line
*
* @param DeclarationTVA $declaration Declaration object
* @param string $line_code CA-3 line code
* @return float Line amount
*/
private function getLineAmount($declaration, $line_code)
{
$sql = "SELECT vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines
WHERE declaration_id = " . $declaration->rowid . "
AND ca3_line = '" . $this->db->escape($line_code) . "'";
$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;
}
/**
* Get VAT amount from line TD
*
* @param DeclarationTVA $declaration Declaration object
* @return float VAT amount from line TD
*/
private function getVATAmountFromLineTD($declaration)
{
$sql = "SELECT vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines
WHERE declaration_id = " . $declaration->rowid . "
AND ca3_line = 'TD'
AND entity = " . $this->entity;
error_log("DeclarationTVA: TD line query: " . $sql);
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
error_log("DeclarationTVA: TD line found with vat_amount: " . $obj->vat_amount);
return (float)$obj->vat_amount;
}
error_log("DeclarationTVA: TD line not found or has no rows");
return 0;
}
/**
* Parse amount string back to float
*
* @param string $amount_string Formatted amount string
* @return float Parsed amount
*/
private function parseAmount($amount_string)
{
// Remove spaces and convert comma to dot
$amount_string = str_replace(' ', '', $amount_string);
$amount_string = str_replace(',', '.', $amount_string);
return (float)$amount_string;
}
/**
* Get account label from chart of accounts
*
* @param string $account_code Account code
* @return string Account label
*/
private function getAccountLabel($account_code)
{
// Map the hardcoded account codes to the actual account numbers in the database
$account_mapping = array(
'4455100' => '44551', // TVA A DECAISSER
'4456700' => '44567', // TVA A PAYER
'658000' => '658', // AUTRES CHARGES DE GESTION COURANTE
'758000' => '758' // AUTRES PRODUITS DE GESTION COURANT
);
// Use mapped account code if available, otherwise use original
$search_code = isset($account_mapping[$account_code]) ? $account_mapping[$account_code] : $account_code;
// Use the same approach as getAccountMappings() in the main class
// Order by rowid ASC to get the oldest entry (custom uppercase labels)
$sql = "SELECT a.label
FROM " . MAIN_DB_PREFIX . "accounting_account a
WHERE a.account_number = '" . $this->db->escape($search_code) . "'
AND a.entity = " . $this->entity . "
ORDER BY a.rowid ASC
LIMIT 1";
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
return $obj->label;
}
// If not found with entity, try without entity filter
$sql = "SELECT a.label
FROM " . MAIN_DB_PREFIX . "accounting_account a
WHERE a.account_number = '" . $this->db->escape($search_code) . "'
ORDER BY a.rowid ASC
LIMIT 1";
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
return $obj->label;
}
// If account not found in chart of accounts, return generic label
return 'Compte ' . $account_code;
}
}