From c89ff96964da1ad55412d4e5e7560e84a9fc096a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur <eldy@users.sourceforge.net> Date: Sun, 16 Dec 2007 19:07:41 +0000 Subject: [PATCH] =?UTF-8?q?Fix:=20Les=20mod=E8le=20PDF=20ne=20g=E9raient?= =?UTF-8?q?=20pas=20l'affichage=20de=20l'avoir=20appliqu=E9=20sur=20une=20?= =?UTF-8?q?facture.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- htdocs/admin/facture.php | 10 +- htdocs/compta/facture.php | 2 +- .../menus/barre_left/eldy_backoffice.php | 2 +- .../modules/facture/pdf_crabe.modules.php | 70 +- .../modules/facture/pdf_huitre.modules.php | 10 +- .../modules/facture/pdf_oursin.modules.php | 779 +++++++++--------- htdocs/langs/en_US/bills.lang | 2 +- htdocs/langs/fr_FR/bills.lang | 2 +- htdocs/lib/functions.inc.php | 26 +- 9 files changed, 497 insertions(+), 406 deletions(-) diff --git a/htdocs/admin/facture.php b/htdocs/admin/facture.php index 2bc054eb3bb..78bb196797e 100644 --- a/htdocs/admin/facture.php +++ b/htdocs/admin/facture.php @@ -389,10 +389,12 @@ while (($file = readdir($handle))!==false) $htmltooltip.='<br><b>'.$langs->trans("Type").'</b>: '.($module->type?$module->type:$langs->trans("Unknown")); $htmltooltip.='<br><b>'.$langs->trans("Height").'/'.$langs->trans("Width").'</b>: '.$module->page_hauteur.'/'.$module->page_largeur; $htmltooltip.='<br><br>'.$langs->trans("FeaturesSupported").':'; - $htmltooltip.='<br><b>'.$langs->trans("Logo").'</b>: '.yn($module->option_logo); - $htmltooltip.='<br><b>'.$langs->trans("PaymentMode").'</b>: '.yn($module->option_modereg); - $htmltooltip.='<br><b>'.$langs->trans("PaymentConditions").'</b>: '.yn($module->option_condreg); - $htmltooltip.='<br><b>'.$langs->trans("MultiLanguage").'</b>: '.yn($module->option_multilang); + $htmltooltip.='<br><b>'.$langs->trans("Logo").'</b>: '.yn($module->option_logo,1,1); + $htmltooltip.='<br><b>'.$langs->trans("PaymentMode").'</b>: '.yn($module->option_modereg,1,1); + $htmltooltip.='<br><b>'.$langs->trans("PaymentConditions").'</b>: '.yn($module->option_condreg,1,1); + $htmltooltip.='<br><b>'.$langs->trans("Escompte").'</b>: '.yn($module->option_escompte,1,1); + $htmltooltip.='<br><b>'.$langs->trans("CreditNote").'</b>: '.yn($module->option_credit_note,1,1); + $htmltooltip.='<br><b>'.$langs->trans("MultiLanguage").'</b>: '.yn($module->option_multilang,1,1); print '<td align="center">'; print $html->textwithhelp('',$htmltooltip,1,0); print '</td>'; diff --git a/htdocs/compta/facture.php b/htdocs/compta/facture.php index 81a342800ef..989a7be2e3a 100644 --- a/htdocs/compta/facture.php +++ b/htdocs/compta/facture.php @@ -2094,8 +2094,8 @@ else while ($i < $num) { $obj = $db->fetch_object($resql); - print '<tr><td colspan="2" align="right">'.$langs->trans("CreditNote").' '; $invoice->fetch($obj->fk_facture_source); + print '<tr><td colspan="2" align="right">'.$langs->trans("CreditNote").' '; print $invoice->getNomUrl(0); print ' :</td>'; print '<td align="right" style="border: 1px solid;">'.price($obj->amount_ttc).'</td>'; diff --git a/htdocs/includes/menus/barre_left/eldy_backoffice.php b/htdocs/includes/menus/barre_left/eldy_backoffice.php index 7904a4d6b2d..b38b1eab0be 100644 --- a/htdocs/includes/menus/barre_left/eldy_backoffice.php +++ b/htdocs/includes/menus/barre_left/eldy_backoffice.php @@ -134,7 +134,7 @@ class MenuLeft { if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/perms.php", $langs->trans("Security")); if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/mails.php", $langs->trans("EMails")); - if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/limits.php", $langs->trans("Limits")); + if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/limits.php", $langs->trans("MenuLimits")); if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/dict.php", $langs->trans("DictionnarySetup")); if ($leftmenu=="setup") $newmenu->add_submenu(DOL_URL_ROOT."/admin/const.php", $langs->trans("OtherSetup")); diff --git a/htdocs/includes/modules/facture/pdf_crabe.modules.php b/htdocs/includes/modules/facture/pdf_crabe.modules.php index d899b25de08..5da5679de22 100644 --- a/htdocs/includes/modules/facture/pdf_crabe.modules.php +++ b/htdocs/includes/modules/facture/pdf_crabe.modules.php @@ -126,7 +126,8 @@ class pdf_crabe extends ModelePDFFactures } $deja_regle = $fac->getSommePaiement(); - + $amount_credit_not_included = $fac->getSommeCreditNote(); + // D�finition de $dir et $file if ($fac->specimen) { @@ -366,7 +367,8 @@ class pdf_crabe extends ModelePDFFactures $posy=$this->_tableau_tot($pdf, $fac, $deja_regle, $bottomlasttab, $outputlangs); // Affiche zone versements - if ($deja_regle) { + if ($deja_regle || $amount_credit_not_included) + { $posy=$this->_tableau_versements($pdf, $fac, $posy, $outputlangs); } @@ -400,10 +402,13 @@ class pdf_crabe extends ModelePDFFactures } - /* - * \brief Affiche tableau des versement - * \param pdf objet PDF - * \param fac objet facture + /** + * \brief Affiche tableau des versement + * \param pdf Objet PDF + * \param fac Objet facture + * \param posy Position y in PDF + * \param outputlangs Object langs for output + * \return int <0 if KO, >0 if OK */ function _tableau_versements(&$pdf, $fac, $posy, $outputlangs) { @@ -427,23 +432,66 @@ class pdf_crabe extends ModelePDFFactures $pdf->SetXY ($tab3_posx+60, $tab3_top-1 ); $pdf->MultiCell(20, 4, $outputlangs->transnoentities("Num"), 0, 'L', 0); + $y=0; + + // Loop on each credit note included + $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,"; + $sql.= " re.description, re.fk_facture_source, re.fk_facture_source"; + $sql.= " FROM ".MAIN_DB_PREFIX ."societe_remise_except as re"; + $sql.= " WHERE fk_facture = ".$fac->id; + $resql=$this->db->query($sql); + if ($resql) + { + $num = $this->db->num_rows($resql); + $i=0; + $invoice=new Facture($this->db); + while ($i < $num) + { + $y+=3; + $obj = $this->db->fetch_object($resql); + + $invoice->fetch($obj->fk_facture_source); + + $pdf->SetXY ($tab3_posx, $tab3_top+$y ); + $pdf->MultiCell(20, 4,'', 0, 'L', 0); + $pdf->SetXY ($tab3_posx+21, $tab3_top+$y); + $pdf->MultiCell(20, 4, price($obj->amount_ttc), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+41, $tab3_top+$y); + $pdf->MultiCell(20, 4, $outputlangs->trans("CreditNote"), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+60, $tab3_top+$y); + $pdf->MultiCell(20, 4, $invoice->ref, 0, 'L', 0); + + $pdf->line($tab3_posx, $tab3_top+$y+3, $tab3_posx+$tab3_width, $tab3_top+$y+3 ); + + $i++; + } + } + else + { + $this->error=$outputlangs->trans("ErrorSQL")." sql=".$sql; + dolibarr_syslog($this->db,$this->error); + return -1; + } + + // Loop on each payment $sql = "SELECT ".$this->db->pdate("p.datep")."as date, pf.amount as amount, p.fk_paiement as type, p.num_paiement as num "; $sql.= "FROM ".MAIN_DB_PREFIX."paiement as p, ".MAIN_DB_PREFIX."paiement_facture as pf "; $sql.= "WHERE pf.fk_paiement = p.rowid and pf.fk_facture = ".$fac->id." "; $sql.= "ORDER BY p.datep"; - if ($this->db->query($sql)) + $resql=$this->db->query($sql); + if ($resql) { $pdf->SetFont('Arial','',6); - $num = $this->db->num_rows(); - $i=0; $y=0; + $num = $this->db->num_rows($resql); + $i=0; while ($i < $num) { $y+=3; - $row = $this->db->fetch_row(); + $row = $this->db->fetch_row($resql); $pdf->SetXY ($tab3_posx, $tab3_top+$y ); $pdf->MultiCell(20, 4, dolibarr_print_date($row[0],'day'), 0, 'L', 0); $pdf->SetXY ($tab3_posx+21, $tab3_top+$y); - $pdf->MultiCell(20, 4, $row[1], 0, 'L', 0); + $pdf->MultiCell(20, 4, price($row[1]), 0, 'L', 0); $pdf->SetXY ($tab3_posx+41, $tab3_top+$y); switch ($row[2]) { diff --git a/htdocs/includes/modules/facture/pdf_huitre.modules.php b/htdocs/includes/modules/facture/pdf_huitre.modules.php index 263be0f4642..cc7f2370a8b 100644 --- a/htdocs/includes/modules/facture/pdf_huitre.modules.php +++ b/htdocs/includes/modules/facture/pdf_huitre.modules.php @@ -18,7 +18,6 @@ * or see http://www.gnu.org/ * * $Id$ - * $Source$ */ /** @@ -64,7 +63,14 @@ class pdf_huitre extends ModelePDFFactures $this->page_hauteur = 297; $this->format = array($this->page_largeur,$this->page_hauteur); - $this->option_logo = 1; // Affiche logo + $this->option_logo = 1; // Affiche logo FAC_PDF_LOGO + $this->option_tva = 0; // Gere option tva FACTURE_TVAOPTION + $this->option_modereg = 0; // Gere choix mode r�glement FACTURE_CHQ_NUMBER, FACTURE_RIB_NUMBER + $this->option_condreg = 1; // Affiche conditions r�glement + $this->option_codeproduitservice = 0; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 0; // G�re les avoirs // Recupere emmetteur $this->emetteur=$mysoc; diff --git a/htdocs/includes/modules/facture/pdf_oursin.modules.php b/htdocs/includes/modules/facture/pdf_oursin.modules.php index 3256a65b246..8d294667c9f 100644 --- a/htdocs/includes/modules/facture/pdf_oursin.modules.php +++ b/htdocs/includes/modules/facture/pdf_oursin.modules.php @@ -67,8 +67,13 @@ class pdf_oursin extends ModelePDFFactures $this->option_logo = 1; // Affiche logo FAC_PDF_LOGO $this->option_tva = 1; // Gere option tva FACTURE_TVAOPTION $this->option_modereg = 1; // Gere choix mode r�glement FACTURE_CHQ_NUMBER, FACTURE_RIB_NUMBER - $this->option_codeproduitservice = 1; // Affiche code produit-service FACTURE_CODEPRODUITSERVICE - if (defined("FACTURE_TVAOPTION") && FACTURE_TVAOPTION == 'franchise') + $this->option_condreg = 1; // Affiche conditions r�glement + $this->option_codeproduitservice = 1; // Affiche code produit-service + $this->option_multilang = 1; // Dispo en plusieurs langues + $this->option_escompte = 0; // Affiche si il y a eu escompte + $this->option_credit_note = 1; // G�re les avoirs + + if (defined("FACTURE_TVAOPTION") && FACTURE_TVAOPTION == 'franchise') $this->franchise=1; // Recupere emmetteur @@ -77,405 +82,425 @@ class pdf_oursin extends ModelePDFFactures } - /** - * \brief Fonction g�n�rant la facture sur le disque - * \param facid id de la facture � g�n�rer - * \return int 1=ok, 0=ko - * \remarks Variables utilis�es - * \remarks MAIN_INFO_SOCIETE_NOM - * \remarks MAIN_INFO_SOCIETE_ADRESSE - * \remarks MAIN_INFO_SOCIETE_CP - * \remarks MAIN_INFO_SOCIETE_VILLE - * \remarks MAIN_INFO_SOCIETE_TEL - * \remarks MAIN_INFO_SOCIETE_FAX - * \remarks MAIN_INFO_SOCIETE_WEB - * \remarks MAIN_INFO_SOCIETE_LOGO - * \remarks MAIN_INFO_SIRET - * \remarks MAIN_INFO_SIREN - * \remarks MAIN_INFO_RCS - * \remarks MAIN_INFO_CAPITAL - * \remarks MAIN_INFO_TVAINTRA - */ - function write_file($fac,$outputlangs='') - { - global $user,$langs,$conf; - - if (! is_object($outputlangs)) $outputlangs=$langs; - $outputlangs->load("main"); - $outputlangs->load("companies"); - $outputlangs->load("bills"); - $outputlangs->load("products"); - - $outputlangs->setPhpLang(); - - $langs->load("main"); - $langs->load("bills"); - $langs->load("products"); - - if ($conf->facture->dir_output) - { - // D�finition de l'objet $fac (pour compatibilite ascendante) - if (! is_object($fac)) - { - $id = $fac; - $fac = new Facture($this->db,"",$id); - $ret=$fac->fetch($id); - } - - // D�finition de $dir et $file - if ($fac->specimen) - { - $dir = $conf->facture->dir_output; - $file = $dir . "/SPECIMEN.pdf"; - } - else - { - $facref = sanitize_string($fac->ref); - $dir = $conf->facture->dir_output . "/" . $facref; - $file = $dir . "/" . $facref . ".pdf"; - } - - if (! file_exists($dir)) - { - if (create_exdir($dir) < 0) - { - $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); - $langs->setPhpLang(); // On restaure langue session - return 0; - } - } + /** + * \brief Fonction g�n�rant la facture sur le disque + * \param fac Objet facture � g�n�rer (ou id si ancienne methode) + * \return int 1=ok, 0=ko + */ + function write_file($fac,$outputlangs='') + { + global $user,$langs,$conf; + + if (! is_object($outputlangs)) $outputlangs=$langs; + $outputlangs->load("main"); + $outputlangs->load("companies"); + $outputlangs->load("bills"); + $outputlangs->load("products"); + + $outputlangs->setPhpLang(); + + if ($conf->facture->dir_output) + { + // D�finition de l'objet $fac (pour compatibilite ascendante) + if (! is_object($fac)) + { + $id = $fac; + $fac = new Facture($this->db,"",$id); + $ret=$fac->fetch($id); + } + + $deja_regle = $fac->getSommePaiement(); + $amount_credit_not_included = $fac->getSommeCreditNote(); + + + // D�finition de $dir et $file + if ($fac->specimen) + { + $dir = $conf->facture->dir_output; + $file = $dir . "/SPECIMEN.pdf"; + } + else + { + $facref = sanitize_string($fac->ref); + $dir = $conf->facture->dir_output . "/" . $facref; + $file = $dir . "/" . $facref . ".pdf"; + } + + if (! file_exists($dir)) + { + if (create_exdir($dir) < 0) + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + $langs->setPhpLang(); // On restaure langue session + return 0; + } + } - if (file_exists($dir)) - { - - // Protection et encryption du pdf + if (file_exists($dir)) + { + + // Protection et encryption du pdf if ($conf->global->PDF_SECURITY_ENCRYPTION) { $pdf=new FPDI_Protection('P','mm',$this->format); - $pdfrights = array('print'); // Ne permet que l'impression du document - $pdfuserpass = ''; // Mot de passe pour l'utilisateur final - $pdfownerpass = NULL; // Mot de passe du propri�taire, cr�� al�atoirement si pas d�fini - $pdf->SetProtection($pdfrights,$pdfuserpass,$pdfownerpass); - } - else - { - $pdf=new FPDI('P','mm',$this->format); + $pdfrights = array('print'); // Ne permet que l'impression du document + $pdfuserpass = ''; // Mot de passe pour l'utilisateur final + $pdfownerpass = NULL; // Mot de passe du propri�taire, cr�� al�atoirement si pas d�fini + $pdf->SetProtection($pdfrights,$pdfuserpass,$pdfownerpass); + } + else + { + $pdf=new FPDI('P','mm',$this->format); } - $pdf->Open(); - $pdf->AddPage(); + $pdf->Open(); + $pdf->AddPage(); - $this->_pagehead($pdf, $fac); + $this->_pagehead($pdf, $fac); - $pdf->SetTitle($fac->ref); - $pdf->SetSubject($langs->transnoentities("Invoice")); - $pdf->SetCreator("Dolibarr ".DOL_VERSION); - $pdf->SetAuthor($user->fullname); + $pdf->SetTitle($fac->ref); + $pdf->SetSubject($outputlangs->transnoentities("Invoice")); + $pdf->SetCreator("Dolibarr ".DOL_VERSION); + $pdf->SetAuthor($user->fullname); - $pdf->SetMargins(10, 10, 10); - $pdf->SetAutoPageBreak(1,0); + $pdf->SetMargins(10, 10, 10); + $pdf->SetAutoPageBreak(1,0); - $tab_top = $this->marges['h']+90; - $tab_height = 110; + $tab_top = $this->marges['h']+90; + $tab_height = 110; - $pdf->SetFillColor(220,220,220); - $pdf->SetFont('Arial','', 9); - $pdf->SetXY ($this->marges['g'], $tab_top + $this->marges['g'] ); + $pdf->SetFillColor(220,220,220); + $pdf->SetFont('Arial','', 9); + $pdf->SetXY ($this->marges['g'], $tab_top + $this->marges['g'] ); - $iniY = $pdf->GetY(); - $curY = $pdf->GetY(); - $nexY = $pdf->GetY(); - $nblignes = sizeof($fac->lignes); + $iniY = $pdf->GetY(); + $curY = $pdf->GetY(); + $nexY = $pdf->GetY(); + $nblignes = sizeof($fac->lignes); - // Boucle sur les lignes de factures - for ($i = 0 ; $i < $nblignes ; $i++) - { - $curY = $nexY; + // Boucle sur les lignes de factures + for ($i = 0 ; $i < $nblignes ; $i++) + { + $curY = $nexY; + + // Description produit + $codeproduitservice=""; + $pdf->SetXY ($this->marges['g']+ 1, $curY ); + if (defined("FACTURE_CODEPRODUITSERVICE") && FACTURE_CODEPRODUITSERVICE) { + // Affiche code produit si ligne associ�e � un code produit + + $prodser = new Product($this->db); + + $prodser->fetch($fac->lignes[$i]->produit_id); + if ($prodser->ref) { + $codeproduitservice=" - ".$outputlangs->transnoentities("ProductCode")." ".$prodser->ref; + } + } + if ($fac->lignes[$i]->date_start && $fac->lignes[$i]->date_end) { + // Affichage dur�e si il y en a une + $codeproduitservice.=" (".$outputlangs->transnoentities("From")." ".dolibarr_print_date($fac->lignes[$i]->date_start)." ".$langs->transnoentities("to")." ".dolibarr_print_date($fac->lignes[$i]->date_end).")"; + } + $pdf->MultiCell(108, 5, $fac->lignes[$i]->desc."$codeproduitservice", 0, 'J'); + + $nexY = $pdf->GetY(); + + // TVA + if ($this->franchise!=1) + { + $pdf->SetXY ($this->marges['g']+119, $curY); + $pdf->MultiCell(10, 5, $fac->lignes[$i]->tva_tx, 0, 'C'); + } + // Prix unitaire HT avant remise + $pdf->SetXY ($this->marges['g']+132, $curY); + $pdf->MultiCell(16, 5, price($fac->lignes[$i]->subprice), 0, 'R', 0); + + // Quantit + $pdf->SetXY ($this->marges['g']+150, $curY); + $pdf->MultiCell(10, 5, $fac->lignes[$i]->qty, 0, 'R'); + + // Remise sur ligne + $pdf->SetXY ($this->marges['g']+160, $curY); + if ($fac->lignes[$i]->remise_percent) { + $pdf->MultiCell(14, 5, $fac->lignes[$i]->remise_percent."%", 0, 'R'); + } + + // Total HT + $pdf->SetXY ($this->marges['g']+168, $curY); + $total = price($fac->lignes[$i]->total_ht); + $pdf->MultiCell(21, 5, $total, 0, 'R', 0); + + + if ($nexY > 200 && $i < $nblignes - 1) + { + $this->_tableau($pdf, $tab_top, $tab_height, $nexY, $fac); + $pdf->AddPage(); + $nexY = $iniY; + $this->_pagehead($pdf, $fac); + $pdf->SetTextColor(0,0,0); + $pdf->SetFont('Arial','', 10); + } - // Description produit - $codeproduitservice=""; - $pdf->SetXY ($this->marges['g']+ 1, $curY ); - if (defined("FACTURE_CODEPRODUITSERVICE") && FACTURE_CODEPRODUITSERVICE) { - // Affiche code produit si ligne associ�e � un code produit + } + $posy=$this->_tableau($pdf, $tab_top, $tab_height, $nexY, $fac); - $prodser = new Product($this->db); + $posy=$this->_tableau_tot($pdf, $fac, $deja_regle); - $prodser->fetch($fac->lignes[$i]->produit_id); - if ($prodser->ref) { - $codeproduitservice=" - ".$langs->transnoentities("ProductCode")." ".$prodser->ref; - } - } - if ($fac->lignes[$i]->date_start && $fac->lignes[$i]->date_end) { - // Affichage dur�e si il y en a une - $codeproduitservice.=" (".$langs->transnoentities("From")." ".dolibarr_print_date($fac->lignes[$i]->date_start)." ".$langs->transnoentities("to")." ".dolibarr_print_date($fac->lignes[$i]->date_end).")"; + // Affiche zone versements + if ($deja_regle || $amount_credit_not_included) + { + $posy=$this->_tableau_versements($pdf, $fac, $posy, $outputlangs); + } + + // Mode de r�glement + if ((! defined("FACTURE_CHQ_NUMBER") || ! FACTURE_CHQ_NUMBER) && (! defined("FACTURE_RIB_NUMBER") || ! FACTURE_RIB_NUMBER)) { + $pdf->SetXY ($this->marges['g'], 228); + $pdf->SetTextColor(200,0,0); + $pdf->SetFont('Arial','B',8); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities("ErrorNoPaiementModeConfigured"),0,'L',0); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities("ErrorCreateBankAccount"),0,'L',0); + $pdf->SetTextColor(0,0,0); + } + + // Propose mode r�glement par CHQ + if (defined("FACTURE_CHQ_NUMBER")) + { + if (FACTURE_CHQ_NUMBER > 0) + { + $account = new Account($this->db); + $account->fetch(FACTURE_CHQ_NUMBER); + + $pdf->SetXY ($this->marges['g'], 225); + $pdf->SetFont('Arial','B',8); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo').' '.$account->proprio.' '.$langs->transnoentities('SendTo').':',0,'L',0); + $pdf->SetXY ($this->marges['g'], 230); + $pdf->SetFont('Arial','',8); + $pdf->MultiCell(80, 3, $account->adresse_proprio, 0, 'L', 0); + } + } + + // Propose mode r�glement par RIB + if (defined("FACTURE_RIB_NUMBER")) + { + if (FACTURE_RIB_NUMBER > 0) + { + $account = new Account($this->db); + $account->fetch(FACTURE_RIB_NUMBER); + + $cury=240; + $pdf->SetXY ($this->marges['g'], $cury); + $pdf->SetFont('Arial','B',8); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities('PaymentByTransferOnThisBankAccount').':', 0, 'L', 0); + $cury=245; + $pdf->SetFont('Arial','B',6); + $pdf->line($this->marges['g'], $cury, $this->marges['g'], $cury+10 ); + $pdf->SetXY ($this->marges['g'], $cury); + $pdf->MultiCell(18, 3, $outputlangs->transnoentities("BankCode"), 0, 'C', 0); + $pdf->line($this->marges['g']+18, $cury, $this->marges['g']+18, $cury+10 ); + $pdf->SetXY ($this->marges['g']+18, $cury); + $pdf->MultiCell(18, 3, $outputlangs->transnoentities("DeskCode"), 0, 'C', 0); + $pdf->line($this->marges['g']+36, $cury, $this->marges['g']+36, $cury+10 ); + $pdf->SetXY ($this->marges['g']+36, $cury); + $pdf->MultiCell(24, 3, $outputlangs->transnoentities("BankAccountNumber"), 0, 'C', 0); + $pdf->line($this->marges['g']+60, $cury, $this->marges['g']+60, $cury+10 ); + $pdf->SetXY ($this->marges['g']+60, $cury); + $pdf->MultiCell(13, 3, $outputlangs->transnoentities("BankAccountNumberKey"), 0, 'C', 0); + $pdf->line($this->marges['g']+73, $cury, $this->marges['g']+73, $cury+10 ); + + $pdf->SetFont('Arial','',8); + $pdf->SetXY ($this->marges['g'], $cury+5); + $pdf->MultiCell(18, 3, $account->code_banque, 0, 'C', 0); + $pdf->SetXY ($this->marges['g']+18, $cury+5); + $pdf->MultiCell(18, 3, $account->code_guichet, 0, 'C', 0); + $pdf->SetXY ($this->marges['g']+36, $cury+5); + $pdf->MultiCell(24, 3, $account->number, 0, 'C', 0); + $pdf->SetXY ($this->marges['g']+60, $cury+5); + $pdf->MultiCell(13, 3, $account->cle_rib, 0, 'C', 0); + + $pdf->SetXY ($this->marges['g'], $cury+15); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities("Residence").' : ' . $account->domiciliation, 0, 'L', 0); + $pdf->SetXY ($this->marges['g'], $cury+25); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities("IbanPrefix").' : ' . $account->iban_prefix, 0, 'L', 0); + $pdf->SetXY ($this->marges['g'], $cury+30); + $pdf->MultiCell(90, 3, $outputlangs->transnoentities("BIC").' : ' . $account->bic, 0, 'L', 0); + } + } + + // Conditions de r�glements + if ($fac->cond_reglement_code) + { + $pdf->SetFont('Arial','B',10); + $pdf->SetXY($this->marges['g'], 217); + $titre = $outputlangs->transnoentities("PaymentConditions").':'; + $pdf->MultiCell(80, 5, $titre, 0, 'L'); + $pdf->SetFont('Arial','',10); + $pdf->SetXY($this->marges['g']+44, 217); + $lib_condition_paiement=$outputlangs->transnoentities("PaymentCondition".$fac->cond_reglement_code)!=('PaymentCondition'.$fac->cond_reglement_code)?$outputlangs->transnoentities("PaymentCondition".$fac->cond_reglement_code):$fac->cond_reglement; + $pdf->MultiCell(80, 5, $lib_condition_paiement,0,'L'); + } + + // Pied de page + $this->_pagefoot($pdf, $fac); + $pdf->AliasNbPages(); + + $pdf->Close(); + + $pdf->Output($file); + + $langs->setPhpLang(); // On restaure langue session + return 1; // Pas d'erreur + } + else + { + $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); + $langs->setPhpLang(); // On restaure langue session + return 0; + } } - $pdf->MultiCell(108, 5, $fac->lignes[$i]->desc."$codeproduitservice", 0, 'J'); - - $nexY = $pdf->GetY(); - - // TVA - if ($this->franchise!=1) - { - $pdf->SetXY ($this->marges['g']+119, $curY); - $pdf->MultiCell(10, 5, $fac->lignes[$i]->tva_tx, 0, 'C'); - } - // Prix unitaire HT avant remise - $pdf->SetXY ($this->marges['g']+132, $curY); - $pdf->MultiCell(16, 5, price($fac->lignes[$i]->subprice), 0, 'R', 0); - - // Quantit - $pdf->SetXY ($this->marges['g']+150, $curY); - $pdf->MultiCell(10, 5, $fac->lignes[$i]->qty, 0, 'R'); - - // Remise sur ligne - $pdf->SetXY ($this->marges['g']+160, $curY); - if ($fac->lignes[$i]->remise_percent) { - $pdf->MultiCell(14, 5, $fac->lignes[$i]->remise_percent."%", 0, 'R'); + else + { + $this->error=$langs->transnoentities("ErrorConstantNotDefined","FAC_OUTPUTDIR"); + $langs->setPhpLang(); // On restaure langue session + return 0; } + $this->error=$langs->transnoentities("ErrorUnknown"); + $langs->setPhpLang(); // On restaure langue session + return 0; // Erreur par defaut + } - // Total HT - $pdf->SetXY ($this->marges['g']+168, $curY); - $total = price($fac->lignes[$i]->total_ht); - $pdf->MultiCell(21, 5, $total, 0, 'R', 0); - - - if ($nexY > 200 && $i < $nblignes - 1) - { - $this->_tableau($pdf, $tab_top, $tab_height, $nexY, $fac); - $pdf->AddPage(); - $nexY = $iniY; - $this->_pagehead($pdf, $fac); - $pdf->SetTextColor(0,0,0); - $pdf->SetFont('Arial','', 10); - } - - } - $this->_tableau($pdf, $tab_top, $tab_height, $nexY, $fac); - - $deja_regle = $fac->getSommePaiement(); - - $this->_tableau_tot($pdf, $fac, $deja_regle); - - if ($deja_regle) { - $this->_tableau_versements($pdf, $fac); - } - - /* - * Mode de r�glement - */ - if ((! defined("FACTURE_CHQ_NUMBER") || ! FACTURE_CHQ_NUMBER) && (! defined("FACTURE_RIB_NUMBER") || ! FACTURE_RIB_NUMBER)) { - $pdf->SetXY ($this->marges['g'], 228); - $pdf->SetTextColor(200,0,0); - $pdf->SetFont('Arial','B',8); - $pdf->MultiCell(90, 3, $langs->transnoentities("ErrorNoPaiementModeConfigured"),0,'L',0); - $pdf->MultiCell(90, 3, $langs->transnoentities("ErrorCreateBankAccount"),0,'L',0); - $pdf->SetTextColor(0,0,0); - } - - /* - * Propose mode r�glement par CHQ - */ - if (defined("FACTURE_CHQ_NUMBER")) - { - if (FACTURE_CHQ_NUMBER > 0) - { - $account = new Account($this->db); - $account->fetch(FACTURE_CHQ_NUMBER); - - $pdf->SetXY ($this->marges['g'], 225); - $pdf->SetFont('Arial','B',8); - $pdf->MultiCell(90, 3, $langs->transnoentities('PaymentByChequeOrderedTo').' '.$account->proprio.' '.$langs->transnoentities('SendTo').':',0,'L',0); - $pdf->SetXY ($this->marges['g'], 230); - $pdf->SetFont('Arial','',8); - $pdf->MultiCell(80, 3, $account->adresse_proprio, 0, 'L', 0); - } - } - - /* - * Propose mode r�glement par RIB - */ - if (defined("FACTURE_RIB_NUMBER")) - { - if (FACTURE_RIB_NUMBER > 0) - { - $account = new Account($this->db); - $account->fetch(FACTURE_RIB_NUMBER); - - $cury=240; - $pdf->SetXY ($this->marges['g'], $cury); - $pdf->SetFont('Arial','B',8); - $pdf->MultiCell(90, 3, $langs->transnoentities('PaymentByTransferOnThisBankAccount').':', 0, 'L', 0); - $cury=245; - $pdf->SetFont('Arial','B',6); - $pdf->line($this->marges['g'], $cury, $this->marges['g'], $cury+10 ); - $pdf->SetXY ($this->marges['g'], $cury); - $pdf->MultiCell(18, 3, $langs->transnoentities("BankCode"), 0, 'C', 0); - $pdf->line($this->marges['g']+18, $cury, $this->marges['g']+18, $cury+10 ); - $pdf->SetXY ($this->marges['g']+18, $cury); - $pdf->MultiCell(18, 3, $langs->transnoentities("DeskCode"), 0, 'C', 0); - $pdf->line($this->marges['g']+36, $cury, $this->marges['g']+36, $cury+10 ); - $pdf->SetXY ($this->marges['g']+36, $cury); - $pdf->MultiCell(24, 3, $langs->transnoentities("BankAccountNumber"), 0, 'C', 0); - $pdf->line($this->marges['g']+60, $cury, $this->marges['g']+60, $cury+10 ); - $pdf->SetXY ($this->marges['g']+60, $cury); - $pdf->MultiCell(13, 3, $langs->transnoentities("BankAccountNumberKey"), 0, 'C', 0); - $pdf->line($this->marges['g']+73, $cury, $this->marges['g']+73, $cury+10 ); - - $pdf->SetFont('Arial','',8); - $pdf->SetXY ($this->marges['g'], $cury+5); - $pdf->MultiCell(18, 3, $account->code_banque, 0, 'C', 0); - $pdf->SetXY ($this->marges['g']+18, $cury+5); - $pdf->MultiCell(18, 3, $account->code_guichet, 0, 'C', 0); - $pdf->SetXY ($this->marges['g']+36, $cury+5); - $pdf->MultiCell(24, 3, $account->number, 0, 'C', 0); - $pdf->SetXY ($this->marges['g']+60, $cury+5); - $pdf->MultiCell(13, 3, $account->cle_rib, 0, 'C', 0); - - $pdf->SetXY ($this->marges['g'], $cury+15); - $pdf->MultiCell(90, 3, $langs->transnoentities("Residence").' : ' . $account->domiciliation, 0, 'L', 0); - $pdf->SetXY ($this->marges['g'], $cury+25); - $pdf->MultiCell(90, 3, $langs->transnoentities("IbanPrefix").' : ' . $account->iban_prefix, 0, 'L', 0); - $pdf->SetXY ($this->marges['g'], $cury+30); - $pdf->MultiCell(90, 3, $langs->transnoentities("BIC").' : ' . $account->bic, 0, 'L', 0); - } - } - - /* - * Conditions de r�glements - */ - if ($fac->cond_reglement_code) - { - $pdf->SetFont('Arial','B',10); - $pdf->SetXY($this->marges['g'], 217); - $titre = $langs->transnoentities("PaymentConditions").':'; - $pdf->MultiCell(80, 5, $titre, 0, 'L'); - $pdf->SetFont('Arial','',10); - $pdf->SetXY($this->marges['g']+44, 217); - $lib_condition_paiement=$outputlangs->transnoentities("PaymentCondition".$fac->cond_reglement_code)?$outputlangs->transnoentities("PaymentCondition".$fac->cond_reglement_code):$fac->cond_reglement; - $pdf->MultiCell(80, 5, $lib_condition_paiement,0,'L'); - } - - /* - * Pied de page - */ - $this->_pagefoot($pdf, $fac); - $pdf->AliasNbPages(); - - $pdf->Close(); - - $pdf->Output($file); - - $langs->setPhpLang(); // On restaure langue session - return 1; // Pas d'erreur - } - else - { - $this->error=$langs->transnoentities("ErrorCanNotCreateDir",$dir); - $langs->setPhpLang(); // On restaure langue session - return 0; - } - } - else - { - $this->error=$langs->transnoentities("ErrorConstantNotDefined","FAC_OUTPUTDIR"); - $langs->setPhpLang(); // On restaure langue session - return 0; - } - $this->error=$langs->transnoentities("ErrorUnknown"); - $langs->setPhpLang(); // On restaure langue session - return 0; // Erreur par defaut - } + /** + * \brief Affiche tableau des versement + * \param pdf Objet PDF + * \param fac Objet facture + * \param posy Position y in PDF + * \param outputlangs Object langs for output + * \return int <0 if KO, >0 if OK + */ + function _tableau_versements(&$pdf, $fac, $posy, $outputlangs) + { + $tab3_posx = $this->marges['g']+110; + $tab3_top = $this->marges['h']+235; + $tab3_width = 80; + $tab3_height = 4; + + $pdf->SetFont('Arial','',8); + $pdf->SetXY ($tab3_posx, $tab3_top - 5); + $pdf->MultiCell(60, 5, $outputlangs->transnoentities("PaymentsAlreadyDone"), 0, 'L', 0); + + $pdf->Rect($tab3_posx, $tab3_top-1, $tab3_width, $tab3_height); + + $pdf->SetXY ($tab3_posx, $tab3_top-1 ); + $pdf->MultiCell(20, 4, $outputlangs->transnoentities("Payment"), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+21, $tab3_top-1 ); + $pdf->MultiCell(20, 4, $outputlangs->transnoentities("Amount"), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+41, $tab3_top-1 ); + $pdf->MultiCell(20, 4, $outputlangs->transnoentities("Type"), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+60, $tab3_top-1 ); + $pdf->MultiCell(20, 4, $outputlangs->transnoentities("Num"), 0, 'L', 0); + + $y=0; + + // Loop on each credit note included + $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,"; + $sql.= " re.description, re.fk_facture_source, re.fk_facture_source"; + $sql.= " FROM ".MAIN_DB_PREFIX ."societe_remise_except as re"; + $sql.= " WHERE fk_facture = ".$fac->id; + $resql=$this->db->query($sql); + if ($resql) + { + $num = $this->db->num_rows($resql); + $i=0; + $invoice=new Facture($this->db); + while ($i < $num) + { + $y+=3; + $obj = $this->db->fetch_object($resql); + + $invoice->fetch($obj->fk_facture_source); + + $pdf->SetXY ($tab3_posx, $tab3_top+$y ); + $pdf->MultiCell(20, 4,'', 0, 'L', 0); + $pdf->SetXY ($tab3_posx+21, $tab3_top+$y); + $pdf->MultiCell(20, 4, price($obj->amount_ttc), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+41, $tab3_top+$y); + $pdf->MultiCell(20, 4, $outputlangs->trans("CreditNote"), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+60, $tab3_top+$y); + $pdf->MultiCell(20, 4, $invoice->ref, 0, 'L', 0); + + $pdf->line($tab3_posx, $tab3_top+$y+3, $tab3_posx+$tab3_width, $tab3_top+$y+3 ); + + $i++; + } + } + else + { + $this->error=$outputlangs->trans("ErrorSQL")." sql=".$sql; + dolibarr_syslog($this->db,$this->error); + return -1; + } + + // Loop on each payment + $sql = "SELECT ".$this->db->pdate("p.datep")."as date, pf.amount as amount, p.fk_paiement as type, p.num_paiement as num "; + $sql.= "FROM ".MAIN_DB_PREFIX."paiement as p, ".MAIN_DB_PREFIX."paiement_facture as pf "; + $sql.= "WHERE pf.fk_paiement = p.rowid and pf.fk_facture = ".$fac->id." "; + $sql.= "ORDER BY p.datep"; + $resql=$this->db->query($sql); + if ($resql) + { + $pdf->SetFont('Arial','',6); + $num = $this->db->num_rows($resql); + $i=0; + while ($i < $num) + { + $y+=3; + $row = $this->db->fetch_row($resql); + + $pdf->SetXY ($tab3_posx, $tab3_top+$y ); + $pdf->MultiCell(20, 4, dolibarr_print_date($row[0],'day'), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+21, $tab3_top+$y); + $pdf->MultiCell(20, 4, price($row[1]), 0, 'L', 0); + $pdf->SetXY ($tab3_posx+41, $tab3_top+$y); + switch ($row[2]) + { + case 1: + $oper = 'TIP'; + break; + case 2: + $oper = 'VIR'; + break; + case 3: + $oper = 'PRE'; + break; + case 4: + $oper = 'LIQ'; + break; + case 5: + $oper = 'VAD'; + break; + case 6: + $oper = 'CB'; + break; + case 7: + $oper = 'CHQ'; + break; + } + $pdf->MultiCell(20, 4, $oper, 0, 'L', 0); + $pdf->SetXY ($tab3_posx+60, $tab3_top+$y); + $pdf->MultiCell(20, 4, $row[3], 0, 'L', 0); - /* - * \brief Affiche tableau des versement - * \param pdf objet PDF - * \param fac objet facture - */ - function _tableau_versements(&$pdf, $fac) - { - global $langs; - $langs->load("main"); - $langs->load("bills"); + $pdf->line($tab3_posx, $tab3_top+$y+3, $tab3_posx+$tab3_width, $tab3_top+$y+3 ); - $tab3_posx = $this->marges['g']+110; - $tab3_top = $this->marges['h']+235; - $tab3_width = 80; - $tab3_height = 4; + $i++; + } + } + else + { + $this->error=$outputlangs->trans("ErrorSQL")." sql=".$sql; + dolibarr_syslog($this->db,$this->error); + return -1; + } - $pdf->SetFont('Arial','',8); - $pdf->SetXY ($tab3_posx, $tab3_top - 5); - $pdf->MultiCell(60, 5, $langs->transnoentities("PaymentsAlreadyDone"), 0, 'L', 0); - - $pdf->Rect($tab3_posx, $tab3_top-1, $tab3_width, $tab3_height); - - $pdf->SetXY ($tab3_posx, $tab3_top-1 ); - $pdf->MultiCell(20, 4, $langs->transnoentities("Payment"), 0, 'L', 0); - $pdf->SetXY ($tab3_posx+21, $tab3_top-1 ); - $pdf->MultiCell(20, 4, $langs->transnoentities("Amount"), 0, 'L', 0); - $pdf->SetXY ($tab3_posx+41, $tab3_top-1 ); - $pdf->MultiCell(20, 4, $langs->transnoentities("Type"), 0, 'L', 0); - $pdf->SetXY ($tab3_posx+60, $tab3_top-1 ); - $pdf->MultiCell(20, 4, $langs->transnoentities("Ref"), 0, 'L', 0); - - $sql = "SELECT ".$this->db->pdate("p.datep")."as date, pf.amount as amount, p.fk_paiement as type, p.num_paiement as num "; - $sql.= "FROM ".MAIN_DB_PREFIX."paiement as p, ".MAIN_DB_PREFIX."paiement_facture as pf "; - $sql.= "WHERE pf.fk_paiement = p.rowid and pf.fk_facture = ".$fac->id." "; - $sql.= "ORDER BY p.datep"; - if ($this->db->query($sql)) - { - $pdf->SetFont('Arial','',6); - $num = $this->db->num_rows(); - $i=0; $y=0; - while ($i < $num) { - $y+=3; - $row = $this->db->fetch_row(); - - $pdf->SetXY ($tab3_posx, $tab3_top+$y ); - $pdf->MultiCell(20, 4, dolibarr_print_date($row[0],'day'), 0, 'L', 0); - $pdf->SetXY ($tab3_posx+21, $tab3_top+$y); - $pdf->MultiCell(20, 4, $row[1], 0, 'L', 0); - $pdf->SetXY ($tab3_posx+41, $tab3_top+$y); - switch ($row[2]) - { - case 1: - $oper = 'TIP'; - break; - case 2: - $oper = 'VIR'; - break; - case 3: - $oper = 'PRE'; - break; - case 4: - $oper = 'LIQ'; - break; - case 5: - $oper = 'VAD'; - break; - case 6: - $oper = 'CB'; - break; - case 7: - $oper = 'CHQ'; - break; - } - $pdf->MultiCell(20, 4, $oper, 0, 'L', 0); - $pdf->SetXY ($tab3_posx+60, $tab3_top+$y); - $pdf->MultiCell(20, 4, $row[3], 0, 'L', 0); - - $pdf->line($tab3_posx, $tab3_top+$y+3, $tab3_posx+$tab3_width, $tab3_top+$y+3 ); - - $i++; } - } - else - { - $this->error=$langs->transnoentities("ErrorSQL")." $sql"; - return 0; - } - - } /* * \brief Affiche le total � payer diff --git a/htdocs/langs/en_US/bills.lang b/htdocs/langs/en_US/bills.lang index 557713e909e..04c52bcb906 100644 --- a/htdocs/langs/en_US/bills.lang +++ b/htdocs/langs/en_US/bills.lang @@ -223,7 +223,7 @@ DiscountAlreadyCounted=Discount already counted BillAddress=Bill address HelpEscompte=This discount is a discount granted to customer because its paiement was made before term. HelpAbandonBadCustomer=This amount has been abandoned (customer said to be a bad customer) and is considered as an exceptionnal loose. -HelpAbandonOther=This amount has been abandoned since it was an error. It must be corrected in accountancy system by creating a credit note. +HelpAbandonOther=This amount has been abandoned since it was an error (wrong customer or invoice replaced by an other for example) InvoiceId=Invoice id InvoiceRef=Invoice ref. InvoiceDateCreation=Invoice creation date diff --git a/htdocs/langs/fr_FR/bills.lang b/htdocs/langs/fr_FR/bills.lang index fd43c586f93..53bf2a05da0 100644 --- a/htdocs/langs/fr_FR/bills.lang +++ b/htdocs/langs/fr_FR/bills.lang @@ -222,7 +222,7 @@ DiscountAlreadyCounted=Remises fixes d BillAddress=Adresse de facturation HelpEscompte=Un <b>escompte</b> est une remise accord�e, sur une facture donn�e, � un client car ce dernier a r�alis� son r�glement bien avant l'�ch�ance. HelpAbandonBadCustomer=Ce montant a �t� abandonn� (client jug� mauvais payeur) et est consid�r� comme un perte exceptionnelle. -HelpAbandonOther=Ce montant a �t� abandonn� car il s'agissait d'un trop r�clam� (oubli de remise ou retour produit du client). Il doit �tre r�gularis� en compta par la saisie d'un avoir. +HelpAbandonOther=Ce montant a �t� abandonn� car il s'agissait d'une erreur de facturation (saisie mauvais client, facture remplac�e par une autre). InvoiceId=Id facture InvoiceRef=Ref. facture InvoiceDateCreation=Date cr�ation facture diff --git a/htdocs/lib/functions.inc.php b/htdocs/lib/functions.inc.php index 0075a4ef789..4909b502d6a 100644 --- a/htdocs/lib/functions.inc.php +++ b/htdocs/lib/functions.inc.php @@ -220,7 +220,7 @@ function dolibarr_syslog($message, $level=LOG_INFO) fwrite($file,$message."\n"); fclose($file); - // If development tag enabled and param log enabled, we show output log on HTML comments + // If enable html log tag enabled and url parameter log defined, we show output log on HTML comments if (! empty($conf->global->MAIN_ENABLE_LOG_HTML) && ! empty($_GET["log"])) { print "\n\n<!-- Log start\n"; @@ -2384,16 +2384,26 @@ function get_default_tva($societe_vendeuse, $societe_acheteuse, $taux_produit) /** \brief Renvoie oui ou non dans la langue choisie - \param yesno variable pour test si oui ou non - \param case Oui/Non ou oui/non + \param yesno Variable pour test si oui ou non + \param case 1=Yes/No, 0=yes/no + \param color 0=texte only, 1=Text is format with a color font style */ -function yn($yesno, $case=1) { +function yn($yesno, $case=1, $color=0) +{ global $langs; + $result='unknown'; if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') // A mettre avant test sur no a cause du == 0 - return $case?$langs->trans("Yes"):$langs->trans("yes"); - if ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') - return $case?$langs->trans("No"):$langs->trans("no"); - return "unknown"; + { + $result=($case?$langs->trans("Yes"):$langs->trans("yes")); + $class='ok'; + } + elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') + { + $result=($case?$langs->trans("No"):$langs->trans("no")); + $class='error'; + } + if ($color) return '<font class="'.$class.'">'.$result.'</font>'; + return $result; } -- GitLab