Compare commits

..

8 Commits

Author SHA1 Message Date
d2bb1a9cc6 fix recap pdf 2025-12-16 09:07:49 +03:00
8cacac1efc fix recap devis multi product 2025-12-15 11:58:11 +03:00
a28b7f6ea6 fix erreur stats thanato 2025-11-20 09:45:34 +03:00
03784fd8e6 fix stats devis thanato 2025-11-19 12:01:51 +03:00
411cc2d5a8 fix statistique client calcul total HT 2025-11-17 17:27:40 +03:00
34717b772d fix statistique pas de filtre mentions 2025-11-17 13:23:47 +03:00
b6ea2550bc fix statistique not status 2025-11-17 13:10:37 +03:00
65d13d70f9 fix statistiques 2025-11-17 12:41:49 +03:00
40 changed files with 855 additions and 897 deletions

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -332,8 +332,6 @@ OC.L10N.register(
"Quantity" : "Quantity",
"Unit price without VAT" : "Unit price (excluding VAT)",
"Total without VAT" : "Total (excluding VAT)",
"Unit price with VAT" : "Unit price (including VAT)",
"Total with VAT" : "Total (including VAT)",
"Add product" : "Add product",
"Save in Nextcloud" : "Save in Nextcloud",
"Send by email" : "Send by email",

View File

@ -331,8 +331,6 @@ OC.L10N.register(
"Offer valid for 1 month from" : "Offre valide 1 mois à compter du",
"Quantity" : "Quantité",
"Unit price without VAT" : "PU HT",
"Unit price with VAT" : "PU TTC",
"Total with VAT" : "Total TTC",
"Total without VAT" : "Total HT",
"Add product" : "Ajouter produit",
"Save in Nextcloud" : "Sauvegarder dans nextcloud",

View File

@ -2657,7 +2657,9 @@ class PageController extends Controller
public function getTotalDevis($numdevis)
{
$total = $this->myDb->getTotalDevis($numdevis, $this->idNextcloud);
return json_encode($total);
$res = array();
$res['total'] = $total;
return json_encode($res);
}
private function calculAge($dateNaissance)

View File

@ -41,7 +41,7 @@ class Bdd
public function __construct(IDbConnection $db, IL10N $l, LoggerInterface $logger)
{
$this->whiteColumn = array("date", "num", "id_client", "id_thanato", "entreprise", "nom", "prenom", "legal_one", "telephone", "mail", "adresse", "produit_id",
"quantite", "date_paiement", "type_paiement", "id_devis", "reference", "description", "prix_unitaire", "tva", "unit_price", "legal_two", "path", "tva_default", "coefficient_ik",
"quantite", "date_paiement", "type_paiement", "id_devis", "reference", "description", "prix_unitaire", "legal_two", "path", "tva_default", "coefficient_ik",
"mentions_default", "version", "mentions", "comment", "status_paiement", "devise", "auto_invoice_number", "changelog", "format", "comment", "user_id",
"facture_prefixe", "arrivee", "depart", "latitude", "longitude", "id_lieu", "rang", "mois", "annee", "id_trajet", "commentaire","source",
"date_habilitation", "sexe", "observations_generales", "ref_pacemaker", "id_defunt", "article_id", "corpulence", "lieu_deces",
@ -79,50 +79,14 @@ class Bdd
{
$articles = json_decode($this->getListArticle($iddevis, $idNextcloud));
$produits = json_decode($this->getListProduit($iddevis, $idNextcloud));
// Grouper les totaux par taux de TVA
$totalsByTva = array();
// Traiter les articles
$total = 0;
foreach($articles as $key => $article) {
$tva = floatval($article->tva ?? 20.00);
$totalHT = floatval($article->prix_unitaire) * floatval($article->quantite);
if (!isset($totalsByTva[$tva])) {
$totalsByTva[$tva] = 0;
}
$totalsByTva[$tva] += $totalHT;
$total += $article->prix_unitaire * $article->quantite;
}
// Traiter les produits
foreach($produits as $key => $produit) {
$tva = floatval($produit->tva ?? 20.00);
$totalHT = floatval($produit->prix_unitaire) * floatval($produit->quantite);
if (!isset($totalsByTva[$tva])) {
$totalsByTva[$tva] = 0;
}
$totalsByTva[$tva] += $totalHT;
$total += $produit->prix_unitaire * $produit->quantite;
}
// Convertir en tableau de résultats
$result = array();
$totalGeneral = 0;
foreach($totalsByTva as $tva => $totalHT) {
$result[] = array(
'tva' => $tva,
'totalHT' => round($totalHT, 2),
'totalTVA' => round($totalHT * $tva / 100, 2),
'totalTTC' => round($totalHT * (1 + $tva / 100), 2)
);
$totalGeneral += $totalHT;
}
return array(
'details' => $result,
'totalGeneral' => round($totalGeneral, 2)
);
return $total;
}
public function getClients($idNextcloud)
@ -924,14 +888,12 @@ class Bdd
public function getListProduit($numdevis, $idNextcloud)
{
$sql = "SELECT ".
$this->tableprefix."produit.id as pid,"
.$this->tableprefix."produit_devis.id as pdid, reference, description,"
.$this->tableprefix."produit_devis.comment, quantite, "
.$this->tableprefix."produit_devis.unit_price as prix_unitaire, "
.$this->tableprefix."produit_devis.tva, "
.$this->tableprefix."devis.id_client
FROM ".$this->tableprefix."produit, ".$this->tableprefix."devis, ".$this->tableprefix."produit_devis
WHERE ".$this->tableprefix."produit.id = produit_id AND ".$this->tableprefix."devis.id = devis_id AND ".$this->tableprefix."devis.id = ?";
$this->tableprefix."produit.id as pid,"
.$this->tableprefix."produit_devis.id as pdid, reference, description,"
.$this->tableprefix."produit_devis.comment, quantite, prix_unitaire, "
.$this->tableprefix."devis.id_client
FROM ".$this->tableprefix."produit, ".$this->tableprefix."devis, ".$this->tableprefix."produit_devis
WHERE ".$this->tableprefix."produit.id = produit_id AND ".$this->tableprefix."devis.id = devis_id AND ".$this->tableprefix."devis.id = ?";
$produits = $this->execSQLNoJsonReturn($sql, [$numdevis]);
@ -940,7 +902,7 @@ class Bdd
$client = $this->getClientById($clientId);
foreach($produits as &$produit) {
$productPrice = $this->getProductPriceByClientGroupId($client['fk_client_group_id'], $produit['pid']);
$produit['prix_unitaire'] = $produit['prix_unitaire'] ?? $productPrice;
$produit['prix_unitaire'] = $productPrice ?? $produit['prix_unitaire'];
}
}
return json_encode($produits);
@ -948,13 +910,7 @@ class Bdd
public function getListArticle($numdevis, $idNextcloud)
{
$sql = "SELECT ".$this->tableprefix."article.id as aid,"
.$this->tableprefix."article_devis.id as adid, reference, description,"
.$this->tableprefix."article_devis.comment, quantite, "
.$this->tableprefix."article_devis.unit_price as prix_unitaire, "
.$this->tableprefix."article_devis.tva "
."FROM "
.$this->tableprefix."article, ".$this->tableprefix."devis, ".$this->tableprefix."article_devis WHERE ".$this->tableprefix."article.id = article_id AND ".$this->tableprefix."devis.id = devis_id AND ".$this->tableprefix."devis.id = ?";
$sql = "SELECT ".$this->tableprefix."article.id as aid,".$this->tableprefix."article_devis.id as adid, reference, description,".$this->tableprefix."article_devis.comment, quantite, prix_unitaire FROM ".$this->tableprefix."article, ".$this->tableprefix."devis, ".$this->tableprefix."article_devis WHERE ".$this->tableprefix."article.id = article_id AND ".$this->tableprefix."devis.id = devis_id AND ".$this->tableprefix."devis.id = ?";
return $this->execSQL($sql, array($numdevis));
}
@ -2659,7 +2615,7 @@ class Bdd
return $data;
}
private function getThanatoDevisListByDate($thanatoId, $date)
private function getThanatoDevisListByDateSave($thanatoId, $date)
{
$dateFormatted = $date->format('Y-m-d');
$sql = "SELECT
@ -2700,6 +2656,42 @@ class Bdd
return $devisList;
}
private function getThanatoDevisListByDate($thanatoId, $date)
{
$dateFormatted = $date->format('Y-m-d');
$sql = "SELECT
devis.id,
devis.date,
devis.mentions,
devis.num as calendar_uuid,
devis.id_defunt as id_defunt,
devis.id_lieu as id_lieu,
devis.id_client as id_client,
devis.id_thanato as id_thanato,
thanato.nom as nom_thanato,
thanato.prenom as prenom_thanato,
defunt.nom as nom_defunt,
lieu.nom as nom_lieu,
lieu.latitude as lieu_latitude,
lieu.longitude as lieu_longitude,
client.nom as nom_client,
client.entreprise as client_entreprise,
client.adresse as client_adresse
FROM ".$this->tableprefix."devis as devis
LEFT JOIN ".$this->tableprefix."thanato as thanato on devis.id_thanato = thanato.id
LEFT JOIN ".$this->tableprefix."lieu as lieu on devis.id_lieu = lieu.id
LEFT JOIN ".$this->tableprefix."defunt as defunt on devis.id_defunt = defunt.id
LEFT JOIN ".$this->tableprefix."client as client on devis.id_client = client.id
WHERE devis.date = ? AND
devis.id_thanato = ?
ORDER BY devis.date ASC;";
$devisList = $this->execSQLNoJsonReturn(
$sql,
[$dateFormatted,$thanatoId]
);
return $devisList;
}
public function getThanatoById($thanatoId)
{
$sql = "SELECT id, nom, prenom,fk_user_uuid FROM ".$this->tableprefix."thanato WHERE id = ? LIMIT 1;";
@ -3119,8 +3111,7 @@ class Bdd
produit_devis.quantite,
produit_devis.discount,
produit_devis.devis_id,
produit_devis.tva,
produit_devis.unit_price as produit_price,
produit.prix_unitaire as produit_price,
produit.reference as produit_reference,
produit.description as produit_description,
produit.fk_product_type_id as fk_product_type_id,
@ -3141,7 +3132,7 @@ class Bdd
$client = $this->getClientById($clientId);
foreach($produitList as &$produit) {
$productPrice = $this->getProductPriceByClientGroupId($client['fk_client_group_id'], $produit['produit_id']);
$produit['produit_price'] = $produit['produit_price'] ?? $productPrice;
$produit['produit_price'] = $productPrice ?? $produit['produit_price'];
}
}
@ -3198,7 +3189,7 @@ class Bdd
return 0;
}
private function getClientFactureStatisticPerMonth($clientId, array $produitList)
private function getClientFactureStatisticPerMonthSave($clientId, array $produitList)
{
$currentYear = date('Y');
$monthLists = range(1, 12);
@ -3219,12 +3210,11 @@ class Bdd
LEFT JOIN ".$this->tableprefix."devis as devis on facture.id_devis = devis.id
WHERE YEAR(facture.date_paiement) = ? AND
MONTH(facture.date_paiement) = ? AND
devis.id_client = ? AND
(devis.mentions = ? OR devis.mentions = ?)
devis.id_client = ?
ORDER BY facture.date_paiement ASC;";
$factureList = $this->execSQLNoJsonReturn(
$sql,
[$currentYear,$monthValue,$clientId,DevisMentionConstant::FACTURED,DevisMentionConstant::FACTURED_FORMATTED]
[$currentYear,$monthValue,$clientId]
);
$factureDevisIds = [];
@ -3254,6 +3244,58 @@ class Bdd
return $data;
}
private function getClientFactureStatisticPerMonth($clientId, array $produitList, $clientGroupId = null)
{
$currentYear = date('Y');
$monthLists = range(1, 12);
$data = [] ;
foreach($monthLists as $monthValue) {
if(!isset($data[$monthValue])) {
$data[$monthValue] = [];
}
$sql = "SELECT
devis.id as devis_id,
devis.id_client as devis_client_id,
devis.date as devis_date,
devis.mentions as devis_mention
FROM ".$this->tableprefix."devis as devis
WHERE YEAR(devis.date) = ? AND
MONTH(devis.date) = ? AND
devis.id_client = ?
ORDER BY devis.date ASC;";
$factureList = $this->execSQLNoJsonReturn(
$sql,
[$currentYear,$monthValue,$clientId]
);
$factureDevisIds = [];
foreach($factureList as $facture) {
$factureDevisIds[] = $facture['devis_id'];
}
$defuntCount = count($factureList);
$produitsPrice = 0;
$statisticForeachProductPerMonth = [];
foreach($produitList as $produit) {
if(!isset($statisticForeachProductPerMonth[$produit['id']])) {
$statisticForeachProductPerMonth[$produit['id']] = 0;
}
$productTotalCount = $this->getDevisProductsQuantityByDevisListAndProductId($factureDevisIds, $produit['id']);
$prixUnitaire = is_null($clientGroupId) ? $produit["prix_unitaire"] : $this->getProductPriceByClientGroupId($clientGroupId, $produit['id']);
$totalWithoutVat = $productTotalCount * $prixUnitaire;
$statisticForeachProductPerMonth[$produit['id']] += $productTotalCount;
$produitsPrice += $totalWithoutVat;
}
$data[$monthValue] = [
"defunt_count" => $defuntCount,
"total_price" => $produitsPrice,
"year" => $currentYear,
"products" => $statisticForeachProductPerMonth
];
}
return $data;
}
public function getExportClientStatData(array $clientIds)
{
$data = [];
@ -3267,9 +3309,10 @@ class Bdd
$client = $this->getClientById($clientId);
if($client != null) {
$clientName = trim($client["client_nom"]) . '-' .trim($client['client_entreprise']);
$clientGroupId = $client['fk_client_group_id'];
}
$data[$clientId]["client_name"] = $clientName;
$data[$clientId]["client_data"] = $this->getClientFactureStatisticPerMonth($clientId, $produitList);
$data[$clientId]["client_data"] = $this->getClientFactureStatisticPerMonth($clientId, $produitList, $clientGroupId);
}
return $data;
}

View File

@ -74,26 +74,9 @@ class DevisDataProcessor
// Variables pour gérer les articles comme dans les factures
$produitsReferenceArray = [];
// ⭐ NOUVEAU: Réinitialiser les montants
$devis_temp['montant_htc'] = 0;
$devis_temp['montant_tva'] = 0;
$devis_temp['montant_ttc'] = 0;
foreach ($produits as $produit) {
$htPrice = $this->getProductPrice($produit, $devis, $filter);
$totalHT = $htPrice * $produit->quantite;
// ⭐ NOUVEAU: Récupérer le taux de TVA spécifique du produit (défaut 20%)
$tvaValue = isset($produit->tva) && $produit->tva !== null ? floatval($produit->tva) : 20.00;
// ⭐ NOUVEAU: Calculer le montant TTC avec le bon taux
$totalTTC = $totalHT * (1 + $tvaValue / 100);
$totalTVA = $totalTTC - $totalHT;
// ⭐ NOUVEAU: Additionner les montants
$devis_temp['montant_htc'] += $totalHT;
$devis_temp['montant_tva'] += $totalTVA;
$devis_temp['montant_ttc'] += $totalTTC;
$devis_temp['montant_htc'] += $htPrice * $produit->quantite;
// Collecter les références comme dans les factures
if (isset($produit->reference) && !empty($produit->reference)) {
@ -102,9 +85,12 @@ class DevisDataProcessor
}
// Traitement identique aux factures
$produitsReferenceArray = array_unique($produitsReferenceArray);
// $produitsReferenceArray = array_unique($produitsReferenceArray);
$produitsReferenceAsString = implode("-", $produitsReferenceArray);
$devis_temp['article'] = !empty($produitsReferenceAsString) ? $produitsReferenceAsString : 'SOINS';
$devis_temp['article'] = !empty($produitsReferenceAsString) ? $produitsReferenceAsString : '';
$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;
}

View File

@ -81,16 +81,31 @@ class DevisPdfGenerator
private function calculatePagination($totalItems)
{
if ($totalItems <= 8) {
// Tout sur 1 page
return [
'nb_pages' => 1,
'items_per_page' => 15,
'current_index' => 0
];
}
// Réserver 8 items pour dernière page
$itemsAvantDernierePage = $totalItems - 8;
$nbPagesNormales = ceil($itemsAvantDernierePage / 15);
return [
'nb_pages' => ceil($totalItems / 12), // RÉDUIRE à 12 par page
'items_per_page' => 12,
'nb_pages' => $nbPagesNormales + 1,
'items_per_page' => 15,
'current_index' => 0
];
}
// VOICI LA FONCTION CORRIGÉE - 4 paramètres au lieu de 10
private function generateSinglePage(PageContext $context, $num_page, &$pagination, &$totals)
{
$startIndex = $pagination['current_index'];
$remainingItems = count($context->dataDevis) - $startIndex;
$context->pdf->AddPage();
$context->pdf->SetAutoPagebreak(false);
$context->pdf->SetMargins(0, 0, 10);
@ -100,7 +115,17 @@ class DevisPdfGenerator
$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']);
// ✅ Calculer items
$isLastPage = ($num_page == $pagination['nb_pages']);
$hasAmounts = !$context->montant;
if ($isLastPage && $hasAmounts) {
// Dernière page avec totaux : max 8 items
$itemsToProcess = min(8, $remainingItems);
} else {
// Pages normales : 15 items
$itemsToProcess = min(15, $remainingItems);
}
$config = [
'pagination' => $pagination,
@ -111,7 +136,7 @@ class DevisPdfGenerator
'sansMontant' => $context->montant
];
$this->tableRenderer->createDevisTable(
$finalY = $this->tableRenderer->createDevisTable(
$context->pdf,
$context->dataDevis,
$config

View File

@ -63,17 +63,18 @@ class DevisPdfLayoutManager
);
}
public function addLegalFooter($pdf, $config)
public function addLegalFooter($pdf, $config, $tableEndY = 0)
{
$y0 = 260;
$y0 = 280;
$pageWidth = $pdf->GetPageWidth();
$pdf->SetFont('Arial', '', 6);
$pdf->SetXY(1, $y0 + 4);
$pdf->SetXY(1, $y0);
$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->SetXY(1, $y0 + 4);
$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->SetXY(1, $y0 + 8);
$pdf->Cell($pageWidth, 5, mb_convert_encoding(html_entity_decode($config->telephone), 'ISO-8859-1', 'UTF-8'), 0, 0, 'C');
}

View File

@ -13,40 +13,59 @@ class DevisPdfTableRenderer
$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);
$tableEndY = $this->drawTableStructure($pdf, $numPage, $nbPage, $sansMontant, $itemsThisPage);
$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);
$finalY = $this->addTableTotals($pdf, $totals, $tableEndY);
} else {
$finalY = $tableEndY;
}
return $finalY;
}
private function drawTableStructure($pdf, $numPage, $nbPage, $sansMontant)
private function drawTableStructure($pdf, $numPage, $nbPage, $sansMontant, $itemsOnPage)
{
$pdf->SetLineWidth(0.2);
$pdf->Rect(5, 105, 200, 130, "D");
$headerHeight = 10;
$rowHeight = 10;
$tableHeight = $headerHeight + ($itemsOnPage * $rowHeight);
$endY = 105 + $tableHeight;
$isLastPage = ($numPage == $nbPage);
$hasAmounts = !$sansMontant;
if ($isLastPage && $hasAmounts) {
// Dessiner 3 côtés seulement (haut, gauche, droite)
$pdf->Line(5, 105, 205, 105); // Haut
$pdf->Line(5, 105, 5, $endY); // Gauche
$pdf->Line(205, 105, 205, $endY); // Droite
// Pas de ligne du bas - elle sera tracée dans addTableTotals
} else {
// Dessiner rectangle complet
$pdf->Rect(5, 105, 200, $tableHeight, "D");
}
$pdf->Line(5, 115, 205, 115);
$endY = ($numPage == $nbPage && !$sansMontant) ? 225 : 235;
if (!$sansMontant) {
// Ajout de la colonne Thanatopracteur entre Défunt et H.T.
$verticalLines = [24, 41, 58, 77, 102, 127, 147, 167, 178, 189];
} else {
// Pour sans montant: ajout de Thanatopracteur à la fin
$verticalLines = [27, 47, 67, 85, 110, 135, 160];
}
foreach ($verticalLines as $x) {
$pdf->Line($x, 105, $x, $endY);
}
return $endY;
}
private function addTableHeaders($pdf, $sansMontant)
@ -176,43 +195,44 @@ class DevisPdfTableRenderer
}
}
private function addTableTotals($pdf, $totals)
private function addTableTotals($pdf, $totals, $tableEndY)
{
$pdf->Line(5, 225, 205, 225);
$totalEndY = $tableEndY + 8;
$pdf->SetLineWidth(0.2);
// Juste les lignes horizontales haut et bas
$pdf->Line(5, $tableEndY, 205, $tableEndY);
$pdf->Line(5, $totalEndY, 205, $totalEndY);
$pdf->Line(5, $tableEndY, 5, $totalEndY);
$pdf->Line(205, $tableEndY, 205, $totalEndY);
$pdf->SetFont('Arial', 'B', 8);
// Alignement des totaux avec les colonnes HT, TVA, TTC avec espaces entre les valeurs
$pdf->SetXY(5, 225);
$pdf->SetXY(5, $tableEndY);
$pdf->Cell(162, 8, 'TOTAL', 0, 0, 'C');
// POSITIONS avec espaces entre les valeurs - plus d'espace entre HT et TVA
$pdf->SetXY(167, 225);
$pdf->SetXY(167, $tableEndY);
$pdf->Cell(9, 8, number_format($totals['ht'], 2, '.', '') . chr(128), 0, 0, 'R');
$pdf->SetXY(181, 225);
$pdf->SetXY(181, $tableEndY);
$pdf->Cell(9, 8, number_format($totals['tva'], 2, '.', '') . chr(128), 0, 0, 'R');
$pdf->SetXY(189, 225);
$pdf->SetXY(189, $tableEndY);
$pdf->Cell(16, 8, number_format($totals['ttc'], 2, '.', '') . chr(128), 0, 0, 'R');
// CADRE TOTAL TTC - Cadre encore plus agrandi
$pdf->SetXY(170, 241);
$cadreY = $totalEndY + 3;
$pdf->SetXY(170, $cadreY + 1);
$pdf->Cell(19, 6, 'TOTAL TTC', 0, 0, 'C');
$pdf->SetXY(189, 241);
$pdf->SetXY(189, $cadreY + 1);
$pdf->Cell(16, 6, number_format($totals['ttc'], 2, '.', '') . chr(128), 0, 0, 'C');
// Cadre TOTAL TTC aligné avec la fin du tableau (205)
$lines = [
[170, 240, 170, 248], // Ligne verticale gauche (déplacée de 173 à 170 pour agrandir)
[189, 240, 189, 248], // Ligne de séparation (alignée avec colonne T.T.C)
[205, 240, 205, 248], // Ligne verticale droite (alignée avec fin du tableau)
[170, 240, 205, 240], // Ligne horizontale haute
[170, 248, 205, 248] // Ligne horizontale basse
];
$pdf->Rect(170, $cadreY, 35, 8, 'D');
$pdf->Line(189, $cadreY, 189, $cadreY + 8);
foreach ($lines as $line) {
$pdf->Line($line[0], $line[1], $line[2], $line[3]);
}
return $cadreY + 8;
}
}

View File

@ -27,17 +27,18 @@ declare(strict_types=1);
namespace OCA\Gestion\Service\Devis\Pdf;
use DateTime;
use FPDF;
use \FPDF;
use OCA\Gestion\Helpers\FileExportHelpers;
use OCA\Gestion\Helpers\PriceHelpers;
class DevisPdfHandler extends FPDF
{
private $multipleDevisData = [];
private $devisData = [];
private $logo = null;
private $logoPath = "/var/www/html/data/admin/files/.gestion/";
public function Header()
function Header()
{
if ($this->logo != "nothing") {
$this->Image($this->logoPath . "logo.png", 4, 2, 36, 37);
@ -45,7 +46,7 @@ class DevisPdfHandler extends FPDF
$this->Cell(55, 30, '');
}
}
public function Footer()
function Footer()
{
$this->SetY(-40);
$this->SetFont('Arial', '', 7);
@ -117,6 +118,7 @@ class DevisPdfHandler extends FPDF
private function DrawArticlesTableHeader()
{
$tvaValue = $this->devisData["configuration"]->tva_default;
$this->SetFont('Arial', '', 10);
$this->SetXY(10, 106);
$this->Cell(20, 8, "Date", 0, 0, 'C');
@ -128,109 +130,56 @@ class DevisPdfHandler extends FPDF
$this->Cell(20, 8, "Prix Uni. HT", 0, 0, 'C');
$this->SetXY(155, 106);
$this->Cell(20, 8, 'TVA', 0, 0, 'C');
$this->Cell(20, 8, 'TVA ' . $tvaValue . '%', 0, 0, 'C');
$this->SetXY(175, 106);
$this->Cell(25, 8, "Prix Uni. TTC", 0, 0, 'C');
}
public function DrawArticlesTableValueAndReturnTotalPrice()
{
$this->SetFont('Arial', '', 10);
// Grouper les totaux par taux de TVA
$totalsByTva = [];
$tvaValue = $this->devisData["configuration"]->tva_default;
$totalHt = 0;
$totalTtc = 0;
$totalTva = 0;
$products = $this->devisData["products"];
$yValue = 116;
foreach ($products as $product) {
// Récupérer le taux de TVA du produit (défaut à 20% si non défini)
$tvaValue = isset($product['tva']) ? floatval($product['tva']) : 20.00;
$valueHt = $product['produit_price'] * $product['quantite'];
$valueTtc = PriceHelpers::calculPriceWithVatValue($valueHt, $tvaValue);
$tvaAmount = $valueTtc - $valueHt;
// Grouper par taux de TVA
if (!isset($totalsByTva[$tvaValue])) {
$totalsByTva[$tvaValue] = [
'totalHT' => 0,
'totalTVA' => 0,
'totalTTC' => 0
];
}
$totalsByTva[$tvaValue]['totalHT'] += $valueHt;
$totalsByTva[$tvaValue]['totalTVA'] += $tvaAmount;
$totalsByTva[$tvaValue]['totalTTC'] += $valueTtc;
// Affichage du produit
$totalHt += $valueHt;
$totalTtc += $valueTtc;
$productDescription = $product["produit_description"];
$dateValue = "";
if ($product === end($products)) {
$dateValue = $this->devisData['devis_date'];
$productDescription .= " de " . FileExportHelpers::GetSexeLabel($this->devisData['defunt_sexe']) . ' ' . $this->devisData["defunt_nom"];
}
$tvaAmount = $valueTtc - $valueHt;
$this->SetXY(10, $yValue);
$this->Cell(20, 6, $dateValue, 0, 0);
$this->SetXY(35, $yValue);
$this->MultiAlignCell(100, 6, utf8_decode(html_entity_decode($productDescription)), 0, '0');
$this->MultiAlignCell(100, 6, utf8_decode(html_entity_decode($productDescription)), 0, '0', );
$this->SetXY(135, $yValue);
$this->Cell(20, 6, number_format($valueHt, 2, '.', '') . chr(128), 0, 0, 'C');
$this->SetXY(155, $yValue);
$this->Cell(20, 6, $tvaValue . '%', 0, 0, 'C');
$this->Cell(20, 6, number_format($tvaAmount, 2, '.', '') . chr(128), 0, 0, 'C');
$this->SetXY(175, $yValue);
$this->Cell(25, 6, number_format($valueTtc, 2, '.', '') . chr(128), 0, 1, 'C');
$yValue += 12;
$totalTva += $tvaAmount;
}
// Trier par taux de TVA
ksort($totalsByTva);
// ⭐ NOUVEAU: Ajouter les lignes de totaux par TVA DANS le tableau
$this->SetFont('Arial', 'B', 10);
// Ligne de séparation avant les totaux
$this->Line(10, $yValue - 2, 200, $yValue - 2);
$yValue += 2;
foreach ($totalsByTva as $tva => $totals) {
$this->SetXY(35, $yValue);
$this->Cell(100, 6, 'Total TVA ' . number_format($tva, 0) . '%', 0, 0, 'L');
$this->SetXY(135, $yValue);
$this->Cell(20, 6, number_format($totals['totalHT'], 2, '.', '') . chr(128), 0, 0, 'C');
$this->SetXY(155, $yValue);
$this->Cell(20, 6, number_format($totals['totalTVA'], 2, '.', '') . chr(128), 0, 0, 'C');
$this->SetXY(175, $yValue);
$this->Cell(25, 6, number_format($totals['totalTTC'], 2, '.', '') . chr(128), 0, 0, 'C');
$yValue += 8;
}
// Calculer les totaux généraux pour le cadre en bas à droite
$totalGeneralHT = 0;
$totalGeneralTVA = 0;
$totalGeneralTTC = 0;
foreach ($totalsByTva as $totals) {
$totalGeneralHT += $totals['totalHT'];
$totalGeneralTVA += $totals['totalTVA'];
$totalGeneralTTC += $totals['totalTTC'];
}
// Retourner uniquement les totaux généraux
return [
'totalHT' => $totalGeneralHT,
'totalTVA' => $totalGeneralTVA,
'totalTTC' => $totalGeneralTTC
"TOTAL HT" => $totalHt,
"TVA " . $tvaValue . "%" => $totalTva,
"TOTAL TTC" => $totalTtc
];
}
@ -251,27 +200,17 @@ class DevisPdfHandler extends FPDF
$ibanCursorX = $this->GetX();
$this->Cell($ibanWidth, 7, 'Code SWIFT : AGRI FR PP 836', 1, 1, 'C');
//TABLE DES TOTAUX GÉNÉRAUX UNIQUEMENT (3 lignes)
//TABLE HT
$ibanLastPositionX = $ibanCursorX + $ibanWidth + 20;
$startOfArrayX = $ibanLastPositionX;
$startOfArrayY = $ibanCursorY;
// Afficher les 3 totaux généraux
$this->SetFont('Arial', 'B', 11);
$this->SetXY($startOfArrayX, $startOfArrayY);
$this->Cell(40, 7, 'TOTAL HT', 1, 0, 'C');
$this->Cell(40, 7, number_format($totalPriceArray['totalHT'], 2, '.', '') . chr(128), 1, 1, 'C');
$startOfArrayY += 7;
$this->SetXY($startOfArrayX, $startOfArrayY);
$this->Cell(40, 7, 'TOTAL TVA', 1, 0, 'C');
$this->Cell(40, 7, number_format($totalPriceArray['totalTVA'], 2, '.', '') . chr(128), 1, 1, 'C');
$startOfArrayY += 7;
$this->SetXY($startOfArrayX, $startOfArrayY);
$this->Cell(40, 7, 'TOTAL TTC', 1, 0, 'C');
$this->Cell(40, 7, number_format($totalPriceArray['totalTTC'], 2, '.', '') . chr(128), 1, 1, 'C');
foreach ($totalPriceArray as $label => $price) {
$this->SetXY($startOfArrayX, $startOfArrayY);
$this->Cell(40, 7, $label, 1, 1, 'C');
$this->SetXY($startOfArrayX + 40, $startOfArrayY);
$this->Cell(40, 7, number_format($price, 2, '.', '') . chr(128), 1, 1, 'C');
$startOfArrayY += 7;
}
}
public function SetMultipleDevisContent()
@ -294,7 +233,7 @@ class DevisPdfHandler extends FPDF
$this->DrawBankAndTotalPriceInfo($totalPriceValue);
}
public function MultiAlignCell($w, $h, $text, $border = 0, $ln = 0, $align = 'L', $fill = false)
function MultiAlignCell($w, $h, $text, $border = 0, $ln = 0, $align = 'L', $fill = false)
{
// Store reset values for (x,y) positions
$x = $this->GetX() + $w;
@ -309,22 +248,19 @@ class DevisPdfHandler extends FPDF
}
}
public function NbLines($w, $txt)
function NbLines($w, $txt)
{
// Compute the number of lines a MultiCell of width w will take
if (!isset($this->CurrentFont)) {
if (!isset($this->CurrentFont))
$this->Error('No font has been set');
}
$cw = $this->CurrentFont['cw'];
if ($w == 0) {
if ($w == 0)
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', (string) $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
if ($nb > 0 && $s[$nb - 1] == "\n")
$nb--;
}
$sep = -1;
$i = 0;
$j = 0;
@ -340,25 +276,21 @@ class DevisPdfHandler extends FPDF
$nl++;
continue;
}
if ($c == ' ') {
if ($c == ' ')
$sep = $i;
}
$l += $cw[$c];
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) {
if ($i == $j)
$i++;
}
} else {
} else
$i = $sep + 1;
}
$sep = -1;
$j = $i;
$l = 0;
$nl++;
} else {
} else
$i++;
}
}
return $nl;
}

View File

@ -30,94 +30,102 @@ use OCA\Gestion\Db\Bdd;
use OCA\Gestion\Helpers\FileExportHelpers;
use Psr\Log\LoggerInterface;
class ExportClientStatisticService {
/** @var Bdd */
private $gestionBdd;
class ExportClientStatisticService
{
/** @var Bdd */
private $gestionBdd;
/** @var LoggerInterface */
private $logger;
/** @var LoggerInterface */
private $logger;
public function __construct(
Bdd $gestionBdd,
LoggerInterface $logger) {
$this->logger = $logger;
$this->gestionBdd = $gestionBdd;
}
public function __construct(
Bdd $gestionBdd,
LoggerInterface $logger
) {
$this->logger = $logger;
$this->gestionBdd = $gestionBdd;
}
public function getFileName(array $clientIds){
public function getFileName(array $clientIds)
{
$filename = "";
$clients = $this->gestionBdd->getClientsByClientsID($clientIds);
foreach($clients as $client){
foreach($clients as $client) {
$filename .= $client['client_nom'] . '-' . $client['client_entreprise'] . '--';
}
$filename = rtrim($filename, '-');
$filename = str_replace(' ','-', $filename);
$filename = str_replace('&nbsp;','-', $filename);
$filename = str_replace(' ', '-', $filename);
$filename = str_replace('&nbsp;', '-', $filename);
return $filename;
}
public function getExportClientFileHeader(): string{
$fileHeader =
'CLIENT'.';'.
public function getExportClientFileHeader(): string
{
$fileHeader =
'CLIENT'.';'.
'MOIS'.';'.
'ANNEE'.';'.
'NB DE DEFUNTS'.';';
'NB DE DEFUNTS'.';';
$produitList = $this->gestionBdd->getProduitsListAsArray();
foreach($produitList as $produit){
$fileHeader .= FileExportHelpers::FormatTextForExport($produit['reference']).';';
}
$fileHeader .= 'TOTAL HT'.';'."\n";
return $fileHeader;
}
$produitList = $this->gestionBdd->getProduitsListAsArray();
foreach($produitList as $produit) {
$fileHeader .= FileExportHelpers::FormatTextForExport($produit['reference']).';';
}
$fileHeader .= 'TOTAL HT'.';'."\n";
return $fileHeader;
}
public function populateExportDataIntoFileContent(array $exportData,string $fileContent): string{
foreach($exportData as $clientId => $clientData){
$clientName = $clientData["client_name"];
$clientStatPerMonth = $clientData["client_data"];
$totalPrice = 0;
if(!empty($clientStatPerMonth)){
foreach($clientStatPerMonth as $month => $stat){
$stat["client_name"] = $clientName;
$totalPrice+=$stat["total_price"];
$fileContent = $this->populateClientStatDataIntoFileContent($fileContent,$month,$stat);
}
$fileContent = $this->populateTotalPriceIntoFileContent($fileContent,$totalPrice,count($stat["products"]));
}
}
return $fileContent;
}
public function populateExportDataIntoFileContent(array $exportData, string $fileContent): string
{
foreach($exportData as $clientId => $clientData) {
$clientName = $clientData["client_name"];
$clientStatPerMonth = $clientData["client_data"];
$totalPrice = 0;
if(!empty($clientStatPerMonth)) {
foreach($clientStatPerMonth as $month => $stat) {
$stat["client_name"] = $clientName;
$totalPrice += $stat["total_price"];
$fileContent = $this->populateClientStatDataIntoFileContent($fileContent, $month, $stat);
}
$fileContent = $this->populateTotalPriceIntoFileContent($fileContent, $totalPrice, count($stat["products"]));
}
}
return $fileContent;
}
private function populateTotalPriceIntoFileContent(string $fileContent,$totalPrice,$productsCount){
$fileContent = $fileContent.
''.';'.
''.';'.
''.';'.
''.';';
while($productsCount > 0){
$fileContent .= ''.';';
$productsCount--;
}
$fileContent .= "$totalPrice".";"."\n";
return $fileContent;
}
private function populateTotalPriceIntoFileContent(string $fileContent, $totalPrice, $productsCount)
{
$fileContent = $fileContent.
''.';'.
''.';'.
''.';'.
''.';';
while($productsCount > 0) {
$fileContent .= ''.';';
$productsCount--;
}
$fileContent .= "$totalPrice".";"."\n";
return $fileContent;
}
private function populateClientStatDataIntoFileContent(string $fileContent,$month,array $statPerMonth){
$yearValue = $statPerMonth["year"];
$defuntCount = $statPerMonth["defunt_count"];
$products = $statPerMonth["products"];
$fileContent = $fileContent.
FileExportHelpers::FormatTextForExport($statPerMonth['client_name']).';'.
"$month".';'.
"$yearValue".';'.
"$defuntCount".';';
private function populateClientStatDataIntoFileContent(string $fileContent, $month, array $statPerMonth)
{
$yearValue = $statPerMonth["year"];
$defuntCount = $statPerMonth["defunt_count"];
$products = $statPerMonth["products"];
$priceTotal = $statPerMonth["total_price"];
foreach($products as $productCount){
$fileContent .= "$productCount".";";
}
$fileContent .= "\n";
return $fileContent;
$fileContent = $fileContent.
FileExportHelpers::FormatTextForExport($statPerMonth['client_name']).';'.
"$month".';'.
"$yearValue".';'.
"$defuntCount".';';
}
foreach($products as $productCount) {
$fileContent .= "$productCount".";";
}
$fileContent .= "$priceTotal".';'."\n";
return $fileContent;
}
}

View File

@ -34,149 +34,158 @@ use OCA\Gestion\Constants\BddConstant;
use OCA\Gestion\Helpers\FileExportHelpers;
use OCA\Gestion\Constants\AbsenceTypeConstant;
class ExportThanatoStatisticService {
/** @var Bdd */
private $gestionBdd;
class ExportThanatoStatisticService
{
/** @var Bdd */
private $gestionBdd;
/** @var LoggerInterface */
private $logger;
/** @var LoggerInterface */
private $logger;
/** @var IRootFolder */
private $rootFolder;
/** @var IRootFolder */
private $rootFolder;
private $geoService;
private $geoService;
public function __construct(
Bdd $gestionBdd,
LoggerInterface $logger,
IRootFolder $rootFolder,
GeoService $geoService) {
$this->geoService = $geoService;
$this->rootFolder = $rootFolder;
$this->logger = $logger;
$this->gestionBdd = $gestionBdd;
}
public function __construct(
Bdd $gestionBdd,
LoggerInterface $logger,
IRootFolder $rootFolder,
GeoService $geoService
) {
$this->geoService = $geoService;
$this->rootFolder = $rootFolder;
$this->logger = $logger;
$this->gestionBdd = $gestionBdd;
}
private function getFilename($thanatoName,$thanatoLastName,$month,$year){
$filename = "$year-$month-";
$filename .= $thanatoName . '-' . $thanatoLastName;
$filename = str_replace(' ','-', $filename);
$filename = str_replace('&nbsp;','-', $filename);
private function getFilename($thanatoName, $thanatoLastName, $month, $year)
{
$filename = "$year-$month-";
$filename .= $thanatoName . '-' . $thanatoLastName;
$filename = str_replace(' ', '-', $filename);
$filename = str_replace('&nbsp;', '-', $filename);
return $filename;
}
}
private function exportThanatoStatistic($thanatoId,$month,$year,$idNextcloud){
$thanato = $this->gestionBdd->getThanatoById($thanatoId);
if($thanato == null){
return null;
}
$exportData = $this->gestionBdd->getExportThanatoStatisticData($thanatoId,$month,$year);
if(empty($exportData)){
return null;
}
$defaultConfig = json_decode($this->gestionBdd->getConfiguration(BddConstant::DEFAULT_ADMIN_ID_NEXTCLOUD));
$racineFolder = html_entity_decode($defaultConfig[0]->path).'/';
$thanatoFolder = $racineFolder.'STATISTIQUES/THANATOS/';
$fileHeader = $this->getExportThanatoFileHeader();
$fileContent = $this->populateExportDataIntoFileContent($exportData,$fileHeader);
$storage = $this->rootFolder->getUserFolder($idNextcloud);
try{
$storage->newFolder($thanatoFolder);
}
catch(\OCP\Files\NotPermittedException $e) {
}
$filename = $this->getFilename($thanato["nom"],$thanato["prenom"],$month,$year);
$fileNamePath = $thanatoFolder."STAT-THANATO-" . $filename . '.csv';
$storage->newFile($fileNamePath);
$file = $storage->get($fileNamePath);
$file->putContent($fileContent);
return $fileNamePath;
}
private function exportThanatoStatistic($thanatoId, $month, $year, $idNextcloud)
{
$thanato = $this->gestionBdd->getThanatoById($thanatoId);
if($thanato == null) {
return null;
}
$exportData = $this->gestionBdd->getExportThanatoStatisticData($thanatoId, $month, $year);
if(empty($exportData)) {
return null;
}
$defaultConfig = json_decode($this->gestionBdd->getConfiguration(BddConstant::DEFAULT_ADMIN_ID_NEXTCLOUD));
$racineFolder = html_entity_decode($defaultConfig[0]->path).'/';
$thanatoFolder = $racineFolder.'STATISTIQUES/THANATOS/';
$fileHeader = $this->getExportThanatoFileHeader();
$fileContent = $this->populateExportDataIntoFileContent($exportData, $fileHeader);
$storage = $this->rootFolder->getUserFolder($idNextcloud);
try {
$storage->newFolder($thanatoFolder);
} catch(\OCP\Files\NotPermittedException $e) {
public function exportThanatosListStatistic(array $thanatoIds,$month,$year,$idNextcloud){
$filenames = [];
foreach($thanatoIds as $thanatoId){
$filename = $this->exportThanatoStatistic($thanatoId,$month,$year,$idNextcloud);
if($filename != null){
$filenames[] = $filename;
}
}
return $filenames;
}
}
$filename = $this->getFilename($thanato["nom"], $thanato["prenom"], $month, $year);
$fileNamePath = $thanatoFolder."STAT-THANATO-" . $filename . '.csv';
$storage->newFile($fileNamePath);
$file = $storage->get($fileNamePath);
$file->putContent($fileContent);
return $fileNamePath;
}
public function getExportThanatoFileHeader(): string{
$fileHeader =
'FACTURE'.';'.
'THANATOPRACTEUR'.';'.
'DATE'.';'.
'HEURE DE DEBUT'.';'.
'HEURE DE FIN'.';'.
'SOINS'.';'.
'JOUR/FERIE'.';'.
'CONGE'.';'.
'REPOS'.';'.
'MALADIE'.';'.
'NOM ET PRENOM'.';'.
'LIEU'.';'.
'POMPES FUNEBRES'.';'.
'ADRESSE'.';'.
'DISTANCE TOTALE KM'.';'.
'HEURES TOTAL DE SOIN'.';'.
'HEURES TOTAL DE CONGE'.';'.
'HEURES TOTAL DE REPOS'.';'.
'HEURES TOTAL DE MALADIE'.';'.
'HEURES TOTAL DE TRAVAIL'.';'.
'HEURES TOTAL DE PARCOURS ENTRE DEVIS'.';'.
'NOMBRE DE SOINS ET TOILETTES'.';'.
"\n";
return $fileHeader;
}
public function exportThanatosListStatistic(array $thanatoIds, $month, $year, $idNextcloud)
{
$filenames = [];
foreach($thanatoIds as $thanatoId) {
$filename = $this->exportThanatoStatistic($thanatoId, $month, $year, $idNextcloud);
if($filename != null) {
$filenames[] = $filename;
}
}
return $filenames;
}
private function populateNoDevisDataInADay(string $fileContent,$leave){
$startTimeValue = "";
$endTimeValue = "";
$leaveValue = "Non";
if($leave["onLeave"]){
$startTimeValue = $leave["startTime"];
$endTimeValue = $leave["endTime"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE){
$leaveValue = "Oui";
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE){
$diseaseValue = "Oui";
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST){
$restValue = "Oui";
}
}
$fileContent = $fileContent.
''.';'.
FileExportHelpers::FormatTextForExport($leave['thanatoName']).';'.
$leave['date'].';'.
$startTimeValue.';'.
$endTimeValue.';'.
''.';'.
DateHelpers::getPublicHolidayText($leave['isPublicHoliday']).';'.
$leaveValue.';'.
$restValue.';'.
$diseaseValue.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'."\n";
return $fileContent;
}
public function getExportThanatoFileHeader(): string
{
$fileHeader =
'DEVIS'.';'.
'THANATOPRACTEUR'.';'.
'DATE'.';'.
'HEURE DE DEBUT'.';'.
'HEURE DE FIN'.';'.
'SOINS'.';'.
'JOUR/FERIE'.';'.
'CONGE'.';'.
'REPOS'.';'.
'MALADIE'.';'.
'NOM ET PRENOM'.';'.
'LIEU'.';'.
'POMPES FUNEBRES'.';'.
'ADRESSE'.';'.
'DISTANCE TOTALE KM'.';'.
'HEURES TOTAL DE SOIN'.';'.
'HEURES TOTAL DE CONGE'.';'.
'HEURES TOTAL DE REPOS'.';'.
'HEURES TOTAL DE MALADIE'.';'.
'HEURES TOTAL DE TRAVAIL'.';'.
'HEURES TOTAL DE PARCOURS ENTRE DEVIS'.';'.
'NOMBRE DE SOINS ET TOILETTES'.';'.
"\n";
return $fileHeader;
}
public function populateExportDataIntoFileContent(array $exportData,string $fileContent): string{
private function populateNoDevisDataInADay(string $fileContent, $leave)
{
$startTimeValue = "";
$endTimeValue = "";
$leaveValue = "Non";
$restValue = "Non"; // AJOUTER CETTE LIGNE
$diseaseValue = "Non"; // AJOUTER CETTE LIGNE
if($leave["onLeave"]) {
$startTimeValue = $leave["startTime"];
$endTimeValue = $leave["endTime"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE) {
$leaveValue = "Oui";
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE) {
$diseaseValue = "Oui";
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST) {
$restValue = "Oui";
}
}
$fileContent = $fileContent.
''.';'.
FileExportHelpers::FormatTextForExport($leave['thanatoName']).';'.
$leave['date'].';'.
$startTimeValue.';'.
$endTimeValue.';'.
''.';'.
DateHelpers::getPublicHolidayText($leave['isPublicHoliday']).';'.
$leaveValue.';'.
$restValue.';'.
$diseaseValue.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'."\n";
return $fileContent;
}
public function populateExportDataIntoFileContent(array $exportData, string $fileContent): string
{
$g_totalDistance = 0;
$g_totalDevisHours = 0;
@ -185,81 +194,81 @@ class ExportThanatoStatisticService {
$g_totalTravelingHoursBetweenDevisLocation = 0;
$g_totalDiseaseHours = 0;
$g_totalRestHours = 0;
$g_totalDevisCount = 0;
$g_totalDevisCount = 0;
foreach($exportData as $devisDate => $devisData){
$totalDevisHours = 0;
$totalWorkedHours = 8;
$totalLeaveHours = 0;
$totalDiseaseHours = 0;
$totalRestHours = 0;
$totalDistance = 0;
$totalDevisCount = 0;
$totalTravelingHoursBetweenDevisLocation = 0;
$hasDevisInTheCurrentDate = $devisData['hasDevis'];
if($hasDevisInTheCurrentDate === false){
$leaves = $devisData["leaves"];
foreach($leaves as $leave){
$fileContent = $this->populateNoDevisDataInADay($fileContent,$leave);
if($leave["onLeave"]){
$totalLeaveHoursInsideWorkingHours = $leave["totalWorkedHours"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE ){
$totalLeaveHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST){
$totalRestHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE){
$totalDiseaseHours += $totalLeaveHoursInsideWorkingHours;
}
}
}
$totalAbsenceHours = $totalLeaveHours + $totalRestHours + $totalDiseaseHours;
$totalWorkedHours -= $totalAbsenceHours;
}
else{
$totalDevisCount += count($devisData["devisId"]);
$routeLines = $this->gestionBdd->getRouteLinesByDevisIdList($devisData["devisId"]);
$totalDistanceAndTotalTravelingHoursBetweenDevis = $this->geoService->getTotalDistanceAndTotalTravelingHoursBetweenDevisLocationByRouteLines($routeLines);
$totalDistance = $totalDistanceAndTotalTravelingHoursBetweenDevis["totalDistance"];
$totalTravelingHoursBetweenDevisLocation = $totalDistanceAndTotalTravelingHoursBetweenDevis["totalTravelingHours"];
$devisList = $devisData["devis"];
$leaves = $devisData["leaves"];
if(!empty($devisList)){
foreach($devisList as $devis){
$fileContent = $this->populateDevisDataIntoThanatoExportFileContent($fileContent,$devis);
$totalDevisHours += $devis["totalHours"];
}
}
foreach($leaves as $leave){
$fileContent = $this->populateNoDevisDataInADay($fileContent,$leave);
if($leave["onLeave"]){
$totalLeaveHoursInsideWorkingHours = $leave["totalWorkedHours"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE){
$totalLeaveHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST){
$totalRestHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE){
$totalDiseaseHours += $totalLeaveHoursInsideWorkingHours;
}
}
}
$totalAbsenceHours = $totalLeaveHours + $totalRestHours + $totalDiseaseHours;
$totalWorkedHours -= $totalAbsenceHours;
}
foreach($exportData as $devisDate => $devisData) {
$totalDevisHours = 0;
$totalWorkedHours = 8;
$totalLeaveHours = 0;
$totalDiseaseHours = 0;
$totalRestHours = 0;
$totalDistance = 0;
$totalDevisCount = 0;
$totalTravelingHoursBetweenDevisLocation = 0;
$hasDevisInTheCurrentDate = $devisData['hasDevis'];
if($hasDevisInTheCurrentDate === false) {
$leaves = $devisData["leaves"];
foreach($leaves as $leave) {
$fileContent = $this->populateNoDevisDataInADay($fileContent, $leave);
if($leave["onLeave"]) {
$totalLeaveHoursInsideWorkingHours = $leave["totalWorkedHours"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE) {
$totalLeaveHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST) {
$totalRestHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE) {
$totalDiseaseHours += $totalLeaveHoursInsideWorkingHours;
}
}
}
$totalAbsenceHours = $totalLeaveHours + $totalRestHours + $totalDiseaseHours;
$totalWorkedHours -= $totalAbsenceHours;
} else {
$totalDevisCount += count($devisData["devisId"]);
$routeLines = $this->gestionBdd->getRouteLinesByDevisIdList($devisData["devisId"]);
$totalDistanceAndTotalTravelingHoursBetweenDevis = $this->geoService->getTotalDistanceAndTotalTravelingHoursBetweenDevisLocationByRouteLines($routeLines);
$totalDistance = $totalDistanceAndTotalTravelingHoursBetweenDevis["totalDistance"];
$totalTravelingHoursBetweenDevisLocation = $totalDistanceAndTotalTravelingHoursBetweenDevis["totalTravelingHours"];
$devisList = $devisData["devis"];
$leaves = $devisData["leaves"];
if(!empty($devisList)) {
foreach($devisList as $devis) {
$fileContent = $this->populateDevisDataIntoThanatoExportFileContent($fileContent, $devis);
$totalDevisHours += $devis["totalHours"];
}
}
foreach($leaves as $leave) {
$fileContent = $this->populateNoDevisDataInADay($fileContent, $leave);
if($leave["onLeave"]) {
$totalLeaveHoursInsideWorkingHours = $leave["totalWorkedHours"];
if($leave["absenceTypeKey"] == AbsenceTypeConstant::LEAVE) {
$totalLeaveHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::REST) {
$totalRestHours += $totalLeaveHoursInsideWorkingHours;
}
if($leave["absenceTypeKey"] == AbsenceTypeConstant::DISEASE) {
$totalDiseaseHours += $totalLeaveHoursInsideWorkingHours;
}
}
}
$totalAbsenceHours = $totalLeaveHours + $totalRestHours + $totalDiseaseHours;
$totalWorkedHours -= $totalAbsenceHours;
}
$fileContent = $this->populateLastRecapForTheLine(
$fileContent,
$totalDistance,
$totalDevisHours,
$totalWorkedHours,
$totalLeaveHours,
$totalTravelingHoursBetweenDevisLocation,
$totalDiseaseHours,
$totalRestHours,$totalDevisCount
);
$fileContent = $this->populateLastRecapForTheLine(
$fileContent,
$totalDistance,
$totalDevisHours,
$totalWorkedHours,
$totalLeaveHours,
$totalTravelingHoursBetweenDevisLocation,
$totalDiseaseHours,
$totalRestHours,
$totalDevisCount
);
$g_totalDistance += $totalDistance;
$g_totalDevisHours += $totalDevisHours;
@ -268,8 +277,8 @@ class ExportThanatoStatisticService {
$g_totalTravelingHoursBetweenDevisLocation += $totalTravelingHoursBetweenDevisLocation;
$g_totalDiseaseHours += $totalDiseaseHours;
$g_totalRestHours += $totalRestHours;
$g_totalDevisCount += $totalDevisCount;
}
$g_totalDevisCount += $totalDevisCount;
}
$fileContent = $this->populateLastRecapForTheLine(
$fileContent,
@ -279,77 +288,113 @@ class ExportThanatoStatisticService {
$g_totalLeaveHours,
$g_totalTravelingHoursBetweenDevisLocation,
$g_totalDiseaseHours,
$g_totalRestHours,$g_totalDevisCount
$g_totalRestHours,
$g_totalDevisCount
);
return $fileContent;
}
return $fileContent;
}
private function populateLastRecapForTheLine(string $fileContent,$distance,$totalDevisHours,$totalWorkedHours,$totalLeaveHours,$totalTravelingHours ,$totalDiseaseHours = 0,$totalRestHours = 0,$totalDevisCount = 0){
$fileContent = $fileContent.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
"$distance".';'.
"$totalDevisHours".';'.
"$totalLeaveHours".';'.
"$totalRestHours".';'.
"$totalDiseaseHours".';'.
"$totalWorkedHours".';'.
"$totalTravelingHours".';'.
"$totalDevisCount"."\n";
return $fileContent;
}
private function populateLastRecapForTheLine(string $fileContent, $distance, $totalDevisHours, $totalWorkedHours, $totalLeaveHours, $totalTravelingHours, $totalDiseaseHours = 0, $totalRestHours = 0, $totalDevisCount = 0)
{
$fileContent = $fileContent.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
"$distance".';'.
"$totalDevisHours".';'.
"$totalLeaveHours".';'.
"$totalRestHours".';'.
"$totalDiseaseHours".';'.
"$totalWorkedHours".';'.
"$totalTravelingHours".';'.
"$totalDevisCount"."\n";
return $fileContent;
}
private function getFormatDevisProduitsAsString($devisProduits){
$result = '';
foreach ($devisProduits as $produit) {
$result .= $produit['produit_reference'] . '-' . $produit['produit_description'] . '--';
}
// Remove the trailing "--" at the end
$result = rtrim($result, '-');
return $result;
}
private function getFormatDevisProduitsAsString($devisProduits)
{
$result = '';
foreach ($devisProduits as $produit) {
$result .= $produit['produit_reference'] . '-' . $produit['produit_description'] . '--';
}
// Remove the trailing "--" at the end
$result = rtrim($result, '-');
return $result;
}
private function populateDevisDataIntoThanatoExportFileContent(string $fileContent,array $devis){
$produitAsString = $this->getFormatDevisProduitsAsString($devis["produits"]);
$factureNum = $devis["facture_num"] ?? $devis["facture_on_group_num"] ?? "";
$fileContent = $fileContent.
FileExportHelpers::FormatTextForExport($factureNum).';'.
FileExportHelpers::FormatTextForExport($devis['nom_thanato'] . ' ' . $devis['prenom_thanatho']).';'.
FileExportHelpers::FormatTextForExport($devis["date"]).';'.
FileExportHelpers::FormatTextForExport($devis["startTime"]).';'.
FileExportHelpers::FormatTextForExport($devis["endTime"]).';'.
FileExportHelpers::FormatTextForExport($produitAsString).';'.
FileExportHelpers::FormatTextForExport($devis["dayType"]).';'.
FileExportHelpers::FormatTextForExport('Non').';'.
''.';'.
''.';'.
FileExportHelpers::FormatTextForExport($devis["nom_defunt"]).';'.
FileExportHelpers::FormatTextForExport($devis["nom_lieu"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["nom_client"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["client_adresse"] ?? "").
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'."\n";
return $fileContent;
private function populateDevisDataIntoThanatoExportFileContentSave(string $fileContent, array $devis)
{
$produitAsString = $this->getFormatDevisProduitsAsString($devis["produits"]);
$factureNum = $devis["facture_num"] ?? $devis["facture_on_group_num"] ?? "";
$fileContent = $fileContent.
FileExportHelpers::FormatTextForExport($factureNum).';'.
FileExportHelpers::FormatTextForExport($devis['nom_thanato'] . ' ' . $devis['prenom_thanatho']).';'.
FileExportHelpers::FormatTextForExport($devis["date"]).';'.
FileExportHelpers::FormatTextForExport($devis["startTime"]).';'.
FileExportHelpers::FormatTextForExport($devis["endTime"]).';'.
FileExportHelpers::FormatTextForExport($produitAsString).';'.
FileExportHelpers::FormatTextForExport($devis["dayType"]).';'.
FileExportHelpers::FormatTextForExport('Non').';'.
''.';'.
''.';'.
FileExportHelpers::FormatTextForExport($devis["nom_defunt"]).';'.
FileExportHelpers::FormatTextForExport($devis["nom_lieu"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["nom_client"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["client_adresse"] ?? "").
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'."\n";
}
return $fileContent;
}
private function populateDevisDataIntoThanatoExportFileContent(string $fileContent, array $devis)
{
$produitAsString = $this->getFormatDevisProduitsAsString($devis["produits"]);
$devisNum = (string)($devis["calendar_uuid"] ?? "");
$fileContent = $fileContent.
FileExportHelpers::FormatTextForExport($devisNum).';'.
FileExportHelpers::FormatTextForExport(($devis['nom_thanato'] ?? '') . ' ' . ($devis['prenom_thanato'] ?? '')).';'.
FileExportHelpers::FormatTextForExport($devis["date"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["startTime"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["endTime"] ?? "").';'.
FileExportHelpers::FormatTextForExport($produitAsString).';'.
FileExportHelpers::FormatTextForExport($devis["dayType"] ?? "").';'.
'Non'.';'.
''.';'.
''.';'.
FileExportHelpers::FormatTextForExport($devis["nom_defunt"] ?? "").';'. // ICI
FileExportHelpers::FormatTextForExport($devis["nom_lieu"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["nom_client"] ?? "").';'.
FileExportHelpers::FormatTextForExport($devis["client_adresse"] ?? "").';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'.
''.';'."\n";
return $fileContent;
}
}

View File

@ -30,28 +30,31 @@ use Exception;
use OCA\Gestion\Constants\GeoConstant;
use OCA\Gestion\Helpers\GeoHelpers;
class GeoService {
public function __construct() {
}
class GeoService
{
public function __construct()
{
}
/**
* Calcul la distance entre les deux points à vol d'oiseau
*/
private function getDistanceInKmBetweenTwoPoints($lat1, $lon1, $lat2, $lon2) {
private function getDistanceInKmBetweenTwoPoints($lat1, $lon1, $lat2, $lon2)
{
$R = 6371; // Rayon moyen de la Terre en kilomètres
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
$a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$d = $R * $c;
return round($d, 2);
}
private function getTravelingHourBetweenTwoPoints(array $origin,array $destination,$mode = "driving"){
private function getTravelingHourBetweenTwoPoints(array $origin, array $destination, $mode = "driving")
{
$baseUrl = "https://api.geoapify.com/v1/routing";
$originPoints = GeoHelpers::getPointsTextFromLatitudeAndLongitude($origin["latitude"],$origin["longitude"]);
$destinationPoints = GeoHelpers::getPointsTextFromLatitudeAndLongitude($destination["latitude"],$destination["longitude"]);
$originPoints = GeoHelpers::getPointsTextFromLatitudeAndLongitude($origin["latitude"], $origin["longitude"]);
$destinationPoints = GeoHelpers::getPointsTextFromLatitudeAndLongitude($destination["latitude"], $destination["longitude"]);
$fullUrl = $baseUrl."?waypoints=$originPoints|$destinationPoints&mode=$mode&apiKey=9e23d93e7f454c988344f9171bf867aa";
$curl = curl_init();
@ -66,11 +69,11 @@ class GeoService {
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$response = curl_exec($curl);
curl_close($curl);
if ($response === false) {
return 0;
} else {
@ -79,45 +82,45 @@ class GeoService {
$travelTimeHours = round($travelTimeHours, 2);
return $travelTimeHours;
}
}
catch(Exception $e){
} catch(Exception $e) {
return 0;
}
}
public function getTotalDistanceAndTotalTravelingHoursBetweenDevisLocationByRouteLines(array $routeLines){
public function getTotalDistanceAndTotalTravelingHoursBetweenDevisLocationByRouteLinesSave(array $routeLines)
{
$distanceCumul = 0;
$totalTravelingHoursBetweenTwoDevisLocation = 0;
$lastPoint = NULL;
for ($i=0; $i < sizeof($routeLines); $i++) {
$lastPoint = null;
for ($i = 0; $i < sizeof($routeLines); $i++) {
$currentDistance = 0;
if($routeLines[$i]['lieu_id'] != NULL){
if($routeLines[$i]['lieu_id'] != null) {
$lastPoint = $routeLines[$i];
}
if($lastPoint['lieu_id'] != NULL && $routeLines[$i+1]['lieu_id'] != NULL){
}
if($lastPoint['lieu_id'] != null && $routeLines[$i + 1]['lieu_id'] != null) {
$currentDistance = $this->getDistanceInKmBetweenTwoPoints(
floatval(value: $lastPoint['latitude']),
floatval($lastPoint['longitude']),
floatval($routeLines[$i+1]['latitude']),
floatval($routeLines[$i+1]['longitude'])
floatval(value: $lastPoint['latitude']),
floatval($lastPoint['longitude']),
floatval($routeLines[$i + 1]['latitude']),
floatval($routeLines[$i + 1]['longitude'])
);
$targetIsBetweenTwoDevisLocation = $lastPoint['source'] != "siege" && $routeLines[$i+1]["source"] != "siege";
if($targetIsBetweenTwoDevisLocation){
$targetIsBetweenTwoDevisLocation = $lastPoint['source'] != "siege" && $routeLines[$i + 1]["source"] != "siege";
if($targetIsBetweenTwoDevisLocation) {
$originPoints = [
"latitude" => $lastPoint["latitude"],
"longitude" => $lastPoint["longitude"]
];
$destinationPoints = [
"latitude" => $routeLines[$i+1]["latitude"],
"longitude" => $routeLines[$i+1]["longitude"]
"latitude" => $routeLines[$i + 1]["latitude"],
"longitude" => $routeLines[$i + 1]["longitude"]
];
$totalTravelingHoursBetweenTwoDevisLocation+= $this->getTravelingHourBetweenTwoPoints(
$totalTravelingHoursBetweenTwoDevisLocation += $this->getTravelingHourBetweenTwoPoints(
$originPoints,
$destinationPoints,
GeoConstant::DRIVING_MODE
);
}
}
}
$distanceCumul += $currentDistance;
}
return [
@ -125,4 +128,65 @@ class GeoService {
"totalTravelingHours" => $totalTravelingHoursBetweenTwoDevisLocation
];
}
public function getTotalDistanceAndTotalTravelingHoursBetweenDevisLocationByRouteLines(array $routeLines)
{
$distanceCumul = 0;
$totalTravelingHoursBetweenTwoDevisLocation = 0;
$lastPoint = null;
for ($i = 0; $i < sizeof($routeLines); $i++) {
$currentDistance = 0;
// Vérifier que l'élément actuel existe
if(!isset($routeLines[$i])) {
continue;
}
if($routeLines[$i]['lieu_id'] != null) {
$lastPoint = $routeLines[$i];
}
// Vérifier que $i+1 existe ET que $lastPoint n'est pas null
if($lastPoint !== null &&
isset($routeLines[$i + 1]) &&
isset($lastPoint['lieu_id']) &&
isset($routeLines[$i + 1]['lieu_id']) &&
$lastPoint['lieu_id'] != null &&
$routeLines[$i + 1]['lieu_id'] != null) {
$currentDistance = $this->getDistanceInKmBetweenTwoPoints(
floatval($lastPoint['latitude']),
floatval($lastPoint['longitude']),
floatval($routeLines[$i + 1]['latitude']),
floatval($routeLines[$i + 1]['longitude'])
);
$targetIsBetweenTwoDevisLocation = $lastPoint['source'] != "siege" && $routeLines[$i + 1]["source"] != "siege";
if($targetIsBetweenTwoDevisLocation) {
$originPoints = [
"latitude" => $lastPoint["latitude"],
"longitude" => $lastPoint["longitude"]
];
$destinationPoints = [
"latitude" => $routeLines[$i + 1]["latitude"],
"longitude" => $routeLines[$i + 1]["longitude"]
];
$totalTravelingHoursBetweenTwoDevisLocation += $this->getTravelingHourBetweenTwoPoints(
$originPoints,
$destinationPoints,
GeoConstant::DRIVING_MODE
);
}
}
$distanceCumul += $currentDistance;
}
return [
"totalDistance" => round($distanceCumul, 2),
"totalTravelingHours" => round($totalTravelingHoursBetweenTwoDevisLocation, 2)
];
}
}

View File

@ -2,7 +2,7 @@ import "@nextcloud/dialogs/dist/index.css";
import "datatables.net-dt/css/jquery.dataTables.css";
import "../css/mycss.css";
import { getArticlesById, getMailServerFrom, getProduitsById, savePdfToNextcloud, exportDevisToPdf, updateDB} from "./modules/ajaxRequest.mjs";
import { getArticlesById, getMailServerFrom, getProduitsById, savePdfToNextcloud,exportDevisToPdf} from "./modules/ajaxRequest.mjs";
import { getGlobal, globalConfiguration } from "./modules/mainFunction.mjs";
import "./listener/main_listener";
import { Client } from "./objects/client.mjs";
@ -34,23 +34,4 @@ window.addEventListener("DOMContentLoaded", function () {
capture(sendMail);
(document.getElementById("modalMail")).style.display = "none";
});
$(document).on('change', '.tva-select', function() {
var table = $(this).data('table');
var column = $(this).data('column');
var id = $(this).data('id');
var value = $(this).val();
// Mettre à jour la base de données
updateDB(table, column, value, id);
// Recharger les produits ou articles selon la table
setTimeout(function() {
if (table === 'produit_devis') {
getProduitsById();
} else if (table === 'article_devis') {
getArticlesById();
}
}, 500);
});
});

View File

@ -357,7 +357,7 @@ export function getProduitsByIdDevis(id_devis) {
/**
* Get a product in database using id
*/
export function getProduitsById() {
export function getProduitsById() {
var devis_id = $('#devisid').data('id');
var myData = { numdevis: devis_id, };
@ -376,49 +376,12 @@ export function getProduitsById() {
}
$.each(JSON.parse(response), function (arrayID, myresp) {
var tvaValue = myresp.tva || 20.00;
var prixUnitaireHT = parseFloat(myresp.prix_unitaire);
var quantite = parseFloat(myresp.quantite);
// Calculs
var prixUnitaireTTC = prixUnitaireHT * (1 + tvaValue / 100);
var totalHT = prixUnitaireHT * quantite;
var totalTTC = prixUnitaireTTC * quantite;
$('#produits tbody').append('<tr>' +
// Colonne 1: Reference (avec delete et selectable)
'<td><div data-html2canvas-ignore data-modifier="getProduitsById" data-id="' + myresp.pdid + '" data-table="produit_devis" class="' + deleteDisable + ' deleteItem icon-delete"></div><div style="display:inline;" data-val="' + myresp.pid + '" data-id="' + myresp.pdid + '" class="selectable">' + myresp.reference + '</div></td>' +
// Colonne 2: Designation
$('#produits tbody').append('<tr><td><div data-html2canvas-ignore data-modifier="getProduitsById" data-id="' + myresp.pdid + '" data-table="produit_devis" class="' + deleteDisable + ' deleteItem icon-delete"></div><div style="display:inline;" data-val="' + myresp.pid + '" data-id="' + myresp.pdid + '" class="selectable">' + myresp.reference + '</div></td>' +
'<td>' + myresp.description + '</td>' +
// Colonne 3: Comment (editable)
'<td><div class="editable" data-table="produit_devis" data-column="comment" data-id="' + myresp.pdid + '">' + ((myresp.comment.length === 0) ? '-' : myresp.comment) + '</div></td>' +
// Colonne 4: TVA (select)
'<td><select class="form-select tva-select" data-table="produit_devis" data-column="tva" data-id="' + myresp.pdid + '">' +
'<option value="10"' + (tvaValue == 10 ? ' selected' : '') + '>10%</option>' +
'<option value="15"' + (tvaValue == 15 ? ' selected' : '') + '>15%</option>' +
'<option value="20"' + (tvaValue == 20 ? ' selected' : '') + '>20%</option>' +
'</select></td>' +
// Colonne 5: Quantity (editable)
'<td><div class="editableNumber getProduitsById" style="display:inline;" data-modifier="getProduitsById" data-table="produit_devis" data-column="quantite" data-id=' + myresp.pdid + '>' + myresp.quantite + '</div></td>' +
// Colonne 6: Unit price with VAT (Prix unitaire TTC)
'<td>' + cur.format(prixUnitaireTTC) + '</td>' +
// Colonne 7: Unit price without VAT (Prix unitaire HT)
'<td><div class="editableNumber getProduitsById" style="display:inline;" data-modifier="getProduitsById" data-table="produit_devis" data-column="unit_price" data-id="' + myresp.pdid + '">' + prixUnitaireHT.toFixed(2) + '</div><span> €</span></td>' +
// Colonne 8: Total with VAT (Total TTC)
'<td>' + cur.format(totalTTC) + '</td>' +
// Colonne 9: Total without VAT (Total HT)
'<td>' + cur.format(totalHT) + '</td>' +
'</tr>');
'<td><div class="editableNumber getProduitsById" style="display:inline;" data-modifier="getProduitsById" data-table="produit_devis" data-column="quantite" data-id=' + myresp.pdid + '>' + myresp.quantite + '</div> </td>' +
'<td>' + cur.format(myresp.prix_unitaire) + '</td>' +
'<td>' + cur.format((myresp.quantite * myresp.prix_unitaire)) + '</td></tr>');
total += (myresp.quantite * myresp.prix_unitaire);
});
@ -451,50 +414,12 @@ export function getArticlesById() {
}
$.each(JSON.parse(response), function (arrayID, myresp) {
var tvaValue = myresp.tva || 20.00;
var prixUnitaireHT = parseFloat(myresp.prix_unitaire);
var quantite = parseFloat(myresp.quantite);
// Calculs
var prixUnitaireTTC = prixUnitaireHT * (1 + tvaValue / 100);
var totalHT = prixUnitaireHT * quantite;
var totalTTC = prixUnitaireTTC * quantite;
$('#articles tbody').append('<tr>' +
// Colonne 1: Reference (avec delete et selectable)
'<td><div data-html2canvas-ignore data-modifier="getArticlesById" data-id="' + myresp.adid + '" data-table="article_devis" class="' + deleteDisable + ' deleteItem icon-delete"></div><div style="display:inline;" data-val="' + myresp.aid + '" data-id="' + myresp.adid + '" class="articleSelectable">' + myresp.reference + '</div></td>' +
// Colonne 2: Designation
$('#articles tbody').append('<tr><td><div data-html2canvas-ignore data-modifier="getArticlesById" data-id="' + myresp.adid + '" data-table="article_devis" class="' + deleteDisable + ' deleteItem icon-delete"></div><div style="display:inline;" data-val="' + myresp.aid + '" data-id="' + myresp.adid + '" class="articleSelectable">' + myresp.reference + '</div></td>' +
'<td>' + myresp.description + '</td>' +
// Colonne 3: Comment (editable)
'<td><div class="editable" data-table="article_devis" data-column="comment" data-id="' + myresp.adid + '">' + ((myresp.comment.length === 0) ? '-' : myresp.comment) + '</div></td>' +
// Colonne 4: TVA (select)
'<td><select class="form-select tva-select" data-table="article_devis" data-column="tva" data-id="' + myresp.adid + '">' +
'<option value="10"' + (tvaValue == 10 ? ' selected' : '') + '>10%</option>' +
'<option value="15"' + (tvaValue == 15 ? ' selected' : '') + '>15%</option>' +
'<option value="20"' + (tvaValue == 20 ? ' selected' : '') + '>20%</option>' +
'</select></td>' +
// Colonne 5: Quantity (ml) (editable)
'<td><div class="editableNumber getArticlesById" style="display:inline;" data-modifier="getArticlesById" data-table="article_devis" data-column="quantite" data-id=' + myresp.adid + '>' + myresp.quantite + '</div></td>' +
// Colonne 6: Unit price with VAT (Prix unitaire TTC)
'<td>' + cur.format(prixUnitaireTTC) + '</td>' +
// Colonne 7: Unit price without VAT (Prix unitaire HT)
'<td><div class="editableNumber getArticlesById" style="display:inline;" data-modifier="getArticlesById" data-table="article_devis" data-column="unit_price" data-id="' + myresp.adid + '">' + prixUnitaireHT.toFixed(2) + '</div><span> €</span></td>' +
// Colonne 8: Total with VAT (Total TTC)
'<td>' + cur.format(totalTTC) + '</td>' +
// Colonne 9: Total without VAT (Total HT)
'<td>' + cur.format(totalHT) + '</td>' +
'</tr>'
);
'<td><div class="editableNumber getArticlesById" style="display:inline;" data-modifier="getArticlesById" data-table="article_devis" data-column="quantite" data-id=' + myresp.adid + '>' + myresp.quantite + '</div> </td>' +
'<td>' + cur.format(myresp.prix_unitaire) + '</td>' +
'<td>' + cur.format((myresp.quantite * myresp.prix_unitaire)) + '</td></tr>');
total += (myresp.quantite * myresp.prix_unitaire);
});

View File

@ -215,71 +215,10 @@ export function getGlobal(id_devis) {
})
}).done((function (res) {
var myresp = JSON.parse(response)[0];
var totaux = JSON.parse(res);
// Fonction de formatage locale
function formatMoney(amount) {
if (isNaN(amount)) return '0,00 €';
return parseFloat(amount).toFixed(2).replace('.', ',') + ' €';
}
var total = JSON.parse(res).total;
var tva = parseFloat(myresp.tva_default);
$('#totaldevis tbody').empty();
// Si on a des détails par TVA
if (totaux.details && totaux.details.length > 0) {
var totalGeneralHT = 0;
var totalGeneralTVA = 0;
var totalGeneralTTC = 0;
// Afficher une ligne par taux de TVA
totaux.details.forEach(function(detail) {
var totalHT = parseFloat(detail.totalHT);
var totalTVA = parseFloat(detail.totalTVA);
var totalTTC = parseFloat(detail.totalTTC);
var tva = parseFloat(detail.tva);
$('#totaldevis tbody').append(
'<tr>' +
'<td class="text-center">' + formatMoney(totalHT) + '</td>' +
'<td class="text-center">' + tva + ' %</td>' +
'<td class="text-center">' + formatMoney(totalTVA) + '</td>' +
'<td class="text-center">' + formatMoney(totalTTC) + '</td>' +
'</tr>'
);
totalGeneralHT += totalHT;
totalGeneralTVA += totalTVA;
totalGeneralTTC += totalTTC;
});
// Si plusieurs taux de TVA, afficher une ligne de total
if (totaux.details.length > 1) {
$('#totaldevis tbody').append(
'<tr class="fw-bold bg-light">' +
'<td class="text-center"><strong>' + formatMoney(totalGeneralHT) + '</strong></td>' +
'<td class="text-center"><strong>TOTAL</strong></td>' +
'<td class="text-center"><strong>' + formatMoney(totalGeneralTVA) + '</strong></td>' +
'<td class="text-center"><strong>' + formatMoney(totalGeneralTTC) + '</strong></td>' +
'</tr>'
);
}
} else {
// Fallback si pas de détails (ancien format)
var tva = parseFloat(myresp.tva_default);
var total = parseFloat(totaux.totalGeneral) || 0;
var montantTVA = (total * tva) / 100;
var totalTTC = total * (1 + tva / 100);
$('#totaldevis tbody').append(
'<tr>' +
'<td class="text-center">' + formatMoney(total) + '</td>' +
'<td class="text-center">' + tva + ' %</td>' +
'<td class="text-center">' + formatMoney(montantTVA) + '</td>' +
'<td class="text-center">' + formatMoney(totalTTC) + '</td>' +
'</tr>'
);
}
$('#totaldevis tbody').append('<tr><td>' + cur.format(total) + '</td><td id="tva">' + tva + ' %</td><td id="totaltva">' + cur.format(Math.round((total * tva)) / 100) + '</td><td>' + cur.format(Math.round((total * (tva + 100))) / 100) + '</td></tr>');
$('#mentions_default').html(myresp.mentions_default);
}));
})

View File

@ -146,25 +146,70 @@
<h2 class="mt-3 mb-3 text-center"> <?php p($l->t('Quote')); ?>
<div id="devisid" style="display:inline" data-table="devis" data-column="num"
data-id="<?php echo $currentDevis->id; ?>">sur le defunt <?php echo $currentDevis->nom_defunt; ?></div>
<span data-html2canvas-ignore>(</span>
<div data-html2canvas-ignore id="devisversion" style="display:inline" data-table="devis"
data-column="version" data-id="<?php echo $currentDevis->id; ?>"><?php echo $currentDevis->lieu; ?>)</div>
</h2>
<hr />
<div class="row">
<div class="col col-md">
<label class="fw-bold"><?php p($l->t('Number')); ?> : </label>
<div class="col col-xl mb-3 text-center" style="display:inline">
<?php echo $currentDevis->num; ?>
</div>
<div class="col-5 h-100 m-0" style="min-height:250px;">
<?php $res = json_decode($_['configuration'])[0]; ?>
<h5 class="p-3 m-0 text-dark text-center border border-2 border-dark"><?php p($l->t('FROM')); ?>
<?php echo $res->entreprise; ?></h5>
<p
class="p-3 m-0 h-auto text-center text-dark text-center border border-top-0 border-2 border-dark">
<?php echo $res->prenom . " " . $res->nom; ?><br />
<?php echo $res->adresse; ?><br />
<?php echo $res->mail; ?><br />
<?php echo $res->telephone; ?><br />
<span id="nothing"></span><br />
</p>
</div>
<div class="col col-md">
<label class="fw-bold"><?php p($l->t('Date')); ?> : </label>
<div class="col col-xl mb-3 text-center" style="display:inline">
<?php echo $currentDevis->date; ?>
</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>";
}
?>
</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')); ?>
<span id="entreprise"><?php echo $currentDevis->entreprise ?></span></h6>
<p
class="p-3 m-0 h-auto text-center text-dark text-center border border-top-0 border-2 border-dark">
<span id="nomprenom" data-id="0" data-table="devis"
data-column="id_client"><?php echo $currentDevis->prenom . ' ' . $currentDevis->nom ?></span><br />
<span id="adresse"><?php echo $currentDevis->adresse_cli ?></span><br />
<span id="mail"><?php echo $currentDevis->mail_cli ?></span><br />
<span id="telephone"><?php echo $currentDevis->telephone_cli ?></span><br />
<span id="legal_one"><?php echo $currentDevis->legalone_cli ?></span><br />
<span id="dateContext" style="display: none"><?php echo $facture->date ?></span>
<span id="nomcli" style="display: none"><?php echo $currentDevis->prenom . ' ' . $currentDevis->nom ?></span>
<span id="idcli" style="display: none"><?php echo $currentDevis->id_cli ?></span>
<span id="etp" style="display: none"><?php echo $currentDevis->entreprise ?></span>
<span class="pdf"
style="display: none"><?php echo $currentDevis->entreprise . "_" . $currentDevis->id . "_v" . $currentDevis->version ?></span>
</p>
</div>
</div>
<div class="row">
<div class="col col-md">
<div class="d-flex flex-column">
<span>Defunt : <b><?php echo $currentDevis->nom_defunt; ?></b></span>
<hr />
<div class="col col-xl mb-3 text-center">
<b><span><?php p($l->t('Offer valid for 1 month from')); ?> :
</span><span><?php echo (new DateTime($currentDevis->date))->format('d-m-Y'); ?></span></b></div>
<hr />
</div>
</div>
<div class="row">
<div class="col col-md">
<div class="col col-xl text-center">
<span>Date de soin :
<b><?php echo (new DateTime($currentDevis->date))->format('d-m-Y'); ?></b>,</span>&nbsp;<span
id="devisid" data-id=<?php echo $currentDevis->id; ?>>Defunt associé :
<b><?php echo $currentDevis->nom_defunt; ?></b></span><br />
<span>Lieu : <b><?php echo $currentDevis->lieu; ?> (<?php echo $currentDevis->adresse_soin; ?>)</b>
</div>
</div>
@ -200,72 +245,23 @@
<th><?php p($l->t('Reference')); ?></th>
<th><?php p($l->t('Designation')); ?></th>
<th><?php p($l->t('Comment')); ?></th>
<th><?php p($l->t('TVA'));?></th>
<th><?php p($l->t('Quantity')); ?></th>
<th><?php p($l->t('Unit price without VAT'));?></th>
<th><?php p($l->t('Total without VAT'));?></th>
<th>Total TTC</th>
<th><?php p($l->t('Unit price without VAT')); ?></th>
<th><?php p($l->t('Total without VAT')); ?></th>
</tr>
</thead>
<tbody>
<?php
// ⭐ NOUVEAU: Grouper par taux de TVA pour les calculs
$totalsByTva = [];
foreach ($currentDevis->dproduits as $key => $produit) {
// Récupérer le taux de TVA du produit (défaut 20%)
$tvaValue = isset($produit->tva) && $produit->tva !== null ? floatval($produit->tva) : 20.00;
$totalHT = $produit->prix_unitaire * $produit->quantite;
$totalTTC = $totalHT * (1 + $tvaValue / 100);
// Grouper par taux
if (!isset($totalsByTva[$tvaValue])) {
$totalsByTva[$tvaValue] = [
'totalHT' => 0,
'totalTVA' => 0,
'totalTTC' => 0
];
}
$totalsByTva[$tvaValue]['totalHT'] += $totalHT;
$totalsByTva[$tvaValue]['totalTVA'] += ($totalTTC - $totalHT);
$totalsByTva[$tvaValue]['totalTTC'] += $totalTTC;
?>
<?php foreach ($currentDevis->dproduits as $key => $produit) { ?>
<tr>
<td><?php echo $produit->reference ?></td>
<td><?php echo $produit->description ?></td>
<td><?php echo $produit->comment ?></td>
<td><?php echo number_format($tvaValue, 0) ?>%</td>
<td><?php echo $produit->quantite ?></td>
<td>&euro;<?php echo number_format($produit->prix_unitaire, 2) ?></td>
<td>&euro;<?php echo number_format($totalHT, 2) ?></td>
<td>&euro;<?php echo number_format($totalTTC, 2) ?></td>
<td>&euro;<?php echo number_format($produit->prix_unitaire * $produit->quantite, 2) ?>
</td>
</tr>
<?php }
// ⭐ NOUVEAU: Trier par taux
ksort($totalsByTva);
// ⭐ NOUVEAU: Afficher les totaux par TVA dans le tableau
if (count($totalsByTva) > 0) {
?>
<tr class="table-secondary">
<td colspan="8"><hr style="border-top: 2px solid #000; margin: 5px 0;"></td>
</tr>
<?php
foreach ($totalsByTva as $tva => $totals) {
?>
<tr class="fw-bold">
<td colspan="3">Total TVA <?php echo number_format($tva, 0) ?>%</td>
<td></td>
<td></td>
<td></td>
<td>&euro;<?php echo number_format($totals['totalHT'], 2) ?></td>
<td>&euro;<?php echo number_format($totals['totalTTC'], 2) ?></td>
</tr>
<?php
}
}
?>
<?php } ?>
</tbody>
</table>
</div>
@ -274,27 +270,28 @@
<thead class="bg-dark text-white">
<tr>
<th class="text-center"><?php p($l->t('Total without VAT')); ?></th>
<th class="text-center"><?php p($l->t('VAT Rate')); ?></th>
<th class="text-center"><?php p($l->t('Total VAT')); ?></th>
<th class="text-center"><?php p($l->t('Total Price')); ?></th>
</tr>
</thead>
<tbody>
<?php
// ⭐ NOUVEAU: Calculer les totaux généraux
$totalGeneralHT = 0;
$totalGeneralTVA = 0;
$totalGeneralTTC = 0;
foreach ($totalsByTva as $totals) {
$totalGeneralHT += $totals['totalHT'];
$totalGeneralTVA += $totals['totalTVA'];
$totalGeneralTTC += $totals['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 class="fw-bold">
<td class="text-center">&euro;<?php echo number_format($totalGeneralHT, 2) ?></td>
<td class="text-center">&euro;<?php echo number_format($totalGeneralTVA, 2) ?></td>
<td class="text-center">&euro;<?php echo number_format($totalGeneralTTC, 2) ?></td>
<tr>
<td>&euro;<?php echo number_format($totalhtc, 2) ?></td>
<td><?php echo $tva ?> &percnt;</td>
<td>&euro;<?php echo number_format($totalttc, 2) ?></td>
<td>&euro;<?php echo number_format($totalprice, 2) ?></td>
</tr>
</tbody>
</table>

View File

@ -19,12 +19,12 @@
</div>
<div class="col-2 h-100 m-0" style="min-height:250px;">
<?php
if(isset($_['logo']) && $_['logo'] !== "nothing") {
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 {
}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'));?> <span id="entreprise"></span></h5>
@ -66,7 +66,7 @@
style="display:inline"
data-table="devis" data-column="order_number"
data-id="<?php echo $_['devis'][0]->devisid;?>">
<?php echo ($_['devis'][0]->order_number == "") ? "-" : $_['devis'][0]->order_number ; ?>
<?php echo ($_['devis'][0]->order_number == "" ) ? "-" : $_['devis'][0]->order_number ; ?>
</div>
</div>
<hr />
@ -78,7 +78,7 @@
style="display:inline"
data-table="devis" data-column="case_number"
data-id="<?php echo $_['devis'][0]->devisid;?>">
<?php echo ($_['devis'][0]->case_number == "") ? "-" : $_['devis'][0]->case_number ; ?>
<?php echo ($_['devis'][0]->case_number == "" ) ? "-" : $_['devis'][0]->case_number ; ?>
</div>
</div>
<hr />
@ -95,11 +95,8 @@
<th><?php p($l->t('Reference'));?></th>
<th><?php p($l->t('Designation'));?></th>
<th><?php p($l->t('Comment'));?></th>
<th><?php p($l->t('TVA'));?></th>
<th><?php p($l->t('Quantity'));?></th>
<th>PU TTC</th>
<th><?php p($l->t('Unit price without VAT'));?></th>
<th>Total TTC</th>
<th><?php p($l->t('Total without VAT'));?></th>
</tr>
</thead>
@ -119,11 +116,8 @@
<th><?php p($l->t('Reference'));?></th>
<th><?php p($l->t('Designation'));?></th>
<th><?php p($l->t('Comment'));?></th>
<th><?php p($l->t('TVA'));?></th>
<th><?php p($l->t('Quantity'));?>(ml)</th>
<th>PU TTC</th>
<th><?php p($l->t('Unit price without VAT'));?></th>
<th>Total TTC</th>
<th><?php p($l->t('Total without VAT'));?></th>
</tr>
</thead>