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/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/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='id . '">' . $langs->trans("Back") . '';
+ $backbutton='id . '">' . $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 90c7434a42a..5250c48c33e 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,74 @@ 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.'/projet/class/project.class.php';
+ include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+
+ $tmpinvoice = new Facture($db);
+ $tmptimespent=new Task($db);
+ $tmpproduct=new Product($db);
+ $fuser = new User($db);
+
+ $db->begin();
+
+ $idprod = GETPOST('idprod', 'int');
+ if ($idprod > 0)
+ {
+ $tmpproduct->fetch($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();
+ foreach($toselect as $key => $value)
+ {
+ // Get userid, timepent
+ $object->fetchTimeSpent($value);
+
+ $arrayoftasks[$object->timespent_fk_user]['timespent']+=$object->timespent_duration;
+ }
+ 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');
+ //var_dump($tmpinvoice);
+
+ if (! $error)
+ {
+ $db->commit();
+ }
+ else
+ {
+ $db->rollback();
+ }
+ }
+}
+
/*
* View
@@ -315,7 +387,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();