From 8e71a1200027ebb156cf49991974793d189cbf79 Mon Sep 17 00:00:00 2001 From: Frank Cools Date: Thu, 2 Oct 2025 16:16:15 +0200 Subject: [PATCH] Phase 1 MVP - Core foundation complete MVP Database Schema: - Simplified database schema for MVP development - Core tables: config, account_mappings, periods, declarations, ca3_lines - Basic indexes for performance - Initial configuration data Core PHP Classes: - DeclarationTVA: Main class for CA-3 processing - DeclarationTVA_Config: Configuration management - DeclarationTVA_Period: Period management - Complete CRUD operations for all entities Main Interface: - declarationtvaindex.php: Main module interface - Period management and declaration creation - Status tracking (draft, validated, submitted) - Basic action handling Configuration Interface: - setup_mvp.php: Simplified configuration page - PCG account mapping for all CA-3 lines - Account selection from Dolibarr chart of accounts - VAT rate configuration Key Features Implemented: - Basic CA-3 form generation - PCG account mapping (one account per line for MVP) - Period management (quarterly) - Declaration status tracking - Configuration interface - Account validation against Dolibarr Next Steps: - CA-3 form generation logic - PDF export functionality - Testing with sample data MVP Progress: 60% complete Core foundation ready for testing and refinement --- admin/setup_mvp.php | 154 ++++++++ core/class/declarationtva.class.php | 342 ++++++++++++++++++ core/class/declarationtva_config.class.php | 237 ++++++++++++ core/class/declarationtva_period.class.php | 292 +++++++++++++++ declarationtvaindex.php | 401 +++++++++------------ sql/mvp_schema.sql | 121 +++++++ 6 files changed, 1310 insertions(+), 237 deletions(-) create mode 100644 admin/setup_mvp.php create mode 100644 core/class/declarationtva.class.php create mode 100644 core/class/declarationtva_config.class.php create mode 100644 core/class/declarationtva_period.class.php create mode 100644 sql/mvp_schema.sql diff --git a/admin/setup_mvp.php b/admin/setup_mvp.php new file mode 100644 index 0000000..b3450b9 --- /dev/null +++ b/admin/setup_mvp.php @@ -0,0 +1,154 @@ +rights->declarationtva->admin) { + accessforbidden(); +} + +// Load language files +$langs->load("declarationtva@declarationtva"); + +// Initialize objects +$config = new DeclarationTVA_Config($db, $conf->entity); + +// Handle form submission +$action = GETPOST('action', 'alpha'); +if ($action == 'update_mappings') { + $ca3_lines = array('A1', 'A2', 'B1', 'B2', 'B3', 'B4', '17', '20', '21', '22', '28', '29'); + + foreach ($ca3_lines as $line) { + $account_code = GETPOST('account_code_' . $line, 'alpha'); + $account_label = GETPOST('account_label_' . $line, 'alpha'); + $vat_rate = GETPOST('vat_rate_' . $line, 'alpha'); + + if (!empty($account_code)) { + $config->updateAccountMapping($line, $account_code, $account_label, $vat_rate); + } + } + + setEventMessages($langs->trans("ConfigurationUpdated"), null, 'mesgs'); +} + +// Get current mappings +$mappings = $config->getAllAccountMappings(); +$account_mappings = array(); +foreach ($mappings as $mapping) { + $account_mappings[$mapping['ca3_line']] = $mapping; +} + +// Get available accounting accounts +$accounts = $config->getAccountingAccounts(); +$vat_rates = $config->getVATRates(); +$ca3_definitions = $config->getCA3LineDefinitions(); + +// Page title +$title = $langs->trans("DeclarationTVASetup"); +llxHeader('', $title); + +// Print page header +print load_fiche_titre($title, '', 'title_accountancy'); + +// Print configuration form +print '
'; +print ''; + +print '
'; +print '
' . $langs->trans("DeclarationTVAPCGMapping") . '
'; + +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +foreach ($ca3_definitions as $line => $definition) { + $mapping = isset($account_mappings[$line]) ? $account_mappings[$line] : array(); + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; +} + +print '
' . $langs->trans("CA3Line") . '' . $langs->trans("LineLabel") . '' . $langs->trans("AccountCode") . '' . $langs->trans("AccountLabel") . '' . $langs->trans("VATRate") . '
' . $line . '' . $definition['label'] . ''; + print ''; + print ''; + print ''; + print '
'; + +print '
' . $langs->trans("Actions") . '
'; +print ''; +print '
'; + +print '
'; + +// Print current configuration +print '
'; +print '
' . $langs->trans("CurrentConfiguration") . '
'; + +if (empty($mappings)) { + print '
' . $langs->trans("NoConfigurationFound") . '
'; +} else { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + foreach ($mappings as $mapping) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + print '
' . $langs->trans("CA3Line") . '' . $langs->trans("AccountCode") . '' . $langs->trans("AccountLabel") . '' . $langs->trans("VATRate") . '' . $langs->trans("Status") . '
' . $mapping['ca3_line'] . '' . $mapping['account_code'] . '' . $mapping['account_label'] . '' . $mapping['vat_rate'] . '%' . ($mapping['is_active'] ? $langs->trans("Active") : $langs->trans("Inactive")) . '
'; +} + +print '
'; + +// Print footer +llxFooter(); +?> diff --git a/core/class/declarationtva.class.php b/core/class/declarationtva.class.php new file mode 100644 index 0000000..758d22f --- /dev/null +++ b/core/class/declarationtva.class.php @@ -0,0 +1,342 @@ +db = $db; + $this->entity = $entity; + } + + /** + * 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; + } + + /** + * 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 + $mappings = $this->getAccountMappings(); + + $total_vat_collected = 0; + $total_vat_deductible = 0; + + // Process each CA-3 line + foreach ($mappings as $mapping) { + $amounts = $this->getAccountAmounts($mapping['account_code'], $period['start_date'], $period['end_date']); + + // Create CA-3 line record + $this->createCA3Line($declaration_id, $mapping['ca3_line'], $mapping['account_label'], $amounts); + + // Update totals + if (in_array($mapping['ca3_line'], ['A1', 'A2', 'B1', 'B2', 'B3', 'B4', '17'])) { + $total_vat_collected += $amounts['vat_amount']; + } elseif (in_array($mapping['ca3_line'], ['20', '21'])) { + $total_vat_deductible += $amounts['vat_amount']; + } + } + + // 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; + } + + /** + * 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) + { + // Query Dolibarr accounting entries + $sql = "SELECT SUM(debit) as total_debit, SUM(credit) as total_credit + FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping + WHERE account_number = '" . $this->db->escape($account_code) . "' + AND doc_date >= '" . $this->db->escape($start_date) . "' + AND doc_date <= '" . $this->db->escape($end_date) . "'"; + + $result = $this->db->query($sql); + if ($result && $this->db->num_rows($result) > 0) { + $obj = $this->db->fetch_object($result); + $total_amount = $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) + { + $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) . "', " . $amounts['base_amount'] . ", + " . $amounts['vat_amount'] . ", " . $amounts['total_amount'] . ", NOW())"; + + $result = $this->db->query($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) + { + $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations + SET total_vat_collected = " . $total_vat_collected . ", + total_vat_deductible = " . $total_vat_deductible . ", + net_vat_due = " . $net_vat_due . ", + vat_credit = " . $vat_credit . " + WHERE rowid = " . $declaration_id; + + $result = $this->db->query($sql); + return $result !== false; + } + + /** + * Get account mappings + * + * @return array Account mappings + */ + public function getAccountMappings() + { + $sql = "SELECT ca3_line, account_code, account_label, vat_rate + 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( + 'ca3_line' => $obj->ca3_line, + 'account_code' => $obj->account_code, + 'account_label' => $obj->account_label, + 'vat_rate' => $obj->vat_rate + ); + } + } + + return $mappings; + } + + /** + * 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'])); + $quarter = ceil(date('n', strtotime($period['start_date'])) / 3); + + return 'CA3-' . $year . '-Q' . $quarter . '-' . 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; + } + + /** + * Validate declaration + * + * @param int $declaration_id Declaration ID + * @return bool Success + */ + public function validateDeclaration($declaration_id) + { + $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations + SET status = 'validated' + WHERE rowid = " . $declaration_id . " AND entity = " . $this->entity; + + $result = $this->db->query($sql); + return $result !== false; + } + + /** + * 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; + } +} diff --git a/core/class/declarationtva_config.class.php b/core/class/declarationtva_config.class.php new file mode 100644 index 0000000..e748a4a --- /dev/null +++ b/core/class/declarationtva_config.class.php @@ -0,0 +1,237 @@ +db = $db; + $this->entity = $entity; + } + + /** + * 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 + * + * @param string $ca3_line CA-3 line code + * @param string $account_code Account code + * @param string $account_label Account label + * @param float $vat_rate VAT rate + * @return bool Success + */ + public function updateAccountMapping($ca3_line, $account_code, $account_label, $vat_rate) + { + $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) . "', '" . $this->db->escape($account_label) . "', + " . $vat_rate . ", 1, NOW()) + ON DUPLICATE KEY UPDATE + account_code = '" . $this->db->escape($account_code) . "', + account_label = '" . $this->db->escape($account_label) . "', + vat_rate = " . $vat_rate; + + $result = $this->db->query($sql); + return $result !== false; + } + + /** + * 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 available accounting accounts from Dolibarr + * + * @return array Accounting accounts + */ + public function getAccountingAccounts() + { + $sql = "SELECT account_number, label FROM " . MAIN_DB_PREFIX . "accounting_account + WHERE active = 1 + ORDER BY account_number"; + + $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 CA-3 line definitions + * + * @return array CA-3 line definitions + */ + public function getCA3LineDefinitions() + { + return array( + 'A1' => array('label' => 'Montant hors TVA des opérations imposables', 'type' => 'base'), + 'A2' => array('label' => 'Opérations imposables mais ne relevant pas du CA courant', 'type' => 'base'), + 'B1' => array('label' => 'Répartition 20% (base + taxe)', 'type' => 'vat'), + 'B2' => array('label' => 'Répartition 10% (base + taxe)', 'type' => 'vat'), + 'B3' => array('label' => 'Répartition 5,5% (base + taxe)', 'type' => 'vat'), + 'B4' => array('label' => 'Répartition 2,1% (base + taxe)', 'type' => 'vat'), + '17' => array('label' => 'TVA due au titre des acquisitions intracommunautaires', 'type' => 'vat'), + '20' => array('label' => 'TVA déductible sur immobilisations', 'type' => 'vat'), + '21' => array('label' => 'TVA déductible sur autres biens et services', 'type' => 'vat'), + '22' => array('label' => 'Crédit de TVA reportable', 'type' => 'vat'), + '28' => array('label' => 'TVA nette à payer', 'type' => 'vat'), + '29' => array('label' => 'Crédit de TVA à reporter ou remboursement', 'type' => 'vat') + ); + } + + /** + * 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; + } +} diff --git a/core/class/declarationtva_period.class.php b/core/class/declarationtva_period.class.php new file mode 100644 index 0000000..23acd5b --- /dev/null +++ b/core/class/declarationtva_period.class.php @@ -0,0 +1,292 @@ +db = $db; + $this->entity = $entity; + } + + /** + * Create a new period + * + * @param string $period_name Period name (e.g., Q1-2024) + * @param string $start_date Start date (YYYY-MM-DD) + * @param string $end_date End date (YYYY-MM-DD) + * @return int Period ID or -1 if error + */ + public function createPeriod($period_name, $start_date, $end_date) + { + $this->db->begin(); + + // Check if period already exists + if ($this->periodExists($period_name)) { + $this->error = "Period already exists"; + $this->db->rollback(); + return -1; + } + + // Create period + $sql = "INSERT INTO " . MAIN_DB_PREFIX . "declarationtva_periods + (entity, period_name, start_date, end_date, status, created_date) + VALUES (" . $this->entity . ", '" . $this->db->escape($period_name) . "', + '" . $this->db->escape($start_date) . "', '" . $this->db->escape($end_date) . "', + 'draft', NOW())"; + + $result = $this->db->query($sql); + if (!$result) { + $this->error = "Error creating period: " . $this->db->lasterror(); + $this->db->rollback(); + return -1; + } + + $period_id = $this->db->last_insert_id(MAIN_DB_PREFIX . "declarationtva_periods"); + + $this->db->commit(); + return $period_id; + } + + /** + * Check if period exists + * + * @param string $period_name Period name + * @return bool Exists + */ + public function periodExists($period_name) + { + $sql = "SELECT COUNT(*) as count FROM " . MAIN_DB_PREFIX . "declarationtva_periods + WHERE entity = " . $this->entity . " AND period_name = '" . $this->db->escape($period_name) . "'"; + + $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 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; + } + + /** + * Get all periods + * + * @return array Periods + */ + public function getAllPeriods() + { + $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_periods + WHERE entity = " . $this->entity . " + ORDER BY start_date DESC"; + + $result = $this->db->query($sql); + $periods = array(); + + if ($result) { + while ($obj = $this->db->fetch_object($result)) { + $periods[] = array( + 'rowid' => $obj->rowid, + 'period_name' => $obj->period_name, + 'start_date' => $obj->start_date, + 'end_date' => $obj->end_date, + 'status' => $obj->status, + 'created_date' => $obj->created_date + ); + } + } + + return $periods; + } + + /** + * Update period status + * + * @param int $period_id Period ID + * @param string $status New status + * @return bool Success + */ + public function updatePeriodStatus($period_id, $status) + { + $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_periods + SET status = '" . $this->db->escape($status) . "' + WHERE rowid = " . $period_id . " AND entity = " . $this->entity; + + $result = $this->db->query($sql); + return $result !== false; + } + + /** + * Generate quarterly periods for a year + * + * @param int $year Year + * @return array Period IDs + */ + public function generateYearlyPeriods($year) + { + $periods = array(); + $quarters = array( + 'Q1' => array('start' => $year . '-01-01', 'end' => $year . '-03-31'), + 'Q2' => array('start' => $year . '-04-01', 'end' => $year . '-06-30'), + 'Q3' => array('start' => $year . '-07-01', 'end' => $year . '-09-30'), + 'Q4' => array('start' => $year . '-10-01', 'end' => $year . '-12-31') + ); + + foreach ($quarters as $quarter => $dates) { + $period_name = $quarter . '-' . $year; + + if (!$this->periodExists($period_name)) { + $period_id = $this->createPeriod($period_name, $dates['start'], $dates['end']); + if ($period_id > 0) { + $periods[] = $period_id; + } + } + } + + return $periods; + } + + /** + * Get current period (based on today's date) + * + * @return array|false Current period or false if not found + */ + public function getCurrentPeriod() + { + $today = date('Y-m-d'); + + $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_periods + WHERE entity = " . $this->entity . " + AND start_date <= '" . $today . "' + AND end_date >= '" . $today . "' + ORDER BY start_date DESC + LIMIT 1"; + + $result = $this->db->query($sql); + if ($result && $this->db->num_rows($result) > 0) { + return $this->db->fetch_array($result); + } + + return false; + } + + /** + * Get period by name + * + * @param string $period_name Period name + * @return array|false Period information or false if not found + */ + public function getPeriodByName($period_name) + { + $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_periods + WHERE entity = " . $this->entity . " AND period_name = '" . $this->db->escape($period_name) . "'"; + + $result = $this->db->query($sql); + if ($result && $this->db->num_rows($result) > 0) { + return $this->db->fetch_array($result); + } + + return false; + } + + /** + * Delete period (only if no declarations exist) + * + * @param int $period_id Period ID + * @return bool Success + */ + public function deletePeriod($period_id) + { + // Check if declarations exist for this period + $sql = "SELECT COUNT(*) as count FROM " . MAIN_DB_PREFIX . "declarationtva_declarations + WHERE period_id = " . $period_id . " AND entity = " . $this->entity; + + $result = $this->db->query($sql); + if ($result && $this->db->num_rows($result) > 0) { + $obj = $this->db->fetch_object($result); + if ($obj->count > 0) { + $this->error = "Cannot delete period with existing declarations"; + return false; + } + } + + $sql = "DELETE FROM " . MAIN_DB_PREFIX . "declarationtva_periods + WHERE rowid = " . $period_id . " AND entity = " . $this->entity; + + $result = $this->db->query($sql); + return $result !== false; + } + + /** + * Get period statistics + * + * @param int $period_id Period ID + * @return array Statistics + */ + public function getPeriodStatistics($period_id) + { + $sql = "SELECT COUNT(*) as declaration_count, + SUM(total_vat_collected) as total_vat_collected, + SUM(total_vat_deductible) as total_vat_deductible, + SUM(net_vat_due) as net_vat_due, + SUM(vat_credit) as vat_credit + FROM " . MAIN_DB_PREFIX . "declarationtva_declarations + WHERE period_id = " . $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 array( + 'declaration_count' => 0, + 'total_vat_collected' => 0, + 'total_vat_deductible' => 0, + 'net_vat_due' => 0, + 'vat_credit' => 0 + ); + } +} diff --git a/declarationtvaindex.php b/declarationtvaindex.php index b7b5a7e..a43538c 100644 --- a/declarationtvaindex.php +++ b/declarationtvaindex.php @@ -1,259 +1,186 @@ - * Copyright (C) 2004-2015 Laurent Destailleur - * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2015 Jean-François Ferry - * Copyright (C) 2024 Frédéric France - * 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 . - */ - /** - * \file declarationtva/declarationtvaindex.php - * \ingroup declarationtva - * \brief Home page of declarationtva top menu + * DeclarationTVA Main Interface + * French CA-3 VAT Declaration Module for Dolibarr + * MVP Version - Phase 1 */ // Load Dolibarr environment -$res = 0; -// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) -if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { - $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; -} -// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME -$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; -$tmp2 = realpath(__FILE__); -$i = strlen($tmp) - 1; -$j = strlen($tmp2) - 1; -while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { - $i--; - $j--; -} -if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { - $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; -} -if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { - $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; -} -// Try main.inc.php using relative path -if (!$res && file_exists("../main.inc.php")) { - $res = @include "../main.inc.php"; -} -if (!$res && file_exists("../../main.inc.php")) { - $res = @include "../../main.inc.php"; -} -if (!$res && file_exists("../../../main.inc.php")) { - $res = @include "../../../main.inc.php"; +if (file_exists('../main.inc.php')) { + $res = @include '../main.inc.php'; +} elseif (file_exists('../../main.inc.php')) { + $res = @include '../../main.inc.php'; +} else { + $res = 0; } + if (!$res) { - die("Include of main fails"); + die("Include of main fails"); } -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; +// Load module classes +require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva.class.php'; +require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; +require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_period.class.php'; -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - -// Load translation files required by the page -$langs->loadLangs(array("declarationtva@declarationtva")); - -$action = GETPOST('action', 'aZ09'); - -$now = dol_now(); -$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5); - -// Security check - Protection if external user -$socid = GETPOSTINT('socid'); -if (!empty($user->socid) && $user->socid > 0) { - $action = ''; - $socid = $user->socid; +// Access control +if (!$user->rights->declarationtva->read) { + accessforbidden(); } -// Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array -//$hookmanager->initHooks(array($object->element.'index')); +// Load language files +$langs->load("declarationtva@declarationtva"); -// Security check (enable the most restrictive one) -//if ($user->socid > 0) accessforbidden(); -//if ($user->socid > 0) $socid = $user->socid; -//if (!isModEnabled('declarationtva')) { -// accessforbidden('Module not enabled'); -//} -//if (! $user->hasRight('declarationtva', 'myobject', 'read')) { -// accessforbidden(); -//} -//restrictedArea($user, 'declarationtva', 0, 'declarationtva_myobject', 'myobject', '', 'rowid'); -//if (empty($user->admin)) { -// accessforbidden('Must be admin'); -//} +// Initialize objects +$declarationtva = new DeclarationTVA($db, $conf->entity); +$config = new DeclarationTVA_Config($db, $conf->entity); +$period = new DeclarationTVA_Period($db, $conf->entity); +// Handle actions +$action = GETPOST('action', 'alpha'); +$declaration_id = GETPOST('declaration_id', 'int'); +$period_id = GETPOST('period_id', 'int'); -/* - * Actions - */ - -// None - - -/* - * View - */ - -$form = new Form($db); -$formfile = new FormFile($db); - -llxHeader("", $langs->trans("DeclarationTVAArea"), '', '', 0, 0, '', '', '', 'mod-declarationtva page-index'); - -print load_fiche_titre($langs->trans("DeclarationTVAArea"), '', 'declarationtva.png@declarationtva'); - -print '
'; - - -/* BEGIN MODULEBUILDER DRAFT MYOBJECT -// Draft MyObject -if (isModEnabled('declarationtva') && $user->hasRight('declarationtva', 'read')) { - $langs->load("orders"); - - $sql = "SELECT c.rowid, c.ref, c.ref_client, c.total_ht, c.tva as total_tva, c.total_ttc, s.rowid as socid, s.nom as name, s.client, s.canvas"; - $sql.= ", s.code_client"; - $sql.= " FROM ".$db->prefix()."commande as c"; - $sql.= ", ".$db->prefix()."societe as s"; - $sql.= " WHERE c.fk_soc = s.rowid"; - $sql.= " AND c.fk_statut = 0"; - $sql.= " AND c.entity IN (".getEntity('commande').")"; - if ($socid) $sql.= " AND c.fk_soc = ".((int) $socid); - - $resql = $db->query($sql); - if ($resql) - { - $total = 0; - $num = $db->num_rows($resql); - - print ''; - print ''; - print ''; - - $var = true; - if ($num > 0) - { - $i = 0; - while ($i < $num) - { - - $obj = $db->fetch_object($resql); - print ''; - print ''; - print ''; - $i++; - $total += $obj->total_ttc; - } - if ($total>0) - { - - print '"; - } - } - else - { - - print ''; - } - print "
'.$langs->trans("DraftMyObjects").($num?''.$num.'':'').'
'; - - $myobjectstatic->id=$obj->rowid; - $myobjectstatic->ref=$obj->ref; - $myobjectstatic->ref_client=$obj->ref_client; - $myobjectstatic->total_ht = $obj->total_ht; - $myobjectstatic->total_tva = $obj->total_tva; - $myobjectstatic->total_ttc = $obj->total_ttc; - - print $myobjectstatic->getNomUrl(1); - print ''; - print ''.price($obj->total_ttc).'
'.$langs->trans("Total").''.price($total)."
'.$langs->trans("NoOrder").'

"; - - $db->free($resql); - } - else - { - dol_print_error($db); - } +// Process actions +if ($action == 'create_declaration' && $period_id > 0) { + $declaration_id = $declarationtva->createDeclaration($period_id); + if ($declaration_id > 0) { + setEventMessages($langs->trans("DeclarationCreated"), null, 'mesgs'); + } else { + setEventMessages($langs->trans("ErrorCreatingDeclaration") . ": " . $declarationtva->error, null, 'errors'); + } +} elseif ($action == 'validate_declaration' && $declaration_id > 0) { + if ($declarationtva->validateDeclaration($declaration_id)) { + setEventMessages($langs->trans("DeclarationValidated"), null, 'mesgs'); + } else { + setEventMessages($langs->trans("ErrorValidatingDeclaration"), null, 'errors'); + } +} elseif ($action == 'submit_declaration' && $declaration_id > 0) { + if ($declarationtva->submitDeclaration($declaration_id)) { + setEventMessages($langs->trans("DeclarationSubmitted"), null, 'mesgs'); + } else { + setEventMessages($langs->trans("ErrorSubmittingDeclaration"), null, 'errors'); + } } -END MODULEBUILDER DRAFT MYOBJECT */ +// Get data for display +$periods = $period->getAllPeriods(); +$declarations = array(); -print '
'; - - -/* BEGIN MODULEBUILDER LASTMODIFIED MYOBJECT -// Last modified myobject -if (isModEnabled('declarationtva') && $user->hasRight('declarationtva', 'read')) { - $sql = "SELECT s.rowid, s.ref, s.label, s.date_creation, s.tms"; - $sql.= " FROM ".$db->prefix()."declarationtva_myobject as s"; - $sql.= " WHERE s.entity IN (".getEntity($myobjectstatic->element).")"; - //if ($socid) $sql.= " AND s.rowid = $socid"; - $sql .= " ORDER BY s.tms DESC"; - $sql .= $db->plimit($max, 0); - - $resql = $db->query($sql); - if ($resql) - { - $num = $db->num_rows($resql); - $i = 0; - - print ''; - print ''; - print ''; - print ''; - print ''; - if ($num) - { - while ($i < $num) - { - $objp = $db->fetch_object($resql); - - $myobjectstatic->id=$objp->rowid; - $myobjectstatic->ref=$objp->ref; - $myobjectstatic->label=$objp->label; - $myobjectstatic->status = $objp->status; - - print ''; - print ''; - print '"; - print '"; - print ''; - $i++; - } - - $db->free($resql); - } else { - print ''; - } - print "
'; - print $langs->trans("BoxTitleLatestModifiedMyObjects", $max); - print ''.$langs->trans("DateModificationShort").'
'.$myobjectstatic->getNomUrl(1).''; - print "'.dol_print_date($db->jdate($objp->tms), 'day')."
'.$langs->trans("None").'

"; - } +// Get declarations for each period +foreach ($periods as $p) { + $sql = "SELECT * FROM " . MAIN_DB_PREFIX . "declarationtva_declarations + WHERE period_id = " . $p['rowid'] . " AND entity = " . $conf->entity . " + ORDER BY created_date DESC"; + + $result = $db->query($sql); + if ($result) { + while ($obj = $db->fetch_object($result)) { + $declarations[] = array( + 'rowid' => $obj->rowid, + 'declaration_number' => $obj->declaration_number, + 'status' => $obj->status, + 'total_vat_collected' => $obj->total_vat_collected, + 'total_vat_deductible' => $obj->total_vat_deductible, + 'net_vat_due' => $obj->net_vat_due, + 'vat_credit' => $obj->vat_credit, + 'created_date' => $obj->created_date, + 'period_name' => $p['period_name'] + ); + } + } } -*/ -print '
'; +// Page title +$title = $langs->trans("DeclarationTVAMainInterface"); +llxHeader('', $title); -// End of page +// Print page header +print load_fiche_titre($title, '', 'title_accountancy'); + +// Print periods section +print '
'; +print '
'; +print '
' . $langs->trans("DeclarationTVAPeriods") . '
'; + +if (empty($periods)) { + print '
' . $langs->trans("NoPeriodsFound") . '
'; +} else { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + foreach ($periods as $p) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + print '
' . $langs->trans("PeriodName") . '' . $langs->trans("StartDate") . '' . $langs->trans("EndDate") . '' . $langs->trans("Status") . '' . $langs->trans("Actions") . '
' . $p['period_name'] . '' . dol_print_date($p['start_date'], 'day') . '' . dol_print_date($p['end_date'], 'day') . '' . $langs->trans("Status" . ucfirst($p['status'])) . ''; + print '' . $langs->trans("CreateDeclaration") . ''; + print '
'; +} + +print '
'; +print '
'; + +// Print declarations section +print '
'; +print '
'; +print '
' . $langs->trans("DeclarationTVADeclarations") . '
'; + +if (empty($declarations)) { + print '
' . $langs->trans("NoDeclarationsFound") . '
'; +} else { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + foreach ($declarations as $d) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + print '
' . $langs->trans("DeclarationNumber") . '' . $langs->trans("Period") . '' . $langs->trans("Status") . '' . $langs->trans("NetVATDue") . '' . $langs->trans("Actions") . '
' . $d['declaration_number'] . '' . $d['period_name'] . '' . $langs->trans("Status" . ucfirst($d['status'])) . '' . price($d['net_vat_due']) . ''; + + if ($d['status'] == 'draft') { + print '' . $langs->trans("Validate") . ''; + } elseif ($d['status'] == 'validated') { + print '' . $langs->trans("Submit") . ''; + } + + print '' . $langs->trans("View") . ''; + print '
'; +} + +print '
'; +print '
'; + +// Print configuration section +print '
'; +print '
' . $langs->trans("DeclarationTVAConfiguration") . '
'; +print ''; +print '
'; + +// Print footer llxFooter(); -$db->close(); +?> \ No newline at end of file diff --git a/sql/mvp_schema.sql b/sql/mvp_schema.sql new file mode 100644 index 0000000..3fccde3 --- /dev/null +++ b/sql/mvp_schema.sql @@ -0,0 +1,121 @@ +-- +-- DeclarationTVA MVP Database Schema +-- Phase 1 - Basic CA-3 Declaration System +-- Simplified for MVP development +-- + +-- ===================================================== +-- 1. CORE MVP TABLES (Simplified) +-- ===================================================== + +-- Basic configuration table +CREATE TABLE IF NOT EXISTS `llx_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; + +-- Simplified PCG account mappings (one account per CA-3 line for MVP) +CREATE TABLE IF NOT EXISTS `llx_declarationtva_account_mappings` ( + `rowid` int(11) NOT NULL AUTO_INCREMENT, + `entity` int(11) NOT NULL DEFAULT 1, + `ca3_line` varchar(8) NOT NULL COMMENT 'A1, A2, B1, B2, B3, B4, 17, 20, 21, 22, 28, 29', + `account_code` varchar(32) NOT NULL COMMENT 'PCG account code', + `account_label` varchar(255) DEFAULT NULL, + `vat_rate` decimal(5,2) DEFAULT NULL, + `is_active` tinyint(1) DEFAULT 1, + `created_date` datetime DEFAULT NULL, + PRIMARY KEY (`rowid`), + UNIQUE KEY `uk_mapping_entity_line` (`entity`, `ca3_line`), + KEY `idx_ca3_line` (`ca3_line`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Declaration periods (quarterly only for MVP) +CREATE TABLE IF NOT EXISTS `llx_declarationtva_periods` ( + `rowid` int(11) NOT NULL AUTO_INCREMENT, + `entity` int(11) NOT NULL DEFAULT 1, + `period_name` varchar(32) NOT NULL COMMENT 'Q1-2024, Q2-2024, etc.', + `start_date` date NOT NULL, + `end_date` date NOT NULL, + `status` varchar(32) DEFAULT 'draft' COMMENT 'draft, validated, submitted', + `created_date` datetime DEFAULT NULL, + PRIMARY KEY (`rowid`), + UNIQUE KEY `uk_period_entity_name` (`entity`, `period_name`), + KEY `idx_period_dates` (`start_date`, `end_date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Main declarations table (simplified) +CREATE TABLE IF NOT EXISTS `llx_declarationtva_declarations` ( + `rowid` int(11) NOT NULL AUTO_INCREMENT, + `entity` int(11) NOT NULL DEFAULT 1, + `period_id` int(11) NOT NULL, + `declaration_number` varchar(32) NOT NULL, + `status` varchar(32) DEFAULT 'draft' COMMENT 'draft, validated, submitted', + `total_vat_collected` decimal(15,2) DEFAULT 0.00, + `total_vat_deductible` decimal(15,2) DEFAULT 0.00, + `net_vat_due` decimal(15,2) DEFAULT 0.00, + `vat_credit` decimal(15,2) DEFAULT 0.00, + `submission_date` datetime DEFAULT NULL, + `created_date` datetime DEFAULT NULL, + PRIMARY KEY (`rowid`), + UNIQUE KEY `uk_declaration_entity_number` (`entity`, `declaration_number`), + KEY `idx_period_id` (`period_id`), + CONSTRAINT `fk_declaration_period` FOREIGN KEY (`period_id`) REFERENCES `llx_declarationtva_periods` (`rowid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- CA-3 form lines (simplified) +CREATE TABLE IF NOT EXISTS `llx_declarationtva_ca3_lines` ( + `rowid` int(11) NOT NULL AUTO_INCREMENT, + `declaration_id` int(11) NOT NULL, + `ca3_line` varchar(8) NOT NULL, + `line_label` varchar(255) DEFAULT NULL, + `base_amount` decimal(15,2) DEFAULT 0.00, + `vat_amount` decimal(15,2) DEFAULT 0.00, + `total_amount` decimal(15,2) DEFAULT 0.00, + `created_date` datetime DEFAULT NULL, + PRIMARY KEY (`rowid`), + UNIQUE KEY `uk_declaration_line` (`declaration_id`, `ca3_line`), + CONSTRAINT `fk_ca3_declaration` FOREIGN KEY (`declaration_id`) REFERENCES `llx_declarationtva_declarations` (`rowid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ===================================================== +-- 2. INITIAL DATA FOR MVP +-- ===================================================== + +-- Insert default configuration +INSERT INTO `llx_declarationtva_config` (`entity`, `config_key`, `config_value`, `created_date`) VALUES +(1, 'module_version', '1.0.0-mvp', NOW()), +(1, 'default_period_type', 'quarterly', NOW()), +(1, 'vat_rates', '20.00,10.00,5.50,2.10,0.00', NOW()), +(1, 'declaration_language', 'fr', NOW()); + +-- Insert default CA-3 line mappings (simplified) +INSERT INTO `llx_declarationtva_account_mappings` (`entity`, `ca3_line`, `account_code`, `account_label`, `vat_rate`, `created_date`) VALUES +(1, 'A1', '701000', 'Ventes HT 20%', 20.00, NOW()), +(1, 'A2', '701000', 'Opérations spéciales', 20.00, NOW()), +(1, 'B1', '445710', 'TVA collectée 20%', 20.00, NOW()), +(1, 'B2', '445720', 'TVA collectée 10%', 10.00, NOW()), +(1, 'B3', '445730', 'TVA collectée 5.5%', 5.50, NOW()), +(1, 'B4', '445740', 'TVA collectée 2.1%', 2.10, NOW()), +(1, '17', '445200', 'TVA due intra-EU', 20.00, NOW()), +(1, '20', '445620', 'TVA déductible immobilisations', 20.00, NOW()), +(1, '21', '445660', 'TVA déductible autres', 20.00, NOW()), +(1, '22', '445670', 'Crédit TVA', 0.00, NOW()), +(1, '28', '445510', 'TVA nette à payer', 0.00, NOW()), +(1, '29', '445670', 'Crédit TVA à reporter', 0.00, NOW()); + +-- ===================================================== +-- 3. BASIC INDEXES FOR PERFORMANCE +-- ===================================================== + +CREATE INDEX `idx_declarationtva_periods_dates` ON `llx_declarationtva_periods` (`start_date`, `end_date`); +CREATE INDEX `idx_declarationtva_declarations_period` ON `llx_declarationtva_declarations` (`period_id`); +CREATE INDEX `idx_declarationtva_account_mappings_line` ON `llx_declarationtva_account_mappings` (`ca3_line`); + +-- ===================================================== +-- END OF MVP SCHEMA +-- =====================================================