Version 2.1.0: Enhanced PDF Export with Detailed Breakdown Pages
- Added comprehensive PDF export combining CA-3 form with detailed breakdown pages - Implemented pdftk-based PDF merging that preserves form fields - Added support for new CA-3 lines (25, 26, 27, TD, 28, 32) with conditional visibility - Fixed multi-select configuration saving issues - Enhanced error handling and debugging for PDF generation - Added French status translation for detailed PDFs - Optimized page breaks to reduce paper usage - Improved form field preservation during PDF merging Technical improvements: - Better error handling with comprehensive logging - Modular PDF generation with fallback options - Fixed pdftk filename conflicts - Enhanced debugging capabilities - Status translation without external dependencies
This commit is contained in:
parent
8277217cc7
commit
9255a39d42
22
ChangeLog.md
22
ChangeLog.md
@ -1,5 +1,27 @@
|
||||
# CHANGELOG MODULE DECLARATIONTVA FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 1.1
|
||||
|
||||
### New Features
|
||||
- **Enhanced PDF Export**: CA-3 form now includes detailed breakdown pages in a single comprehensive PDF
|
||||
- **Detailed Breakdown Pages**: Account-level details for each CA-3 line showing exactly where amounts come from
|
||||
- **Improved PDF Merging**: Uses pdftk for reliable PDF merging that preserves form fields
|
||||
- **Conditional Page Breaks**: Optimized page usage to reduce paper consumption
|
||||
- **Enhanced Line Calculations**: Added support for lines 25, 26, 27, TD, 28, 32 with proper conditional visibility
|
||||
- **Status Translation**: Proper French translation of declaration status in detailed PDFs
|
||||
|
||||
### Technical Improvements
|
||||
- **Better Error Handling**: Comprehensive logging and debugging for PDF generation
|
||||
- **Form Field Preservation**: pdftk-based merging maintains all form data integrity
|
||||
- **Modular PDF Generation**: Separate detailed PDF generation with fallback options
|
||||
- **Enhanced Debugging**: Detailed logging for troubleshooting PDF generation issues
|
||||
|
||||
### Bug Fixes
|
||||
- **Fixed Multi-Select Saving**: Configuration fields now properly save when all selections are removed
|
||||
- **Fixed PDF Merge Issues**: Resolved pdftk filename conflicts that prevented detailed pages from being added
|
||||
- **Fixed Status Translation**: Eliminated "Call to a member function trans() on null" errors
|
||||
- **Fixed Form Corruption**: Prevented FPDI merge from corrupting filled form data
|
||||
|
||||
## 1.0
|
||||
|
||||
Initial version
|
||||
|
||||
138
TASK.md
138
TASK.md
@ -712,7 +712,143 @@ The MVP is now **feature-complete** and ready for real-world French VAT declarat
|
||||
|
||||
## Discovered During Work
|
||||
|
||||
*Tasks discovered during development will be added here*
|
||||
### Bug Fixes - Configuration Screen
|
||||
- [x] **Task Bug-1**: Fix multiple selection fields not saving when all selections are removed ✅ **COMPLETED**
|
||||
- **Issue**: When removing all selections from multi-select fields in config screen, changes were not persisted
|
||||
- **Root Cause**: Form processing logic only called `updateAccountMapping` when `isset($_POST['field_name'])` was true, but empty multi-select fields are not present in POST data
|
||||
- **Solution**: Modified form processing to always call `updateAccountMapping` for each field, regardless of POST data presence
|
||||
- **Files Modified**: `admin/setup_mvp.php`, `core/class/declarationtva_config.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 2 hours
|
||||
|
||||
### Bug Fixes - CA-3 Line 25 Calculation
|
||||
- [x] **Task Bug-2**: Fix line 25 calculation and title ✅ **COMPLETED**
|
||||
- **Issue**: Line 25 had incorrect title and calculation logic
|
||||
- **Requirements**: Title should be 'Crédit de TVA (ligne 23 - ligne16)', calculation should be line 23 - line 16, only display if > 0
|
||||
- **Root Cause**: Line 25 was using incorrect calculation logic based on total VAT collected instead of line 23 - line 16
|
||||
- **Solution**:
|
||||
- Added `getLineAmount()` helper method to retrieve specific line amounts
|
||||
- Modified `calculateDSectionLines()` to calculate line 25 as line 23 - line 16
|
||||
- Updated title to 'Crédit de TVA (ligne 23 - ligne16)'
|
||||
- Only display value if > 0
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 1 hour
|
||||
|
||||
### Bug Fixes - CA-3 Line 28 Calculation
|
||||
- [x] **Task Bug-3**: Fix line 28 calculation and title ✅ **COMPLETED**
|
||||
- **Issue**: Line 28 had incorrect title and calculation logic
|
||||
- **Requirements**: Title should be 'TVA nette dûe (ligne 16 - ligne 23)', calculation should be line 16 - line 23, only display if > 0
|
||||
- **Root Cause**: Line 28 was using incorrect calculation logic based on net VAT due instead of line 16 - line 23
|
||||
- **Solution**:
|
||||
- Modified `calculateDSectionLines()` to calculate line 28 as line 16 - line 23
|
||||
- Updated title to 'TVA nette dûe (ligne 16 - ligne 23)'
|
||||
- Only display value if > 0
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 30 minutes
|
||||
|
||||
### Bug Fixes - CA-3 Line 28 Calculation (Updated)
|
||||
- [x] **Task Bug-5**: Update line 28 to use same value as line TD ✅ **COMPLETED**
|
||||
- **Issue**: Line 28 needed to be updated to use same value as line TD
|
||||
- **Requirements**: Title should be 'TVA nette due (ligne TD – ligne X5)', value should be same as line TD, only display if > 0
|
||||
- **Root Cause**: Line 28 was calculating independently instead of using line TD value
|
||||
- **Solution**:
|
||||
- Modified `calculateDSectionLines()` to use `$line_td_amount` for line 28
|
||||
- Updated title to 'TVA nette due (ligne TD – ligne X5)'
|
||||
- Line 28 now shows same value as line TD
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 15 minutes
|
||||
|
||||
### Bug Fixes - CA-3 Line TD Calculation
|
||||
- [x] **Task Bug-4**: Fix line TD calculation and title ✅ **COMPLETED**
|
||||
- **Issue**: Line TD had incorrect title and calculation logic
|
||||
- **Requirements**: Title should be 'TVA due (ligne 16 – ligne 23)', calculation should be line 16 - line 23, only display if > 0
|
||||
- **Root Cause**: Line TD was using incorrect calculation logic based on net VAT due instead of line 16 - line 23
|
||||
- **Solution**:
|
||||
- Modified `calculateDSectionLines()` to calculate line TD as line 16 - line 23
|
||||
- Updated title to 'TVA due (ligne 16 – ligne 23)'
|
||||
- Only display value if > 0
|
||||
- Removed special handling in view to use database line_label
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`, `declarationtva_view.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 30 minutes
|
||||
|
||||
### Bug Fixes - Remove Lines 26 and 29
|
||||
- [x] **Task Bug-6**: Remove lines 26 and 29 from CA-3 calculation ✅ **COMPLETED**
|
||||
- **Issue**: Lines 26 and 29 are no longer needed in the CA-3 form
|
||||
- **Requirements**: Remove lines 26 and 29 from both calculation and display
|
||||
- **Root Cause**: Lines 26 and 29 were part of the original CA-3 structure but are no longer required
|
||||
- **Solution**:
|
||||
- Removed line 26 calculation logic from `calculateDSectionLines()`
|
||||
- Removed line 29 calculation logic from `calculateDSectionLines()`
|
||||
- Updated view to remove lines 26 and 29 from `$section_d_lines` array
|
||||
- Section D now only shows lines 25, TD, and 28
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`, `declarationtva_view.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 15 minutes
|
||||
|
||||
### Bug Fixes - Add Line 32
|
||||
- [x] **Task Bug-7**: Add line 32 to CA-3 calculation ✅ **COMPLETED**
|
||||
- **Issue**: Need to add line 32 for total amount to pay
|
||||
- **Requirements**: Title should be 'Total à payer (lignes 28 + 29 + Z5 – AB) (N'oubliez pas d'effectuer le règlement correspondant)', value should be same as line 28, only show if > 0, use bold formatting
|
||||
- **Root Cause**: Line 32 was missing from the CA-3 form structure
|
||||
- **Solution**:
|
||||
- Added line 32 calculation logic to `calculateDSectionLines()`
|
||||
- Line 32 uses same value as line 28
|
||||
- Added line 32 to `$section_d_lines` array in view
|
||||
- Added special bold formatting for line 32 in view display
|
||||
- Section D now shows lines 25, TD, 28, and 32
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`, `declarationtva_view.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 20 minutes
|
||||
|
||||
### Bug Fixes - Add Line 27
|
||||
- [x] **Task Bug-8**: Add line 27 to CA-3 calculation ✅ **COMPLETED**
|
||||
- **Issue**: Need to add line 27 for VAT credit to carry forward
|
||||
- **Requirements**: Title should be 'Crédit de TVA à reporter (ligne 25 – ligne 26) (Cette somme est à reporter ligne 22 de la prochaine déclaration)', value should be same as line 25, only show if > 0
|
||||
- **Root Cause**: Line 27 was missing from the CA-3 form structure
|
||||
- **Solution**:
|
||||
- Added line 27 calculation logic to `calculateDSectionLines()`
|
||||
- Line 27 uses same value as line 25
|
||||
- Added line 27 to `$section_d_lines` array in view
|
||||
- Section D now shows lines 25, 27, TD, 28, and 32
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`, `declarationtva_view.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 15 minutes
|
||||
|
||||
### Bug Fixes - PDF Field Mapping
|
||||
- [x] **Task Bug-9**: Add missing lines to PDF field mapping ✅ **COMPLETED**
|
||||
- **Issue**: New CA-3 lines (27, 32) were not included in PDF generation
|
||||
- **Requirements**: All new lines should be available in generated PDF
|
||||
- **Root Cause**: PDF field mapping was missing lines 27 and 32
|
||||
- **Solution**:
|
||||
- Added `D27_amount` field mapping for line 27
|
||||
- Added `D32_amount` field mapping for line 32
|
||||
- Updated debug logging to include new lines
|
||||
- Removed obsolete lines 26 and 29 from PDF mapping
|
||||
- **Files Modified**: `core/class/declarationtva_pdf.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 10 minutes
|
||||
|
||||
### Bug Fixes - Add Line 26 and Conditional Visibility
|
||||
- [x] **Task Bug-10**: Add line 26 and conditional visibility logic ✅ **COMPLETED**
|
||||
- **Issue**: Need line 26 for VAT credit refund and conditional visibility for line 27
|
||||
- **Requirements**:
|
||||
- Line 27: visible if > 0 and < 760
|
||||
- Line 26: visible if >= 760
|
||||
- Add line 26 to PDF fields
|
||||
- **Root Cause**: Missing conditional logic for VAT credit handling
|
||||
- **Solution**:
|
||||
- Added line 26 calculation with visibility >= 760
|
||||
- Modified line 27 visibility to only show if > 0 and < 760
|
||||
- Added `D26_amount` to PDF field mapping
|
||||
- Updated view to include line 26 in display
|
||||
- Both lines use same value as line 25 but with different visibility rules
|
||||
- **Files Modified**: `core/class/declarationtva.class.php`, `declarationtva_view.php`, `core/class/declarationtva_pdf.class.php`
|
||||
- **Date**: 2025-01-27
|
||||
- **Estimated**: 20 minutes
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -47,30 +47,25 @@ if ($action == 'update_mappings') {
|
||||
$base_account_codes = GETPOST('base_account_codes_' . $line, 'array');
|
||||
$vat_account_codes = GETPOST('vat_account_codes_' . $line, 'array');
|
||||
|
||||
// Process base accounts
|
||||
if (isset($_POST['base_account_codes_' . $line])) {
|
||||
$result = $config->updateAccountMapping($line . '_BASE', $base_account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
// Always process base accounts (even if empty)
|
||||
$result = $config->updateAccountMapping($line . '_BASE', $base_account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
|
||||
// Process VAT accounts
|
||||
if (isset($_POST['vat_account_codes_' . $line])) {
|
||||
$result = $config->updateAccountMapping($line . '_VAT', $vat_account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
// Always process VAT accounts (even if empty)
|
||||
$result = $config->updateAccountMapping($line . '_VAT', $vat_account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
} else {
|
||||
// Normal processing for other lines
|
||||
$account_codes = GETPOST('account_codes_' . $line, 'array');
|
||||
|
||||
if (isset($_POST['account_codes_' . $line])) {
|
||||
$result = $config->updateAccountMapping($line, $account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
// Always process account mappings (even if empty)
|
||||
$result = $config->updateAccountMapping($line, $account_codes);
|
||||
if ($result) {
|
||||
$updated_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +395,28 @@ class DeclarationTVA
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get amount for a specific CA-3 line
|
||||
*
|
||||
* @param int $declaration_id Declaration ID
|
||||
* @param string $ca3_line CA-3 line code
|
||||
* @return float Line amount
|
||||
*/
|
||||
private function getLineAmount($declaration_id, $ca3_line)
|
||||
{
|
||||
$sql = "SELECT vat_amount FROM " . MAIN_DB_PREFIX . "declarationtva_ca3_lines
|
||||
WHERE declaration_id = " . (int)$declaration_id . "
|
||||
AND ca3_line = '" . $this->db->escape($ca3_line) . "'";
|
||||
|
||||
$result = $this->db->query($sql);
|
||||
if ($result && $this->db->num_rows($result) > 0) {
|
||||
$obj = $this->db->fetch_object($result);
|
||||
return (float)$obj->vat_amount;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate D-section result lines (25, 26, 28, 29)
|
||||
*
|
||||
@ -405,74 +427,103 @@ class DeclarationTVA
|
||||
*/
|
||||
private function calculateDSectionLines($declaration_id, $total_vat_collected, $total_vat_deductible)
|
||||
{
|
||||
// Get line 16 and 23 amounts for line 25 calculation
|
||||
$line_16_amount = $this->getLineAmount($declaration_id, '16');
|
||||
$line_23_amount = $this->getLineAmount($declaration_id, '23');
|
||||
|
||||
// Calculate net VAT due
|
||||
$net_vat_due = $total_vat_collected - $total_vat_deductible;
|
||||
|
||||
// Line 25: TVA brute due (Total VAT due) - only if negative (VAT credit - we receive money)
|
||||
if ($net_vat_due < 0) {
|
||||
$line_25_amount = $total_vat_collected;
|
||||
$this->createCA3Line($declaration_id, '25', 'Calculated from sections A and B', array(
|
||||
// Line 25: Crédit de TVA (ligne 23 - ligne16) - only if > 0
|
||||
$line_25_amount = $line_23_amount - $line_16_amount;
|
||||
if ($line_25_amount > 0) {
|
||||
$this->createCA3Line($declaration_id, '25', 'Crédit de TVA (ligne 23 - ligne16)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $line_25_amount,
|
||||
'total_amount' => $line_25_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, '25', 'Calculated from sections A and B', array(
|
||||
$this->createCA3Line($declaration_id, '25', 'Crédit de TVA (ligne 23 - ligne16)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
));
|
||||
}
|
||||
|
||||
// Line 26: TVA déductible totale (Total deductible VAT)
|
||||
$line_26_amount = $total_vat_deductible;
|
||||
$this->createCA3Line($declaration_id, '26', 'Calculated from section C', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $line_26_amount,
|
||||
'total_amount' => $line_26_amount
|
||||
));
|
||||
// Line 27: Crédit de TVA à reporter (ligne 25 – ligne 26) - same value as line 25, only if > 0 and < 760
|
||||
$line_27_amount = $line_25_amount; // Same value as line 25
|
||||
if ($line_27_amount > 0 && $line_27_amount < 760) {
|
||||
$this->createCA3Line($declaration_id, '27', 'Crédit de TVA à reporter (ligne 25 – ligne 26) (Cette somme est à reporter ligne 22 de la prochaine déclaration)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $line_27_amount,
|
||||
'total_amount' => $line_27_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, '27', 'Crédit de TVA à reporter (ligne 25 – ligne 26) (Cette somme est à reporter ligne 22 de la prochaine déclaration)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
));
|
||||
}
|
||||
|
||||
// Line TD: TVA due (VAT due) - only if positive (we need to pay)
|
||||
if ($net_vat_due > 0) {
|
||||
$line_td_amount = $net_vat_due;
|
||||
$this->createCA3Line($declaration_id, 'TD', 'TVA due (amount to pay)', array(
|
||||
// Line 26: Remboursement de crédit de TVA demandé sur formulaire n°3519 - same value as line 25, only if >= 760
|
||||
$line_26_amount = $line_25_amount; // Same value as line 25
|
||||
if ($line_26_amount >= 760) {
|
||||
$this->createCA3Line($declaration_id, '26', 'Remboursement de crédit de TVA demandé sur formulaire n°3519', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $line_26_amount,
|
||||
'total_amount' => $line_26_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, '26', 'Remboursement de crédit de TVA demandé sur formulaire n°3519', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
));
|
||||
}
|
||||
|
||||
// Line TD: TVA due (ligne 16 – ligne 23) - only if > 0
|
||||
$line_td_amount = $line_16_amount - $line_23_amount;
|
||||
if ($line_td_amount > 0) {
|
||||
$this->createCA3Line($declaration_id, 'TD', 'TVA due (ligne 16 – ligne 23)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $line_td_amount,
|
||||
'total_amount' => $line_td_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, 'TD', 'TVA due (amount to pay)', array(
|
||||
$this->createCA3Line($declaration_id, 'TD', 'TVA due (ligne 16 – ligne 23)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
));
|
||||
}
|
||||
|
||||
// Line 28: TVA nette due (Net VAT due) - if positive
|
||||
if ($net_vat_due > 0) {
|
||||
$this->createCA3Line($declaration_id, '28', 'Calculated: 25 - 26', array(
|
||||
// Line 28: TVA nette due (ligne TD – ligne X5) - same value as line TD, only if > 0
|
||||
$line_28_amount = $line_td_amount; // Same value as line TD
|
||||
if ($line_28_amount > 0) {
|
||||
$this->createCA3Line($declaration_id, '28', 'TVA nette due (ligne TD – ligne X5)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $net_vat_due,
|
||||
'total_amount' => $net_vat_due
|
||||
'vat_amount' => $line_28_amount,
|
||||
'total_amount' => $line_28_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, '28', 'Calculated: 25 - 26', array(
|
||||
$this->createCA3Line($declaration_id, '28', 'TVA nette due (ligne TD – ligne X5)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
));
|
||||
}
|
||||
|
||||
// Line 29: Crédit de TVA (VAT Credit) - if negative
|
||||
if ($net_vat_due < 0) {
|
||||
$vat_credit = abs($net_vat_due);
|
||||
$this->createCA3Line($declaration_id, '29', 'Calculated: 26 - 25', array(
|
||||
// Line 32: Total à payer (lignes 28 + 29 + Z5 – AB) - same value as line 28, only if > 0
|
||||
$line_32_amount = $line_28_amount; // Same value as line 28
|
||||
if ($line_32_amount > 0) {
|
||||
$this->createCA3Line($declaration_id, '32', 'Total à payer (lignes 28 + 29 + Z5 – AB) (N\'oubliez pas d\'effectuer le règlement correspondant)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => $vat_credit,
|
||||
'total_amount' => $vat_credit
|
||||
'vat_amount' => $line_32_amount,
|
||||
'total_amount' => $line_32_amount
|
||||
));
|
||||
} else {
|
||||
$this->createCA3Line($declaration_id, '29', 'Calculated: 26 - 25', array(
|
||||
$this->createCA3Line($declaration_id, '32', 'Total à payer (lignes 28 + 29 + Z5 – AB) (N\'oubliez pas d\'effectuer le règlement correspondant)', array(
|
||||
'base_amount' => 0,
|
||||
'vat_amount' => 0,
|
||||
'total_amount' => 0
|
||||
|
||||
@ -108,8 +108,8 @@ class DeclarationTVA_Config
|
||||
WHERE entity = " . $this->entity . " AND ca3_line = '" . $this->db->escape($ca3_line) . "'";
|
||||
$this->db->query($sql);
|
||||
|
||||
// Then insert/activate new mappings
|
||||
if (!empty($account_codes)) {
|
||||
// Then insert/activate new mappings (only if account_codes is not empty)
|
||||
if (!empty($account_codes) && is_array($account_codes)) {
|
||||
foreach ($account_codes as $account_code) {
|
||||
if (!empty($account_code)) {
|
||||
// Check if mapping already exists (active or inactive)
|
||||
@ -138,6 +138,8 @@ class DeclarationTVA_Config
|
||||
}
|
||||
}
|
||||
}
|
||||
// If account_codes is empty, all mappings for this line remain deactivated
|
||||
// This ensures that clearing all selections properly saves the empty state
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -149,6 +149,55 @@ class DeclarationTVA_PDF
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate detailed CA-3 declaration PDF with breakdown pages
|
||||
*
|
||||
* @param int $declaration_id Declaration ID
|
||||
* @param string $outputlangs Output language
|
||||
* @return string|false PDF file path or false on error
|
||||
*/
|
||||
public function generateDetailedCA3PDF($declaration_id, $outputlangs = '')
|
||||
{
|
||||
global $conf, $langs, $user;
|
||||
|
||||
// Load declaration data
|
||||
$declaration = new DeclarationTVA($this->db);
|
||||
$result = $declaration->fetch($declaration_id);
|
||||
if ($result <= 0) {
|
||||
$this->error = 'Declaration not found';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get CA-3 line data
|
||||
$ca3_data = $declaration->getCA3Lines($declaration_id);
|
||||
if (empty($ca3_data)) {
|
||||
$this->error = 'No CA-3 data found';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get company information
|
||||
$company = new Societe($this->db);
|
||||
$company->fetch($conf->entity);
|
||||
|
||||
// Generate PDF filename
|
||||
$filename = 'CA3_Detailed_' . $declaration->declaration_number . '_' . date('Y-m-d') . '.pdf';
|
||||
$filepath = DOL_DATA_ROOT . '/declarationtva/' . $filename;
|
||||
|
||||
// Ensure directory exists
|
||||
if (!is_dir(DOL_DATA_ROOT . '/declarationtva/')) {
|
||||
dol_mkdir(DOL_DATA_ROOT . '/declarationtva/');
|
||||
}
|
||||
|
||||
// Generate detailed PDF with breakdown pages
|
||||
$result = $this->generateDetailedPDF($filepath, $declaration, $ca3_data, $company);
|
||||
|
||||
if ($result) {
|
||||
return $filepath;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template file path (custom or default)
|
||||
@ -247,6 +296,8 @@ class DeclarationTVA_PDF
|
||||
|
||||
// Check if output file was created successfully
|
||||
if (file_exists($output_path) && filesize($output_path) > 0) {
|
||||
// Add detailed pages to the filled PDF
|
||||
$this->addDetailedPagesToPDF($output_path, $declaration, $ca3_data, $company);
|
||||
return true;
|
||||
} else {
|
||||
$this->error = 'pdftk failed to generate output: ' . $result;
|
||||
@ -373,9 +424,10 @@ class DeclarationTVA_PDF
|
||||
// Section D: Résultat (Calculated)
|
||||
$field_data['D25_amount'] = $this->getCA3LineAmount($ca3_data, '25');
|
||||
$field_data['D26_amount'] = $this->getCA3LineAmount($ca3_data, '26');
|
||||
$field_data['D27_amount'] = $this->getCA3LineAmount($ca3_data, '27');
|
||||
$field_data['DTD_amount'] = $this->getCA3LineAmount($ca3_data, 'TD');
|
||||
$field_data['D28_amount'] = $this->getCA3LineAmount($ca3_data, '28');
|
||||
$field_data['D29_amount'] = $this->getCA3LineAmount($ca3_data, '29');
|
||||
$field_data['D32_amount'] = $this->getCA3LineAmount($ca3_data, '32');
|
||||
|
||||
// Subtotals
|
||||
$field_data['subtotal_B16_amount'] = $this->getCA3LineAmount($ca3_data, '16');
|
||||
@ -388,7 +440,7 @@ class DeclarationTVA_PDF
|
||||
$field_data['vat_credit'] = $this->formatAmount($declaration->vat_credit);
|
||||
|
||||
// Debug: Log specific D-section fields
|
||||
error_log("DeclarationTVA: D-section fields - D25: " . $field_data['D25_amount'] . ", D26: " . $field_data['D26_amount'] . ", DTD: " . $field_data['DTD_amount'] . ", D28: " . $field_data['D28_amount'] . ", D29: " . $field_data['D29_amount']);
|
||||
error_log("DeclarationTVA: D-section fields - D25: " . $field_data['D25_amount'] . ", D26: " . $field_data['D26_amount'] . ", D27: " . $field_data['D27_amount'] . ", DTD: " . $field_data['DTD_amount'] . ", D28: " . $field_data['D28_amount'] . ", D32: " . $field_data['D32_amount']);
|
||||
|
||||
return $field_data;
|
||||
}
|
||||
@ -705,6 +757,9 @@ class DeclarationTVA_PDF
|
||||
|
||||
file_put_contents($data_file, $data_content);
|
||||
|
||||
// Add detailed pages to the filled PDF
|
||||
$this->addDetailedPagesToPDF($output_path, $declaration, $ca3_data, $company);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
$this->error = 'Failed to copy template file';
|
||||
@ -756,14 +811,14 @@ class DeclarationTVA_PDF
|
||||
$pdf->SetFont('helvetica', '', 12);
|
||||
$pdf->Cell(0, 8, 'Numéro de déclaration: ' . $declaration->declaration_number, 0, 1);
|
||||
$pdf->Cell(0, 8, 'Période: ' . dol_print_date($declaration->start_date, 'day') . ' - ' . dol_print_date($declaration->end_date, 'day'), 0, 1);
|
||||
$pdf->Cell(0, 8, 'Statut: ' . $declaration->status, 0, 1);
|
||||
$pdf->Cell(0, 8, 'Statut: ' . $this->translateStatus($declaration->status), 0, 1);
|
||||
$pdf->Ln(10);
|
||||
|
||||
// Add CA-3 sections
|
||||
$this->addCA3Section($pdf, 'A. Opérations imposables', $ca3_data, array('A1', 'A2', 'A3', 'A4', 'A5'));
|
||||
$this->addCA3Section($pdf, 'B. TVA due', $ca3_data, array('08', '09', '9B', '17'));
|
||||
$this->addCA3Section($pdf, 'C. TVA déductible', $ca3_data, array('20', '21', '22'));
|
||||
$this->addCA3Section($pdf, 'D. Résultat', $ca3_data, array('25', '26', 'TD', '28', '29'));
|
||||
$this->addCA3Section($pdf, 'D. Résultat', $ca3_data, array('25', '26', '27', 'TD', '28', '32'));
|
||||
|
||||
// Add totals
|
||||
$pdf->Ln(10);
|
||||
@ -776,6 +831,9 @@ class DeclarationTVA_PDF
|
||||
$pdf->Cell(0, 8, 'CRÉDIT DE TVA: ' . price($declaration->vat_credit, 0, '', 1, 0), 0, 1);
|
||||
}
|
||||
|
||||
// Add detailed breakdown pages
|
||||
$this->addDetailPages($pdf, $declaration, $ca3_data);
|
||||
|
||||
// Output PDF
|
||||
$pdf->Output($output_path, 'F');
|
||||
|
||||
@ -805,7 +863,12 @@ class DeclarationTVA_PDF
|
||||
if (isset($ca3_data[$line])) {
|
||||
$data = $ca3_data[$line];
|
||||
$amount = isset($data['vat_amount']) ? $data['vat_amount'] : 0;
|
||||
$pdf->Cell(20, 6, $line, 1, 0, 'C');
|
||||
|
||||
// Add link to detail page for this line
|
||||
$link = $pdf->AddLink();
|
||||
$pdf->SetLink($link, 0, $this->getDetailPageNumber($line));
|
||||
|
||||
$pdf->Cell(20, 6, $line, 1, 0, 'C', false, $link);
|
||||
$pdf->Cell(100, 6, $data['line_label'], 1, 0);
|
||||
$pdf->Cell(30, 6, price($amount, 0, '', 1, 0), 1, 1, 'R');
|
||||
}
|
||||
@ -813,6 +876,554 @@ class DeclarationTVA_PDF
|
||||
$pdf->Ln(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add detailed breakdown pages to PDF
|
||||
*
|
||||
* @param TCPDF $pdf PDF object
|
||||
* @param DeclarationTVA $declaration Declaration object
|
||||
* @param array $ca3_data CA-3 line data
|
||||
*/
|
||||
private function addDetailPages($pdf, $declaration, $ca3_data)
|
||||
{
|
||||
// Define calculated lines that should be excluded from detailed breakdown
|
||||
$calculated_lines = array('25', '26', '27', 'TD', '28', '32', '16', '23');
|
||||
|
||||
// Define the order of lines as they appear in the view page
|
||||
$ordered_lines = array(
|
||||
// Section A: Opérations imposables
|
||||
'A1', 'A2', 'A3', 'A4', 'A5', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'F1', 'F2', 'F6', 'F7', 'F8',
|
||||
// Section B: TVA due
|
||||
'08', '09', '9B', '17',
|
||||
// Section C: TVA déductible
|
||||
'19', '20', '21', '22'
|
||||
);
|
||||
|
||||
// Create a lookup array for quick access to line data
|
||||
$ca3_lookup = array();
|
||||
foreach ($ca3_data as $line_data) {
|
||||
$ca3_lookup[$line_data['ca3_line']] = $line_data;
|
||||
}
|
||||
|
||||
// Get lines that have actual values and are not calculated lines, in the correct order
|
||||
$lines_with_data = array();
|
||||
foreach ($ordered_lines as $line_code) {
|
||||
// Skip if line doesn't exist in data
|
||||
if (!isset($ca3_lookup[$line_code])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip calculated lines - they don't have account mappings
|
||||
if (in_array($line_code, $calculated_lines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line_data = $ca3_lookup[$line_code];
|
||||
$has_vat_amount = isset($line_data['vat_amount']) && $line_data['vat_amount'] > 0;
|
||||
$has_base_amount = isset($line_data['base_amount']) && $line_data['base_amount'] > 0;
|
||||
$has_total_amount = isset($line_data['total_amount']) && $line_data['total_amount'] > 0;
|
||||
|
||||
// Only include lines that have meaningful values and are not calculated
|
||||
if ($has_vat_amount || $has_base_amount || $has_total_amount) {
|
||||
$lines_with_data[] = $line_code;
|
||||
}
|
||||
}
|
||||
|
||||
// If no lines have data, add a message page
|
||||
if (empty($lines_with_data)) {
|
||||
$pdf->AddPage();
|
||||
$pdf->SetFont('helvetica', 'B', 16);
|
||||
$pdf->Cell(0, 10, 'Aucune donnée détaillée disponible', 0, 1, 'C');
|
||||
$pdf->Ln(10);
|
||||
$pdf->SetFont('helvetica', '', 12);
|
||||
$pdf->Cell(0, 8, 'Aucune ligne de la déclaration ne contient de données comptables.', 0, 1);
|
||||
$pdf->Cell(0, 8, 'Veuillez configurer les mappings de comptes dans la section Administration.', 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a detail section for each line with data in the correct order
|
||||
foreach ($lines_with_data as $line_code) {
|
||||
$this->addLineDetailPage($pdf, $declaration, $line_code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a detail section for a specific CA-3 line
|
||||
*
|
||||
* @param TCPDF $pdf PDF object
|
||||
* @param DeclarationTVA $declaration Declaration object
|
||||
* @param string $line_code CA-3 line code
|
||||
*/
|
||||
private function addLineDetailPage($pdf, $declaration, $line_code)
|
||||
{
|
||||
// Check if we need a new page (if current position is too low)
|
||||
$current_y = $pdf->GetY();
|
||||
$page_height = $pdf->getPageHeight() - $pdf->getMargins()['bottom'];
|
||||
|
||||
// If we're too close to the bottom, start a new page
|
||||
if ($current_y > $page_height - 100) {
|
||||
$pdf->AddPage();
|
||||
} else {
|
||||
// Add some space between sections
|
||||
$pdf->Ln(10);
|
||||
}
|
||||
|
||||
// Set section title with separator
|
||||
$pdf->SetFont('helvetica', 'B', 16);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
$pdf->Cell(0, 8, 'Détail de la ligne ' . $line_code, 0, 1, 'C');
|
||||
|
||||
// Add separator line
|
||||
$pdf->SetDrawColor(0, 0, 0);
|
||||
$pdf->SetLineWidth(0.5);
|
||||
$pdf->Line($pdf->getMargins()['left'], $pdf->GetY(), $pdf->getPageWidth() - $pdf->getMargins()['right'], $pdf->GetY());
|
||||
$pdf->Ln(5);
|
||||
|
||||
// Get line details
|
||||
$line_details = $declaration->getCA3LineDetails($declaration->rowid, $line_code);
|
||||
|
||||
if (empty($line_details) || empty($line_details['account_details'])) {
|
||||
$pdf->SetFont('helvetica', '', 12);
|
||||
$pdf->Cell(0, 8, 'Aucun détail comptable disponible pour cette ligne.', 0, 1);
|
||||
$pdf->Cell(0, 8, 'Cette ligne peut être calculée automatiquement ou ne pas avoir de mapping de comptes.', 0, 1);
|
||||
|
||||
// Still show the calculated value if available
|
||||
if (!empty($line_details['calculated_line'])) {
|
||||
$calc = $line_details['calculated_line'];
|
||||
$pdf->Ln(5);
|
||||
$pdf->SetFont('helvetica', 'B', 12);
|
||||
$pdf->Cell(0, 8, 'Valeur calculée:', 0, 1);
|
||||
$pdf->SetFont('helvetica', '', 10);
|
||||
if ($calc->base_amount > 0) {
|
||||
$pdf->Cell(0, 6, 'Montant de base: ' . price($calc->base_amount, 0, '', 1, 0), 0, 1);
|
||||
}
|
||||
if ($calc->vat_amount > 0) {
|
||||
$pdf->Cell(0, 6, 'Montant de TVA: ' . price($calc->vat_amount, 0, '', 1, 0), 0, 1);
|
||||
}
|
||||
if ($calc->total_amount > 0) {
|
||||
$pdf->Cell(0, 6, 'Total: ' . price($calc->total_amount, 0, '', 1, 0), 0, 1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add line summary
|
||||
$pdf->SetFont('helvetica', 'B', 12);
|
||||
$pdf->Cell(0, 8, 'Résumé de la ligne ' . $line_code, 0, 1);
|
||||
|
||||
$pdf->SetFont('helvetica', '', 10);
|
||||
$pdf->Cell(0, 6, 'Période: ' . dol_print_date($line_details['start_date'], 'day') . ' - ' . dol_print_date($line_details['end_date'], 'day'), 0, 1);
|
||||
$pdf->Cell(0, 6, 'Nombre de comptes: ' . $line_details['account_count'], 0, 1);
|
||||
|
||||
if (!empty($line_details['calculated_line'])) {
|
||||
$calc = $line_details['calculated_line'];
|
||||
$pdf->Cell(0, 6, 'Montant calculé: ' . price($calc->vat_amount, 0, '', 1, 0), 0, 1);
|
||||
}
|
||||
|
||||
$pdf->Ln(10);
|
||||
|
||||
// Add account details table
|
||||
$this->addAccountDetailsTable($pdf, $line_details['account_details'], $line_code);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add account details table to PDF
|
||||
*
|
||||
* @param TCPDF $pdf PDF object
|
||||
* @param array $account_details Account details array
|
||||
* @param string $line_code CA-3 line code
|
||||
*/
|
||||
private function addAccountDetailsTable($pdf, $account_details, $line_code)
|
||||
{
|
||||
// Group accounts by type for lines 08, 09, 9B (same as view page)
|
||||
$base_accounts = array();
|
||||
$vat_accounts = array();
|
||||
$other_accounts = array();
|
||||
|
||||
foreach ($account_details as $account) {
|
||||
if (strpos($account['mapping_type'], '_BASE') !== false) {
|
||||
$base_accounts[] = $account;
|
||||
} elseif (strpos($account['mapping_type'], '_VAT') !== false) {
|
||||
$vat_accounts[] = $account;
|
||||
} else {
|
||||
$other_accounts[] = $account;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate table width and center position
|
||||
$table_width = 150; // 35 + 80 + 35
|
||||
$page_width = $pdf->getPageWidth() - $pdf->getMargins()['left'] - $pdf->getMargins()['right'];
|
||||
$start_x = ($page_width - $table_width) / 2 + $pdf->getMargins()['left'];
|
||||
|
||||
// Set X position to center the table
|
||||
$pdf->SetX($start_x);
|
||||
|
||||
// Table header - same as view page: Code compte, Libellé compte, Montant
|
||||
$pdf->SetFont('helvetica', 'B', 10);
|
||||
$pdf->Cell(35, 8, 'Code compte', 1, 0, 'C');
|
||||
$pdf->Cell(80, 8, 'Libellé compte', 1, 0, 'C');
|
||||
$pdf->Cell(35, 8, 'Montant', 1, 1, 'C');
|
||||
|
||||
$total_base_base = 0;
|
||||
$total_vat_base = 0;
|
||||
$total_base_vat = 0;
|
||||
$total_vat_vat = 0;
|
||||
$total_base_other = 0;
|
||||
$total_vat_other = 0;
|
||||
|
||||
// Display BASE accounts first (if any)
|
||||
if (!empty($base_accounts)) {
|
||||
$pdf->SetFont('helvetica', 'B', 9);
|
||||
$pdf->Cell(0, 6, 'Comptes de base (ventes)', 0, 1, 'L');
|
||||
|
||||
$pdf->SetFont('helvetica', '', 9);
|
||||
foreach ($base_accounts as $account) {
|
||||
$total_base_base += $account['base_amount'];
|
||||
$total_vat_base += $account['vat_amount'];
|
||||
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
|
||||
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($account['base_amount'], 0, '', 1, 0), 1, 1, 'R');
|
||||
}
|
||||
|
||||
// Subtotal for BASE accounts
|
||||
$pdf->SetFont('helvetica', 'B', 9);
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(115, 6, 'Sous-total comptes de base', 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($total_base_base, 0, '', 1, 0), 1, 1, 'R');
|
||||
$pdf->Ln(2);
|
||||
}
|
||||
|
||||
// Display VAT accounts second (if any)
|
||||
if (!empty($vat_accounts)) {
|
||||
$pdf->SetFont('helvetica', 'B', 9);
|
||||
$pdf->Cell(0, 6, 'Comptes de TVA', 0, 1, 'L');
|
||||
|
||||
$pdf->SetFont('helvetica', '', 9);
|
||||
foreach ($vat_accounts as $account) {
|
||||
$total_base_vat += $account['base_amount'];
|
||||
$total_vat_vat += $account['vat_amount'];
|
||||
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
|
||||
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($account['vat_amount'], 0, '', 1, 0), 1, 1, 'R');
|
||||
}
|
||||
|
||||
// Subtotal for VAT accounts
|
||||
$pdf->SetFont('helvetica', 'B', 9);
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(115, 6, 'Sous-total comptes de TVA', 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($total_vat_vat, 0, '', 1, 0), 1, 1, 'R');
|
||||
$pdf->Ln(2);
|
||||
}
|
||||
|
||||
// Display other accounts (normal lines)
|
||||
if (!empty($other_accounts)) {
|
||||
$pdf->SetFont('helvetica', '', 9);
|
||||
foreach ($other_accounts as $account) {
|
||||
$total_base_other += $account['base_amount'];
|
||||
$total_vat_other += $account['vat_amount'];
|
||||
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(35, 6, $account['account_code'], 1, 0, 'C');
|
||||
$pdf->Cell(80, 6, $this->truncateText($account['account_label'], 35), 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($account['vat_amount'], 0, '', 1, 0), 1, 1, 'R');
|
||||
}
|
||||
|
||||
// Total for other accounts
|
||||
if (!empty($other_accounts)) {
|
||||
$pdf->SetFont('helvetica', 'B', 9);
|
||||
$pdf->SetX($start_x);
|
||||
$pdf->Cell(115, 6, 'Total', 1, 0, 'L');
|
||||
$pdf->Cell(35, 6, price($total_vat_other, 0, '', 1, 0), 1, 1, 'R');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add detailed pages to an existing PDF
|
||||
*
|
||||
* @param string $pdf_path Path to the existing PDF
|
||||
* @param DeclarationTVA $declaration Declaration object
|
||||
* @param array $ca3_data CA-3 line data
|
||||
* @param Societe $company Company object
|
||||
* @return bool Success
|
||||
*/
|
||||
private function addDetailedPagesToPDF($pdf_path, $declaration, $ca3_data, $company)
|
||||
{
|
||||
try {
|
||||
error_log("DeclarationTVA: Starting addDetailedPagesToPDF process");
|
||||
|
||||
// Create a temporary detailed PDF
|
||||
$temp_detailed_path = tempnam(sys_get_temp_dir(), 'ca3_detailed_') . '.pdf';
|
||||
error_log("DeclarationTVA: Temporary detailed PDF path: " . $temp_detailed_path);
|
||||
|
||||
$result = $this->generateDetailedPDF($temp_detailed_path, $declaration, $ca3_data, $company);
|
||||
|
||||
if (!$result) {
|
||||
error_log("DeclarationTVA: Failed to generate detailed PDF: " . $this->error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if detailed PDF was created and has content
|
||||
if (!file_exists($temp_detailed_path)) {
|
||||
error_log("DeclarationTVA: Detailed PDF file does not exist at: " . $temp_detailed_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filesize($temp_detailed_path) == 0) {
|
||||
error_log("DeclarationTVA: Detailed PDF file is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log("DeclarationTVA: Detailed PDF created successfully, size: " . filesize($temp_detailed_path) . " bytes");
|
||||
|
||||
// Use a different approach: use pdftk to merge PDFs (preserves form fields)
|
||||
$merge_result = $this->mergePDFsWithPdftk($pdf_path, $temp_detailed_path, $pdf_path);
|
||||
|
||||
if (!$merge_result) {
|
||||
error_log("DeclarationTVA: Failed to merge PDFs with pdftk: " . $this->error);
|
||||
// Fallback: try FPDI merge
|
||||
$merge_result = $this->mergePDFs($pdf_path, $temp_detailed_path, $pdf_path);
|
||||
if (!$merge_result) {
|
||||
error_log("DeclarationTVA: Failed to merge PDFs with FPDI: " . $this->error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error_log("DeclarationTVA: PDFs merged successfully");
|
||||
|
||||
// Clean up temporary file
|
||||
if (file_exists($temp_detailed_path)) {
|
||||
unlink($temp_detailed_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error = 'Failed to add detailed pages: ' . $e->getMessage();
|
||||
error_log("DeclarationTVA: Exception in addDetailedPagesToPDF: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two PDFs using pdftk (preserves form fields)
|
||||
*
|
||||
* @param string $pdf1_path First PDF path
|
||||
* @param string $pdf2_path Second PDF path
|
||||
* @param string $output_path Output PDF path
|
||||
* @return bool Success
|
||||
*/
|
||||
private function mergePDFsWithPdftk($pdf1_path, $pdf2_path, $output_path)
|
||||
{
|
||||
try {
|
||||
// Check if pdftk is available
|
||||
if (!$this->isPdftkAvailable()) {
|
||||
$this->error = 'pdftk is not available for PDF merging';
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log("DeclarationTVA: Starting PDF merge with pdftk - PDF1: " . $pdf1_path . " (size: " . filesize($pdf1_path) . "), PDF2: " . $pdf2_path . " (size: " . filesize($pdf2_path) . ")");
|
||||
|
||||
// Use pdftk to merge PDFs (preserves form fields)
|
||||
// Create a temporary output file to avoid pdftk input/output filename conflict
|
||||
$temp_output_path = tempnam(sys_get_temp_dir(), 'ca3_merged_') . '.pdf';
|
||||
|
||||
$pdftk_path = $this->getPdftkPath();
|
||||
$command = "\"$pdftk_path\" \"$pdf1_path\" \"$pdf2_path\" cat output \"$temp_output_path\"";
|
||||
error_log("DeclarationTVA: pdftk command: " . $command);
|
||||
$result = shell_exec($command . ' 2>&1');
|
||||
error_log("DeclarationTVA: pdftk result: " . $result);
|
||||
|
||||
// If pdftk succeeded, copy the merged file to the final output path
|
||||
if (file_exists($temp_output_path) && filesize($temp_output_path) > 0) {
|
||||
copy($temp_output_path, $output_path);
|
||||
unlink($temp_output_path); // Clean up temporary file
|
||||
}
|
||||
|
||||
// Check if output file was created successfully
|
||||
if (file_exists($output_path) && filesize($output_path) > 0) {
|
||||
error_log("DeclarationTVA: PDFs merged successfully with pdftk, size: " . filesize($output_path) . " bytes");
|
||||
return true;
|
||||
} else {
|
||||
$this->error = 'pdftk failed to merge PDFs: ' . $result;
|
||||
error_log("DeclarationTVA: pdftk merge failed: " . $result);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error = 'Failed to merge PDFs with pdftk: ' . $e->getMessage();
|
||||
error_log("DeclarationTVA: Exception in mergePDFsWithPdftk: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two PDFs using FPDI with TCPDF
|
||||
*
|
||||
* @param string $pdf1_path First PDF path
|
||||
* @param string $pdf2_path Second PDF path
|
||||
* @param string $output_path Output PDF path
|
||||
* @return bool Success
|
||||
*/
|
||||
private function mergePDFs($pdf1_path, $pdf2_path, $output_path)
|
||||
{
|
||||
try {
|
||||
error_log("DeclarationTVA: Starting PDF merge - PDF1: " . $pdf1_path . " (size: " . filesize($pdf1_path) . "), PDF2: " . $pdf2_path . " (size: " . filesize($pdf2_path) . ")");
|
||||
|
||||
// Use TCPDF-based FPDI
|
||||
$pdf = new \setasign\Fpdi\Tcpdf\Fpdi();
|
||||
|
||||
// Import pages from first PDF
|
||||
$page_count1 = $pdf->setSourceFile($pdf1_path);
|
||||
error_log("DeclarationTVA: PDF1 has " . $page_count1 . " pages");
|
||||
for ($i = 1; $i <= $page_count1; $i++) {
|
||||
$pdf->AddPage();
|
||||
$pdf->useTemplate($pdf->importPage($i));
|
||||
}
|
||||
|
||||
// Import pages from second PDF
|
||||
$page_count2 = $pdf->setSourceFile($pdf2_path);
|
||||
error_log("DeclarationTVA: PDF2 has " . $page_count2 . " pages");
|
||||
for ($i = 1; $i <= $page_count2; $i++) {
|
||||
$pdf->AddPage();
|
||||
$pdf->useTemplate($pdf->importPage($i));
|
||||
}
|
||||
|
||||
// Output merged PDF
|
||||
$pdf->Output($output_path, 'F');
|
||||
|
||||
error_log("DeclarationTVA: Merged PDF created successfully, size: " . filesize($output_path) . " bytes");
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error = 'Failed to merge PDFs: ' . $e->getMessage();
|
||||
error_log("DeclarationTVA: Exception in mergePDFs: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate status to French
|
||||
*
|
||||
* @param string $status Status in English
|
||||
* @return string Status in French
|
||||
*/
|
||||
private function translateStatus($status)
|
||||
{
|
||||
$translations = array(
|
||||
'draft' => 'Brouillon',
|
||||
'validated' => 'Validé',
|
||||
'submitted' => 'Soumis',
|
||||
'approved' => 'Approuvé',
|
||||
'rejected' => 'Rejeté'
|
||||
);
|
||||
|
||||
return isset($translations[$status]) ? $translations[$status] : ucfirst($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping type label for display
|
||||
*
|
||||
* @param string $mapping_type Mapping type (e.g., '08_BASE', '08_VAT')
|
||||
* @return string Display label
|
||||
*/
|
||||
private function getMappingTypeLabel($mapping_type)
|
||||
{
|
||||
if (strpos($mapping_type, '_BASE') !== false) {
|
||||
return 'Base';
|
||||
} elseif (strpos($mapping_type, '_VAT') !== false) {
|
||||
return 'TVA';
|
||||
} else {
|
||||
return 'Standard';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text to fit in table cell
|
||||
*
|
||||
* @param string $text Text to truncate
|
||||
* @param int $max_length Maximum length
|
||||
* @return string Truncated text
|
||||
*/
|
||||
private function truncateText($text, $max_length)
|
||||
{
|
||||
if (strlen($text) <= $max_length) {
|
||||
return $text;
|
||||
}
|
||||
return substr($text, 0, $max_length - 3) . '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detail page number for a line (for linking)
|
||||
*
|
||||
* @param string $line_code CA-3 line code
|
||||
* @return int Page number
|
||||
*/
|
||||
private function getDetailPageNumber($line_code)
|
||||
{
|
||||
// This is a simplified approach - in a real implementation,
|
||||
// you'd track page numbers as you create them
|
||||
return 2; // Start from page 2 (after main form)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate detailed PDF with breakdown pages
|
||||
*
|
||||
* @param string $output_path Output file path
|
||||
* @param DeclarationTVA $declaration Declaration object
|
||||
* @param array $ca3_data CA-3 line data
|
||||
* @param Societe $company Company object
|
||||
* @return bool Success
|
||||
*/
|
||||
private function generateDetailedPDF($output_path, $declaration, $ca3_data, $company)
|
||||
{
|
||||
try {
|
||||
// Create a new PDF document
|
||||
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
|
||||
|
||||
// Set document information
|
||||
$pdf->SetCreator('DeclarationTVA Module');
|
||||
$pdf->SetAuthor($company->name);
|
||||
$pdf->SetTitle('CA-3 Declaration Details ' . $declaration->declaration_number);
|
||||
$pdf->SetSubject('French VAT Declaration - Detailed Breakdown');
|
||||
|
||||
// Set margins
|
||||
$pdf->SetMargins(15, 15, 15);
|
||||
$pdf->SetHeaderMargin(5);
|
||||
$pdf->SetFooterMargin(10);
|
||||
|
||||
// Add title page
|
||||
$pdf->AddPage();
|
||||
$pdf->SetFont('helvetica', 'B', 16);
|
||||
$pdf->Cell(0, 10, 'Détails de la Déclaration TVA CA-3', 0, 1, 'C');
|
||||
$pdf->Ln(10);
|
||||
|
||||
$pdf->SetFont('helvetica', '', 12);
|
||||
$pdf->Cell(0, 8, 'Numéro de déclaration: ' . $declaration->declaration_number, 0, 1);
|
||||
$pdf->Cell(0, 8, 'Période: ' . dol_print_date($declaration->start_date, 'day') . ' - ' . dol_print_date($declaration->end_date, 'day'), 0, 1);
|
||||
$pdf->Cell(0, 8, 'Statut: ' . $this->translateStatus($declaration->status), 0, 1);
|
||||
$pdf->Ln(10);
|
||||
|
||||
|
||||
// Add detailed breakdown pages
|
||||
$this->addDetailPages($pdf, $declaration, $ca3_data);
|
||||
|
||||
// Output PDF
|
||||
$pdf->Output($output_path, 'F');
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error = 'Detailed PDF generation failed: ' . $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload custom template
|
||||
*
|
||||
|
||||
@ -76,7 +76,7 @@ class modDeclarationTVA extends DolibarrModules
|
||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@declarationtva'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '2.0.0';
|
||||
$this->version = '2.1.0';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
||||
@ -76,6 +76,28 @@ if ($action == 'export_pdf') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'export_pdf_detailed') {
|
||||
// Load PDF generator
|
||||
require_once DOL_DOCUMENT_ROOT . '/custom/declarationtva/core/class/declarationtva_pdf.class.php';
|
||||
$pdf_generator = new DeclarationTVA_PDF($db);
|
||||
|
||||
// Generate detailed PDF with breakdown pages
|
||||
$pdf_path = $pdf_generator->generateDetailedCA3PDF($id);
|
||||
|
||||
if ($pdf_path && file_exists($pdf_path)) {
|
||||
// Set headers for PDF download
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: attachment; filename="CA3_Detailed_' . $declarationtva->declaration_number . '.pdf"');
|
||||
header('Content-Length: ' . filesize($pdf_path));
|
||||
|
||||
// Output PDF file
|
||||
readfile($pdf_path);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($pdf_generator->error ?: $langs->trans("ErrorGeneratingDetailedPDF"), null, 'errors');
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch declaration
|
||||
if ($declarationtva->fetch($id) < 0) {
|
||||
setEventMessages($langs->trans("DeclarationNotFound"), null, 'errors');
|
||||
@ -353,21 +375,21 @@ print '<th colspan="2">' . $langs->trans("Description") . '</th>';
|
||||
print '<th class="right">' . $langs->trans("Amount") . '</th>';
|
||||
print '</tr>';
|
||||
|
||||
$section_d_lines = array('25', '26', 'TD', '28', '29');
|
||||
$section_d_lines = array('25', '26', '27', 'TD', '28', '32');
|
||||
foreach ($section_d_lines as $line) {
|
||||
$data = isset($ca3_data[$line]) ? $ca3_data[$line] : array('line_label' => '', 'vat_amount' => 0);
|
||||
|
||||
// Special handling for line TD
|
||||
if ($line == 'TD') {
|
||||
$description = 'TVA due (montant à payer)';
|
||||
} else {
|
||||
$description = isset($ca3_definitions[$line]) ? $ca3_definitions[$line]['label'] : $data['line_label'];
|
||||
}
|
||||
// Prioritize database line_label over definitions (for calculated lines)
|
||||
$description = !empty($data['line_label']) ? $data['line_label'] : (isset($ca3_definitions[$line]) ? $ca3_definitions[$line]['label'] : '');
|
||||
|
||||
// Special formatting for line 32 (bold)
|
||||
$is_line_32 = ($line == '32');
|
||||
$bold_style = $is_line_32 ? 'font-weight: bold;' : '';
|
||||
|
||||
print '<tr style="background-color: #ffe6e6 !important;">';
|
||||
print '<td style="background-color: #ffe6e6 !important; text-align: center;"><strong>' . $line . '</strong></td>';
|
||||
print '<td colspan="2" style="background-color: #ffe6e6 !important;">' . $description . '</td>';
|
||||
print '<td class="right" style="background-color: #ffe6e6 !important;">' . formatAmount($data['vat_amount']) . '</td>';
|
||||
print '<td style="background-color: #ffe6e6 !important; text-align: center; ' . $bold_style . '"><strong>' . $line . '</strong></td>';
|
||||
print '<td colspan="2" style="background-color: #ffe6e6 !important; ' . $bold_style . '">' . $description . '</td>';
|
||||
print '<td class="right" style="background-color: #ffe6e6 !important; ' . $bold_style . '">' . formatAmount($data['vat_amount']) . '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
@ -390,8 +412,9 @@ print '<div class="center">';
|
||||
// Recalculate button (always available)
|
||||
print '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $id . '&action=recalculate&token=' . newToken() . '" class="butAction">' . $langs->trans("Recalculate") . '</a> ';
|
||||
|
||||
// PDF Export button (always available)
|
||||
// PDF Export buttons (always available)
|
||||
print '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $id . '&action=export_pdf" class="butAction">' . $langs->trans("ExportPDF") . '</a> ';
|
||||
print '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $id . '&action=export_pdf_detailed" class="butAction">' . $langs->trans("ExportPDFDetailed") . '</a> ';
|
||||
|
||||
if ($declarationtva->status == 'draft') {
|
||||
print '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $id . '&action=validate" class="butAction">' . $langs->trans("Validate") . '</a> ';
|
||||
|
||||
@ -484,6 +484,8 @@ SubtotalBaseAccounts = Subtotal Base Accounts
|
||||
SubtotalVATAccounts = Subtotal VAT Accounts
|
||||
GrandTotal = Grand Total
|
||||
ExportPDF = Export PDF
|
||||
ExportPDFDetailed = Export Detailed PDF
|
||||
ErrorGeneratingDetailedPDF = Error generating detailed PDF
|
||||
TemplateUploaded = PDF template uploaded successfully
|
||||
TemplateReset = Reset to official template
|
||||
TemplateResetFailed = Error resetting to official template
|
||||
|
||||
@ -456,6 +456,8 @@ SubtotalBaseAccounts = Sous-total comptes de base
|
||||
SubtotalVATAccounts = Sous-total comptes de TVA
|
||||
GrandTotal = Total général
|
||||
ExportPDF = Exporter PDF
|
||||
ExportPDFDetailed = Exporter PDF Détaillé
|
||||
ErrorGeneratingDetailedPDF = Erreur lors de la génération du PDF détaillé
|
||||
TemplateUploaded = Modèle PDF téléchargé avec succès
|
||||
TemplateReset = Retour au modèle officiel
|
||||
TemplateResetFailed = Erreur lors du retour au modèle officiel
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user