Merge branch 'develop-tolotsoa' into production-hfc

This commit is contained in:
Tolotsoa 2025-09-03 21:02:37 +03:00
commit 923c40a238
17 changed files with 6037 additions and 4256 deletions

53
.gitignore vendored
View File

@ -1,11 +1,46 @@
*
# Dépendances (tous niveaux)
**/node_modules/
**/vendor/
**/bower_components/
# Artifacts de build / caches (tous niveaux)
**/dist/
**/build/
**/.cache/
**/.parcel-cache/
**/.vite/
**/coverage/
**/.next/
**/.nuxt/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Fichiers d'environnement
.env
.env.*.local
docker-compose.yml
# Fichiers système / IDE
.DS_Store
Thumbs.db
.idea/
.vscode/
# Dumps & fichiers sensibles courants
*.sql
!.gitignore
!calendar/
!gestion/
!Jenkinsfile
!calendar/**
!gestion/**
# Spécifiques Nextcloud (si jamais présents dans le repo)
data/
config/
themes/
apps-external/
.updater-*
occ
autotest.sh
tests/
coverage/

View File

@ -1,4 +1,5 @@
<?php
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
@ -72,6 +73,7 @@ return [
['name' => 'page#exportDevisToFacture', 'url' => '/exportDevisToFacture', 'verb' => 'POST'],
['name' => 'page#exportFactureToPdf', 'url' => '/facture/exportFactureToPdf', 'verb' => 'POST'],
['name' => 'page#exportFactureByClientAndMonthYearToPdf', 'url' => '/facture/exportFactureByClientAndMonthYearToPdf', 'verb' => 'POST'],
['name' => 'page#exportDevisRecap', 'url' => '/devis/exportDevisRecap', 'verb' => 'POST'],
['name' => 'page#getProduits', 'url' => '/getProduits', 'verb' => 'PROPFIND'],
['name' => 'page#getProduitsById', 'url' => '/getProduitsById', 'verb' => 'POST'],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
<?php
namespace OCA\Gestion\Service\Devis;
use OCA\Gestion\Db\Bdd;
use OCA\Gestion\Constants\MultipleFactureTypeConstant;
/**
* Classe responsable du traitement et de la préparation des données de devis
*/
class DevisDataProcessor
{
/** @var Bdd */
private $gestionBdd;
public function __construct(Bdd $gestionBdd)
{
$this->gestionBdd = $gestionBdd;
}
public function prepareDevisData($devisData, $currentConfig, $filter, $idNextCloud)
{
$data_devis = [];
foreach ($devisData as $devis) {
$devis_temp = $this->createDevisStructure($devis, $currentConfig);
$devis_temp = $this->calculateDevisAmounts($devis_temp, $devis, $filter, $idNextCloud);
$data_devis[] = $devis_temp;
}
return $data_devis;
}
public function createDevisStructure($devis, $currentConfig)
{
return array(
'devis_id' => $devis['devis_id'],
'devis_full_number' => $devis['devis_full_number'],
'case_number' => $devis['case_number'], // NOUVEAU : remplace calendar_uuid
'order_number' => $devis['order_number'], // NOUVEAU : remplace num_commande vide
'devis_date' => $devis['devis_date'],
'lieu_nom' => $devis['lieu_nom'],
'lieu_adresse' => $devis['lieu_adresse'],
'defunt_nom' => $devis['defunt_nom'],
'defunt_sexe' => $devis['defunt_sexe'],
'client_nom' => $devis['client_nom'],
'client_entreprise' => $devis['client_entreprise'],
'client_adresse' => $devis['client_adresse'],
'group_name' => $devis['group_name'],
'group_address' => $devis['group_address'],
'group_postal_code' => $devis['group_postal_code'],
'group_city' => $devis['group_city'],
'group_email' => $devis['group_email'],
'group_siret_number' => $devis['group_siret_number'],
'article' => 'SOINS',
'montant_htc' => 0,
'tva' => $currentConfig->tva_default,
'montant_tva' => 0,
'montant_ttc' => 0
);
}
public function calculateDevisAmounts($devis_temp, $devis, $filter, $idNextCloud)
{
$produits = json_decode($this->gestionBdd->getListProduit($devis['devis_id'], $idNextCloud));
// Variables pour gérer les articles comme dans les factures
$produitsReferenceArray = [];
foreach ($produits as $produit) {
$htPrice = $this->getProductPrice($produit, $devis, $filter);
$devis_temp['montant_htc'] += $htPrice * $produit->quantite;
// Collecter les références comme dans les factures
if (isset($produit->reference) && !empty($produit->reference)) {
$produitsReferenceArray[] = $produit->reference;
}
}
// Traitement identique aux factures
$produitsReferenceArray = array_unique($produitsReferenceArray);
$produitsReferenceAsString = implode("-", $produitsReferenceArray);
$devis_temp['article'] = !empty($produitsReferenceAsString) ? $produitsReferenceAsString : 'SOINS';
$devis_temp['montant_tva'] = ($devis_temp['montant_htc'] * $devis_temp['tva']) / 100;
$devis_temp['montant_ttc'] = $devis_temp['montant_tva'] + $devis_temp['montant_htc'];
return $devis_temp;
}
public function getProductPrice($produit, $devis, $filter)
{
$htPrice = $produit->prix_unitaire;
if ($devis['group_name'] != null && isset($produit->id)) {
$price = $this->gestionBdd->getProductPriceByClientGroupId($filter, $produit->id);
if ($price != null) {
$htPrice = $price;
}
}
return $htPrice;
}
public function getClientInfoForDevis($devis, $filterType)
{
if ($filterType === 'group' && !empty($devis['group_name'])) {
return [
'name' => 'Groupe ' . $devis['group_name'],
'address' => $devis['group_address'],
'city' => $devis['group_postal_code'] . ' ' . $devis['group_city'],
'siret' => $devis['group_siret_number'],
'email' => $devis['group_email']
];
} else {
$clientAddresses = \OCA\Gestion\Helpers\FileExportHelpers::GetAddressAndCityFromAddress($devis['client_adresse']);
return [
'name' => $devis['client_nom'],
'address' => $clientAddresses['address'],
'city' => $clientAddresses['city'],
'siret' => '',
'email' => ''
];
}
}
public function generateCsvContent($devisData)
{
$headers = [
'Numéro Devis',
'Date',
'Client Nom',
'Client Entreprise',
'Défunt',
'Lieu',
'Article',
'Thanatopracteur',
'Commentaire',
'N° Dossier', // case_number
'N° Commande' // order_number
];
$csvContent = $this->arrayToCsv($headers);
foreach ($devisData as $devis) {
$row = [
$devis['devis_full_number'] ?? '',
$devis['devis_date'] ?? '',
$devis['client_nom'] ?? '',
$devis['client_entreprise'] ?? '',
$devis['defunt_nom'] ?? '',
$devis['lieu_nom'] ?? '',
$devis['article'] ?? 'SOINS',
trim(($devis['thanato_nom'] ?? '') . ' ' . ($devis['thanato_prenom'] ?? '')),
$devis['devis_comment'] ?? '',
$devis['case_number'] ?? '', // NOUVEAU
$devis['order_number'] ?? '' // NOUVEAU
];
$csvContent .= $this->arrayToCsv($row);
}
return $csvContent;
}
public function getClientNameForFolder($firstDevis, $filterType)
{
if ($filterType === MultipleFactureTypeConstant::GROUP_FILTER_TYPE && !empty($firstDevis["group_name"])) {
return $firstDevis["group_name"];
}
return $firstDevis["client_nom"] ?? 'CLIENT_INCONNU';
}
public function getRecapFilename($month, $year)
{
$monthStr = str_pad($month, 2, '0', STR_PAD_LEFT);
return $year . $monthStr;
}
private function arrayToCsv($array)
{
$output = fopen('php://temp', 'r+');
fputcsv($output, $array, ';');
rewind($output);
$csvLine = fgets($output);
fclose($output);
return $csvLine;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace OCA\Gestion\Service\Devis;
use OCP\Files\IRootFolder;
use OCP\Files\NotPermittedException;
class DevisPdfGenerator
{
/** @var IRootFolder */
private $rootFolder;
/** @var DevisPdfLayoutManager */
private $layoutManager;
/** @var DevisPdfTableRenderer */
private $tableRenderer;
public const DEFAULT_NEXTCLOUD_ADMIN = 'admin';
public function __construct(IRootFolder $rootFolder)
{
$this->rootFolder = $rootFolder;
$this->layoutManager = new DevisPdfLayoutManager($rootFolder);
$this->tableRenderer = new DevisPdfTableRenderer();
}
public function generatePdfDocument($storage, $currentConfig, $data_devis, $clientInfo, $month, $year, $montant)
{
$configurationAddresses = \OCA\Gestion\Helpers\FileExportHelpers::GetAddressAndCityFromAddress($currentConfig->adresse);
$configurationAddress = $configurationAddresses["address"];
$configurationAddressCity = $configurationAddresses["city"];
$pdf = $this->createPdfInstance();
$folderPath = $this->createDestinationFolder($storage, $currentConfig, $data_devis[0], $month, $year);
// Créer le contexte une seule fois
$context = new PageContext($pdf, $currentConfig, $configurationAddress, $configurationAddressCity, $data_devis, $clientInfo, $year, $montant);
$this->generatePdfPages($context);
return $this->savePdfFile($storage, $pdf, $folderPath, $clientInfo, $month, $year);
}
public function createPdfInstance()
{
$pdf = new \FPDF();
$pdf->AddFont('Arial', '', 'Comic Sans MS.php');
$pdf->AddFont('Arial', 'B', 'comic-sans-bold.php');
return $pdf;
}
public function createDestinationFolder($storage, $currentConfig, $firstDevis, $month, $year)
{
$date_temp = date("t-m-Y", strtotime($firstDevis['devis_date']));
$formatter = new \IntlDateFormatter('fr_FR', \IntlDateFormatter::LONG, \IntlDateFormatter::NONE);
$date_formated = $formatter->format(\DateTime::createFromFormat('d-m-Y', $date_temp));
$folderPath = html_entity_decode($currentConfig->path) . '/DOCUMENTS RECAPITULATIFS/' . $year . '/' .
str_pad($month, 2, '0', STR_PAD_LEFT) . ' ' .
strtoupper(\OCA\Gestion\Helpers\FileExportHelpers::ConvertSpecialChar(explode(' ', $date_formated)[1])) . '/';
try {
$storage->newFolder($folderPath);
} catch(NotPermittedException $e) {
// Le dossier existe déjà
}
return $folderPath;
}
private function generatePdfPages(PageContext $context)
{
$pagination = $this->calculatePagination(count($context->dataDevis));
$totals = ['ht' => 0, 'tva' => 0, 'ttc' => 0];
for ($num_page = 1; $num_page <= $pagination['nb_pages']; $num_page++) {
$this->generateSinglePage($context, $num_page, $pagination, $totals);
}
}
private function calculatePagination($totalItems)
{
return [
'nb_pages' => ceil($totalItems / 12), // RÉDUIRE à 12 par page
'items_per_page' => 12,
'current_index' => 0
];
}
// VOICI LA FONCTION CORRIGÉE - 4 paramètres au lieu de 10
private function generateSinglePage(PageContext $context, $num_page, &$pagination, &$totals)
{
$context->pdf->AddPage();
$context->pdf->SetAutoPagebreak(false);
$context->pdf->SetMargins(0, 0, 10);
$this->layoutManager->addPageHeader($context->pdf, $context->currentConfig, $context->configurationAddress, $context->configurationAddressCity);
$this->layoutManager->addPageNumber($context->pdf, $num_page, $pagination['nb_pages']);
$this->layoutManager->addClientInfoSection($context->pdf, $context->clientInfo, $context->dataDevis[0]);
$this->layoutManager->addDocumentTitle($context->pdf, $context->dataDevis[0], $context->year);
$itemsToProcess = min($pagination['items_per_page'], count($context->dataDevis) - $pagination['current_index']);
$config = [
'pagination' => $pagination,
'itemsToProcess' => $itemsToProcess,
'numPage' => $num_page,
'nbPage' => $pagination['nb_pages'],
'totals' => &$totals,
'sansMontant' => $context->montant
];
$this->tableRenderer->createDevisTable(
$context->pdf,
$context->dataDevis,
$config
);
$this->layoutManager->addLegalFooter($context->pdf, $context->currentConfig);
$pagination['current_index'] += $itemsToProcess;
}
private function savePdfFile($storage, $pdf, $folderPath, $clientInfo, $month, $year)
{
$filename = $this->generateDevisRecapFilename($clientInfo, $month, $year);
$fullPdfPath = $folderPath . $filename . '.pdf';
$storage->newFile($fullPdfPath);
$pdfContent = $pdf->Output('', 'S');
$file_pdf = $storage->get($fullPdfPath);
$file_pdf->putContent($pdfContent);
return $fullPdfPath;
}
private function generateDevisRecapFilename($clientInfo, $month, $year)
{
$monthName = strtoupper(\OCA\Gestion\Helpers\FileExportHelpers::ConvertSpecialChar(date('F', mktime(0, 0, 0, $month, 10))));
$clientName = strtoupper(\OCA\Gestion\Helpers\FileExportHelpers::ConvertSpecialChar($clientInfo['name']));
return $clientName . '_RECAP_DEVIS_' . $monthName . '_' . $year;
}
}

View File

@ -0,0 +1,155 @@
<?php
namespace OCA\Gestion\Service\Devis;
use OCP\Files\IRootFolder;
class DevisPdfLayoutManager
{
/** @var IRootFolder */
private $rootFolder;
private $defaultImagePath = "/var/www/html/data/admin/files/.gestion/";
public const DEFAULT_NEXTCLOUD_ADMIN = 'admin';
public function __construct(IRootFolder $rootFolder)
{
$this->rootFolder = $rootFolder;
}
public function addPageHeader($pdf, $config, $address, $city)
{
if ($this->doesLogoExist()) {
$pdf->Image($this->defaultImagePath."logo.png", 10, 10, 25, 0);
}
$this->addCompanyInfo($pdf, $config, $address, $city);
}
public function addPageNumber($pdf, $currentPage, $totalPages)
{
if ($totalPages > 1) {
$pdf->SetXY(120, 5);
$pdf->SetFont("Arial", "B", 9);
$pdf->Cell(160, 8, $currentPage . '/' . $totalPages, 0, 0, 'C');
}
}
public function addClientInfoSection($pdf, $clientInfo, $firstDevis)
{
$date_temp = date("t-m-Y", strtotime($firstDevis['devis_date']));
$formatter = new \IntlDateFormatter('fr_FR', \IntlDateFormatter::LONG, \IntlDateFormatter::NONE);
$date_formated = $formatter->format(\DateTime::createFromFormat('d-m-Y', $date_temp));
$this->addClientInfo($pdf, $clientInfo, $date_formated);
}
public function addDocumentTitle($pdf, $firstDevis, $year)
{
$date_temp = date("t-m-Y", strtotime($firstDevis['devis_date']));
$formatter = new \IntlDateFormatter('fr_FR', \IntlDateFormatter::LONG, \IntlDateFormatter::NONE);
$date_formated = $formatter->format(\DateTime::createFromFormat('d-m-Y', $date_temp));
$pdf->SetFont("Arial", "B", 14);
$pdf->SetXY(10, 90);
$pdf->Cell(
0,
10,
"RECAPITULATIF DEVIS " .
strtoupper(\OCA\Gestion\Helpers\FileExportHelpers::ConvertSpecialChar(explode(' ', $date_formated)[1])) . " " . $year,
0,
0,
'C'
);
}
public function addLegalFooter($pdf, $config)
{
$y0 = 260;
$pageWidth = $pdf->GetPageWidth();
$pdf->SetFont('Arial', '', 6);
$pdf->SetXY(1, $y0 + 4);
$pdf->Cell($pageWidth, 5, mb_convert_encoding(html_entity_decode($config->legal_one), 'ISO-8859-1', 'UTF-8'), 0, 0, 'C');
$pdf->SetXY(1, $y0 + 8);
$pdf->Cell($pageWidth, 5, mb_convert_encoding(html_entity_decode($config->legal_two), 'ISO-8859-1', 'UTF-8'), 0, 0, 'C');
$pdf->SetXY(1, $y0 + 12);
$pdf->Cell($pageWidth, 5, mb_convert_encoding(html_entity_decode($config->telephone), 'ISO-8859-1', 'UTF-8'), 0, 0, 'C');
}
private function addCompanyInfo($pdf, $config, $address, $city)
{
$companyInfoXAxis = 10;
$companyInfoYAxis = 40;
$pdf->SetFont('Arial', '', 11);
$pdf->SetXY($companyInfoXAxis, $companyInfoYAxis);
$pdf->Cell(0, 7, \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($config->entreprise));
$companyInfoYAxis += 7;
$pdf->SetXY($companyInfoXAxis, $companyInfoYAxis);
$pdf->Cell(0, 7, \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($address));
$companyInfoYAxis += 7;
$pdf->SetXY($companyInfoXAxis, $companyInfoYAxis);
$pdf->Cell(0, 7, \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($city));
$companyInfoYAxis += 7;
$pdf->SetXY($companyInfoXAxis, $companyInfoYAxis);
$pdf->Cell(0, 7, mb_convert_encoding(html_entity_decode('Tél : '), 'ISO-8859-1', 'UTF-8') . \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($config->telephone));
$companyInfoYAxis += 7;
$pdf->SetXY($companyInfoXAxis, $companyInfoYAxis);
$pdf->Cell(0, 7, 'Mail : ' . $config->mail);
}
private function addClientInfo($pdf, $clientInfo, $dateFormatted)
{
$clientInfoXAxis = 125;
$clientInfoYAxis = 40;
$pdf->SetFont('Arial', '', 11);
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->Cell(0, 7, mb_convert_encoding($clientInfo['name'], 'ISO-8859-1', 'UTF-8'));
$clientInfoYAxis += 7;
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->SetMargins(0, 0, 10);
$pdf->MultiCell(0, 7, trim(\OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($clientInfo['address'])));
$pdf->SetMargins(0, 0, 0);
$clientInfoYAxis += 14;
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->Cell(0, 7, trim(mb_convert_encoding(html_entity_decode($clientInfo['city']), 'ISO-8859-1', 'UTF-8')));
if (!empty($clientInfo['siret'])) {
$clientInfoYAxis += 7;
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->Cell(0, 7, 'Siret: ' . \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($clientInfo['siret']));
}
if (!empty($clientInfo['email'])) {
$clientInfoYAxis += 7;
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->Cell(0, 7, 'Email: ' . mb_convert_encoding(html_entity_decode($clientInfo['email']), 'ISO-8859-1', 'UTF-8'));
}
$clientInfoYAxis += 7;
$pdf->SetXY($clientInfoXAxis, $clientInfoYAxis);
$pdf->Cell(0, 7, "le " . mb_convert_encoding($dateFormatted, 'ISO-8859-1', 'UTF-8'));
}
private function doesLogoExist()
{
$storage = $this->rootFolder->getUserFolder(self::DEFAULT_NEXTCLOUD_ADMIN);
try {
if (isset($storage)) {
$storage->get('/.gestion/logo.png');
return true;
}
return false;
} catch (\OCP\Files\NotFoundException $e) {
return false;
}
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace OCA\Gestion\Service\Devis;
class DevisPdfTableRenderer
{
public function createDevisTable($pdf, $dataDevis, $config)
{
$pagination = $config['pagination'];
$itemsToProcess = $config['itemsToProcess'];
$numPage = $config['numPage'];
$nbPage = $config['nbPage'];
$totals = &$config['totals'];
$sansMontant = $config['sansMontant'] ?? false;
// Système de pagination comme les factures
$maxItemsPerPage = 22; // Nombre maximum d'éléments par page
$startIndex = $pagination['current_index'];
$itemsThisPage = min($itemsToProcess, count($dataDevis) - $startIndex);
$this->drawTableStructure($pdf, $numPage, $nbPage, $sansMontant);
$this->addTableHeaders($pdf, $sansMontant);
$this->populateTableData($pdf, $dataDevis, $startIndex, $itemsThisPage, $totals, $sansMontant);
// Totaux seulement sur la dernière page
if ($numPage == $nbPage && !$sansMontant) {
$this->addTableTotals($pdf, $totals);
}
}
private function drawTableStructure($pdf, $numPage, $nbPage, $sansMontant)
{
$pdf->SetLineWidth(0.2);
$pdf->Rect(5, 105, 200, 130, "D");
$pdf->Line(5, 115, 205, 115);
$endY = ($numPage == $nbPage && !$sansMontant) ? 225 : 235;
if (!$sansMontant) {
// Ajustement final des positions des lignes verticales
// Article et Défunt de même taille, TTC réduit
$verticalLines = [22, 39, 56, 75, 105, 135, 155, 170, 185];
} else {
// Pour sans montant: Article et Défunt de même taille
$verticalLines = [27, 47, 67, 85, 115, 145];
}
foreach ($verticalLines as $x) {
$pdf->Line($x, 105, $x, $endY);
}
}
private function addTableHeaders($pdf, $sansMontant)
{
$pdf->SetFont('Arial', 'B', 7);
if (!$sansMontant) {
$headers = [
[5, 17, "N° Devis"],
[22, 17, "N° Dossier"],
[39, 17, "N° Commande"],
[56, 19, "Date"],
[75, 30, "Article"], // Taille augmentée
[105, 30, "Lieu du soin"],
[135, 20, "Défunt"], // Taille réduite pour équilibrer
[155, 15, "H.T."],
[170, 15, "TVA"],
[185, 15, "T.T.C"] // Taille réduite
];
} else {
$headers = [
[5, 22, "N° Devis"],
[27, 20, "N° Dossier"],
[47, 20, "N° Commande"],
[67, 18, "Date"],
[85, 30, "Article"], // Même taille que Défunt
[115, 30, "Lieu du soin"],
[145, 30, "Défunt"] // Même taille que Article
];
}
foreach ($headers as $header) {
$pdf->SetXY($header[0] + 1, 106);
$pdf->Cell($header[1] - 2, 8, mb_convert_encoding($header[2], 'ISO-8859-1', 'UTF-8'), 0, 0, 'C');
}
}
private function populateTableData($pdf, $dataDevis, $startIndex, $itemsToProcess, &$totals, $sansMontant)
{
$formatterDate = new \IntlDateFormatter('fr_FR', \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE);
$formatterDate->setPattern('dd-MMM');
$yDevis = 115;
for ($i = $startIndex; $i < $startIndex + $itemsToProcess && $i < count($dataDevis); $i++) {
$devis = $dataDevis[$i];
$dateSoin = new \DateTime($devis['devis_date']);
$this->addTableRow($pdf, $devis, $formatterDate, $dateSoin, $yDevis, $sansMontant);
// Calculer les totaux seulement si on affiche les montants
if (!$sansMontant) {
$totals['ht'] += $devis['montant_htc'];
$totals['tva'] += $devis['montant_tva'];
$totals['ttc'] += $devis['montant_ttc'];
}
$yDevis += 10;
}
}
private function addTableRow($pdf, $devis, $formatterDate, $dateSoin, $yDevis, $sansMontant)
{
$pdf->SetFont('Arial', '', 7);
$addSmartCell = function ($pdf, $x, $y, $width, $text, $align = 'L') use ($yDevis) {
$textWidth = $pdf->GetStringWidth($text);
$maxWidth = $width - 2;
if ($textWidth > $maxWidth && strlen($text) > 10) {
$pdf->SetXY($x, $y);
$pdf->MultiCell($width, 2.5, $text, 0, $align);
} else {
$pdf->SetXY($x, $y);
$pdf->Cell($width, 5, $text, 0, 0, $align);
}
};
if (!$sansMontant) {
// LARGEURS ÉQUILIBRÉES avec Article et Défunt de même taille
$addSmartCell($pdf, 6, $yDevis, 16, $devis['devis_full_number']);
$addSmartCell($pdf, 23, $yDevis, 16, $devis['case_number'] ?? '');
$addSmartCell($pdf, 40, $yDevis, 16, $devis['order_number'] ?? '');
$addSmartCell($pdf, 57, $yDevis, 18, mb_convert_encoding($formatterDate->format($dateSoin), 'ISO-8859-1', 'UTF-8'));
$articleText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['article'] ?? 'SOINS');
$addSmartCell($pdf, 76, $yDevis, 29, $articleText); // Article agrandi
$lieuText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['lieu_nom'] ?? '');
$addSmartCell($pdf, 106, $yDevis, 29, $lieuText);
$defuntText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['defunt_nom'] ?? '');
$addSmartCell($pdf, 136, $yDevis, 19, $defuntText); // Défunt réduit
$addSmartCell($pdf, 156, $yDevis, 14, number_format($devis['montant_htc'], 2, '.', '') . chr(128), 'R');
$addSmartCell($pdf, 171, $yDevis, 14, number_format($devis['montant_tva'], 2, '.', '') . chr(128), 'R');
$addSmartCell($pdf, 186, $yDevis, 14, number_format($devis['montant_ttc'], 2, '.', '') . chr(128), 'R'); // TTC réduit
} else {
// Pour sans montant: Article et Défunt de même taille
$addSmartCell($pdf, 6, $yDevis, 21, $devis['devis_full_number']);
$addSmartCell($pdf, 28, $yDevis, 19, $devis['case_number'] ?? '');
$addSmartCell($pdf, 48, $yDevis, 19, $devis['order_number'] ?? '');
$addSmartCell($pdf, 68, $yDevis, 17, mb_convert_encoding($formatterDate->format($dateSoin), 'ISO-8859-1', 'UTF-8'));
$articleText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['article'] ?? 'SOINS');
$addSmartCell($pdf, 86, $yDevis, 29, $articleText); // Article agrandi
$lieuText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['lieu_nom'] ?? '');
$addSmartCell($pdf, 116, $yDevis, 29, $lieuText);
$defuntText = \OCA\Gestion\Helpers\FileExportHelpers::FormatTextForExport($devis['defunt_nom'] ?? '');
$addSmartCell($pdf, 146, $yDevis, 29, $defuntText); // Défunt réduit
}
}
private function addTableTotals($pdf, $totals)
{
$pdf->Line(5, 225, 205, 225);
$pdf->SetFont('Arial', 'B', 8);
// Alignement des totaux avec les colonnes HT, TVA, TTC
$pdf->SetXY(5, 225);
$pdf->Cell(130, 8, 'TOTAL', 0, 0, 'C');
// POSITIONS alignées avec les colonnes du tableau
$pdf->SetXY(155, 225);
$pdf->Cell(15, 8, number_format($totals['ht'], 2, '.', '') . chr(128), 0, 0, 'R');
$pdf->SetXY(170, 225);
$pdf->Cell(15, 8, number_format($totals['tva'], 2, '.', '') . chr(128), 0, 0, 'R');
$pdf->SetXY(185, 225);
$pdf->Cell(15, 8, number_format($totals['ttc'], 2, '.', '') . chr(128), 0, 0, 'R');
// CADRE TOTAL TTC - Texte à l'intérieur du cadre aligné à droite
$pdf->SetXY(155, 240);
$pdf->Cell(30, 8, 'TOTAL TTC', 0, 0, 'C');
// Valeur alignée avec la fin du tableau tout à droite
$pdf->SetXY(185, 240);
$pdf->Cell(15, 8, number_format($totals['ttc'], 2, '.', '') . chr(128), 0, 0, 'R');
// Cadre TOTAL TTC aligné avec la fin du tableau (205)
$lines = [
[155, 240, 155, 248], // Ligne verticale gauche
[185, 240, 185, 248], // Ligne de séparation
[205, 240, 205, 248], // Ligne verticale droite (alignée avec fin du tableau)
[155, 240, 205, 240], // Ligne horizontale haute
[155, 248, 205, 248] // Ligne horizontale basse
];
foreach ($lines as $line) {
$pdf->Line($line[0], $line[1], $line[2], $line[3]);
}
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace OCA\Gestion\Service\Devis;
use OCA\Gestion\Db\Bdd;
use OCP\Files\IRootFolder;
use OCA\Gestion\Constants\MultipleFactureTypeConstant;
/**
* Service principal pour la génération des récapitulatifs de devis
*/
class DevisRecapService
{
/** @var Bdd */
private $gestionBdd;
/** @var IRootFolder */
private $rootFolder;
/** @var DevisDataProcessor */
private $dataProcessor;
/** @var DevisPdfGenerator */
private $pdfGenerator;
public const DEFAULT_NEXTCLOUD_ADMIN = 'admin';
public function __construct(
Bdd $gestionBdd,
IRootFolder $rootFolder,
) {
$this->gestionBdd = $gestionBdd;
$this->rootFolder = $rootFolder;
$this->dataProcessor = new DevisDataProcessor($gestionBdd);
$this->pdfGenerator = new DevisPdfGenerator($rootFolder);
}
public function generateDevisRecap($filter, $month, $year, $idNextCloud, $filterType, $montant)
{
$storage = $this->rootFolder->getUserFolder($idNextCloud);
$configs = json_decode($this->gestionBdd->getConfiguration(self::DEFAULT_NEXTCLOUD_ADMIN));
$currentConfig = $configs[0];
$devisData = $this->getDevisData($filter, $month, $year, $currentConfig, $filterType);
if (empty($devisData)) {
return null;
}
$processedData = $this->dataProcessor->prepareDevisData($devisData, $currentConfig, $filter, $idNextCloud);
$clientInfo = $this->dataProcessor->getClientInfoForDevis($processedData[0], $filterType);
return $this->pdfGenerator->generatePdfDocument(
$storage,
$currentConfig,
$processedData,
$clientInfo,
$month,
$year,
$montant
);
}
private function getDevisData($filter, $month, $year, $currentConfig, $filterType)
{
$isFilterByClient = $filterType === MultipleFactureTypeConstant::CLIENT_FILTER_TYPE;
if ($isFilterByClient) {
return $this->gestionBdd->getDevisPdfDataByClientAndMonthYear($filter, $month, $year, $currentConfig);
} else {
return $this->gestionBdd->getDevisPdfDataByClientGroupFacturationAndMonthYear($filter, $month, $year, $currentConfig);
}
}
// Méthodes utilitaires conservées pour la compatibilité CSV
public function generateCsvContent($devisData)
{
return $this->dataProcessor->generateCsvContent($devisData);
}
public function getClientNameForFolder($firstDevis, $filterType)
{
return $this->dataProcessor->getClientNameForFolder($firstDevis, $filterType);
}
public function getRecapFilename($month, $year)
{
return $this->dataProcessor->getRecapFilename($month, $year);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace OCA\Gestion\Service\Devis;
use OCP\Files\IRootFolder;
/**
* Objet simple pour regrouper les paramètres de contexte
*/
class PageContext
{
public $pdf;
public $currentConfig;
public $configurationAddress;
public $configurationAddressCity;
public $dataDevis;
public $clientInfo;
public $year;
public $montant;
public function __construct($pdf, $currentConfig, $configurationAddress, $configurationAddressCity, $dataDevis, $clientInfo, $year, $montant)
{
$this->pdf = $pdf;
$this->currentConfig = $currentConfig;
$this->configurationAddress = $configurationAddress;
$this->configurationAddressCity = $configurationAddressCity;
$this->dataDevis = $dataDevis;
$this->clientInfo = $clientInfo;
$this->year = $year;
$this->montant = $montant;
}
}

View File

@ -41,75 +41,77 @@ use OCA\Gestion\Service\InvoiceRecap\InvoiceRecapService;
use OCP\DB\Exception;
use OCP\Files\IRootFolder;
class InvoicePdfService {
/** @var Bdd */
private $gestionBdd;
class InvoicePdfService
{
/** @var Bdd */
private $gestionBdd;
/** @var IRootFolder */
private $rootFolder;
private $rootFolder;
/** @var InvoiceRecapService */
private $invoiceRecapService;
private $invoiceRecapService;
private const DEFAULT_NEXTCLOUD_ADMIN = "admin";
public function __construct(
Bdd $gestionBdd,
public function __construct(
Bdd $gestionBdd,
IRootFolder $rootFolder,
InvoiceRecapService $invoiceRecapService) {
InvoiceRecapService $invoiceRecapService
) {
$this->gestionBdd = $gestionBdd;
$this->rootFolder = $rootFolder;
$this->invoiceRecapService = $invoiceRecapService;
}
}
private function getLogo(){
private function getLogo()
{
$storage = $this->rootFolder->getUserFolder(self::DEFAULT_NEXTCLOUD_ADMIN);
try{
try {
if(isset($storage)){
$file = $storage->get('/.gestion/logo.png');
}else{
return "nothing";
}
} catch(\OCP\Files\NotFoundException $e) {
$file = $storage->get('/.gestion/logo.jpeg');
}
}
catch(\OCP\Files\NotFoundException $e) {
return "nothing";
}
try {
try {
if(isset($storage)) {
$file = $storage->get('/.gestion/logo.png');
} else {
return "nothing";
}
} catch(\OCP\Files\NotFoundException $e) {
$file = $storage->get('/.gestion/logo.jpeg');
}
} catch(\OCP\Files\NotFoundException $e) {
return "nothing";
}
return base64_encode($file->getContent());
}
return base64_encode($file->getContent());
}
private function generateFactureSinglePdfByFactureId($factureId,$idNextCloud){
private function generateFactureSinglePdfByFactureId($factureId, $idNextCloud)
{
$storage = $this->rootFolder->getUserFolder($idNextCloud);
$configs = json_decode($this->gestionBdd->getConfiguration(self::DEFAULT_NEXTCLOUD_ADMIN));
$currentConfig = $configs[0];
$logo = $this->getLogo();
$invoicePdfData = $this->gestionBdd->getInvoicePdfData($factureId,$currentConfig);
if($invoicePdfData == null){
return null;
}
$currentConfig = $configs[0];
$logo = $this->getLogo();
$invoicePdfData = $this->gestionBdd->getInvoicePdfData($factureId, $currentConfig);
if($invoicePdfData == null) {
return null;
}
$clean_folder = html_entity_decode(string: $currentConfig->path).'/';
$factureFolders = $this->getFacturesFolder($invoicePdfData,$clean_folder);
$factureFolders = $this->getFacturesFolder($invoicePdfData, $clean_folder);
$pdf = new InvoicePdfHandler();
// $pdf->AddFont('ComicSans','','Comic Sans MS.php');
// $pdf->AddFont('ComicSans','B','comic-sans-bold.php');
$pdf->InvoicePdfFactory($invoicePdfData,$logo);
$pdf->InvoicePdfFactory($invoicePdfData, $logo);
$pdf->SetFactureContent();
$pdfContent = $pdf->Output('','S');
$pdfContent = $pdf->Output('', 'S');
$pdfFilename = $pdf->GetInvoiceFilename();
$prefixPdf = "FACTURE";
if($invoicePdfData['is_negative']){
if($invoicePdfData['is_negative']) {
$prefixPdf = "AVOIR";
}
$pdfFilename = $prefixPdf."_".$pdfFilename;
$filenames = [];
foreach($factureFolders as $folder){
foreach($factureFolders as $folder) {
try {
$storage->newFolder($folder);
}
catch(\OCP\Files\NotPermittedException $e) {
} catch(\OCP\Files\NotPermittedException $e) {
}
$ff_pdf = $folder.$pdfFilename.'.pdf';
$storage->newFile($ff_pdf);
@ -124,18 +126,19 @@ class InvoicePdfService {
];
}
public function generateFacturePdfByFactureId($factureId,$idNextCloud){
public function generateFacturePdfByFactureId($factureId, $idNextCloud)
{
$factureType = $this->gestionBdd->getFactureTypeByFactureId($factureId);
if($factureType == FactureTypeConstant::TYPE_SINGLE){
return $this->generateFactureSinglePdfByFactureId($factureId,$idNextCloud);
}
else{
return $this->generateFactureGroupPdfByFactureId($factureId,$idNextCloud);
if($factureType == FactureTypeConstant::TYPE_SINGLE) {
return $this->generateFactureSinglePdfByFactureId($factureId, $idNextCloud);
} else {
return $this->generateFactureGroupPdfByFactureId($factureId, $idNextCloud);
}
}
private function getGroupFactureFolder(array $factureData,$racinePath){
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($factureData["group_name"],'UTF-8').'/';
private function getGroupFactureFolder(array $factureData, $racinePath)
{
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($factureData["group_name"], 'UTF-8').'/';
$factureDate = $factureData['date_paiement'];
$factureDatetime = new DateTime($factureDate);
$factureDateYear = $factureDatetime->format('Y');
@ -146,9 +149,10 @@ class InvoicePdfService {
];
}
private function getFacturesFolder(array $factureData,$racinePath){
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($factureData["client_nom"],'UTF-8').'/';
$defuntsFolder = $clientRacineFolder.'DEFUNTS/'.mb_strtoupper($factureData['defunt_nom'],'UTF-8').'/'.'FACTURES'.'/';
private function getFacturesFolder(array $factureData, $racinePath)
{
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($factureData["client_nom"], 'UTF-8').'/';
$defuntsFolder = $clientRacineFolder.'DEFUNTS/'.mb_strtoupper($factureData['defunt_nom'], 'UTF-8').'/'.'FACTURES'.'/';
$devisDate = $factureData['devis_date'];
$devisDatetime = new DateTime($devisDate);
$devisDateYear = $devisDatetime->format('Y');
@ -160,18 +164,19 @@ class InvoicePdfService {
];
}
private function generateFactureGroupPdfByFactureId($factureId,$idNextCloud){
private function generateFactureGroupPdfByFactureId($factureId, $idNextCloud)
{
$storage = $this->rootFolder->getUserFolder($idNextCloud);
$configs = json_decode($this->gestionBdd->getConfiguration(self::DEFAULT_NEXTCLOUD_ADMIN));
$currentConfig = $configs[0];
$logo = $this->getLogo();
$invoicePdfData = $this->gestionBdd->getInvoiceGroupPdfData($factureId,$currentConfig);
if($invoicePdfData == null){
return "";
}
$currentConfig = $configs[0];
$logo = $this->getLogo();
$invoicePdfData = $this->gestionBdd->getInvoiceGroupPdfData($factureId, $currentConfig);
if($invoicePdfData == null) {
return "";
}
$templateType = $invoicePdfData['template_type_key'];
$clean_folder = html_entity_decode(string: $currentConfig->path).'/';
$factureFolders = $this->getGroupFactureFolder($invoicePdfData,$clean_folder);
$factureFolders = $this->getGroupFactureFolder($invoicePdfData, $clean_folder);
//For testing
// $templateType = ClientTemplateTypeConstant::OGF;
@ -190,16 +195,15 @@ class InvoicePdfService {
}
// $pdf->AddFont('ComicSans','','Comic Sans MS.php');
// $pdf->AddFont('ComicSans','B','comic-sans-bold.php');
$pdf->InvoicePdfFactory($invoicePdfData,$logo);
$pdf->InvoicePdfFactory($invoicePdfData, $logo);
$pdf->SetFactureContent();
$pdfContent = $pdf->Output('','S');
$pdfContent = $pdf->Output('', 'S');
$pdfFilename = $pdf->GetInvoiceFilename();
$filenames = [];
foreach($factureFolders as $folder){
foreach($factureFolders as $folder) {
try {
$storage->newFolder($folder);
}
catch(\OCP\Files\NotPermittedException $e) {
} catch(\OCP\Files\NotPermittedException $e) {
}
$ff_pdf = $folder.$pdfFilename.'.pdf';
$storage->newFile($ff_pdf);
@ -214,41 +218,42 @@ class InvoicePdfService {
];
}
public function generateFacturePdfByFactureIds(array $factureIds,$idNextCloud){
foreach( $factureIds as $factureId ){
$this->generateFacturePdfByFactureId($factureId,$idNextCloud);
public function generateFacturePdfByFactureIds(array $factureIds, $idNextCloud)
{
foreach($factureIds as $factureId) {
$this->generateFacturePdfByFactureId($factureId, $idNextCloud);
}
}
public function generateMultipleInvoicePdfByClientAndMonthYear($filter,$month,$year,$idNextCloud,$filterType){
public function generateMultipleInvoicePdfByClientAndMonthYear($filter, $month, $year, $idNextCloud, $filterType)
{
$storage = $this->rootFolder->getUserFolder($idNextCloud);
$configs = json_decode($this->gestionBdd->getConfiguration(self::DEFAULT_NEXTCLOUD_ADMIN));
$currentConfig = $configs[0];
$currentConfig = $configs[0];
$logo = $this->getLogo();
$invoiceData = $this->gestionBdd->getInvoicePdfDataByClientAndMonthYear($filter,$month,$year,$currentConfig,$filterType);
if(empty($invoiceData)){
return null;
}
$invoiceData = $this->gestionBdd->getInvoicePdfDataByClientAndMonthYear($filter, $month, $year, $currentConfig, $filterType);
if(empty($invoiceData)) {
return null;
}
$pdf = new InvoicePdfHandler();
// $pdf->AddFont('ComicSans','','Comic Sans MS.php');
// $pdf->AddFont('ComicSans','B','comic-sans-bold.php');
$pdf->MutlipleInvoicePdfFactory($invoiceData,$logo);
$pdf->MutlipleInvoicePdfFactory($invoiceData, $logo);
$pdf->SetMultipleFactureContent();
$racinePath = html_entity_decode(string: $currentConfig->path).'/';
$clientNameInFolder = $invoiceData[0]["client_nom"];
if($invoiceData[0]['facture_type'] == MultipleFactureTypeConstant::GROUP_FILTER_TYPE){
if($invoiceData[0]["group_name"] != null && $invoiceData[0]["group_name"] != ""){
if($invoiceData[0]['facture_type'] == MultipleFactureTypeConstant::GROUP_FILTER_TYPE) {
if($invoiceData[0]["group_name"] != null && $invoiceData[0]["group_name"] != "") {
$clientNameInFolder = $invoiceData[0]["group_name"];
}
}
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($clientNameInFolder,'UTF-8').'/';
$filename = "FACTURE".'_'.$pdf->GetMultipleInvoiceFilename($month,$year);
$clientRacineFolder = $racinePath.'CLIENTS/'.mb_strtoupper($clientNameInFolder, 'UTF-8').'/';
$filename = "FACTURE".'_'.$pdf->GetMultipleInvoiceFilename($month, $year);
$filenamePath = $clientRacineFolder.$filename.'.pdf';
$pdfContent = $pdf->Output('','S');
$pdfContent = $pdf->Output('', 'S');
try {
$storage->newFolder($clientRacineFolder);
}
catch(\OCP\Files\NotPermittedException $e) {
} catch(\OCP\Files\NotPermittedException $e) {
}
$storage->newFile($filenamePath);
$file_pdf = $storage->get($filenamePath);
@ -256,12 +261,14 @@ class InvoicePdfService {
return $filenamePath;
}
public function generateInvoiceRecap($filter,$filterType,$date,$idNextCloud){
$this->invoiceRecapService->generateInvoiceRecap($filter,$filterType,$date,$idNextCloud);
public function generateInvoiceRecap($filter, $filterType, $date, $idNextCloud)
{
$this->invoiceRecapService->generateInvoiceRecap($filter, $filterType, $date, $idNextCloud);
}
public function exportGroupOfDevisIntoFacture($clientId,$clientType,$month,$year,$facturationDate,$idNextcloud = BddConstant::DEFAULT_ADMIN_ID_NEXTCLOUD){
try{
public function exportGroupOfDevisIntoFacture($clientId, $clientType, $month, $year, $facturationDate, $idNextcloud = BddConstant::DEFAULT_ADMIN_ID_NEXTCLOUD)
{
try {
$datetime = new Datetime();
$month = $month ?? $datetime->format('m');
$year = $year ?? $datetime->format('Y');
@ -277,15 +284,15 @@ class InvoicePdfService {
//Si il a devis qui n est pas encore facturés
//Cree un facture, atttaché l ID du facture au devis et generer le pdf
if($clientType == MultipleFactureTypeConstant::CLIENT_FILTER_TYPE){
$devisIds = $this->gestionBdd->getDevisIdsByClientIdAndDate($clientId,$facturationDate,$devisMentionFiltersToBeInvoiced);
if($clientType == MultipleFactureTypeConstant::CLIENT_FILTER_TYPE) {
$devisIds = $this->gestionBdd->getDevisIdsByClientIdAndDate($clientId, $facturationDate, $devisMentionFiltersToBeInvoiced);
$fkClientId = $clientId;
$factureId = $this->gestionBdd->getFactureIdByClientIdAndDate($clientId,$facturationDate);
$factureId = $this->gestionBdd->getFactureIdByClientIdAndDate($clientId, $facturationDate);
}else{
$devisIds = $this->gestionBdd->getDevisIdsByClientGroupFacturationIdAnDate($clientId , $facturationDate , $devisMentionFiltersToBeInvoiced );
} else {
$devisIds = $this->gestionBdd->getDevisIdsByClientGroupFacturationIdAnDate($clientId, $facturationDate, $devisMentionFiltersToBeInvoiced);
$fkClientGroupFacturationId = $clientId;
$factureId = $this->gestionBdd->getFactureIdByClientGroupFacturationIdAndDate($clientId,$facturationDate);
$factureId = $this->gestionBdd->getFactureIdByClientGroupFacturationIdAndDate($clientId, $facturationDate);
}
// if($clientType == MultipleFactureTypeConstant::CLIENT_FILTER_TYPE){
@ -310,8 +317,8 @@ class InvoicePdfService {
if (!empty($devisIds)) {
//Get facture by date and client
$clientIsAlreadyFacturedForThisDate = $factureId != null && $factureId != 0;
if (!$clientIsAlreadyFacturedForThisDate) {
$clientIsAlreadyFacturedForThisDate = $factureId != null && $factureId != 0;
if (!$clientIsAlreadyFacturedForThisDate) {
$factureId = $this->gestionBdd->createFactureAndReturnFactureId(
$facturationDate,
FactureTypeConstant::TYPE_GROUP,
@ -320,14 +327,13 @@ class InvoicePdfService {
$fkClientId,
$fkClientGroupFacturationId
);
}
$this->gestionBdd->invoiceListOfDevisIds($devisIds , $factureId);
$factureGeneratedResponse = $this->generateFactureGroupPdfByFactureId($factureId,$idNextcloud);
}
$this->gestionBdd->invoiceListOfDevisIds($devisIds, $factureId);
$factureGeneratedResponse = $this->generateFactureGroupPdfByFactureId($factureId, $idNextcloud);
return $factureGeneratedResponse["filenames"];
}
return null;
}
catch(Exception){
} catch(Exception) {
return null;
}

View File

@ -5,7 +5,7 @@ import "../css/mycss.css";
import { globalConfiguration } from "./modules/mainFunction.mjs";
import "./listener/main_listener";
import "./listener/devisListener";
import { exportClientDevisByMonthAndYearToPdf } from "./modules/ajaxRequest.mjs";
import { exportClientDevisByMonthAndYearToPdf, exportClientDevisRecap } from "./modules/ajaxRequest.mjs";
import 'select2/dist/css/select2.css';
import 'select2';
import '../css/mycss.css';

View File

@ -148,7 +148,6 @@ document.onchange = function(event) {
};
$('body').on('click', '#showGroupDevisFacturationModal', function () {
console.log("sdsfs");
$('#groupDevisFacturationModal').show();
});
@ -156,6 +155,14 @@ $('body').on('click', '#closeGroupDevisModal', function () {
$('#groupDevisFacturationModal').hide();
});
$('body').on('click', '#showDevisRecapModal', function () {
$('#devisRecapMontant').show();
});
$('body').on('click', '#closeDevisRecapModal', function () {
$('#devisRecapMontant').hide();
});
$('body').on('click', '#invoiceGroupQuote', function () {
var dateValue = document.getElementById("facturationDate").value;
@ -202,3 +209,95 @@ $('body').on('click', '#invoiceGroupQuote', function () {
hideLoader();
});
});
$('body').on('click', '#devisRecapAction', function () {
var valMontant = document.getElementById("sansMontant").checked;
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('cli');
const year = urlParams.get('annee');
const month = urlParams.get('mois');
const filterType = urlParams.get('filterType');
var devisPayload = {
clientId: filter,
month: month,
year: year,
clientType: filterType,
montant: valMontant
};
showLoader();
$.ajax({
url: baseUrl + '/devis/exportDevisRecap',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(devisPayload)
}).done(function (response) {
if (response != null && response.trim() !== "") {
// Le retour est une string directe, pas un array JSON
var filename = response.replace(/\\/g, '/'); // Corriger les backslashes Windows
showSuccess('Sauvegardé dans ' + filename);
// Fermeture de la modal avec gestion d'erreur intégrée
var modalId = 'devisRecapMontant';
// Essayer Bootstrap 5 d'abord
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
var modalElement = document.getElementById(modalId);
if (modalElement) {
var modalInstance = bootstrap.Modal.getInstance(modalElement);
if (modalInstance) {
modalInstance.hide();
} else {
// Si pas d'instance, créer et fermer
modalInstance = new bootstrap.Modal(modalElement);
modalInstance.hide();
}
}
}
// Essayer Bootstrap 4/3 avec jQuery
else if (typeof $ !== 'undefined' && $.fn.modal) {
try {
$('#' + modalId).modal('hide');
} catch (e) {
console.warn('Erreur lors de la fermeture de la modal avec jQuery:', e);
// Fallback manuel
var modalElement = document.getElementById(modalId);
if (modalElement) {
modalElement.classList.remove('show');
modalElement.style.display = 'none';
}
}
}
// Fallback : fermeture manuelle pure JavaScript
else {
var modalElement = document.getElementById(modalId);
if (modalElement) {
// Retirer les classes Bootstrap
modalElement.classList.remove('show');
modalElement.style.display = 'none';
modalElement.setAttribute('aria-hidden', 'true');
// Retirer le backdrop si présent
var backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
// Retirer la classe modal-open du body
document.body.classList.remove('modal-open');
document.body.style.removeProperty('padding-right');
document.body.style.removeProperty('overflow');
}
}
} else {
showError(t('gestion', "Les données pour sauvegarde sont vides"));
}
}).fail(function (response, code) {
showError(t('gestion', "Erreur dans la génération du récapitulatif devis"));
}).always(function () {
hideLoader();
});
});

View File

@ -858,6 +858,7 @@ export function exportClientDevisByMonthAndYearToPdf(clientId,year,month,filterT
});
};
/**
* Set bijoux photo
* @param {*} bijouxId

View File

@ -3,6 +3,7 @@
<form method="get" class="d-flex flex-row align-items-center">
<select name="cli" id="clientselector">
<?php
$showRecapButton = false;
foreach ($_['clients'] as $key => $client) {
?>
<option <?php
@ -17,8 +18,9 @@
?>
</select>&nbsp;&nbsp;
<select name="annee" id="yearselector">
<option value="-1" <?php if ((int) $_GET['annee'] == -1)
echo 'selected' ?>>Toutes les années</option>
<option value="-1" <?php if ((int) $_GET['annee'] == -1) {
echo 'selected';
} ?>>Toutes les années</option>
<?php
$currentYear = date('Y');
for ($year = $currentYear; $year >= $currentYear - 10; $year--) {
@ -27,73 +29,89 @@
?>
</select>&nbsp;&nbsp;
<select name="mois" id="monthselector">
<option value="0" <?php if ((int) $_GET['mois'] == 0)
echo 'selected' ?>>Tous les mois</option>
<option value="1" <?php if ((int) $_GET['mois'] == 1)
echo 'selected' ?>>Janvier</option>
<option value="2" <?php if ((int) $_GET['mois'] == 2)
echo 'selected' ?>>Fevrier</option>
<option value="3" <?php if ((int) $_GET['mois'] == 3)
echo 'selected' ?>>Mars</option>
<option value="4" <?php if ((int) $_GET['mois'] == 4)
echo 'selected' ?>>Avril</option>
<option value="5" <?php if ((int) $_GET['mois'] == 5)
echo 'selected' ?>>Mai</option>
<option value="6" <?php if ((int) $_GET['mois'] == 6)
echo 'selected' ?>>Juin</option>
<option value="7" <?php if ((int) $_GET['mois'] == 7)
echo 'selected' ?>>Juillet</option>
<option value="8" <?php if ((int) $_GET['mois'] == 8)
echo 'selected' ?>>Août</option>
<option value="9" <?php if ((int) $_GET['mois'] == 9)
echo 'selected' ?>>Septembre</option>
<option value="10" <?php if ((int) $_GET['mois'] == 10)
echo 'selected' ?>>Octobre</option>
<option value="11" <?php if ((int) $_GET['mois'] == 11)
echo 'selected' ?>>Novembre</option>
<option value="12" <?php if ((int) $_GET['mois'] == 12)
echo 'selected' ?>>Decembre</option>
<option value="0" <?php if ((int) $_GET['mois'] == 0) {
echo 'selected';
} ?>>Tous les mois</option>
<option value="1" <?php if ((int) $_GET['mois'] == 1) {
echo 'selected';
} ?>>Janvier</option>
<option value="2" <?php if ((int) $_GET['mois'] == 2) {
echo 'selected';
} ?>>Fevrier</option>
<option value="3" <?php if ((int) $_GET['mois'] == 3) {
echo 'selected';
} ?>>Mars</option>
<option value="4" <?php if ((int) $_GET['mois'] == 4) {
echo 'selected';
} ?>>Avril</option>
<option value="5" <?php if ((int) $_GET['mois'] == 5) {
echo 'selected';
} ?>>Mai</option>
<option value="6" <?php if ((int) $_GET['mois'] == 6) {
echo 'selected';
} ?>>Juin</option>
<option value="7" <?php if ((int) $_GET['mois'] == 7) {
echo 'selected';
} ?>>Juillet</option>
<option value="8" <?php if ((int) $_GET['mois'] == 8) {
echo 'selected';
} ?>>Août</option>
<option value="9" <?php if ((int) $_GET['mois'] == 9) {
echo 'selected';
} ?>>Septembre</option>
<option value="10" <?php if ((int) $_GET['mois'] == 10) {
echo 'selected';
} ?>>Octobre</option>
<option value="11" <?php if ((int) $_GET['mois'] == 11) {
echo 'selected';
} ?>>Novembre</option>
<option value="12" <?php if ((int) $_GET['mois'] == 12) {
echo 'selected';
} ?>>Decembre</option>
</select>&nbsp;&nbsp;
<input type="hidden" name="filterType" id="filterType"
value="<?php echo ($_GET['filterType'] ?? 'group'); ?>">
value="<?php echo($_GET['filterType'] ?? 'group'); ?>">
<input type="submit" value="Filtrer" />
</form>
<div class="d-flex flex-row">
<?php
$clients = $_['clients'];
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$devis = array_filter($_['devis'], function ($currentDevis) {
if ($currentDevis->cid) {
$datesplit = explode("-", $currentDevis->date);
$year = (int) $datesplit[0];
$month = (int) $datesplit[1];
$checkClient = false;
$filterType = "group";
if (array_key_exists('filterType', $_GET) && $_GET['filterType'] == 'client') {
$filterType = "client";
}
$clientIsNotSelected = strcmp($_GET['cli'], '') == 0;
if ($clientIsNotSelected) {
if ($filterType == "group") {
$checkClient = $_['clients'][0]->fk_client_group_facturation_id == $currentDevis->cid;
} else {
$checkClient = $_['clients'][0]->id == $currentDevis->cid;
}
} else {
if ($filterType == "group") {
$checkClient = $currentDevis->fk_client_group_facturation_id == $_GET['cli'];
} else {
$checkClient = $currentDevis->cid == $_GET['cli'];
}
}
$checkYear = ((int) ($_GET['annee']) == -1) ? (true) : ($year == ((int) $_GET['annee']));
$checkMounth = (((int) $_GET['mois']) == 0) ? (true) : ($month == ((int) $_GET['mois']));
return $checkClient && $checkYear && $checkMounth;
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if(intval($_GET['mois']) != 0 && intval($_GET['annee']) != 0) {
$showRecapButton = true;
}
return false;
});
if (strcmp($_GET['cli'], '') != 0 && sizeof($devis) > 0) {
?>
$devis = array_filter($_['devis'], function ($currentDevis) {
if ($currentDevis->cid) {
$datesplit = explode("-", $currentDevis->date);
$year = (int) $datesplit[0];
$month = (int) $datesplit[1];
$checkClient = false;
$filterType = "group";
if (array_key_exists('filterType', $_GET) && $_GET['filterType'] == 'client') {
$filterType = "client";
}
$clientIsNotSelected = strcmp($_GET['cli'], '') == 0;
if ($clientIsNotSelected) {
if ($filterType == "group") {
$checkClient = $_['clients'][0]->fk_client_group_facturation_id == $currentDevis->cid;
} else {
$checkClient = $_['clients'][0]->id == $currentDevis->cid;
}
} else {
if ($filterType == "group") {
$checkClient = $currentDevis->fk_client_group_facturation_id == $_GET['cli'];
} else {
$checkClient = $currentDevis->cid == $_GET['cli'];
}
}
$checkYear = ((int) ($_GET['annee']) == -1) ? (true) : ($year == ((int) $_GET['annee']));
$checkMounth = (((int) $_GET['mois']) == 0) ? (true) : ($month == ((int) $_GET['mois']));
return $checkClient && $checkYear && $checkMounth;
}
return false;
});
if (strcmp($_GET['cli'], '') != 0 && sizeof($devis) > 0) {
?>
<button class="btn btn-secondary" type="button"
id="exportMultipleDevisToPdf"><?php p($l->t('Save in Nextcloud')); ?></button>
<button class="btn btn-secondary" type="button" id="showGroupDevisFacturationModal" data-toggle="modal"
@ -101,22 +119,28 @@
Facturer
</button>
<?php
}
}
if (strcmp($_GET['cli'], '') != 0 && sizeof($devis) > 0) {
?>
<?php if($showRecapButton) {?><button class="btn btn-secondary" type="button" id="showDevisRecapModal" data-toggle="modal"
data-target="#devisRecapMontant">Generer le document recapitulatif</button><?php }
}
?>
}
?>
</div>
</div>
<hr>
<div id="gestion-canvas" class="canvas_div_pdf">
<?php
if ($_SERVER['REQUEST_METHOD'] == 'GET' && strcmp($_GET['cli'], '') != 0) {
if (sizeof($devis) == 0)
if (sizeof($devis) == 0) {
echo "Aucun devis trouvé.";
}
}
;
;
foreach ($devis as $key => $currentDevis) {
?>
foreach ($devis as $key => $currentDevis) {
?>
<div class="bootstrap-iso d-flex flex-column justify-content-between">
<div class="d-flex flex-column w-100">
<h2 class="mt-3 mb-3 text-center"> <?php p($l->t('Quote')); ?>
@ -143,12 +167,12 @@
</div>
<div class="col-2 h-100 m-0" style="min-height:250px;">
<?php
if (isset($_['logo']) && $_['logo'] !== "nothing") {
echo "<center><a><img alt='" . $l->t('Company logo') . "' class=\"img-fluid\" src=\"data:image/png;base64, " . $_['logo'] . "\"/></a></center>";
} else {
echo "<span style='font-size:12px' id='Company-logo' data-html2canvas-ignore><b><center>" . $l->t('You can add your company logo here.') . "</center></b><br/><i>" . $l->t('To add a logo, drop the logo.png file in ".gestion" folder at the root of your Nextcloud Files app. Remember to set "Show hidden files".') . "</i><br/><br/><center>" . $l->t('This message will not appear on generated PDF.') . "</center></span>";
}
?>
if (isset($_['logo']) && $_['logo'] !== "nothing") {
echo "<center><a><img alt='" . $l->t('Company logo') . "' class=\"img-fluid\" src=\"data:image/png;base64, " . $_['logo'] . "\"/></a></center>";
} else {
echo "<span style='font-size:12px' id='Company-logo' data-html2canvas-ignore><b><center>" . $l->t('You can add your company logo here.') . "</center></b><br/><i>" . $l->t('To add a logo, drop the logo.png file in ".gestion" folder at the root of your Nextcloud Files app. Remember to set "Show hidden files".') . "</i><br/><br/><center>" . $l->t('This message will not appear on generated PDF.') . "</center></span>";
}
?>
</div>
<div class="col-5 h-100 m-0" style="min-height:250px;">
<h5 class="p-3 m-0 text-dark text-center border border-2 border-dark"><?php p($l->t('TO')); ?>
@ -197,7 +221,7 @@
style="display:inline"
data-table="devis" data-column="order_number"
data-id="<?php echo $currentDevis->id;?>">
<?php echo ($currentDevis->order_number == "" ) ? "-" : $currentDevis->order_number ; ?>
<?php echo ($currentDevis->order_number == "") ? "-" : $currentDevis->order_number ; ?>
</div>
</div>
<hr />
@ -209,7 +233,7 @@
style="display:inline"
data-table="devis" data-column="case_number"
data-id="<?php echo $currentDevis->id;?>">
<?php echo ($currentDevis->case_number == "" ) ? "-" : $currentDevis->case_number ; ?>
<?php echo ($currentDevis->case_number == "") ? "-" : $currentDevis->case_number ; ?>
</div>
</div>
<hr />
@ -253,16 +277,16 @@
</thead>
<tbody>
<?php
$totalhtc = 0;
$tva = json_decode($_['configuration'])[0]->tva_default;
$totalttc = 0;
$totalprice = 0;
foreach ($currentDevis->dproduits as $key => $produit) {
$totalhtc = $totalhtc + ($produit->quantite * $produit->prix_unitaire);
}
$totalttc = ($totalhtc * $tva) / 100;
$totalprice = $totalhtc + $totalttc;
?>
$totalhtc = 0;
$tva = json_decode($_['configuration'])[0]->tva_default;
$totalttc = 0;
$totalprice = 0;
foreach ($currentDevis->dproduits as $key => $produit) {
$totalhtc = $totalhtc + ($produit->quantite * $produit->prix_unitaire);
}
$totalttc = ($totalhtc * $tva) / 100;
$totalprice = $totalhtc + $totalttc;
?>
<tr>
<td>&euro;<?php echo number_format($totalhtc, 2) ?></td>
<td><?php echo $tva ?> &percnt;</td>
@ -288,9 +312,32 @@
<hr data-html2canvas-ignore>
<hr data-html2canvas-ignore>
<?php
}
?>
}
?>
</div>
<div class="modal" id="devisRecapMontant" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Récapitulatif des devis avec montant</h5>
</div>
<div class="modal-body">
<div class="form-check d-flex align-items-center">
<input class="form-check-input me-2" type="checkbox" id="sansMontant" value="">
<label class="form-check-label" for="sansMontant">
Sans Montant
</label>
</div>
</div>
<div class="modal-footer">
<button id="closeDevisRecapModal" type="button" class="btn btn-secondary">Annuler</button>
<button id="devisRecapAction" type="button" class="btn btn-primary">Générer</button>
</div>
</div>
</div>
</div>
<div class="modal" id="groupDevisFacturationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">