diff --git a/admin/setup_mvp.php b/admin/setup_mvp.php index 17d56d5..b979516 100644 --- a/admin/setup_mvp.php +++ b/admin/setup_mvp.php @@ -522,6 +522,15 @@ print ''; $account_options[$account['account_number']] = $account['account_number'] . ' - ' . $account['label']; } + // Accounting entries toggle + print ''; + print '' . $langs->trans("CreateAccountingEntries") . ''; + print ''; + print '' . $langs->trans("CreateAccountingEntriesDescription") . '
'; + print $form->selectyesno('journal_create_accounting_entries', $journal_config['create_accounting_entries'], 1); + print ''; + print ''; + print ''; print '' . $langs->trans("VATToPayAccount") . ''; print ''; diff --git a/core/class/declarationtva.class.php b/core/class/declarationtva.class.php index e767aa9..ec664b2 100644 --- a/core/class/declarationtva.class.php +++ b/core/class/declarationtva.class.php @@ -784,21 +784,6 @@ class DeclarationTVA } - /** - * Submit declaration - * - * @param int $declaration_id Declaration ID - * @return bool Success - */ - public function submitDeclaration($declaration_id) - { - $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations - SET status = 'submitted', submission_date = NOW() - WHERE rowid = " . $declaration_id . " AND entity = " . $this->entity; - - $result = $this->db->query($sql); - return $result !== false; - } /** * Delete a declaration @@ -901,6 +886,480 @@ class DeclarationTVA return 0; } + /** + * Create accounting entries for declaration submission + * + * @param int $declaration_id Declaration ID + * @return bool Success + */ + public function createAccountingEntries($declaration_id) + { + global $user; + + // Get declaration data + $declaration = $this->fetch($declaration_id); + if (!$declaration) { + $this->error = 'Declaration not found'; + return false; + } + + // Get CA-3 lines data + $ca3_lines = $this->getCA3Lines($declaration_id); + if (empty($ca3_lines)) { + $this->error = 'No CA-3 lines found for declaration'; + return false; + } + + // Create lookup array for CA-3 data + $ca3_lookup = array(); + foreach ($ca3_lines as $line) { + $ca3_lookup[$line['ca3_line']] = $line; + } + + // Get journal configuration + require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; + $config = new DeclarationTVA_Config($this->db, $this->entity); + $journal_config = $config->getJournalConfiguration(); + + // Create OD journal entries + $od_entries = $this->createODJournalEntries($declaration, $ca3_lookup, $journal_config); + if (!$od_entries) { + return false; + } + + // Create bank journal entries + $bank_entries = $this->createBankJournalEntries($declaration, $ca3_lookup, $journal_config); + if (!$bank_entries) { + return false; + } + + return true; + } + + /** + * Create OD journal entries for VAT accounts + * + * @param object $declaration Declaration object + * @param array $ca3_lookup CA-3 data lookup array + * @param array $journal_config Journal configuration + * @return bool Success + */ + private function createODJournalEntries($declaration, $ca3_lookup, $journal_config) + { + $entries = array(); + + // Get line 8 VAT accounts (debit side) + $line8_entries = $this->getLine8VATAccountsForAccounting($declaration, $ca3_lookup); + $entries = array_merge($entries, $line8_entries); + + // Get line 20 accounts (credit side) + $line20_entries = $this->getLine20AccountsForAccounting($declaration, $ca3_lookup); + $entries = array_merge($entries, $line20_entries); + + // Add balancing entry + $balancing_entry = $this->getVATResultEntryForAccounting($declaration, $ca3_lookup, $journal_config); + if ($balancing_entry) { + $entries[] = $balancing_entry; + } + + // Create accounting entries in Dolibarr + return $this->saveAccountingEntries($entries, $declaration); + } + + /** + * Create bank journal entries for payments/refunds + * + * @param object $declaration Declaration object + * @param array $ca3_lookup CA-3 data lookup array + * @param array $journal_config Journal configuration + * @return bool Success + */ + private function createBankJournalEntries($declaration, $ca3_lookup, $journal_config) + { + // Get bank account configuration + require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; + $config = new DeclarationTVA_Config($this->db, $this->entity); + $bank_config = $config->getBankAccountConfiguration(); + $bank_account_id = $bank_config['bank_account']; + + if (empty($bank_account_id) || $bank_account_id == 0) { + // No bank account configured - skip bank entries + return true; + } + + // Get bank account details + $bank_account = $this->getBankAccountDetails($bank_account_id); + if (!$bank_account) { + $this->error = 'Bank account not found'; + return false; + } + + // Calculate VAT amounts + $line_16_amount = $this->getLineAmount($declaration->rowid, '16'); + $line_23_amount = $this->getLineAmount($declaration->rowid, '23'); + $td_amount = $line_16_amount - $line_23_amount; + + $entries = array(); + + if ($td_amount > 0) { + // VAT payment case - money going out + $entries[] = array( + 'account_code' => $bank_account['account_code'], + 'account_label' => $bank_account['account_label'], + 'entry_label' => 'Paiement TVA - ' . $declaration->declaration_name, + 'debit' => '', + 'credit' => $td_amount + ); + + $entries[] = array( + 'account_code' => $journal_config['vat_to_pay'], + 'account_label' => $this->getAccountLabel($journal_config['vat_to_pay']), + 'entry_label' => 'Paiement TVA - ' . $declaration->declaration_name, + 'debit' => $td_amount, + 'credit' => '' + ); + } elseif ($td_amount < 0) { + // VAT refund case - money coming in + $vat_credit = abs($td_amount); + + // Determine which credit account to use based on threshold + $threshold_config = $config->getVATRefundThresholdConfiguration(); + $refund_threshold = $threshold_config['refund_threshold']; + $threshold_enabled = $threshold_config['refund_threshold_enabled']; + + if ($threshold_enabled && $vat_credit < $refund_threshold) { + // Use carry-forward account + $vat_account = $journal_config['vat_to_receive']; + } else { + // Use immediate refund account + $vat_account = $journal_config['vat_refund']; + } + + $entries[] = array( + 'account_code' => $bank_account['account_code'], + 'account_label' => $bank_account['account_label'], + 'entry_label' => 'Remboursement TVA - ' . $declaration->declaration_name, + 'debit' => $vat_credit, + 'credit' => '' + ); + + $entries[] = array( + 'account_code' => $vat_account, + 'account_label' => $this->getAccountLabel($vat_account), + 'entry_label' => 'Remboursement TVA - ' . $declaration->declaration_name, + 'debit' => '', + 'credit' => $vat_credit + ); + } + + // Create accounting entries in Dolibarr + return $this->saveAccountingEntries($entries, $declaration); + } + + /** + * Get line 8 VAT accounts for accounting entries + * + * @param object $declaration Declaration object + * @param array $ca3_lookup CA-3 data lookup array + * @return array Accounting entries + */ + private function getLine8VATAccountsForAccounting($declaration, $ca3_lookup) + { + $entries = array(); + + if (!isset($ca3_lookup['08'])) { + return $entries; + } + + $line8_details = $this->getCA3LineDetails($declaration->rowid, '08'); + + if (empty($line8_details) || empty($line8_details['account_details'])) { + return $entries; + } + + foreach ($line8_details['account_details'] as $account) { + // Only include 445 accounts (VAT accounts) with non-zero amounts + if (strpos($account['account_code'], '445') === 0 && $account['vat_amount'] > 0) { + $entries[] = array( + 'account_code' => $account['account_code'], + 'account_label' => $account['account_label'], + 'entry_label' => $declaration->declaration_name, + 'debit' => $account['vat_amount'], + 'credit' => 0 + ); + } + } + + return $entries; + } + + /** + * Get line 20 accounts for accounting entries + * + * @param object $declaration Declaration object + * @param array $ca3_lookup CA-3 data lookup array + * @return array Accounting entries + */ + private function getLine20AccountsForAccounting($declaration, $ca3_lookup) + { + $entries = array(); + + if (!isset($ca3_lookup['20'])) { + return $entries; + } + + $line20_details = $this->getCA3LineDetails($declaration->rowid, '20'); + + if (empty($line20_details) || empty($line20_details['account_details'])) { + return $entries; + } + + foreach ($line20_details['account_details'] as $account) { + // Only include 445 accounts (VAT accounts) with non-zero amounts + if (strpos($account['account_code'], '445') === 0 && $account['vat_amount'] > 0) { + $entries[] = array( + 'account_code' => $account['account_code'], + 'account_label' => $account['account_label'], + 'entry_label' => $declaration->declaration_name, + 'debit' => 0, + 'credit' => $account['vat_amount'] + ); + } + } + + return $entries; + } + + /** + * Get VAT result entry for accounting + * + * @param object $declaration Declaration object + * @param array $ca3_lookup CA-3 data lookup array + * @param array $journal_config Journal configuration + * @return array|null Accounting entry + */ + private function getVATResultEntryForAccounting($declaration, $ca3_lookup, $journal_config) + { + // Get TD line amount (Line 16 - Line 23) + $line_16_amount = $this->getLineAmount($declaration->rowid, '16'); + $line_23_amount = $this->getLineAmount($declaration->rowid, '23'); + $td_amount = $line_16_amount - $line_23_amount; + + if ($td_amount > 0) { + // VAT due case + return array( + 'account_code' => $journal_config['vat_to_pay'], + 'account_label' => $this->getAccountLabel($journal_config['vat_to_pay']), + 'entry_label' => $declaration->declaration_name, + 'debit' => 0, + 'credit' => $td_amount + ); + } elseif ($td_amount < 0) { + // VAT credit case + $vat_credit = abs($td_amount); + + // Determine which credit account to use based on threshold + require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; + $config = new DeclarationTVA_Config($this->db, $this->entity); + $threshold_config = $config->getVATRefundThresholdConfiguration(); + $refund_threshold = $threshold_config['refund_threshold']; + $threshold_enabled = $threshold_config['refund_threshold_enabled']; + + if ($threshold_enabled && $vat_credit < $refund_threshold) { + // Use carry-forward account + $vat_account = $journal_config['vat_to_receive']; + } else { + // Use immediate refund account + $vat_account = $journal_config['vat_refund']; + } + + return array( + 'account_code' => $vat_account, + 'account_label' => $this->getAccountLabel($vat_account), + 'entry_label' => $declaration->declaration_name, + 'debit' => $vat_credit, + 'credit' => 0 + ); + } + + return null; + } + + /** + * Save accounting entries to Dolibarr + * + * @param array $entries Accounting entries + * @param object $declaration Declaration object + * @return bool Success + */ + private function saveAccountingEntries($entries, $declaration) + { + if (empty($entries)) { + return true; // No entries to save + } + + // Use Dolibarr's accounting integration + require_once DOL_DOCUMENT_ROOT . '/compta/accounting/class/accountingbookkeeping.class.php'; + + $bookkeeping = new AccountingBookkeeping($this->db); + + foreach ($entries as $entry) { + // Create accounting entry + $bookkeeping->doc_type = 'declarationtva'; + $bookkeeping->doc_ref = $declaration->declaration_number; + $bookkeeping->doc_date = $declaration->end_date; // Use declaration end date for OD entries + $bookkeeping->account_number = $entry['account_code']; + $bookkeeping->account_label = $entry['account_label']; + $bookkeeping->debit = $entry['debit']; + $bookkeeping->credit = $entry['credit']; + $bookkeeping->label = $entry['entry_label']; + $bookkeeping->entity = $this->entity; + + $result = $bookkeeping->create($user); + if (!$result) { + $this->error = 'Failed to create accounting entry: ' . $bookkeeping->error; + return false; + } + } + + return true; + } + + /** + * Get bank account details + * + * @param int $bank_account_id Bank account ID + * @return array|false Bank account details + */ + private function getBankAccountDetails($bank_account_id) + { + $sql = "SELECT ba.rowid, ba.label, ba.number, a.account_number as account_code, a.label as account_label + FROM " . MAIN_DB_PREFIX . "bank_account ba + LEFT JOIN " . MAIN_DB_PREFIX . "accounting_account a ON a.rowid = ba.account_number + WHERE ba.rowid = " . (int)$bank_account_id . " AND ba.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 account label from account code + * + * @param string $account_code Account code + * @return string Account label + */ + private function getAccountLabel($account_code) + { + $sql = "SELECT label FROM " . MAIN_DB_PREFIX . "accounting_account + WHERE account_number = '" . $this->db->escape($account_code) . "' + AND entity = " . $this->entity; + + $result = $this->db->query($sql); + if ($result && $this->db->num_rows($result) > 0) { + $obj = $this->db->fetch_object($result); + return $obj->label; + } + + return $account_code; // Fallback to account code + } + + /** + * Submit declaration (create accounting entries and update status) + * + * @param int $declaration_id Declaration ID + * @return bool Success + */ + public function submitDeclaration($declaration_id) + { + global $user; + + // Get declaration data + $declaration = $this->fetch($declaration_id); + if (!$declaration) { + $this->error = 'Declaration not found'; + return false; + } + + // Check if declaration is validated + if ($declaration->status !== 'validated') { + $this->error = 'Declaration must be validated before submission'; + return false; + } + + // Check if accounting entries are enabled + require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_config.class.php'; + $config = new DeclarationTVA_Config($this->db, $this->entity); + $journal_config = $config->getJournalConfiguration(); + + if (!isset($journal_config['create_accounting_entries']) || !$journal_config['create_accounting_entries']) { + // Accounting entries disabled - just update status + return $this->updateDeclarationStatus($declaration_id, 'submitted'); + } + + // Create accounting entries + $accounting_result = $this->createAccountingEntries($declaration_id); + if (!$accounting_result) { + $this->error = 'Failed to create accounting entries: ' . $this->error; + return false; + } + + // Update declaration status to submitted + $status_result = $this->updateDeclarationStatus($declaration_id, 'submitted'); + if (!$status_result) { + $this->error = 'Failed to update declaration status: ' . $this->error; + return false; + } + + return true; + } + + /** + * Update declaration status + * + * @param int $declaration_id Declaration ID + * @param string $status New status + * @return bool Success + */ + private function updateDeclarationStatus($declaration_id, $status) + { + $sql = "UPDATE " . MAIN_DB_PREFIX . "declarationtva_declarations + SET status = '" . $this->db->escape($status) . "', + date_submitted = NOW() + WHERE rowid = " . (int)$declaration_id; + + $result = $this->db->query($sql); + if (!$result) { + $this->error = 'Database error: ' . $this->db->lasterror(); + return false; + } + + return true; + } + + /** + * Validate accounting entries balance + * + * @param array $entries Accounting entries + * @return bool Is balanced + */ + private function validateAccountingBalance($entries) + { + $total_debit = 0; + $total_credit = 0; + + foreach ($entries as $entry) { + $total_debit += (float)$entry['debit']; + $total_credit += (float)$entry['credit']; + } + + // Allow for small rounding differences (0.01) + return abs($total_debit - $total_credit) < 0.01; + } + /** * Get account mappings with labels from chart of accounts * diff --git a/core/class/declarationtva_config.class.php b/core/class/declarationtva_config.class.php index 2fabe45..6ae7809 100644 --- a/core/class/declarationtva_config.class.php +++ b/core/class/declarationtva_config.class.php @@ -720,7 +720,8 @@ class DeclarationTVA_Config 'vat_to_receive' => '44567', // TVA A PAYER (carry forward) 'vat_refund' => '445671', // TVA A REMBOURSER (immediate refund) 'other_charges' => '658', // AUTRES CHARGES DE GESTION COURANTE - 'other_products' => '758' // AUTRES PRODUITS DE GESTION COURANT + 'other_products' => '758', // AUTRES PRODUITS DE GESTION COURANT + 'create_accounting_entries' => 1 // Enable accounting entries by default ); $config = array(); diff --git a/declarationtva_view.php b/declarationtva_view.php index c098a65..5b4eb2b 100644 --- a/declarationtva_view.php +++ b/declarationtva_view.php @@ -112,6 +112,15 @@ if ($action == 'unvalidate' && $token) { } } +if ($action == 'submit' && $token) { + // Submit the declaration (create accounting entries and update status) + if ($declarationtva->submitDeclaration($id)) { + setEventMessages($langs->trans("DeclarationSubmitted"), null, 'mesgs'); + } else { + setEventMessages($langs->trans("ErrorSubmittingDeclaration") . ": " . $declarationtva->error, null, 'errors'); + } +} + // Fetch declaration if ($declarationtva->fetch($id) < 0) { setEventMessages($langs->trans("DeclarationNotFound"), null, 'errors'); diff --git a/langs/fr_FR/declarationtva.lang b/langs/fr_FR/declarationtva.lang index 32f857e..b829cb8 100644 --- a/langs/fr_FR/declarationtva.lang +++ b/langs/fr_FR/declarationtva.lang @@ -536,3 +536,8 @@ VATRefundAccountDescription = Compte pour les remboursements immédiats de TVA ( VATRefundThresholdConfiguration = Configuration du seuil de remboursement TVA VATRefundThresholdConfigurationUpdated = Configuration du seuil de remboursement TVA mise à jour VATRefundThresholdConfigurationUpdateFailed = Erreur lors de la mise à jour de la configuration du seuil de remboursement TVA +CreateAccountingEntries = Créer les écritures comptables +CreateAccountingEntriesDescription = Activer la création automatique d'écritures comptables lors de la soumission des déclarations +DeclarationSubmitted = Déclaration soumise avec succès +ErrorSubmittingDeclaration = Erreur lors de la soumission de la déclaration +Submit = Soumettre