Compare commits

...

2 Commits

Author SHA1 Message Date
89e571d4bf fix modify PU HT in devis 2025-10-24 08:10:26 +03:00
666c54dc83 feat multi tva 2025-10-23 00:14:07 +03:00
34 changed files with 470 additions and 177 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,6 +332,8 @@ 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,6 +331,8 @@ 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,9 +2657,7 @@ class PageController extends Controller
public function getTotalDevis($numdevis)
{
$total = $this->myDb->getTotalDevis($numdevis, $this->idNextcloud);
$res = array();
$res['total'] = $total;
return json_encode($res);
return json_encode($total);
}
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", "legal_two", "path", "tva_default", "coefficient_ik",
"quantite", "date_paiement", "type_paiement", "id_devis", "reference", "description", "prix_unitaire", "tva", "unit_price", "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,14 +79,50 @@ class Bdd
{
$articles = json_decode($this->getListArticle($iddevis, $idNextcloud));
$produits = json_decode($this->getListProduit($iddevis, $idNextcloud));
$total = 0;
// Grouper les totaux par taux de TVA
$totalsByTva = array();
// Traiter les articles
foreach($articles as $key => $article) {
$total += $article->prix_unitaire * $article->quantite;
$tva = floatval($article->tva ?? 20.00);
$totalHT = floatval($article->prix_unitaire) * floatval($article->quantite);
if (!isset($totalsByTva[$tva])) {
$totalsByTva[$tva] = 0;
}
$totalsByTva[$tva] += $totalHT;
}
// Traiter les produits
foreach($produits as $key => $produit) {
$total += $produit->prix_unitaire * $produit->quantite;
$tva = floatval($produit->tva ?? 20.00);
$totalHT = floatval($produit->prix_unitaire) * floatval($produit->quantite);
if (!isset($totalsByTva[$tva])) {
$totalsByTva[$tva] = 0;
}
return $total;
$totalsByTva[$tva] += $totalHT;
}
// 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)
);
}
public function getClients($idNextcloud)
@ -890,7 +926,9 @@ class Bdd
$sql = "SELECT ".
$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."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 = ?";
@ -902,7 +940,7 @@ class Bdd
$client = $this->getClientById($clientId);
foreach($produits as &$produit) {
$productPrice = $this->getProductPriceByClientGroupId($client['fk_client_group_id'], $produit['pid']);
$produit['prix_unitaire'] = $productPrice ?? $produit['prix_unitaire'];
$produit['prix_unitaire'] = $produit['prix_unitaire'] ?? $productPrice;
}
}
return json_encode($produits);
@ -910,7 +948,13 @@ 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, 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 = ?";
$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 = ?";
return $this->execSQL($sql, array($numdevis));
}
@ -3075,7 +3119,8 @@ class Bdd
produit_devis.quantite,
produit_devis.discount,
produit_devis.devis_id,
produit.prix_unitaire as produit_price,
produit_devis.tva,
produit_devis.unit_price as produit_price,
produit.reference as produit_reference,
produit.description as produit_description,
produit.fk_product_type_id as fk_product_type_id,
@ -3096,7 +3141,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'] = $productPrice ?? $produit['produit_price'];
$produit['produit_price'] = $produit['produit_price'] ?? $productPrice;
}
}

View File

@ -74,9 +74,26 @@ 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);
$devis_temp['montant_htc'] += $htPrice * $produit->quantite;
$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;
// Collecter les références comme dans les factures
if (isset($produit->reference) && !empty($produit->reference)) {
@ -89,9 +106,6 @@ class DevisDataProcessor
$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;
}

View File

@ -27,18 +27,17 @@ 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/";
function Header()
public function Header()
{
if ($this->logo != "nothing") {
$this->Image($this->logoPath . "logo.png", 4, 2, 36, 37);
@ -46,7 +45,7 @@ class DevisPdfHandler extends FPDF
$this->Cell(55, 30, '');
}
}
function Footer()
public function Footer()
{
$this->SetY(-40);
$this->SetFont('Arial', '', 7);
@ -118,7 +117,6 @@ 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');
@ -130,56 +128,109 @@ class DevisPdfHandler extends FPDF
$this->Cell(20, 8, "Prix Uni. HT", 0, 0, 'C');
$this->SetXY(155, 106);
$this->Cell(20, 8, 'TVA ' . $tvaValue . '%', 0, 0, 'C');
$this->Cell(20, 8, 'TVA', 0, 0, 'C');
$this->SetXY(175, 106);
$this->Cell(25, 8, "Prix Uni. TTC", 0, 0, 'C');
}
public function DrawArticlesTableValueAndReturnTotalPrice()
{
$this->SetFont('Arial', '', 10);
$tvaValue = $this->devisData["configuration"]->tva_default;
$totalHt = 0;
$totalTtc = 0;
$totalTva = 0;
// Grouper les totaux par taux de TVA
$totalsByTva = [];
$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);
$totalHt += $valueHt;
$totalTtc += $valueTtc;
$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
$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, number_format($tvaAmount, 2, '.', '') . chr(128), 0, 0, 'C');
$this->Cell(20, 6, $tvaValue . '%', 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 [
"TOTAL HT" => $totalHt,
"TVA " . $tvaValue . "%" => $totalTva,
"TOTAL TTC" => $totalTtc
'totalHT' => $totalGeneralHT,
'totalTVA' => $totalGeneralTVA,
'totalTTC' => $totalGeneralTTC
];
}
@ -200,17 +251,27 @@ class DevisPdfHandler extends FPDF
$ibanCursorX = $this->GetX();
$this->Cell($ibanWidth, 7, 'Code SWIFT : AGRI FR PP 836', 1, 1, 'C');
//TABLE HT
//TABLE DES TOTAUX GÉNÉRAUX UNIQUEMENT (3 lignes)
$ibanLastPositionX = $ibanCursorX + $ibanWidth + 20;
$startOfArrayX = $ibanLastPositionX;
$startOfArrayY = $ibanCursorY;
foreach ($totalPriceArray as $label => $price) {
// Afficher les 3 totaux généraux
$this->SetFont('Arial', 'B', 11);
$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');
$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');
}
public function SetMultipleDevisContent()
@ -233,7 +294,7 @@ class DevisPdfHandler extends FPDF
$this->DrawBankAndTotalPriceInfo($totalPriceValue);
}
function MultiAlignCell($w, $h, $text, $border = 0, $ln = 0, $align = 'L', $fill = false)
public function MultiAlignCell($w, $h, $text, $border = 0, $ln = 0, $align = 'L', $fill = false)
{
// Store reset values for (x,y) positions
$x = $this->GetX() + $w;
@ -248,19 +309,22 @@ class DevisPdfHandler extends FPDF
}
}
function NbLines($w, $txt)
public 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;
@ -276,22 +340,26 @@ 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

@ -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} from "./modules/ajaxRequest.mjs";
import { getArticlesById, getMailServerFrom, getProduitsById, savePdfToNextcloud, exportDevisToPdf, updateDB} from "./modules/ajaxRequest.mjs";
import { getGlobal, globalConfiguration } from "./modules/mainFunction.mjs";
import "./listener/main_listener";
import { Client } from "./objects/client.mjs";
@ -34,4 +34,23 @@ 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,12 +376,49 @@ export function getProduitsByIdDevis(id_devis) {
}
$.each(JSON.parse(response), function (arrayID, myresp) {
$('#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>' +
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
'<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>' +
'<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>');
// 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>');
total += (myresp.quantite * myresp.prix_unitaire);
});
@ -414,12 +451,50 @@ export function getArticlesById() {
}
$.each(JSON.parse(response), function (arrayID, myresp) {
$('#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>' +
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
'<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>' +
'<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>');
// 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>'
);
total += (myresp.quantite * myresp.prix_unitaire);
});

View File

@ -215,10 +215,71 @@ export function getGlobal(id_devis) {
})
}).done((function (res) {
var myresp = JSON.parse(response)[0];
var total = JSON.parse(res).total;
var tva = parseFloat(myresp.tva_default);
var totaux = JSON.parse(res);
// Fonction de formatage locale
function formatMoney(amount) {
if (isNaN(amount)) return '0,00 €';
return parseFloat(amount).toFixed(2).replace('.', ',') + ' €';
}
$('#totaldevis tbody').empty();
$('#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>');
// 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>'
);
}
$('#mentions_default').html(myresp.mentions_default);
}));
})

View File

@ -146,70 +146,25 @@
<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-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 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-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 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>
</div>
<div class="row">
<div class="col col-md">
<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 />
<div class="d-flex flex-column">
<span>Defunt : <b><?php echo $currentDevis->nom_defunt; ?></b></span>
<span>Lieu : <b><?php echo $currentDevis->lieu; ?> (<?php echo $currentDevis->adresse_soin; ?>)</b>
</div>
</div>
@ -245,23 +200,72 @@
<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><?php p($l->t('Unit price without VAT'));?></th>
<th><?php p($l->t('Total without VAT'));?></th>
<th>Total TTC</th>
</tr>
</thead>
<tbody>
<?php foreach ($currentDevis->dproduits as $key => $produit) { ?>
<?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;
?>
<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($produit->prix_unitaire * $produit->quantite, 2) ?>
</td>
<td>&euro;<?php echo number_format($totalHT, 2) ?></td>
<td>&euro;<?php echo number_format($totalTTC, 2) ?></td>
</tr>
<?php } ?>
<?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
}
}
?>
</tbody>
</table>
</div>
@ -270,28 +274,27 @@
<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
$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);
// ⭐ 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'];
}
$totalttc = ($totalhtc * $tva) / 100;
$totalprice = $totalhtc + $totalttc;
?>
<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 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>
</tbody>
</table>

View File

@ -19,9 +19,9 @@
</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>";
}
?>
@ -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,8 +95,11 @@
<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>
@ -116,8 +119,11 @@
<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>