DeclarationTVA/core/class/declarationtva_config.class.php
Frank Cools abddcc1c30 Fix duplicate accounting entries settings
- Remove duplicate 'Create accounting entries' toggle from journal configuration
- Use existing 'Auto-create accounting entries' setting for submission workflow
- Clean up unused language strings
- Consolidate accounting entry control into single, clear setting
2025-10-08 21:32:37 +02:00

793 lines
29 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_Config Class
* Configuration management for DeclarationTVA module
* MVP Version - Phase 1
*/
class DeclarationTVA_Config
{
/**
* @var DoliDB Database handler
*/
public $db;
/**
* @var int Entity ID
*/
public $entity;
/**
* @var string Error message
*/
public $error;
/**
* Constructor
*
* @param DoliDB $db Database handler
* @param int $entity Entity ID
*/
public function __construct($db, $entity = 1)
{
$this->db = $db;
$this->entity = $entity;
// Ensure the config table exists
$this->ensureConfigTableExists();
}
/**
* Ensure the declarationtva_config table exists
*
* @return bool Success
*/
private function ensureConfigTableExists()
{
// Check if table exists
$sql = "SHOW TABLES LIKE '" . MAIN_DB_PREFIX . "declarationtva_config'";
$result = $this->db->query($sql);
if (!$result || $this->db->num_rows($result) == 0) {
// Table doesn't exist, create it
$create_sql = "CREATE TABLE IF NOT EXISTS `" . MAIN_DB_PREFIX . "declarationtva_config` (
`rowid` int(11) NOT NULL AUTO_INCREMENT,
`entity` int(11) NOT NULL DEFAULT 1,
`config_key` varchar(64) NOT NULL,
`config_value` text,
`created_date` datetime DEFAULT NULL,
PRIMARY KEY (`rowid`),
UNIQUE KEY `uk_config_entity_key` (`entity`, `config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$create_result = $this->db->query($create_sql);
if (!$create_result) {
return false;
}
// Insert default configuration values
$this->insertDefaultConfigValues();
}
return true;
}
/**
* Insert default configuration values
*
* @return bool Success
*/
private function insertDefaultConfigValues()
{
$default_configs = array(
'module_version' => '2.4.0',
'amount_calc_amount_calculation_method' => 'truncate',
'auto_create_auto_create_accounting' => '0',
'bank_bank_account' => '0',
'journal_vat_to_pay' => '44551',
'journal_vat_to_receive' => '44567',
'journal_other_charges' => '658',
'journal_other_products' => '758'
);
foreach ($default_configs as $key => $value) {
$sql = "INSERT IGNORE INTO " . MAIN_DB_PREFIX . "declarationtva_config
(entity, config_key, config_value, created_date)
VALUES (" . $this->entity . ", '" . $this->db->escape($key) . "',
'" . $this->db->escape($value) . "', NOW())";
$this->db->query($sql);
}
return true;
}
/**
* Get configuration value
*
* @param string $key Configuration key
* @param mixed $default Default value
* @return mixed Configuration value
*/
public function get($key, $default = null)
{
$sql = "SELECT config_value FROM " . MAIN_DB_PREFIX . "declarationtva_config
WHERE entity = " . $this->entity . " AND config_key = '" . $this->db->escape($key) . "'";
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
return $obj->config_value;
}
return $default;
}
/**
* Set configuration value
*
* @param string $key Configuration key
* @param mixed $value Configuration value
* @return bool Success
*/
public function set($key, $value)
{
$sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_config
(entity, config_key, config_value, created_date)
VALUES (" . $this->entity . ", '" . $this->db->escape($key) . "',
'" . $this->db->escape($value) . "', NOW())
ON DUPLICATE KEY UPDATE config_value = '" . $this->db->escape($value) . "'";
$result = $this->db->query($sql);
return $result !== false;
}
/**
* Get account mapping for a CA-3 line
*
* @param string $ca3_line CA-3 line code
* @return array|false Account mapping or false if not found
*/
public function getAccountMapping($ca3_line)
{
$sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_account_mappings
WHERE entity = " . $this->entity . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'";
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
return $this->db->fetch_array($result);
}
return false;
}
/**
* Update account mapping for multiple accounts
*
* @param string $ca3_line CA-3 line code
* @param array $account_codes Array of account codes
* @return bool Success
*/
public function updateAccountMapping($ca3_line, $account_codes)
{
// First, deactivate all existing mappings for this CA-3 line
$sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_account_mappings
SET is_active = 0
WHERE entity = " . $this->entity . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'";
$this->db->query($sql);
// Then insert/activate new mappings (only if account_codes is not empty)
if (!empty($account_codes) && is_array($account_codes)) {
foreach ($account_codes as $account_code) {
if (!empty($account_code)) {
// Check if mapping already exists (active or inactive)
$check_sql = "SELECT rowid, is_active FROM " . MAIN_DB_PREFIX . "declarationtva_account_mappings
WHERE entity = " . $this->entity . "
AND ca3_line = '" . $this->db->escape($ca3_line) . "'
AND account_code = '" . $this->db->escape($account_code) . "'";
$check_result = $this->db->query($check_sql);
if ($check_result && $this->db->num_rows($check_result) > 0) {
// Update existing mapping (reactivate it)
$sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_account_mappings
SET is_active = 1
WHERE entity = " . $this->entity . "
AND ca3_line = '" . $this->db->escape($ca3_line) . "'
AND account_code = '" . $this->db->escape($account_code) . "'";
$this->db->query($sql);
} else {
// Insert new mapping
$sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_account_mappings
(entity, ca3_line, account_code, account_label, vat_rate, is_active, created_date)
VALUES (" . $this->entity . ", '" . $this->db->escape($ca3_line) . "',
'" . $this->db->escape($account_code) . "', '', 0, 1, NOW())";
$this->db->query($sql);
}
}
}
}
// If account_codes is empty, all mappings for this line remain deactivated
// This ensures that clearing all selections properly saves the empty state
return true;
}
/**
* Get all account mappings
*
* @return array Account mappings
*/
public function getAllAccountMappings()
{
$sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_account_mappings
WHERE entity = " . $this->entity . " AND is_active = 1
ORDER BY ca3_line";
$result = $this->db->query($sql);
$mappings = array();
if ($result) {
while ($obj = $this->db->fetch_object($result)) {
$mappings[] = array(
'rowid' => $obj->rowid,
'ca3_line' => $obj->ca3_line,
'account_code' => $obj->account_code,
'account_label' => $obj->account_label,
'vat_rate' => $obj->vat_rate,
'is_active' => $obj->is_active
);
}
}
return $mappings;
}
/**
* Get account mappings grouped by CA-3 line
*
* @return array Account mappings grouped by CA-3 line
*/
public function getAccountMappingsByLine()
{
$sql = "SELECT ca3_line, account_code FROM " . MAIN_DB_PREFIX . "declarationtva_account_mappings
WHERE entity = " . $this->entity . " AND is_active = 1
ORDER BY ca3_line, account_code";
$result = $this->db->query($sql);
$mappings = array();
if ($result) {
while ($obj = $this->db->fetch_object($result)) {
if (!isset($mappings[$obj->ca3_line])) {
$mappings[$obj->ca3_line] = array();
}
$mappings[$obj->ca3_line][] = $obj->account_code;
}
}
return $mappings;
}
/**
* Get available accounting accounts from Dolibarr
*
* @return array Accounting accounts
*/
public function getAccountingAccounts()
{
// Use the same approach as in the view page - check how it's done there
// Order by rowid DESC to get the most recent account for each account number
$sql = "SELECT a.account_number, a.label
FROM " . MAIN_DB_PREFIX . "accounting_account a
WHERE a.entity = " . $this->entity . "
ORDER BY a.account_number, a.rowid DESC";
$result = $this->db->query($sql);
$accounts = array();
if ($result) {
while ($obj = $this->db->fetch_object($result)) {
$accounts[] = array(
'account_number' => $obj->account_number,
'label' => $obj->label
);
}
}
return $accounts;
}
/**
* Get auto-create accounting configuration
*
* @return array Auto-create accounting configuration
*/
public function getAutoCreateAccountingConfiguration()
{
$default_config = array(
'auto_create_accounting' => 0 // Disabled by default
);
$config = array();
foreach ($default_config as $key => $default_value) {
$config[$key] = $this->get('auto_create_' . $key, $default_value);
}
return $config;
}
/**
* Get amount calculation configuration
*
* @return array Amount calculation configuration
*/
public function getAmountCalculationConfiguration()
{
$default_config = array(
'amount_calculation_method' => 'truncate' // 'round' or 'truncate'
);
$config = array();
foreach ($default_config as $key => $default_value) {
$config[$key] = $this->get('amount_calc_' . $key, $default_value);
}
return $config;
}
/**
* Update amount calculation configuration
*
* @param string $method Calculation method ('round' or 'truncate')
* @return bool Success
*/
public function updateAmountCalculationConfiguration($method)
{
if (!in_array($method, array('round', 'truncate'))) {
return false;
}
return $this->set('amount_calc_amount_calculation_method', $method);
}
/**
* Update auto-create accounting configuration
*
* @param int $auto_create_accounting Auto-create accounting flag (0 or 1)
* @return bool Success
*/
public function updateAutoCreateAccountingConfiguration($auto_create_accounting)
{
return $this->set('auto_create_auto_create_accounting', (int)$auto_create_accounting);
}
/**
* Get bank account configuration
*
* @return array Bank account configuration
*/
public function getBankAccountConfiguration()
{
$default_config = array(
'bank_account' => 0 // No bank account selected by default
);
$config = array();
foreach ($default_config as $key => $default_value) {
$value = $this->get('bank_' . $key, $default_value);
$config[$key] = $value;
}
return $config;
}
/**
* Update bank account configuration
*
* @param int $bank_account_id Bank account ID
* @return bool Success
*/
public function updateBankAccountConfiguration($bank_account_id)
{
$result = $this->set('bank_bank_account', (int)$bank_account_id);
return $result;
}
/**
* Get available bank accounts
*
* @return array Bank accounts
*/
public function getBankAccounts()
{
$sql = "SELECT rowid, label, number, bank
FROM " . MAIN_DB_PREFIX . "bank_account
WHERE entity = " . $this->entity . "
AND clos = 0
ORDER BY label";
$result = $this->db->query($sql);
$accounts = array();
if ($result) {
while ($obj = $this->db->fetch_object($result)) {
$accounts[] = array(
'rowid' => $obj->rowid,
'label' => $obj->label,
'number' => $obj->number,
'bank' => $obj->bank
);
}
}
return $accounts;
}
/**
* Get CA-3 line definitions (Notice 4722 - Latest Official Structure)
*
* @return array CA-3 line definitions
*/
public function getCA3LineDefinitions()
{
return array(
// A. Opérations imposables (Taxable Operations) - Notice 4722
'A1' => array(
'label' => 'Ventes, prestations de services',
'type' => 'base',
'section' => 'A',
'description' => 'Ventes, prestations de services',
'pcg_accounts' => 'Sales: 7xxxx; VAT: 44571x / 44572x / 44573x / 44574x'
),
'A2' => array(
'label' => 'Autres opérations imposables',
'type' => 'base',
'section' => 'A',
'description' => 'Autres opérations imposables',
'pcg_accounts' => '775xxx (gains on disposal), 72xxx (production immobilisée), VAT: 44571x'
),
'A3' => array(
'label' => 'Achats de prestations de services réalisés auprès d\'un assujetti non établi en France (article 283-2 du code général des impôts)',
'type' => 'base',
'section' => 'A',
'description' => 'Achats de prestations de services réalisés auprès d\'un assujetti non établi en France (article 283-2 du code général des impôts)',
'pcg_accounts' => '6xxxx (services purchased), 4452xxx (TVA due autoliquidée)'
),
'A4' => array(
'label' => 'Importations (autres que les produits pétroliers)',
'type' => 'base',
'section' => 'A',
'description' => 'Importations (autres que les produits pétroliers)',
'pcg_accounts' => '6xxxx / 2xxxx purchases or assets; VAT: 4452xxx (import VAT)'
),
'A5' => array(
'label' => 'Sorties de régime fiscal suspensif (autres que les produits pétroliers) et sorties de régime particulier douanier uniquement lorsque des livraisons ont eu lieu en cours de régime',
'type' => 'base',
'section' => 'A',
'description' => 'Sorties de régime fiscal suspensif (autres que les produits pétroliers) et sorties de régime particulier douanier uniquement lorsque des livraisons ont eu lieu en cours de régime',
'pcg_accounts' => 'Specific 6xxxx/2xxxx depending on goods; VAT: 4452xxx'
),
// B. Décompte de la TVA due (VAT Due Calculation) - Notice 4722
'08' => array(
'label' => 'TVA due au taux de 20% (Base HT + TVA)',
'type' => 'base_vat',
'section' => 'B',
'description' => 'Base HT and VAT amounts due at 20% rate',
'pcg_accounts' => 'Base: 7xxxx (Sales); VAT: 44571x (TVA collectée à 20%)'
),
'09' => array(
'label' => 'TVA due au taux de 10% (Base HT + TVA)',
'type' => 'base_vat',
'section' => 'B',
'description' => 'Base HT and VAT amounts due at 10% rate',
'pcg_accounts' => 'Base: 7xxxx (Sales); VAT: 44572x (TVA collectée à 10%)'
),
'9B' => array(
'label' => 'TVA due aux taux réduits (Base HT + TVA)',
'type' => 'base_vat',
'section' => 'B',
'description' => 'Base HT and VAT amounts due at reduced rates (5,5% and 2,1%)',
'pcg_accounts' => 'Base: 7xxxx (Sales); VAT: 44573x (5,5%) / 44574x (2,1%)'
),
'17' => array(
'label' => 'TVA due au titre des acquisitions intracommunautaires (autoliquidation)',
'type' => 'vat',
'section' => 'B',
'description' => 'VAT due on intra-EU acquisitions (autoliquidation)',
'pcg_accounts' => '4452xxx (TVA due intracommunautaire)'
),
'18' => array(
'label' => 'Dont TVA sur opérations à destination de Monaco',
'type' => 'vat',
'section' => 'B',
'description' => 'VAT on operations to Monaco',
'pcg_accounts' => '4452xxx (TVA due Monaco)'
),
'19' => array(
'label' => 'Biens constituant des immobilisations',
'type' => 'vat',
'section' => 'B',
'description' => 'Goods constituting fixed assets',
'pcg_accounts' => '44562x (TVA déductible immobilisations)'
),
'20' => array(
'label' => 'Autres biens et services',
'type' => 'vat',
'section' => 'B',
'description' => 'Other goods and services',
'pcg_accounts' => '44566x (TVA déductible autres)'
),
// Additional deductible VAT lines
'21' => array(
'label' => 'Autre TVA à déduire',
'type' => 'vat',
'section' => 'B',
'description' => 'Other deductible VAT',
'pcg_accounts' => '44566x (Autre TVA à déduire)'
),
'22' => array(
'label' => 'Report du crédit apparaissant ligne 27 de la précédente déclaration',
'type' => 'vat',
'section' => 'B',
'description' => 'Carry forward of credit appearing on line 27 of the previous declaration',
'pcg_accounts' => '44567x (Report crédit TVA)'
),
// E. Export and Non-Taxable Operations (part of Section A) - Notice 4722
'E1' => array(
'label' => 'Exportations hors UE',
'type' => 'base',
'section' => 'A',
'description' => 'Exportations hors UE',
'pcg_accounts' => '7xxxx (exports), 4458xxx (TVA export)'
),
'E2' => array(
'label' => 'Autres opérations non imposables',
'type' => 'base',
'section' => 'A',
'description' => 'Autres opérations non imposables',
'pcg_accounts' => '7xxxx (exempt sales), 4458xxx (TVA exempt)'
),
'E3' => array(
'label' => 'Ventes à distance taxables dans un autre État membre au profit des personnes non assujetties Ventes B to C',
'type' => 'base',
'section' => 'A',
'description' => 'Ventes à distance taxables dans un autre État membre au profit des personnes non assujetties Ventes B to C',
'pcg_accounts' => '7xxxx (distance sales), 4458xxx (TVA distance)'
),
'E4' => array(
'label' => 'Importations (autres que les produits pétroliers)',
'type' => 'base',
'section' => 'A',
'description' => 'Importations (autres que les produits pétroliers)',
'pcg_accounts' => '6xxxx (imports), 4452xxx (TVA import)'
),
'E5' => array(
'label' => 'Sorties de régime fiscal suspensif (autres que les produits pétroliers)',
'type' => 'base',
'section' => 'A',
'description' => 'Sorties de régime fiscal suspensif (autres que les produits pétroliers)',
'pcg_accounts' => '6xxxx (suspensive regime exits), 4452xxx (TVA suspensive)'
),
'E6' => array(
'label' => 'Importations placées sous régime fiscal suspensif (autres que les produits pétroliers)',
'type' => 'base',
'section' => 'A',
'description' => 'Importations placées sous régime fiscal suspensif (autres que les produits pétroliers)',
'pcg_accounts' => '6xxxx (suspensive regime imports), 4452xxx (TVA suspensive)'
),
// F. Intracom Operations (part of Section A) - Notice 4722
'F1' => array(
'label' => 'Acquisitions intracommunautaires',
'type' => 'base',
'section' => 'A',
'description' => 'Acquisitions intracommunautaires',
'pcg_accounts' => '6xxxx (purchases from EU), 2xxxx (assets from EU)'
),
'F2' => array(
'label' => 'Livraisons intracommunautaires à destination d\'une personne assujettie Ventes B to B',
'type' => 'vat',
'section' => 'A',
'description' => 'Livraisons intracommunautaires à destination d\'une personne assujettie Ventes B to B',
'pcg_accounts' => '7xxxx (sales to EU), 4452xxx (TVA due autoliquidée sur livraisons intra-EU)'
),
'F6' => array(
'label' => 'Achats en franchise',
'type' => 'base',
'section' => 'A',
'description' => 'Achats en franchise',
'pcg_accounts' => '6xxxx (franchise purchases), 4458xxx (TVA franchise)'
),
'F7' => array(
'label' => 'Ventes de biens ou prestations de services réalisées par un assujetti non établi en France (article 283-1 du code général des impôts)',
'type' => 'base',
'section' => 'A',
'description' => 'Ventes de biens ou prestations de services réalisées par un assujetti non établi en France (article 283-1 du code général des impôts)',
'pcg_accounts' => '7xxxx (non-established sales), 4452xxx (TVA non-established)'
),
'F8' => array(
'label' => 'Régularisations (important : cf. notice)',
'type' => 'base',
'section' => 'A',
'description' => 'Régularisations (important : cf. notice)',
'pcg_accounts' => 'Various (adjustments), 445xxx (TVA adjustments)'
),
// D. Résultat (Result) - Notice 4722
'25' => array(
'label' => 'TVA brute due (Total TVA due)',
'type' => 'vat',
'section' => 'D',
'description' => 'Total VAT due (sum of all VAT due amounts)',
'pcg_accounts' => 'Sum of 08 + 09 + 9B + 17'
),
'26' => array(
'label' => 'TVA déductible totale (Total deductible VAT)',
'type' => 'vat',
'section' => 'D',
'description' => 'Total deductible VAT (sum of all deductible VAT amounts)',
'pcg_accounts' => 'Sum of 20 + 21 + 22'
),
'28' => array(
'label' => 'TVA nette à payer',
'type' => 'vat',
'section' => 'D',
'description' => 'Net VAT to pay (if total due > total deductible)',
'pcg_accounts' => '445510 (État - TVA à décaisser)'
),
'29' => array(
'label' => 'Crédit de TVA à reporter ou remboursement',
'type' => 'vat',
'section' => 'D',
'description' => 'VAT credit to carry forward or refund (if total deductible > total due)',
'pcg_accounts' => '445670 (État - Crédit de TVA à reporter ou rembourser)'
)
);
}
/**
* Get CA-3 section headers (Notice 4722)
*
* @return array Section headers
*/
public function getCA3SectionHeaders()
{
return array(
'A' => array(
'title' => 'A. MONTANT DES OPÉRATIONS RÉALISÉES',
'description' => '',
'notice' => ''
),
'B' => array(
'title' => 'B. DÉCOMPTE DE LA TVA À PAYER',
'description' => '',
'notice' => ''
)
);
}
/**
* Get supported VAT rates
*
* @return array VAT rates
*/
public function getVATRates()
{
return array(
'20.00' => '20%',
'10.00' => '10%',
'5.50' => '5.5%',
'2.10' => '2.1%',
'0.00' => '0%'
);
}
/**
* Validate account mapping
*
* @param string $ca3_line CA-3 line code
* @param string $account_code Account code
* @return bool Valid
*/
public function validateAccountMapping($ca3_line, $account_code)
{
// Check if account exists in Dolibarr
$sql = "SELECT COUNT(*) as count FROM " . MAIN_DB_PREFIX . "accounting_account
WHERE account_number = '" . $this->db->escape($account_code) . "' AND active = 1";
$result = $this->db->query($sql);
if ($result && $this->db->num_rows($result) > 0) {
$obj = $this->db->fetch_object($result);
return $obj->count > 0;
}
return false;
}
/**
* Get journal configuration
*
* @return array Journal configuration
*/
public function getJournalConfiguration()
{
$default_config = array(
'vat_to_pay' => '44551', // TVA A DECAISSER
'vat_to_receive' => '44567', // TVA A PAYER (carry forward)
'vat_refund' => '445671', // TVA A REMBOURSER (immediate refund)
'other_charges' => '658', // AUTRES CHARGES DE GESTION COURANTE
'other_products' => '758' // AUTRES PRODUITS DE GESTION COURANT
);
$config = array();
foreach ($default_config as $key => $default_value) {
$config[$key] = $this->get('journal_' . $key, $default_value);
}
return $config;
}
/**
* Get VAT refund threshold configuration
*
* @return array VAT refund threshold configuration
*/
public function getVATRefundThresholdConfiguration()
{
$default_config = array(
'refund_threshold' => 760.00, // Threshold for immediate refund vs carry forward
'refund_threshold_enabled' => 1 // Enable/disable threshold logic
);
$config = array();
foreach ($default_config as $key => $default_value) {
$config[$key] = $this->get('vat_refund_' . $key, $default_value);
}
return $config;
}
/**
* Update VAT refund threshold configuration
*
* @param array $threshold_config VAT refund threshold configuration
* @return bool Success
*/
public function updateVATRefundThresholdConfiguration($threshold_config)
{
foreach ($threshold_config as $key => $value) {
$config_key = 'vat_refund_' . $key;
error_log("Saving VAT refund threshold config: " . $config_key . " = " . $value);
$result = $this->set($config_key, $value);
if (!$result) {
error_log("Failed to save VAT refund threshold config: " . $config_key . " = " . $value);
return false;
}
}
return true;
}
/**
* Update journal configuration
*
* @param array $journal_accounts Journal account configuration
* @return bool Success
*/
public function updateJournalConfiguration($journal_accounts)
{
$success = true;
foreach ($journal_accounts as $key => $account_code) {
if (!$this->set('journal_' . $key, $account_code)) {
$success = false;
}
}
return $success;
}
}