DeclarationTVA/core/class/declarationtva.class.php
Frank Cools b305c3d90b Remove debug logging and clean up code
- Removed all error_log debug statements from production code
- Cleaned up hasValidatedDocument method
- Cleaned up declaration list processing
- Code is now production-ready
- PDF detection and generation are working correctly
2025-10-06 17:43:25 +02:00

1366 lines
52 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* DeclarationTVA Class
* French CA-3 VAT Declaration Module for Dolibarr
* MVP Version - Phase 1
*/
class DeclarationTVA
{
/**
* @var DoliDB Database handler
*/
public $db;
/**
* @var int Entity ID
*/
public $entity;
/**
* @var string Error message
*/
public $error;
/**
* @var int Declaration ID
*/
public $rowid;
/**
* @var int Period ID
*/
public $period_id;
/**
* @var string Declaration number
*/
public $declaration_number;
/**
* @var string Declaration name
*/
public $declaration_name;
/**
* @var string Start date
*/
public $start_date;
/**
* @var string End date
*/
public $end_date;
/**
* @var string Status
*/
public $status;
/**
* Constructor
*
* @param DoliDB $db Database handler
* @param int $entity Entity ID
*/
public function __construct($db, $entity = 1)
{
$this->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
));
}
// Line 27: Crédit de TVA à reporter (ligne 25 ligne 26) - same value as line 25, only if > 0 and < 760
$line_27_amount = $line_25_amount; // Same value as line 25
if ($line_27_amount > 0 && $line_27_amount < 760) {
$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 26: Remboursement de crédit de TVA demandé sur formulaire n°3519 - same value as line 25, only if >= 760
$line_26_amount = $line_25_amount; // Same value as line 25
if ($line_26_amount >= 760) {
$this->createCA3Line($declaration_id, '26', 'Remboursement de crédit de TVA demandé sur formulaire n°3519', array(
'base_amount' => 0,
'vat_amount' => $line_26_amount,
'total_amount' => $line_26_amount
));
} else {
$this->createCA3Line($declaration_id, '26', 'Remboursement de crédit de TVA demandé sur formulaire n°3519', 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)
{
// Round amounts to whole numbers
$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;
}
/**
* Submit declaration
*
* @param int $declaration_id Declaration ID
* @return bool Success
*/
public function submitDeclaration($declaration_id)
{
$sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations
SET status = 'submitted', submission_date = NOW()
WHERE rowid = " . $declaration_id . " AND entity = " . $this->entity;
$result = $this->db->query($sql);
return $result !== false;
}
/**
* 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 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)
{
// 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) {
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);
}
return 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)
);
}
/**
* 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 documents directory structure
$vat_declarations_dir = DOL_DATA_ROOT . '/documents/declarationtva/';
$year_dir = $vat_declarations_dir . date('Y') . '/';
$month_dir = $year_dir . date('m') . '/';
// Create directories if they don'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;
}
}
if (!is_dir($year_dir)) {
if (!dol_mkdir($year_dir)) {
$this->error = "Failed to create year directory: " . $year_dir;
return false;
}
}
if (!is_dir($month_dir)) {
if (!dol_mkdir($month_dir)) {
$this->error = "Failed to create month directory: " . $month_dir;
return false;
}
}
// Check if PDF is already in the correct location
if (strpos($pdf_path, $month_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 = $month_dir . $filename;
// Copy PDF to documents directory
if (!copy($pdf_path, $dest_path)) {
$this->error = "Failed to copy PDF to documents 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
error_log("DeclarationTVA: Using ECM files path: " . $ecmfiles_path);
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
error_log("DeclarationTVA: Failed to link document to declaration: " . $this->error);
}
} else {
// Log the error but don't fail the whole process
error_log("DeclarationTVA: Failed to create ECM file record: " . $ecmfile->error);
}
} catch (Exception $e) {
// Log the error but don't fail the whole process
error_log("DeclarationTVA: Exception creating ECM file: " . $e->getMessage());
}
} else {
// Log that ECM files class is not available
error_log("DeclarationTVA: ECM files class not found. Tried paths: " . implode(', ', $ecmfiles_paths));
}
}
// 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 directory
$vat_declarations_dir = DOL_DATA_ROOT . '/documents/declarationtva/';
$year_dir = $vat_declarations_dir . date('Y') . '/';
$month_dir = $year_dir . date('m') . '/';
// Look for PDF files with the declaration number
$pattern = $month_dir . 'CA3_' . $obj->declaration_number . '_*.pdf';
$files = glob($pattern);
// If no files found with the exact pattern, try a more flexible search
if (empty($files)) {
$pattern = $month_dir . 'CA3_*' . $obj->declaration_number . '*.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 directory
$vat_declarations_dir = DOL_DATA_ROOT . '/documents/declarationtva/';
$year_dir = $vat_declarations_dir . date('Y') . '/';
$month_dir = $year_dir . date('m') . '/';
// Look for PDF files with the declaration number
$pattern = $month_dir . 'CA3_' . $obj->declaration_number . '_*.pdf';
$files = glob($pattern);
// If no files found with the exact pattern, try a more flexible search
if (empty($files)) {
$pattern = $month_dir . 'CA3_*' . $obj->declaration_number . '*.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 . '/';
}
}