From 2c8557e2e0621f91dbc8778b4d20ec74c2856055 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 24 Jan 2019 18:16:14 +0100 Subject: [PATCH 1/2] Work on invoicing of time spent --- htdocs/langs/en_US/projects.lang | 4 ++- htdocs/projet/tasks/time.php | 62 ++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/htdocs/langs/en_US/projects.lang b/htdocs/langs/en_US/projects.lang index e2d6d87838c..95c918536d9 100644 --- a/htdocs/langs/en_US/projects.lang +++ b/htdocs/langs/en_US/projects.lang @@ -226,6 +226,7 @@ LatestProjects=Latest %s projects LatestModifiedProjects=Latest %s modified projects OtherFilteredTasks=Other filtered tasks NoAssignedTasks=No assigned tasks found (assign project/tasks to the current user from the top select box to enter time on it) +ThirdPartyRequiredToGenerateInvoice=A third party must be defined on project to be able to invoice it. # Comments trans AllowCommentOnTask=Allow user comments on tasks AllowCommentOnProject=Allow user comments on projects @@ -235,4 +236,5 @@ RecordsClosed=%s project(s) closed SendProjectRef=Information project %s ModuleSalaryToDefineHourlyRateMustBeEnabled=Module 'Salaries' must be enabled to define employee hourly rate to have time spent valorized NewTaskRefSuggested=Task ref already used, a new task ref is required -TimeSpentInvoiced=Time spent billed \ No newline at end of file +TimeSpentInvoiced=Time spent billed +GenerateInvoice=Generate invoice \ No newline at end of file diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 90c7434a42a..60144644262 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -285,9 +285,13 @@ if (! empty($project_ref) && ! empty($withproject)) // To show all time lines for project $projectidforalltimes=0; -if (GETPOST('projectid','int')) +if (GETPOST('projectid','int') > 0) { $projectidforalltimes=GETPOST('projectid','int'); + + $result=$projectstatic->fetch($projectidforalltimes); + if (! empty($projectstatic->socid)) $projectstatic->fetch_thirdparty(); + $res=$projectstatic->fetch_optionals(); } elseif (GETPOST('project_ref','alpha')) { @@ -296,6 +300,60 @@ elseif (GETPOST('project_ref','alpha')) $withproject=1; } +if ($massaction == 'generateinvoice') +{ + if (! empty($projectstatic->socid)) $projectstatic->fetch_thirdparty(); + + //->fetch_thirdparty(); + + if (! ($projectstatic->thirdparty->id > 0)) + { + setEventMessages($langs->trans("ThirdPartyRequiredToGenerateInvoice"), null, 'errors'); + } + else + { + include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + include_once DOL_DOCUMENT_ROOT.'/project/class/projet.class.php'; + include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + + $tmpinvoice = new Facture($db); + $tmptimespent=new Task(); + $tmpproduct=new Product($db); + $fuser = new User($db); + $idprod = 0; + + $txtva = get_default_tva($mysoc, $projectstatic->thirdparty, $idprod); + + $tmpinvoice->fk_soc = $projectstatic->thirdparty->id; + $tmpinvoice->create($user); + + $arrayoftasks=array(); + $totaltimespent = 0; + foreach($toselect as $key => $value) + { + // Get userid, timepent + //$object->fetchTimeSpent(GETPOST('lineid','int')); + + + $arrayoftasks[$userid]['timespent']+=$timespent; + } + + foreach($arrayoftasks as $userid => $value) + { + $fuser->fetch($userid); + $pu_ht = $value['timespent'] * $fuser->thm; + $username = $fuser->getFullName($langs); + + // Add lines + $tmpinvoice->addline($langs->trans("TotalOfTimeSpentBy", $username).' : '.$value['timespent'], $pu_ht, 1, $txtva); + } + + setEventMessages($langs->trans("InvoiceGeneratedFromTimeSpent", $tmpinvoice->ref), null, 'mesgs'); + + exit; + } +} + /* * View @@ -315,7 +373,7 @@ if (($id > 0 || ! empty($ref)) || $projectidforalltimes > 0) /* * Fiche projet en mode visu */ - if ($projectidforalltimes) + if ($projectidforalltimes > 0) { $result=$projectstatic->fetch($projectidforalltimes); if (! empty($projectstatic->socid)) $projectstatic->fetch_thirdparty(); From a5daa257076b6e1360a460f468111639397f71b0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 24 Jan 2019 19:45:17 +0100 Subject: [PATCH 2/2] Move common code to get sell price into a method --- htdocs/compta/facture/card.php | 111 +++--------------------- htdocs/product/class/product.class.php | 114 ++++++++++++++++++++++++- htdocs/product/price.php | 4 +- htdocs/projet/tasks/time.php | 40 ++++++--- 4 files changed, 152 insertions(+), 117 deletions(-) diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index c3bcbf1d45b..1fd2906c26f 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -1775,105 +1775,18 @@ if (empty($reshook)) $label = ((GETPOST('product_label') && GETPOST('product_label') != $prod->label) ? GETPOST('product_label') : ''); - // Update if prices fields are defined - $tva_tx = get_default_tva($mysoc, $object->thirdparty, $prod->id); - $tva_npr = get_default_npr($mysoc, $object->thirdparty, $prod->id); - if (empty($tva_tx)) $tva_npr=0; - - $pu_ht = $prod->price; - $pu_ttc = $prod->price_ttc; - $price_min = $prod->price_min; - $price_base_type = $prod->price_base_type; - - // If price per segment - if (! empty($conf->global->PRODUIT_MULTIPRICES) && ! empty($object->thirdparty->price_level)) - { - $pu_ht = $prod->multiprices[$object->thirdparty->price_level]; - $pu_ttc = $prod->multiprices_ttc[$object->thirdparty->price_level]; - $price_min = $prod->multiprices_min[$object->thirdparty->price_level]; - $price_base_type = $prod->multiprices_base_type[$object->thirdparty->price_level]; - if (! empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) // using this option is a bug. kept for backward compatibility - { - if (isset($prod->multiprices_tva_tx[$object->thirdparty->price_level])) $tva_tx=$prod->multiprices_tva_tx[$object->thirdparty->price_level]; - if (isset($prod->multiprices_recuperableonly[$object->thirdparty->price_level])) $tva_npr=$prod->multiprices_recuperableonly[$object->thirdparty->price_level]; - if (empty($tva_tx)) $tva_npr=0; - } - } - // If price per customer - elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) - { - require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php'; - - $prodcustprice = new Productcustomerprice($db); - - $filter = array('t.fk_product' => $prod->id,'t.fk_soc' => $object->thirdparty->id); - - $result = $prodcustprice->fetch_all('', '', 0, 0, $filter); - if ($result) { - if (count($prodcustprice->lines) > 0) { - $pu_ht = price($prodcustprice->lines[0]->price); - $pu_ttc = price($prodcustprice->lines[0]->price_ttc); - $price_base_type = $prodcustprice->lines[0]->price_base_type; - $tva_tx = $prodcustprice->lines[0]->tva_tx; - if ($prodcustprice->lines[0]->default_vat_code && ! preg_match('/\(.*\)/', $tva_tx)) $tva_tx.= ' ('.$prodcustprice->lines[0]->default_vat_code.')'; - $tva_npr = $prodcustprice->lines[0]->recuperableonly; - if (empty($tva_tx)) $tva_npr=0; - } - } - } - // If price per quantity - elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) - { - if ($prod->prices_by_qty[0]) // yes, this product has some prices per quantity - { - // Search the correct price into loaded array product_price_by_qty using id of array retrieved into POST['pqp']. - $pqp = GETPOST('pbq','int'); - - // Search price into product_price_by_qty from $prod->id - foreach($prod->prices_by_qty_list[0] as $priceforthequantityarray) - { - if ($priceforthequantityarray['rowid'] != $pqp) continue; - // We found the price - if ($priceforthequantityarray['price_base_type'] == 'HT') - { - $pu_ht = $priceforthequantityarray['unitprice']; - } - else - { - $pu_ttc = $priceforthequantityarray['unitprice']; - } - // Note: the remise_percent or price by qty is used to set data on form, so we will use value from POST. - break; - } - } - } - // If price per quantity and customer - elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) - { - if ($prod->prices_by_qty[$object->thirdparty->price_level]) // yes, this product has some prices per quantity - { - // Search the correct price into loaded array product_price_by_qty using id of array retrieved into POST['pqp']. - $pqp = GETPOST('pbq','int'); - - // Search price into product_price_by_qty from $prod->id - foreach($prod->prices_by_qty_list[$object->thirdparty->price_level] as $priceforthequantityarray) - { - if ($priceforthequantityarray['rowid'] != $pqp) continue; - // We found the price - if ($priceforthequantityarray['price_base_type'] == 'HT') - { - $pu_ht = $priceforthequantityarray['unitprice']; - } - else - { - $pu_ttc = $priceforthequantityarray['unitprice']; - } - // Note: the remise_percent or price by qty is used to set data on form, so we will use value from POST. - break; - } - } - } - + // Search the correct price into loaded array product_price_by_qty using id of array retrieved into POST['pqp']. + $pqp = (GETPOST('pbq','int') ? GETPOST('pbq','int') : 0); + + $datapriceofproduct = $prod->getSellPrice($mysoc, $object->thirdparty, $pqp); + + $pu_ht = $datapriceofproduct['pu_ht']; + $pu_ttc = $datapriceofproduct['pu_ttc']; + $price_min = $datapriceofproduct['price_min']; + $price_base_type = $datapriceofproduct['price_base_type']; + $tva_tx = $datapriceofproduct['tva_tx']; + $tva_npr = $datapriceofproduct['tva_npr']; + $tmpvat = price2num(preg_replace('/\s*\(.*\)/', '', $tva_tx)); $tmpprodvat = price2num(preg_replace('/\s*\(.*\)/', '', $prod->tva_tx)); diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index bbdc1d456ee..ee798581feb 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -1526,12 +1526,119 @@ class Product extends CommonObject } } + + /** + * Return price of sell of a product for a seller/buyer/product. + * + * @param Societe $thirdparty_seller Seller + * @param Societe $thirdparty_buyer Buyer + * @param int $pqp Id of product per price if a selection was done of such a price + * @return array Array of price information + * @see get_buyprice() + */ + function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0) + { + global $conf, $db; + + // Update if prices fields are defined + $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id); + $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id); + if (empty($tva_tx)) $tva_npr=0; + + $pu_ht = $this->price; + $pu_ttc = $this->price_ttc; + $price_min = $this->price_min; + $price_base_type = $this->price_base_type; + + // If price per segment + if (! empty($conf->global->PRODUIT_MULTIPRICES) && ! empty($thirdparty_buyer->price_level)) + { + $pu_ht = $this->multiprices[$thirdparty_buyer->price_level]; + $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level]; + $price_min = $this->multiprices_min[$thirdparty_buyer->price_level]; + $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level]; + if (! empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) // using this option is a bug. kept for backward compatibility + { + if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) $tva_tx=$this->multiprices_tva_tx[$thirdparty_buyer->price_level]; + if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) $tva_npr=$this->multiprices_recuperableonly[$thirdparty_buyer->price_level]; + if (empty($tva_tx)) $tva_npr=0; + } + } + // If price per customer + elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) + { + require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php'; + + $prodcustprice = new Productcustomerprice($db); + + $filter = array('t.fk_product' => $this->id,'t.fk_soc' => $thirdparty_buyer->id); + + $result = $prodcustprice->fetch_all('', '', 0, 0, $filter); + if ($result) { + if (count($prodcustprice->lines) > 0) { + $pu_ht = price($prodcustprice->lines[0]->price); + $pu_ttc = price($prodcustprice->lines[0]->price_ttc); + $price_base_type = $prodcustprice->lines[0]->price_base_type; + $tva_tx = $prodcustprice->lines[0]->tva_tx; + if ($prodcustprice->lines[0]->default_vat_code && ! preg_match('/\(.*\)/', $tva_tx)) $tva_tx.= ' ('.$prodcustprice->lines[0]->default_vat_code.')'; + $tva_npr = $prodcustprice->lines[0]->recuperableonly; + if (empty($tva_tx)) $tva_npr=0; + } + } + } + // If price per quantity + elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) + { + if ($this->prices_by_qty[0]) // yes, this product has some prices per quantity + { + // Search price into product_price_by_qty from $this->id + foreach($this->prices_by_qty_list[0] as $priceforthequantityarray) + { + if ($priceforthequantityarray['rowid'] != $pqp) continue; + // We found the price + if ($priceforthequantityarray['price_base_type'] == 'HT') + { + $pu_ht = $priceforthequantityarray['unitprice']; + } + else + { + $pu_ttc = $priceforthequantityarray['unitprice']; + } + break; + } + } + } + // If price per quantity and customer + elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) + { + if ($this->prices_by_qty[$thirdparty_buyer->price_level]) // yes, this product has some prices per quantity + { + // Search price into product_price_by_qty from $this->id + foreach($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) + { + if ($priceforthequantityarray['rowid'] != $pqp) continue; + // We found the price + if ($priceforthequantityarray['price_base_type'] == 'HT') + { + $pu_ht = $priceforthequantityarray['unitprice']; + } + else + { + $pu_ttc = $priceforthequantityarray['unitprice']; + } + break; + } + } + } + + return array('pu_ht'=>$pu_ht, 'pu_ttc'=>$pu_ttc, 'price_min'=>$price_min, 'price_base_type'=>$price_base_type, 'tva_tx'=>$tva_tx, 'tva_npr'=>$tva_npr); + } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps /** - * Read price used by a provider. - * We enter as input couple prodfournprice/qty or triplet qty/product_id/fourn_ref. - * This also set some properties on product like ->buyprice, ->fourn_pu, ... + * Read price used by a provider. + * We enter as input couple prodfournprice/qty or triplet qty/product_id/fourn_ref. + * This also set some properties on product like ->buyprice, ->fourn_pu, ... * * @param int $prodfournprice Id du tarif = rowid table product_fournisseur_price * @param double $qty Quantity asked or -1 to get first entry found @@ -1539,6 +1646,7 @@ class Product extends CommonObject * @param string $fourn_ref Filter on a supplier price ref. 'none' to exclude ref in search. * @param int $fk_soc If of supplier * @return int <-1 if KO, -1 if qty not enough, 0 if OK but nothing found, id_product if OK and found. May also initialize some properties like (->ref_supplier, buyprice, fourn_pu, vatrate_supplier...) + * @see getSellPrice() */ function get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0) { diff --git a/htdocs/product/price.php b/htdocs/product/price.php index c77037abe51..5c06bfee335 100644 --- a/htdocs/product/price.php +++ b/htdocs/product/price.php @@ -1829,7 +1829,7 @@ if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) elseif ($action == 'showlog_customer_price') { // List of all log of prices by customers - print ''."\n"; + print ''."\n"; $filter = array('t.fk_product' => $object->id,'t.fk_soc' => GETPOST('socid', 'int')); @@ -1852,7 +1852,7 @@ if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) $title=$langs->trans('PriceByCustomerLog'); $title.=' - '.$staticsoc->getNomUrl(1); - $backbutton='' . $langs->trans("Back") . ''; + $backbutton='' . $langs->trans("Back") . ''; print_barre_liste($title, $page, $_SERVEUR['PHP_SELF'], $option, $sortfield, $sortorder, $backbutton, count($prodcustprice->lines), $nbtotalofrecords, 'title_accountancy.png'); diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 60144644262..5250c48c33e 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -313,35 +313,41 @@ if ($massaction == 'generateinvoice') else { include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; - include_once DOL_DOCUMENT_ROOT.'/project/class/projet.class.php'; + include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; $tmpinvoice = new Facture($db); - $tmptimespent=new Task(); + $tmptimespent=new Task($db); $tmpproduct=new Product($db); $fuser = new User($db); - $idprod = 0; + + $db->begin(); + + $idprod = GETPOST('idprod', 'int'); + if ($idprod > 0) + { + $tmpproduct->fetch($idprod); + } - $txtva = get_default_tva($mysoc, $projectstatic->thirdparty, $idprod); + $dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0); + $pu_ht = $dataforprice['pu_ht']; + $txtva = $dataforprice['tva_tx']; $tmpinvoice->fk_soc = $projectstatic->thirdparty->id; $tmpinvoice->create($user); - + $arrayoftasks=array(); - $totaltimespent = 0; foreach($toselect as $key => $value) { // Get userid, timepent - //$object->fetchTimeSpent(GETPOST('lineid','int')); + $object->fetchTimeSpent($value); - - $arrayoftasks[$userid]['timespent']+=$timespent; + $arrayoftasks[$object->timespent_fk_user]['timespent']+=$object->timespent_duration; } - foreach($arrayoftasks as $userid => $value) { $fuser->fetch($userid); - $pu_ht = $value['timespent'] * $fuser->thm; + //$pu_ht = $value['timespent'] * $fuser->thm; $username = $fuser->getFullName($langs); // Add lines @@ -349,8 +355,16 @@ if ($massaction == 'generateinvoice') } setEventMessages($langs->trans("InvoiceGeneratedFromTimeSpent", $tmpinvoice->ref), null, 'mesgs'); - - exit; + //var_dump($tmpinvoice); + + if (! $error) + { + $db->commit(); + } + else + { + $db->rollback(); + } } }