DeclarationTVA/core/class/declarationtva_pdf.class.php
Frank Cools 0d27113489 🔄 Add Gitea-Based Template Auto-Update System
NEW FEATURES:
 Gitea API Integration for Template Management
 Automatic Template Update Checking
 Version Control and Manifest System
 One-Click Template Updates in Configuration
 Professional Template Management Workflow

TECHNICAL IMPLEMENTATION:
- Enhanced DeclarationTVA_PDF class with auto-update methods
- Template manifest system (templates/manifest.json)
- Gitea API integration for template downloads
- Update status display in configuration page
- Version comparison and automatic updates

TEMPLATE MANAGEMENT:
- Current version: 30 (10963*30)
- Gitea repository: https://git.covago.com/frank/DeclarationTVA
- Manifest URL: templates/manifest.json
- Auto-update on module activation
- Fallback to built-in template

MAINTENANCE WORKFLOW:
1. Tax authority updates form → Create fillable version (5 min)
2. Update manifest.json with new version (1 min)
3. Upload template to Gitea (2 min)
4. Users get automatic updates (0 min for maintainer)

This creates a professional, self-hosted template management system with minimal maintenance overhead!
2025-10-03 00:17:59 +02:00

490 lines
15 KiB
PHP

<?php
/* 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 core/class/declarationtva_pdf.class.php
* \ingroup declarationtva
* \brief PDF generation for CA-3 declarations
*/
require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
/**
* Class to generate CA-3 declaration PDF
*/
class DeclarationTVA_PDF
{
/**
* @var DoliDB Database handler
*/
public $db;
/**
* @var string Error code (or message)
*/
public $error = '';
/**
* @var string[] Several error codes (or messages)
*/
public $errors = array();
/**
* @var int Entity
*/
public $entity;
/**
* @var string Template path
*/
public $template_path;
/**
* @var string Template version
*/
public $template_version = '30';
/**
* @var string Template document number
*/
public $template_document = '10963';
/**
* @var string Gitea repository URL for templates
*/
public $gitea_repo_url = 'https://git.covago.com/frank/DeclarationTVA';
/**
* @var string Manifest file URL
*/
public $manifest_url = 'https://git.covago.com/frank/DeclarationTVA/raw/branch/main/templates/manifest.json';
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
$this->entity = (int) $conf->entity;
$this->template_path = DOL_DOCUMENT_ROOT.'/custom/declarationtva/templates/declarationtva/';
}
/**
* Generate CA-3 declaration PDF
*
* @param int $declaration_id Declaration ID
* @param string $outputlangs Output language
* @return string|false PDF file path or false on error
*/
public function generateCA3PDF($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_' . $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/');
}
// Check if we have a custom template
$template_file = $this->getTemplatePath();
if (!$template_file) {
$this->error = 'CA-3 template not found';
return false;
}
// Generate PDF using FPDI or similar library
$result = $this->fillPDFTemplate($template_file, $filepath, $declaration, $ca3_data, $company);
if ($result) {
return $filepath;
} else {
return false;
}
}
/**
* Get the template file path (custom or default)
*
* @return string|false Template file path or false if not found
*/
private function getTemplatePath()
{
// Check for custom template first
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
if (file_exists($custom_template)) {
return $custom_template;
}
// Fall back to default template
$default_template = $this->template_path . 'ca3_official_template.pdf';
if (file_exists($default_template)) {
return $default_template;
}
return false;
}
/**
* Fill PDF template with declaration data
*
* @param string $template_path Template file path
* @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 fillPDFTemplate($template_path, $output_path, $declaration, $ca3_data, $company)
{
// This is a placeholder - we'll need to implement actual PDF filling
// For now, we'll create a simple PDF with the data
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 ' . $declaration->declaration_number);
$pdf->SetSubject('French VAT Declaration');
// Set margins
$pdf->SetMargins(15, 15, 15);
$pdf->SetHeaderMargin(5);
$pdf->SetFooterMargin(10);
// Add a page
$pdf->AddPage();
// Add title
$pdf->SetFont('helvetica', 'B', 16);
$pdf->Cell(0, 10, 'Déclaration TVA CA-3', 0, 1, 'C');
$pdf->Ln(10);
// Add declaration information
$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->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'));
// Add totals
$pdf->Ln(10);
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, 'TOTAL TVA COLLECTÉE: ' . price($declaration->total_vat_collected, 0, '', 1, 0), 0, 1);
$pdf->Cell(0, 8, 'TOTAL TVA DÉDUCTIBLE: ' . price($declaration->total_vat_deductible, 0, '', 1, 0), 0, 1);
$pdf->Cell(0, 8, 'TVA NETTE DUE: ' . price($declaration->net_vat_due, 0, '', 1, 0), 0, 1);
if ($declaration->vat_credit > 0) {
$pdf->Cell(0, 8, 'CRÉDIT DE TVA: ' . price($declaration->vat_credit, 0, '', 1, 0), 0, 1);
}
// Output PDF
$pdf->Output($output_path, 'F');
return true;
} catch (Exception $e) {
$this->error = 'PDF generation failed: ' . $e->getMessage();
return false;
}
}
/**
* Add CA-3 section to PDF
*
* @param TCPDF $pdf PDF object
* @param string $section_title Section title
* @param array $ca3_data CA-3 data
* @param array $lines Lines to include
*/
private function addCA3Section($pdf, $section_title, $ca3_data, $lines)
{
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(0, 8, $section_title, 0, 1);
$pdf->SetFont('helvetica', '', 10);
foreach ($lines as $line) {
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');
$pdf->Cell(100, 6, $data['line_label'], 1, 0);
$pdf->Cell(30, 6, price($amount, 0, '', 1, 0), 1, 1, 'R');
}
}
$pdf->Ln(5);
}
/**
* Upload custom template
*
* @param array $file Uploaded file array
* @return bool Success
*/
public function uploadCustomTemplate($file)
{
if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
$this->error = 'No file uploaded';
return false;
}
// Validate file type
$file_info = pathinfo($file['name']);
if (strtolower($file_info['extension']) !== 'pdf') {
$this->error = 'Only PDF files are allowed';
return false;
}
// Validate file size (max 10MB)
if ($file['size'] > 10 * 1024 * 1024) {
$this->error = 'File too large (max 10MB)';
return false;
}
// Move uploaded file
$target_path = $this->template_path . 'ca3_custom_template.pdf';
if (!is_dir($this->template_path)) {
dol_mkdir($this->template_path);
}
if (move_uploaded_file($file['tmp_name'], $target_path)) {
return true;
} else {
$this->error = 'Failed to save template';
return false;
}
}
/**
* Get template information
*
* @return array Template information
*/
public function getTemplateInfo()
{
$info = array(
'version' => $this->template_version,
'document' => $this->template_document,
'official_number' => $this->template_document . '*' . $this->template_version,
'custom_template' => false,
'template_path' => ''
);
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
if (file_exists($custom_template)) {
$info['custom_template'] = true;
$info['template_path'] = $custom_template;
}
return $info;
}
/**
* Reset to default template
*
* @return bool Success
*/
public function resetToDefaultTemplate()
{
$custom_template = $this->template_path . 'ca3_custom_template.pdf';
if (file_exists($custom_template)) {
return unlink($custom_template);
}
return true;
}
/**
* Check for template updates from Gitea
*
* @return array Update information
*/
public function checkTemplateUpdates()
{
$update_info = array(
'update_available' => false,
'current_version' => $this->template_version,
'latest_version' => $this->template_version,
'download_url' => '',
'release_date' => '',
'error' => ''
);
try {
// Fetch manifest from Gitea
$manifest_content = file_get_contents($this->manifest_url);
if ($manifest_content === false) {
$update_info['error'] = 'Failed to fetch manifest from Gitea';
return $update_info;
}
$manifest = json_decode($manifest_content, true);
if (!$manifest || !isset($manifest['templates']['ca3'])) {
$update_info['error'] = 'Invalid manifest format';
return $update_info;
}
$template_info = $manifest['templates']['ca3'];
$latest_version = $template_info['current_version'];
$current_version = $this->template_version;
// Check if update is available
if (version_compare($latest_version, $current_version, '>')) {
$update_info['update_available'] = true;
$update_info['latest_version'] = $latest_version;
if (isset($template_info['releases'][$latest_version])) {
$release_info = $template_info['releases'][$latest_version];
$update_info['download_url'] = $release_info['download_url'];
$update_info['release_date'] = $release_info['release_date'];
}
}
} catch (Exception $e) {
$update_info['error'] = 'Error checking updates: ' . $e->getMessage();
}
return $update_info;
}
/**
* Download and install template update
*
* @param string $version Version to download
* @param string $download_url Download URL
* @return bool Success
*/
public function downloadTemplateUpdate($version, $download_url)
{
try {
// Ensure template directory exists
if (!is_dir($this->template_path)) {
dol_mkdir($this->template_path);
}
// Download template
$template_content = file_get_contents($download_url);
if ($template_content === false) {
$this->error = 'Failed to download template from Gitea';
return false;
}
// Save as official template
$template_file = $this->template_path . 'ca3_official_template.pdf';
if (file_put_contents($template_file, $template_content) === false) {
$this->error = 'Failed to save downloaded template';
return false;
}
// Update version info
$this->template_version = $version;
return true;
} catch (Exception $e) {
$this->error = 'Error downloading template: ' . $e->getMessage();
return false;
}
}
/**
* Get template update status
*
* @return array Status information
*/
public function getTemplateUpdateStatus()
{
$status = array(
'current_version' => $this->template_version,
'update_available' => false,
'latest_version' => $this->template_version,
'last_check' => '',
'error' => ''
);
// Check for updates
$update_info = $this->checkTemplateUpdates();
if ($update_info['update_available']) {
$status['update_available'] = true;
$status['latest_version'] = $update_info['latest_version'];
}
if (!empty($update_info['error'])) {
$status['error'] = $update_info['error'];
}
// Store last check time
$status['last_check'] = date('Y-m-d H:i:s');
return $status;
}
/**
* Auto-update template if available
*
* @return bool Success
*/
public function autoUpdateTemplate()
{
$update_info = $this->checkTemplateUpdates();
if ($update_info['update_available'] && !empty($update_info['download_url'])) {
return $this->downloadTemplateUpdate(
$update_info['latest_version'],
$update_info['download_url']
);
}
return true; // No update needed
}
}