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
This commit is contained in:
Frank Cools 2025-10-02 16:16:15 +02:00
parent 8cda039ade
commit 8e71a12000
6 changed files with 1310 additions and 237 deletions

154
admin/setup_mvp.php Normal file
View File

@ -0,0 +1,154 @@
<?php
/**
* MVP Setup page for DeclarationTVA module
* Simplified configuration for Phase 1
*/
// Load Dolibarr environment
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");
}
// Load module classes
require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php';
// Access control
if (!$user->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 '<form method="POST" action="' . $_SERVER['PHP_SELF'] . '">';
print '<input type="hidden" name="action" value="update_mappings">';
print '<div class="fiche">';
print '<div class="titre">' . $langs->trans("DeclarationTVAPCGMapping") . '</div>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>' . $langs->trans("CA3Line") . '</th>';
print '<th>' . $langs->trans("LineLabel") . '</th>';
print '<th>' . $langs->trans("AccountCode") . '</th>';
print '<th>' . $langs->trans("AccountLabel") . '</th>';
print '<th>' . $langs->trans("VATRate") . '</th>';
print '</tr>';
foreach ($ca3_definitions as $line => $definition) {
$mapping = isset($account_mappings[$line]) ? $account_mappings[$line] : array();
print '<tr>';
print '<td><strong>' . $line . '</strong></td>';
print '<td>' . $definition['label'] . '</td>';
print '<td>';
print '<select name="account_code_' . $line . '" class="flat">';
print '<option value="">' . $langs->trans("SelectAccount") . '</option>';
foreach ($accounts as $account) {
$selected = (isset($mapping['account_code']) && $mapping['account_code'] == $account['account_number']) ? 'selected' : '';
print '<option value="' . $account['account_number'] . '" ' . $selected . '>' . $account['account_number'] . ' - ' . $account['label'] . '</option>';
}
print '</select>';
print '</td>';
print '<td><input type="text" name="account_label_' . $line . '" value="' . (isset($mapping['account_label']) ? $mapping['account_label'] : '') . '" class="flat" size="30"></td>';
print '<td>';
print '<select name="vat_rate_' . $line . '" class="flat">';
foreach ($vat_rates as $rate => $label) {
$selected = (isset($mapping['vat_rate']) && $mapping['vat_rate'] == $rate) ? 'selected' : '';
print '<option value="' . $rate . '" ' . $selected . '>' . $label . '</option>';
}
print '</select>';
print '</td>';
print '</tr>';
}
print '</table>';
print '<div class="titre">' . $langs->trans("Actions") . '</div>';
print '<input type="submit" class="button" value="' . $langs->trans("UpdateConfiguration") . '">';
print '</div>';
print '</form>';
// Print current configuration
print '<div class="fiche">';
print '<div class="titre">' . $langs->trans("CurrentConfiguration") . '</div>';
if (empty($mappings)) {
print '<div class="info">' . $langs->trans("NoConfigurationFound") . '</div>';
} else {
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>' . $langs->trans("CA3Line") . '</th>';
print '<th>' . $langs->trans("AccountCode") . '</th>';
print '<th>' . $langs->trans("AccountLabel") . '</th>';
print '<th>' . $langs->trans("VATRate") . '</th>';
print '<th>' . $langs->trans("Status") . '</th>';
print '</tr>';
foreach ($mappings as $mapping) {
print '<tr>';
print '<td><strong>' . $mapping['ca3_line'] . '</strong></td>';
print '<td>' . $mapping['account_code'] . '</td>';
print '<td>' . $mapping['account_label'] . '</td>';
print '<td>' . $mapping['vat_rate'] . '%</td>';
print '<td>' . ($mapping['is_active'] ? $langs->trans("Active") : $langs->trans("Inactive")) . '</td>';
print '</tr>';
}
print '</table>';
}
print '</div>';
// Print footer
llxFooter();
?>

View File

@ -0,0 +1,342 @@
<?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;
/**
* Constructor
*
* @param DoliDB $db Database handler
* @param int $entity Entity ID
*/
public function __construct($db, $entity = 1)
{
$this->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;
}
}

View File

@ -0,0 +1,237 @@
<?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;
}
/**
* 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;
}
}

View File

@ -0,0 +1,292 @@
<?php
/**
* DeclarationTVA_Period Class
* Period management for DeclarationTVA module
* MVP Version - Phase 1
*/
class DeclarationTVA_Period
{
/**
* @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;
}
/**
* 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
);
}
}

View File

@ -1,259 +1,186 @@
<?php
/* Copyright (C) 2001-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2015 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2025 Frank Cools
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file 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
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;
// 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 (!$res) {
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 '<div class="fichecenter"><div class="fichethirdleft">';
/* 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 '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th colspan="3">'.$langs->trans("DraftMyObjects").($num?'<span class="badge marginleftonlyshort">'.$num.'</span>':'').'</th></tr>';
$var = true;
if ($num > 0)
{
$i = 0;
while ($i < $num)
{
$obj = $db->fetch_object($resql);
print '<tr class="oddeven"><td class="nowrap">';
$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 '</td>';
print '<td class="nowrap">';
print '</td>';
print '<td class="right" class="nowrap">'.price($obj->total_ttc).'</td></tr>';
$i++;
$total += $obj->total_ttc;
}
if ($total>0)
{
print '<tr class="liste_total"><td>'.$langs->trans("Total").'</td><td colspan="2" class="right">'.price($total)."</td></tr>";
}
}
else
{
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("NoOrder").'</td></tr>';
}
print "</table><br>";
$db->free($resql);
}
else
{
dol_print_error($db);
}
}
END MODULEBUILDER DRAFT MYOBJECT */
print '</div><div class="fichetwothirdright">';
/* 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 '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th colspan="2">';
print $langs->trans("BoxTitleLatestModifiedMyObjects", $max);
print '</th>';
print '<th class="right">'.$langs->trans("DateModificationShort").'</th>';
print '</tr>';
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 '<tr class="oddeven">';
print '<td class="nowrap">'.$myobjectstatic->getNomUrl(1).'</td>';
print '<td class="right nowrap">';
print "</td>";
print '<td class="right nowrap">'.dol_print_date($db->jdate($objp->tms), 'day')."</td>";
print '</tr>';
$i++;
}
$db->free($resql);
// 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 {
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("None").'</td></tr>';
setEventMessages($langs->trans("ErrorCreatingDeclaration") . ": " . $declarationtva->error, null, 'errors');
}
print "</table><br>";
} 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');
}
}
*/
print '</div></div>';
// Get data for display
$periods = $period->getAllPeriods();
$declarations = array();
// End of page
// 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']
);
}
}
}
// Page title
$title = $langs->trans("DeclarationTVAMainInterface");
llxHeader('', $title);
// Print page header
print load_fiche_titre($title, '', 'title_accountancy');
// Print periods section
print '<div class="fiche">';
print '<div class="fichehalfleft">';
print '<div class="titre">' . $langs->trans("DeclarationTVAPeriods") . '</div>';
if (empty($periods)) {
print '<div class="info">' . $langs->trans("NoPeriodsFound") . '</div>';
} else {
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>' . $langs->trans("PeriodName") . '</th>';
print '<th>' . $langs->trans("StartDate") . '</th>';
print '<th>' . $langs->trans("EndDate") . '</th>';
print '<th>' . $langs->trans("Status") . '</th>';
print '<th>' . $langs->trans("Actions") . '</th>';
print '</tr>';
foreach ($periods as $p) {
print '<tr>';
print '<td>' . $p['period_name'] . '</td>';
print '<td>' . dol_print_date($p['start_date'], 'day') . '</td>';
print '<td>' . dol_print_date($p['end_date'], 'day') . '</td>';
print '<td>' . $langs->trans("Status" . ucfirst($p['status'])) . '</td>';
print '<td>';
print '<a href="' . $_SERVER['PHP_SELF'] . '?action=create_declaration&period_id=' . $p['rowid'] . '" class="butAction">' . $langs->trans("CreateDeclaration") . '</a>';
print '</td>';
print '</tr>';
}
print '</table>';
}
print '</div>';
print '</div>';
// Print declarations section
print '<div class="fiche">';
print '<div class="fichehalfright">';
print '<div class="titre">' . $langs->trans("DeclarationTVADeclarations") . '</div>';
if (empty($declarations)) {
print '<div class="info">' . $langs->trans("NoDeclarationsFound") . '</div>';
} else {
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>' . $langs->trans("DeclarationNumber") . '</th>';
print '<th>' . $langs->trans("Period") . '</th>';
print '<th>' . $langs->trans("Status") . '</th>';
print '<th>' . $langs->trans("NetVATDue") . '</th>';
print '<th>' . $langs->trans("Actions") . '</th>';
print '</tr>';
foreach ($declarations as $d) {
print '<tr>';
print '<td>' . $d['declaration_number'] . '</td>';
print '<td>' . $d['period_name'] . '</td>';
print '<td>' . $langs->trans("Status" . ucfirst($d['status'])) . '</td>';
print '<td>' . price($d['net_vat_due']) . '</td>';
print '<td>';
if ($d['status'] == 'draft') {
print '<a href="' . $_SERVER['PHP_SELF'] . '?action=validate_declaration&declaration_id=' . $d['rowid'] . '" class="butAction">' . $langs->trans("Validate") . '</a>';
} elseif ($d['status'] == 'validated') {
print '<a href="' . $_SERVER['PHP_SELF'] . '?action=submit_declaration&declaration_id=' . $d['rowid'] . '" class="butAction">' . $langs->trans("Submit") . '</a>';
}
print '<a href="declarationtva_view.php?id=' . $d['rowid'] . '" class="butAction">' . $langs->trans("View") . '</a>';
print '</td>';
print '</tr>';
}
print '</table>';
}
print '</div>';
print '</div>';
// Print configuration section
print '<div class="fiche">';
print '<div class="titre">' . $langs->trans("DeclarationTVAConfiguration") . '</div>';
print '<div class="info">';
print '<a href="admin/setup.php" class="butAction">' . $langs->trans("ConfigureModule") . '</a>';
print '</div>';
print '</div>';
// Print footer
llxFooter();
$db->close();
?>

121
sql/mvp_schema.sql Normal file
View File

@ -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
-- =====================================================