Merge branch 'develop' of git://github.com/Dolibarr/dolibarr into develop

This commit is contained in:
altairis 2019-11-22 15:13:40 +01:00
commit 142ba77266
39 changed files with 1928 additions and 1241 deletions

View File

@ -29,6 +29,7 @@ Following changes may create regressions for some external modules, but were nec
* All properties 'labelstatusshort' and 'labelstatut_short' were renamed into 'labelStatusShort'. * All properties 'labelstatusshort' and 'labelstatut_short' were renamed into 'labelStatusShort'.
* All properties 'type_libelle' were renamed into 'type_label'. * All properties 'type_libelle' were renamed into 'type_label'.
* Renamed property of thirdparty "statut_commercial" into "status_prospect_label" * Renamed property of thirdparty "statut_commercial" into "status_prospect_label"
* The jquery plugin/dependency multiselect has been removed. It was not used by Dolibarr core.
***** ChangeLog for 10.0.3 compared to 10.0.2 ***** ***** ChangeLog for 10.0.3 compared to 10.0.2 *****
IMPORTANT : This version fixes a serious bug in saving the units of weight, size, surface and volume on product card. IMPORTANT : This version fixes a serious bug in saving the units of weight, size, surface and volume on product card.

View File

@ -135,6 +135,10 @@ if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'e
if (empty($reshook)) if (empty($reshook))
{ {
// Actions cancel, add, update, delete or clone
$backurlforlist = $_SERVER["PHP_SELF"].'?action=list';
include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php';
// Selection of new fields // Selection of new fields
include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php'; include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php';
@ -344,7 +348,7 @@ print '<form method="POST" id="searchFormList" action="'.$_SERVER["PHP_SELF"].'"
if ($optioncss != '') print '<input type="hidden" name="optioncss" value="'.$optioncss.'">'; if ($optioncss != '') print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">'; print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">'; print '<input type="hidden" name="formfilteraction" id="formfilteraction" value="list">';
print '<input type="hidden" name="action" value="list">'; print '<input type="hidden" name="action" value="'.($action == 'create' ? 'add' : 'list').'">';
print '<input type="hidden" name="sortfield" value="'.$sortfield.'">'; print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
print '<input type="hidden" name="sortorder" value="'.$sortorder.'">'; print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
print '<input type="hidden" name="page" value="'.$page.'">'; print '<input type="hidden" name="page" value="'.$page.'">';
@ -354,7 +358,7 @@ $newcardbutton = '';
if ($action != 'create') { if ($action != 'create') {
$newcardbutton = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $_SERVER['PHP_SELF'].'?action=create', '', $permissiontoadd); $newcardbutton = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $_SERVER['PHP_SELF'].'?action=create', '', $permissiontoadd);
} else { } else {
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">'; /*print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
if ($optioncss != '') print '<input type="hidden" name="optioncss" value="'.$optioncss.'">'; if ($optioncss != '') print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">'; print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
print '<input type="hidden" name="action" value="add">'; print '<input type="hidden" name="action" value="add">';
@ -362,13 +366,18 @@ if ($action != 'create') {
print '<input type="hidden" name="sortorder" value="'.$sortorder.'">'; print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
print '<input type="hidden" name="page" value="'.$page.'">'; print '<input type="hidden" name="page" value="'.$page.'">';
print '<input type="hidden" name="contextpage" value="'.$contextpage.'">'; print '<input type="hidden" name="contextpage" value="'.$contextpage.'">';
*/
print '<table class="border centpercent tableforfield">'; print '<table class="border centpercent tableforfield">';
print '<tr><td>'.$langs->trans("Label").'</td><td><input type="text" name="label" value="'.GETPOST('label', 'alphanohtml').'"></td></tr>'; print '<tr><td>'.$langs->trans("Label").'</td><td><input type="text" name="label" value="'.GETPOST('label', 'alphanohtml').'"></td></tr>';
print '<tr><td>'.$langs->trans("Email").'</td><td><input type="text" name="email" value="'.GETPOST('email', 'alphanohtml').'"></td></tr>'; print '<tr><td>'.$langs->trans("Email").'</td><td><input type="text" name="email" value="'.GETPOST('email', 'alphanohtml').'"></td></tr>';
print '<tr><td>'.$langs->trans("Signature").'</td><td><textarea name="signature">'.GETPOST('signature', 'none').'</textarea></td></tr>'; print '<tr><td>'.$langs->trans("Signature").'</td><td>';
print '<tr><td>'.$langs->trans("Position").'</td><td><input type="text" name="label" class="maxwidth50" value="'.GETPOST('position', 'int').'"></td></tr>'; require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
$doleditor = new DolEditor('signature', GETPOST('signature'), '', 138, 'dolibarr_notes', 'In', true, true, empty($conf->global->FCKEDITOR_ENABLE_USERSIGN) ? 0 : 1, ROWS_4, '90%');
print $doleditor->Create(1);
print '</td></tr>';
print '<tr><td>'.$langs->trans("Position").'</td><td><input type="text" name="position" class="maxwidth50" value="'.GETPOST('position', 'int').'"></td></tr>';
print '<tr><td>'.$langs->trans("Status").'</td><td>'; print '<tr><td>'.$langs->trans("Status").'</td><td>';
print '<input type="text" name="status" value="'.GETPOST('status', 'int').'">'; print $form->selectyesno('active', GETPOST('active', 'int'), 1);
print '</td></tr>'; print '</td></tr>';
print '</table>'; print '</table>';
print '<br>'; print '<br>';
@ -377,7 +386,7 @@ if ($action != 'create') {
print ' &nbsp; '; print ' &nbsp; ';
print '<input class="button" type="submit" name="cancel" value="'.$langs->trans("Cancel").'">'; print '<input class="button" type="submit" name="cancel" value="'.$langs->trans("Cancel").'">';
print '</div>'; print '</div>';
print '</form>'; //print '</form>';
} }
print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_companies', 0, $newcardbutton, '', $limit); print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_companies', 0, $newcardbutton, '', $limit);

View File

@ -1437,7 +1437,7 @@ if ($id > 0)
// related contact // related contact
print '<tr><td>'.$langs->trans("ActionOnContact").'</td><td>'; print '<tr><td>'.$langs->trans("ActionOnContact").'</td><td>';
print '<div class="maxwidth200onsmartphone">'; print '<div class="maxwidth200onsmartphone">';
print $form->selectcontacts($object->socid, array_keys($object->socpeopleassigned), 'socpeopleassigned[]', 1, '', '', 0, 'quatrevingtpercent', false, 0, 0, array(), 'multiple', 'contactid'); print $form->selectcontacts($object->socid, array_keys($object->socpeopleassigned), 'socpeopleassigned[]', 1, '', '', 1, 'quatrevingtpercent', false, 0, 0, array(), 'multiple', 'contactid');
print '</div>'; print '</div>';
print '</td>'; print '</td>';
print '</tr>'; print '</tr>';

View File

@ -294,7 +294,6 @@ class FormAdvTargetEmailing extends Form
$options_array = array(); $options_array = array();
$sql = "SELECT rowid, code, label as civilite, active FROM ".MAIN_DB_PREFIX."c_civility"; $sql = "SELECT rowid, code, label as civilite, active FROM ".MAIN_DB_PREFIX."c_civility";
$sql .= " WHERE active = 1"; $sql .= " WHERE active = 1";
@ -312,7 +311,6 @@ class FormAdvTargetEmailing extends Form
// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
$label = ($langs->trans("Civility".$obj->code) != "Civility".$obj->code ? $langs->trans("Civility".$obj->code) : ($obj->civilite != '-' ? $obj->civilite : '')); $label = ($langs->trans("Civility".$obj->code) != "Civility".$obj->code ? $langs->trans("Civility".$obj->code) : ($obj->civilite != '-' ? $obj->civilite : ''));
$options_array[$obj->code] = $label; $options_array[$obj->code] = $label;
$i++; $i++;

View File

@ -736,11 +736,10 @@ if ($socid > 0)
// Discount linked to invoice lines // Discount linked to invoice lines
$sql = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx, rc.multicurrency_amount_ht, rc.multicurrency_amount_tva, rc.multicurrency_amount_ttc,"; $sql = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx, rc.multicurrency_amount_ht, rc.multicurrency_amount_tva, rc.multicurrency_amount_ttc,";
$sql .= " rc.datec as dc, rc.description, rc.fk_facture_line, rc.fk_facture,";
$sql .= " rc.fk_facture_source,"; $sql .= " rc.fk_facture_source,";
$sql .= " u.login, u.rowid as user_id,"; $sql .= " u.login, u.rowid as user_id,";
$sql .= " f.rowid, f.ref,"; $sql .= " f.rowid as invoiceid, f.ref,";
$sql .= " fa.ref as ref, fa.type as type"; $sql .= " fa.ref as invoice_source_ref, fa.type as type";
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
$sql .= " , ".MAIN_DB_PREFIX."user as u"; $sql .= " , ".MAIN_DB_PREFIX."user as u";
$sql .= " , ".MAIN_DB_PREFIX."facturedet as fc"; $sql .= " , ".MAIN_DB_PREFIX."facturedet as fc";
@ -753,13 +752,13 @@ if ($socid > 0)
$sql .= " AND rc.discount_type = 0"; // Eliminate supplier discounts $sql .= " AND rc.discount_type = 0"; // Eliminate supplier discounts
$sql .= " ORDER BY dc DESC"; $sql .= " ORDER BY dc DESC";
//$sql.= " UNION "; //$sql.= " UNION ";
// Remises liees a factures // Discount linked to invoices
$sql2 = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx,"; $sql2 = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx,";
$sql2 .= " rc.datec as dc, rc.description, rc.fk_facture_line, rc.fk_facture,"; $sql2 .= " rc.datec as dc, rc.description, rc.fk_facture_line, rc.fk_facture,";
$sql2 .= " rc.fk_facture_source,"; $sql2 .= " rc.fk_facture_source,";
$sql2 .= " u.login, u.rowid as user_id,"; $sql2 .= " u.login, u.rowid as user_id,";
$sql2 .= " f.rowid, f.ref,"; $sql2 .= " f.rowid as invoiceid, f.ref,";
$sql2 .= " fa.ref as ref, fa.type as type"; $sql2 .= " fa.ref as invoice_source_ref, fa.type as type";
$sql2 .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql2 .= " FROM ".MAIN_DB_PREFIX."facture as f";
$sql2 .= " , ".MAIN_DB_PREFIX."user as u"; $sql2 .= " , ".MAIN_DB_PREFIX."user as u";
$sql2 .= " , ".MAIN_DB_PREFIX."societe_remise_except as rc"; $sql2 .= " , ".MAIN_DB_PREFIX."societe_remise_except as rc";
@ -833,7 +832,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturestatic->id = $obj->fk_facture_source; $facturestatic->id = $obj->fk_facture_source;
$facturestatic->ref = $obj->ref; $facturestatic->ref = $obj->invoice_source_ref;
$facturestatic->type = $obj->type; $facturestatic->type = $obj->type;
print preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $obj->description).' '.$facturestatic->getNomURl(1); print preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $obj->description).' '.$facturestatic->getNomURl(1);
print '</td>'; print '</td>';
@ -842,7 +841,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturestatic->id = $obj->fk_facture_source; $facturestatic->id = $obj->fk_facture_source;
$facturestatic->ref = $obj->ref; $facturestatic->ref = $obj->invoice_source_ref;
$facturestatic->type = $obj->type; $facturestatic->type = $obj->type;
print preg_replace('/\(DEPOSIT\)/', $langs->trans("InvoiceDeposit"), $obj->description).' '.$facturestatic->getNomURl(1); print preg_replace('/\(DEPOSIT\)/', $langs->trans("InvoiceDeposit"), $obj->description).' '.$facturestatic->getNomURl(1);
print '</td>'; print '</td>';
@ -851,7 +850,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturestatic->id = $obj->fk_facture_source; $facturestatic->id = $obj->fk_facture_source;
$facturestatic->ref = $obj->ref; $facturestatic->ref = $obj->invoice_source_ref;
$facturestatic->type = $obj->type; $facturestatic->type = $obj->type;
print preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("Invoice"), $obj->description).' '.$facturestatic->getNomURl(1); print preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("Invoice"), $obj->description).' '.$facturestatic->getNomURl(1);
print '</td>'; print '</td>';
@ -862,7 +861,12 @@ if ($socid > 0)
print $obj->description; print $obj->description;
print '</td>'; print '</td>';
} }
print '<td class="left nowrap"><a href="'.DOL_URL_ROOT.'/compta/facture/card.php?facid='.$obj->rowid.'">'.img_object($langs->trans("ShowBill"), 'bill').' '.$obj->ref.'</a></td>'; print '<td class="left nowrap">';
if ($obj->invoiceid)
{
print '<a href="'.DOL_URL_ROOT.'/compta/facture/card.php?facid='.$obj->invoiceid.'">'.img_object($langs->trans("ShowBill"), 'bill').' '.$obj->ref.'</a>';
}
print '</td>';
print '<td class="right">'.price($obj->amount_ht).'</td>'; print '<td class="right">'.price($obj->amount_ht).'</td>';
if (!empty($conf->multicurrency->enabled)) if (!empty($conf->multicurrency->enabled))
{ {
@ -909,8 +913,8 @@ if ($socid > 0)
$sql .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier_line, rc.fk_invoice_supplier,"; $sql .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier_line, rc.fk_invoice_supplier,";
$sql .= " rc.fk_invoice_supplier_source,"; $sql .= " rc.fk_invoice_supplier_source,";
$sql .= " u.login, u.rowid as user_id,"; $sql .= " u.login, u.rowid as user_id,";
$sql .= " f.rowid, f.ref as ref,"; $sql .= " f.rowid as invoiceid, f.ref as ref,";
$sql .= " fa.ref, fa.type as type"; $sql .= " fa.ref as invoice_source_ref, fa.type as type";
$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
$sql .= " , ".MAIN_DB_PREFIX."user as u"; $sql .= " , ".MAIN_DB_PREFIX."user as u";
$sql .= " , ".MAIN_DB_PREFIX."facture_fourn_det as fc"; $sql .= " , ".MAIN_DB_PREFIX."facture_fourn_det as fc";
@ -923,13 +927,13 @@ if ($socid > 0)
$sql .= " AND rc.discount_type = 1"; // Eliminate customer discounts $sql .= " AND rc.discount_type = 1"; // Eliminate customer discounts
$sql .= " ORDER BY dc DESC"; $sql .= " ORDER BY dc DESC";
//$sql.= " UNION "; //$sql.= " UNION ";
// Remises liees a factures // Discount linked to invoices
$sql2 = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx,"; $sql2 = "SELECT rc.rowid, rc.amount_ht, rc.amount_tva, rc.amount_ttc, rc.tva_tx,";
$sql2 .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier_line, rc.fk_invoice_supplier,"; $sql2 .= " rc.datec as dc, rc.description, rc.fk_invoice_supplier_line, rc.fk_invoice_supplier,";
$sql2 .= " rc.fk_invoice_supplier_source,"; $sql2 .= " rc.fk_invoice_supplier_source,";
$sql2 .= " u.login, u.rowid as user_id,"; $sql2 .= " u.login, u.rowid as user_id,";
$sql2 .= " f.rowid, f.ref as ref,"; $sql2 .= " f.rowid as invoiceid, f.ref as ref,";
$sql2 .= " fa.ref, fa.type as type"; $sql2 .= " fa.ref as invoice_source_ref, fa.type as type";
$sql2 .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f"; $sql2 .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
$sql2 .= " , ".MAIN_DB_PREFIX."user as u"; $sql2 .= " , ".MAIN_DB_PREFIX."user as u";
$sql2 .= " , ".MAIN_DB_PREFIX."societe_remise_except as rc"; $sql2 .= " , ".MAIN_DB_PREFIX."societe_remise_except as rc";
@ -1003,7 +1007,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturefournstatic->id = $obj->fk_invoice_supplier_source; $facturefournstatic->id = $obj->fk_invoice_supplier_source;
$facturefournstatic->ref = $obj->ref; $facturefournstatic->ref = $obj->invoice_source_ref;
$facturefournstatic->type = $obj->type; $facturefournstatic->type = $obj->type;
print preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $obj->description).' '.$facturefournstatic->getNomURl(1); print preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $obj->description).' '.$facturefournstatic->getNomURl(1);
print '</td>'; print '</td>';
@ -1012,7 +1016,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturefournstatic->id = $obj->fk_invoice_supplier_source; $facturefournstatic->id = $obj->fk_invoice_supplier_source;
$facturefournstatic->ref = $obj->ref; $facturefournstatic->ref = $obj->invoice_source_ref;
$facturefournstatic->type = $obj->type; $facturefournstatic->type = $obj->type;
print preg_replace('/\(DEPOSIT\)/', $langs->trans("InvoiceDeposit"), $obj->description).' '.$facturefournstatic->getNomURl(1); print preg_replace('/\(DEPOSIT\)/', $langs->trans("InvoiceDeposit"), $obj->description).' '.$facturefournstatic->getNomURl(1);
print '</td>'; print '</td>';
@ -1021,7 +1025,7 @@ if ($socid > 0)
{ {
print '<td class="minwidth100">'; print '<td class="minwidth100">';
$facturefournstatic->id = $obj->fk_invoice_supplier_source; $facturefournstatic->id = $obj->fk_invoice_supplier_source;
$facturefournstatic->ref = $obj->ref; $facturefournstatic->ref = $obj->invoice_source_ref;
$facturefournstatic->type = $obj->type; $facturefournstatic->type = $obj->type;
print preg_replace('/\(EXCESS PAID\)/', $langs->trans("Invoice"), $obj->description).' '.$facturefournstatic->getNomURl(1); print preg_replace('/\(EXCESS PAID\)/', $langs->trans("Invoice"), $obj->description).' '.$facturefournstatic->getNomURl(1);
print '</td>'; print '</td>';
@ -1032,7 +1036,11 @@ if ($socid > 0)
print $obj->description; print $obj->description;
print '</td>'; print '</td>';
} }
print '<td class="left nowrap"><a href="'.DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$obj->rowid.'">'.img_object($langs->trans("ShowBill"), 'bill').' '.$obj->ref.'</a></td>'; print '<td class="left nowrap">';
if ($obj->invoiceid) {
print '<a href="'.DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$obj->invoiceid.'">'.img_object($langs->trans("ShowBill"), 'bill').' '.$obj->ref.'</a>';
}
print '</td>';
print '<td class="right">'.price($obj->amount_ht).'</td>'; print '<td class="right">'.price($obj->amount_ht).'</td>';
if (!empty($conf->multicurrency->enabled)) if (!empty($conf->multicurrency->enabled))
{ {

View File

@ -76,7 +76,7 @@ class Orders extends DolibarrApi
* @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
* @return array|mixed data without useless information * @return array|mixed data without useless information
* *
* @url GET byRef/{ref} * @url GET ref/{ref}
* *
* @throws RestException * @throws RestException
*/ */
@ -94,7 +94,7 @@ class Orders extends DolibarrApi
* @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
* @return array|mixed data without useless information * @return array|mixed data without useless information
* *
* @url GET byRefExt/{ref_ext} * @url GET ref_ext/{ref_ext}
* *
* @throws RestException * @throws RestException
*/ */

View File

@ -75,6 +75,7 @@ if ($action == 'add' && !empty($permissiontoadd))
if (preg_match('/^integer:/i', $object->fields[$key]['type']) && $value == '-1') $value = ''; // This is an implicit foreign key field if (preg_match('/^integer:/i', $object->fields[$key]['type']) && $value == '-1') $value = ''; // This is an implicit foreign key field
if (!empty($object->fields[$key]['foreignkey']) && $value == '-1') $value = ''; // This is an explicit foreign key field if (!empty($object->fields[$key]['foreignkey']) && $value == '-1') $value = ''; // This is an explicit foreign key field
//var_dump($key.' '.$value.' '.$object->fields[$key]['type']);
$object->$key = $value; $object->$key = $value;
if ($val['notnull'] > 0 && $object->$key == '' && !is_null($val['default']) && $val['default'] == '(PROV)') if ($val['notnull'] > 0 && $object->$key == '' && !is_null($val['default']) && $val['default'] == '(PROV)')
{ {

View File

@ -0,0 +1,190 @@
<?php
/* Copyright (C) 2003-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2009 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2005-2009 Regis Houssin <regis.houssin@inodbox.com>
* Copyright (C) 2019 Alexandre Spangaro <aspangaro@open-dsi.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/core/boxes/box_shipments.php
* \ingroup shipment
* \brief Module for generating the display of the shipment box
*/
include_once DOL_DOCUMENT_ROOT.'/core/boxes/modules_boxes.php';
/**
* Class to manage the box to show last shipments
*/
class box_shipments extends ModeleBoxes
{
public $boxcode="lastcustomershipments";
public $boximg="sending";
public $boxlabel="BoxLastCustomerShipments";
public $depends = array("expedition");
/**
* @var DoliDB Database handler.
*/
public $db;
public $param;
public $info_box_head = array();
public $info_box_contents = array();
/**
* Constructor
*
* @param DoliDB $db Database handler
* @param string $param More parameters
*/
public function __construct($db, $param)
{
global $user;
$this->db=$db;
$this->hidden=! ($user->rights->expedition->lire);
}
/**
* Load data for box to show them later
*
* @param int $max Maximum number of records to load
* @return void
*/
public function loadBox($max = 5)
{
global $user, $langs, $conf;
$langs->loadLangs(array('orders', 'sendings'));
$this->max = $max;
include_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
include_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
$shipmentstatic = new Expedition($this->db);
$orderstatic = new Commande($this->db);
$societestatic = new Societe($this->db);
$this->info_box_head = array('text' => $langs->trans("BoxTitleLastCustomerShipments", $max));
if ($user->rights->expedition->lire)
{
$sql = "SELECT s.nom as name";
$sql.= ", s.rowid as socid";
$sql.= ", s.code_client";
$sql.= ", s.logo, s.email";
$sql.= ", e.ref, e.tms";
$sql.= ", e.rowid";
$sql.= ", e.ref_customer";
$sql.= ", e.fk_statut";
$sql.= ", e.fk_user_valid";
$sql.= ", c.ref as commande_ref";
$sql.= ", c.rowid as commande_id";
$sql.= " FROM ".MAIN_DB_PREFIX."expedition as e";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON e.rowid = el.fk_target AND el.targettype = 'shipping' AND el.sourcetype IN ('commande')";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid AND el.sourcetype IN ('commande') AND el.targettype = 'shipping'";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = e.fk_soc";
if (!$user->rights->societe->client->voir && !$user->societe_id) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON e.fk_soc = sc.fk_soc";
$sql.= " WHERE e.entity = ".$conf->entity;
if (! empty($conf->global->ORDER_BOX_LAST_SHIPMENTS_VALIDATED_ONLY)) $sql.=" AND e.fk_statut = 1";
if (!$user->rights->societe->client->voir && !$user->societe_id) $sql.= " AND sc.fk_user = " .$user->id;
else $sql.= " ORDER BY e.date_delivery, e.ref DESC ";
$sql.= $this->db->plimit($max, 0);
$result = $this->db->query($sql);
if ($result) {
$num = $this->db->num_rows($result);
$line = 0;
while ($line < $num) {
$objp = $this->db->fetch_object($result);
$shipmentstatic->id = $objp->rowid;
$shipmentstatic->ref = $objp->ref;
$shipmentstatic->ref_customer = $objp->ref_customer;
$orderstatic->id= $objp->commande_id;
$orderstatic->ref=$objp->commande_ref;
$societestatic->id = $objp->socid;
$societestatic->name = $objp->name;
$societestatic->email = $objp->email;
$societestatic->code_client = $objp->code_client;
$societestatic->logo = $objp->logo;
$this->info_box_contents[$line][] = array(
'td' => '',
'text' => $shipmentstatic->getNomUrl(1),
'asis' => 1,
);
$this->info_box_contents[$line][] = array(
'td' => 'class="tdoverflowmax150 maxwidth150onsmartphone"',
'text' => $societestatic->getNomUrl(1),
'asis' => 1,
);
$this->info_box_contents[$line][] = array(
'td' => '',
'text' => $orderstatic->getNomUrl(1),
'asis' => 1,
);
$this->info_box_contents[$line][] = array(
'td' => 'class="right" width="18"',
'text' => $shipmentstatic->LibStatut($objp->fk_statut, 3),
);
$line++;
}
if ($num==0) $this->info_box_contents[$line][0] = array('td' => 'class="center"','text'=>$langs->trans("NoRecordedShipments"));
$this->db->free($result);
} else {
$this->info_box_contents[0][0] = array(
'td' => '',
'maxlength'=>500,
'text' => ($this->db->error().' sql='.$sql),
);
}
} else {
$this->info_box_contents[0][0] = array(
'td' => 'class="nohover opacitymedium left"',
'text' => $langs->trans("ReadPermissionNotAllowed")
);
}
}
/**
* Method to show box
*
* @param array $head Array with properties of box title
* @param array $contents Array with properties of box lines
* @param int $nooutput No print, only return string
* @return string
*/
public function showBox($head = null, $contents = null, $nooutput = 0)
{
return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
}
}

View File

@ -6571,7 +6571,8 @@ abstract class CommonObject
/** /**
* Function to show lines of extrafields with output datas * Function to show lines of extrafields with output datas.
* This function is responsible to output the <tr> and <td> according to correct number of columns received into $params['colspan']
* *
* @param Extrafields $extrafields Extrafield Object * @param Extrafields $extrafields Extrafield Object
* @param string $mode Show output (view) or input (edit) for extrafield * @param string $mode Show output (view) or input (edit) for extrafield

View File

@ -56,19 +56,28 @@ class EmailSenderProfile extends CommonObject
public $picto = 'emailsenderprofile@monmodule'; public $picto = 'emailsenderprofile@monmodule';
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
/** /**
* 'type' if the field format. * 'type' if the field format ('integer', 'integer:Class:pathtoclass', 'varchar(x)', 'double(24,8)', 'text', 'html', 'datetime', 'timestamp', 'float')
* 'label' the translation key. * 'label' the translation key.
* 'enabled' is a condition when the filed must be managed. * 'enabled' is a condition when the field must be managed.
* 'visible' says if field is visible in list (-1 means not shown by default but can be added into list to be viewed). * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). Using a negative value means field is not shown by default on list but can be selected for viewing)
* 'noteditable' says if field is not editable (1 or 0)
* 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
* 'default' is a default value for creation (can still be replaced by the global setup of default values)
* 'index' if we want an index in database. * 'index' if we want an index in database.
* 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...). * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
* 'position' is the sort order of field. * 'position' is the sort order of field.
* 'searchall' is 1 if we want to search in this field when making a search from the quick search button. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
* 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8). * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
* 'css' is the CSS style to use on field. For example: 'maxwidth200'
* 'help' is a string visible as a tooltip on field * 'help' is a string visible as a tooltip on field
* 'comment' is not used. You can store here any text of your choice. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
* 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
* 'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
*/ */
// BEGIN MODULEBUILDER PROPERTIES // BEGIN MODULEBUILDER PROPERTIES
@ -78,7 +87,7 @@ class EmailSenderProfile extends CommonObject
public $fields=array( public $fields=array(
'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'visible'=>-1, 'enabled'=>1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>'Id',), 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'visible'=>-1, 'enabled'=>1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>'Id',),
'entity' => array('type'=>'integer', 'label'=>'Entity', 'visible'=>-1, 'enabled'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,), 'entity' => array('type'=>'integer', 'label'=>'Entity', 'visible'=>-1, 'enabled'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,),
'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'visible'=>1, 'enabled'=>1, 'position'=>30, 'notnull'=>-1), 'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'visible'=>1, 'enabled'=>1, 'position'=>30, 'notnull'=>1),
'email' => array('type'=>'varchar(255)', 'label'=>'Email', 'visible'=>1, 'enabled'=>1, 'position'=>40, 'notnull'=>-1), 'email' => array('type'=>'varchar(255)', 'label'=>'Email', 'visible'=>1, 'enabled'=>1, 'position'=>40, 'notnull'=>-1),
//'fk_user_creat' => array('type'=>'integer', 'label'=>'UserAuthor', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,), //'fk_user_creat' => array('type'=>'integer', 'label'=>'UserAuthor', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,),
//'fk_user_modif' => array('type'=>'integer', 'label'=>'UserModif', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>-1,), //'fk_user_modif' => array('type'=>'integer', 'label'=>'UserModif', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>-1,),
@ -86,7 +95,7 @@ class EmailSenderProfile extends CommonObject
'position' => array('type'=>'integer', 'label'=>'Position', 'visible'=>1, 'enabled'=>1, 'position'=>405, 'notnull'=>-1, 'index'=>1,), 'position' => array('type'=>'integer', 'label'=>'Position', 'visible'=>1, 'enabled'=>1, 'position'=>405, 'notnull'=>-1, 'index'=>1,),
'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,), 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,),
'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,), 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'visible'=>-1, 'enabled'=>1, 'position'=>500, 'notnull'=>1,),
'active' => array('type'=>'integer', 'label'=>'Status', 'visible'=>1, 'enabled'=>1, 'position'=>1000, 'notnull'=>-1, 'index'=>1), 'active' => array('type'=>'integer', 'label'=>'Status', 'visible'=>1, 'enabled'=>1, 'default'=>1, 'position'=>1000, 'notnull'=>-1, 'index'=>1),
); );
/** /**

View File

@ -6521,6 +6521,7 @@ class Form
elseif ($addjscombo == 2) elseif ($addjscombo == 2)
{ {
// Add other js lib // Add other js lib
// TODO external lib multiselect/jquery.multi-select.js must have been loaded to use this multiselect plugin
// ... // ...
$out .= '$(document).ready(function () { $out .= '$(document).ready(function () {
$(\'#'.$htmlname.'\').multiSelect({ $(\'#'.$htmlname.'\').multiSelect({

View File

@ -133,7 +133,9 @@ class modExpedition extends DolibarrModules
$r++; $r++;
// Boxes // Boxes
$this->boxes = array(); $this->boxes = array(
0=>array('file'=>'box_shipments.php','enabledbydefaulton'=>'Home'),
);
// Permissions // Permissions
$this->rights = array(); $this->rights = array();

View File

@ -667,6 +667,7 @@ class EmailCollector extends CommonObject
/** /**
* Return the connectstring to use with IMAP connection function * Return the connectstring to use with IMAP connection function
* *
* @param int $ssl Add /ssl tag
* @param int $norsh Add /norsh to connectstring * @param int $norsh Add /norsh to connectstring
* @return string * @return string
*/ */
@ -693,21 +694,20 @@ class EmailCollector extends CommonObject
* @param string $str String to encode * @param string $str String to encode
* @return string Encode string * @return string Encode string
*/ */
function getEncodedUtf7($str) { public function getEncodedUtf7($str)
{
if (function_exists('mb_convert_encoding')) { if (function_exists('mb_convert_encoding')) {
# change spaces by entropy because mb_convert fail with spaces // change spaces by entropy because mb_convert fail with spaces
$str=preg_replace("/ /", "xyxy", $str); $str=preg_replace("/ /", "xyxy", $str);
# if mb_convert work // if mb_convert work
if ($str = mb_convert_encoding($str, "UTF-7")) { if ($str = mb_convert_encoding($str, "UTF-7")) {
# change characters // change characters
$str=preg_replace("/\+A/", "&A", $str); $str=preg_replace("/\+A/", "&A", $str);
# change to spaces again // change to spaces again
$str=preg_replace("/xyxy/", " ", $str); $str=preg_replace("/xyxy/", " ", $str);
# return encoded string
return $str; return $str;
# else
} else { } else {
# print error and return false // print error and return false
$this->error = "error: is not possible to encode this string '".$str."'"; $this->error = "error: is not possible to encode this string '".$str."'";
return false; return false;
} }

View File

@ -2459,10 +2459,10 @@ elseif ($id || $ref)
if (!empty($conf->productbatch->enabled)) $colspan++; if (!empty($conf->productbatch->enabled)) $colspan++;
if (!empty($conf->stock->enabled)) $colspan++; if (!empty($conf->stock->enabled)) $colspan++;
$lines[$i]->fetch_optionals($lines[$i]->id); $line = $lines[$i];
$line->fetch_optionals($line->id);
print '<tr class="oddeven">'; if ($action == 'editline' && $line->id == $line_id)
if ($action == 'editline' && $lines[$i]->id == $line_id)
{ {
print $lines[$i]->showOptionals($extrafields, 'edit', array('colspan'=>$colspan), $indiceAsked); print $lines[$i]->showOptionals($extrafields, 'edit', array('colspan'=>$colspan), $indiceAsked);
} }
@ -2470,7 +2470,6 @@ elseif ($id || $ref)
{ {
print $lines[$i]->showOptionals($extrafields, 'view', array('colspan'=>$colspan), $indiceAsked); print $lines[$i]->showOptionals($extrafields, 'view', array('colspan'=>$colspan), $indiceAsked);
} }
print '</tr>';
} }
} }
} }

View File

@ -2674,11 +2674,14 @@ else
else $calculationrule = (empty($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND) ? 'totalofround' : 'roundoftotal'); else $calculationrule = (empty($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND) ? 'totalofround' : 'roundoftotal');
if ($calculationrule == 'totalofround') $calculationrulenum = 1; if ($calculationrule == 'totalofround') $calculationrulenum = 1;
else $calculationrulenum = 2; else $calculationrulenum = 2;
// Show link for "recalculate"
if (empty($object->getVentilExportCompta())) {
$s = $langs->trans("ReCalculate").' '; $s = $langs->trans("ReCalculate").' ';
$s .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=calculate&calculationrule=totalofround">'.$langs->trans("Mode1").'</a>'; $s .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=calculate&calculationrule=totalofround">'.$langs->trans("Mode1").'</a>';
$s .= ' / '; $s .= ' / ';
$s .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=calculate&calculationrule=roundoftotal">'.$langs->trans("Mode2").'</a>'; $s .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=calculate&calculationrule=roundoftotal">'.$langs->trans("Mode2").'</a>';
print $form->textwithtooltip($s, $langs->trans("CalculationRuleDesc", $calculationrulenum).'<br>'.$langs->trans("CalculationRuleDescSupplier"), 2, 1, img_picto('', 'help')); print $form->textwithtooltip($s, $langs->trans("CalculationRuleDesc", $calculationrulenum).'<br>'.$langs->trans("CalculationRuleDescSupplier"), 2, 1, img_picto('', 'help'));
}
print '</div></td></tr>'; print '</div></td></tr>';
// Amount Local Taxes // Amount Local Taxes

View File

@ -1,360 +0,0 @@
// jquery.multi-select.js
// by mySociety
// https://github.com/mysociety/jquery-multi-select
;(function($) {
"use strict";
var pluginName = "multiSelect",
defaults = {
'containerHTML': '<div class="multi-select-container">',
'menuHTML': '<div class="multi-select-menu">',
'buttonHTML': '<span class="multi-select-button">',
'menuItemsHTML': '<div class="multi-select-menuitems">',
'menuItemHTML': '<label class="multi-select-menuitem">',
'presetsHTML': '<div class="multi-select-presets">',
'activeClass': 'multi-select-container--open',
'noneText': '-- Select --',
'allText': undefined,
'presets': undefined,
'positionedMenuClass': 'multi-select-container--positioned',
'positionMenuWithin': undefined,
'viewportBottomGutter': 20,
'menuMinHeight': 200
};
/**
* @constructor
*/
function MultiSelect(element, options) {
this.element = element;
this.$element = $(element);
this.settings = $.extend( {}, defaults, options );
this._defaults = defaults;
this._name = pluginName;
this.init();
}
function arraysAreEqual(array1, array2) {
if ( array1.length != array2.length ){
return false;
}
array1.sort();
array2.sort();
for ( var i = 0; i < array1.length; i++ ){
if ( array1[i] !== array2[i] ){
return false;
}
}
return true;
}
$.extend(MultiSelect.prototype, {
init: function() {
this.checkSuitableInput();
this.findLabels();
this.constructContainer();
this.constructButton();
this.constructMenu();
this.setUpBodyClickListener();
this.setUpLabelsClickListener();
this.$element.hide();
},
checkSuitableInput: function(text) {
if ( this.$element.is('select[multiple]') === false ) {
throw new Error('$.multiSelect only works on <select multiple> elements');
}
},
findLabels: function() {
this.$labels = $('label[for="' + this.$element.attr('id') + '"]');
},
constructContainer: function() {
this.$container = $(this.settings['containerHTML']);
this.$element.data('multi-select-container', this.$container);
this.$container.insertAfter(this.$element);
},
constructButton: function() {
var _this = this;
this.$button = $(this.settings['buttonHTML']);
this.$button.attr({
'role': 'button',
'aria-haspopup': 'true',
'tabindex': 0,
'aria-label': this.$labels.eq(0).text()
})
.on('keydown.multiselect', function(e) {
var key = e.which;
var returnKey = 13;
var spaceKey = 32;
if ((key === returnKey) || (key === spaceKey)) {
_this.$button.click();
}
}).on('click.multiselect', function(e) {
_this.menuToggle();
})
.appendTo(this.$container);
this.$element.on('change.multiselect', function() {
_this.updateButtonContents();
});
this.updateButtonContents();
},
updateButtonContents: function() {
var _this = this;
var options = [];
var selected = [];
this.$element.children('option').each(function() {
var text = /** @type string */ ($(this).text());
options.push(text);
if ($(this).is(':selected')) {
selected.push( $.trim(text) );
}
});
this.$button.empty();
if (selected.length == 0) {
this.$button.text( this.settings['noneText'] );
} else if ( (selected.length === options.length) && this.settings['allText']) {
this.$button.text( this.settings['allText'] );
} else {
this.$button.text( selected.join(', ') );
}
},
constructMenu: function() {
var _this = this;
this.$menu = $(this.settings['menuHTML']);
this.$menu.attr({
'role': 'menu'
}).on('keyup.multiselect', function(e){
var key = e.which;
var escapeKey = 27;
if (key === escapeKey) {
_this.menuHide();
}
})
.appendTo(this.$container);
this.constructMenuItems();
if ( this.settings['presets'] ) {
this.constructPresets();
}
},
constructMenuItems: function() {
var _this = this;
this.$menuItems = $(this.settings['menuItemsHTML']);
this.$menu.append(this.$menuItems);
this.$element.on('change.multiselect', function(e, internal) {
// Don't need to update the menu items if this
// change event was fired by our tickbox handler.
if(internal !== true){
_this.updateMenuItems();
}
});
this.updateMenuItems();
},
updateMenuItems: function() {
var _this = this;
this.$menuItems.empty();
this.$element.children('option').each(function(option_index, option) {
var $item = _this.constructMenuItem($(option), option_index);
_this.$menuItems.append($item);
});
},
constructPresets: function() {
var _this = this;
this.$presets = $(this.settings['presetsHTML']);
this.$menu.prepend(this.$presets);
$.each(this.settings['presets'], function(i, preset){
var unique_id = _this.$element.attr('name') + '_preset_' + i;
var $item = $(_this.settings['menuItemHTML'])
.attr({
'for': unique_id,
'role': 'menuitem'
})
.text(' ' + preset.name)
.appendTo(_this.$presets);
var $input = $('<input>')
.attr({
'type': 'radio',
'name': _this.$element.attr('name') + '_presets',
'id': unique_id
})
.prependTo($item);
$input.on('change.multiselect', function(){
_this.$element.val(preset.options);
_this.$element.trigger('change');
});
});
this.$element.on('change.multiselect', function() {
_this.updatePresets();
});
this.updatePresets();
},
updatePresets: function() {
var _this = this;
$.each(this.settings['presets'], function(i, preset){
var unique_id = _this.$element.attr('name') + '_preset_' + i;
var $input = _this.$presets.find('#' + unique_id);
if ( arraysAreEqual(preset.options || [], _this.$element.val() || []) ){
$input.prop('checked', true);
} else {
$input.prop('checked', false);
}
});
},
constructMenuItem: function($option, option_index) {
var unique_id = this.$element.attr('name') + '_' + option_index;
var $item = $(this.settings['menuItemHTML'])
.attr({
'for': unique_id,
'role': 'menuitem'
})
.text(' ' + $option.text());
var $input = $('<input>')
.attr({
'type': 'checkbox',
'id': unique_id,
'value': $option.val()
})
.prependTo($item);
if ( $option.is(':disabled') ) {
$input.attr('disabled', 'disabled');
}
if ( $option.is(':selected') ) {
$input.prop('checked', 'checked');
}
$input.on('change.multiselect', function() {
if ($(this).prop('checked')) {
$option.prop('selected', true);
} else {
$option.prop('selected', false);
}
// .prop() on its own doesn't generate a change event.
// Other plugins might want to do stuff onChange.
$option.trigger('change', [true]);
});
return $item;
},
setUpBodyClickListener: function() {
var _this = this;
// Hide the $menu when you click outside of it.
$('html').on('click.multiselect', function(){
_this.menuHide();
});
// Stop click events from inside the $button or $menu from
// bubbling up to the body and closing the menu!
this.$container.on('click.multiselect', function(e){
e.stopPropagation();
});
},
setUpLabelsClickListener: function() {
var _this = this;
this.$labels.on('click.multiselect', function(e) {
e.preventDefault();
e.stopPropagation();
_this.menuToggle();
});
},
menuShow: function() {
$('html').trigger('click.multiselect'); // Close any other open menus
this.$container.addClass(this.settings['activeClass']);
if ( this.settings['positionMenuWithin'] && this.settings['positionMenuWithin'] instanceof $ ) {
var menuLeftEdge = this.$menu.offset().left + this.$menu.outerWidth();
var withinLeftEdge = this.settings['positionMenuWithin'].offset().left +
this.settings['positionMenuWithin'].outerWidth();
if ( menuLeftEdge > withinLeftEdge ) {
this.$menu.css( 'width', (withinLeftEdge - this.$menu.offset().left) );
this.$container.addClass(this.settings['positionedMenuClass']);
}
}
var menuBottom = this.$menu.offset().top + this.$menu.outerHeight();
var viewportBottom = $(window).scrollTop() + $(window).height();
if ( menuBottom > viewportBottom - this.settings['viewportBottomGutter'] ) {
this.$menu.css({
'maxHeight': Math.max(
viewportBottom - this.settings['viewportBottomGutter'] - this.$menu.offset().top,
this.settings['menuMinHeight']
),
'overflow': 'scroll'
});
} else {
this.$menu.css({
'maxHeight': '',
'overflow': ''
});
}
},
menuHide: function() {
this.$container.removeClass(this.settings['activeClass']);
this.$container.removeClass(this.settings['positionedMenuClass']);
this.$menu.css('width', 'auto');
},
menuToggle: function() {
if ( this.$container.hasClass(this.settings['activeClass']) ) {
this.menuHide();
} else {
this.menuShow();
}
}
});
$.fn[ pluginName ] = function(options) {
return this.each(function() {
if ( !$.data(this, "plugin_" + pluginName) ) {
$.data(this, "plugin_" + pluginName,
new MultiSelect(this, options) );
}
});
};
})(jQuery);

View File

@ -1,9 +0,0 @@
(function(c){function f(b,a){this.b=c(b);this.a=c.extend({},g,a);this.H()}var g={containerHTML:'<div class="multi-select-container">',menuHTML:'<div class="multi-select-menu">',buttonHTML:'<span class="multi-select-button">',menuItemsHTML:'<div class="multi-select-menuitems">',menuItemHTML:'<label class="multi-select-menuitem">',presetsHTML:'<div class="multi-select-presets">',activeClass:"multi-select-container--open",noneText:"-- Select --",allText:void 0,presets:void 0,positionedMenuClass:"multi-select-container--positioned",
positionMenuWithin:void 0,viewportBottomGutter:20,menuMinHeight:200};c.extend(f.prototype,{H:function(){this.v();this.G();this.A();this.w();this.B();this.J();this.K();this.b.hide()},v:function(){if(!1===this.b.is("select[multiple]"))throw Error("$.multiSelect only works on <select multiple> elements");},G:function(){this.l=c('label[for="'+this.b.attr("id")+'"]')},A:function(){this.f=c(this.a.containerHTML);this.b.data("multi-select-container",this.f);this.f.insertAfter(this.b)},w:function(){var b=
this;this.g=c(this.a.buttonHTML);this.g.attr({role:"button","aria-haspopup":"true",tabindex:0,"aria-label":this.l.eq(0).text()}).on("keydown.multiselect",function(a){a=a.which;13!==a&&32!==a||b.g.click()}).on("click.multiselect",function(){b.m()}).appendTo(this.f);this.b.on("change.multiselect",function(){b.o()});this.o()},o:function(){var b=[],a=[];this.b.children("option").each(function(){var d=c(this).text();b.push(d);c(this).is(":selected")&&a.push(c.trim(d))});this.g.empty();0==a.length?this.g.text(this.a.noneText):
a.length===b.length&&this.a.allText?this.g.text(this.a.allText):this.g.text(a.join(", "))},B:function(){var b=this;this.c=c(this.a.menuHTML);this.c.attr({role:"menu"}).on("keyup.multiselect",function(a){27===a.which&&b.j()}).appendTo(this.f);this.D();this.a.presets&&this.F()},D:function(){var b=this;this.h=c(this.a.menuItemsHTML);this.c.append(this.h);this.b.on("change.multiselect",function(a,c){!0!==c&&b.s()});this.s()},s:function(){var b=this;this.h.empty();this.b.children("option").each(function(a,
d){a=b.C(c(d),a);b.h.append(a)})},F:function(){var b=this;this.i=c(this.a.presetsHTML);this.c.prepend(this.i);c.each(this.a.presets,function(a,d){a=b.b.attr("name")+"_preset_"+a;var h=c(b.a.menuItemHTML).attr({"for":a,role:"menuitem"}).text(" "+d.name).appendTo(b.i);c("<input>").attr({type:"radio",name:b.b.attr("name")+"_presets",id:a}).prependTo(h).on("change.multiselect",function(){b.b.val(d.options);b.b.trigger("change")})});this.b.on("change.multiselect",function(){b.u()});this.u()},u:function(){var b=
this;c.each(this.a.presets,function(a,c){a=b.b.attr("name")+"_preset_"+a;a=b.i.find("#"+a);a:{c=c.options||[];var d=b.b.val()||[];if(c.length!=d.length)c=!1;else{c.sort();d.sort();for(var e=0;e<c.length;e++)if(c[e]!==d[e]){c=!1;break a}c=!0}}c?a.prop("checked",!0):a.prop("checked",!1)})},C:function(b,a){var d=this.b.attr("name")+"_"+a;a=c(this.a.menuItemHTML).attr({"for":d,role:"menuitem"}).text(" "+b.text());d=c("<input>").attr({type:"checkbox",id:d,value:b.val()}).prependTo(a);b.is(":disabled")&&
d.attr("disabled","disabled");b.is(":selected")&&d.prop("checked","checked");d.on("change.multiselect",function(){c(this).prop("checked")?b.prop("selected",!0):b.prop("selected",!1);b.trigger("change",[!0])});return a},J:function(){var b=this;c("html").on("click.multiselect",function(){b.j()});this.f.on("click.multiselect",function(a){a.stopPropagation()})},K:function(){var b=this;this.l.on("click.multiselect",function(a){a.preventDefault();a.stopPropagation();b.m()})},I:function(){c("html").trigger("click.multiselect");
this.f.addClass(this.a.activeClass);if(this.a.positionMenuWithin&&this.a.positionMenuWithin instanceof c){var b=this.c.offset().left+this.c.outerWidth(),a=this.a.positionMenuWithin.offset().left+this.a.positionMenuWithin.outerWidth();b>a&&(this.c.css("width",a-this.c.offset().left),this.f.addClass(this.a.positionedMenuClass))}b=this.c.offset().top+this.c.outerHeight();a=c(window).scrollTop()+c(window).height();b>a-this.a.viewportBottomGutter?this.c.css({maxHeight:Math.max(a-this.a.viewportBottomGutter-
this.c.offset().top,this.a.menuMinHeight),overflow:"scroll"}):this.c.css({maxHeight:"",overflow:""})},j:function(){this.f.removeClass(this.a.activeClass);this.f.removeClass(this.a.positionedMenuClass);this.c.css("width","auto")},m:function(){this.f.hasClass(this.a.activeClass)?this.j():this.I()}});c.fn.multiSelect=function(b){return this.each(function(){c.data(this,"plugin_multiSelect")||c.data(this,"plugin_multiSelect",new f(this,b))})}})(jQuery);

View File

@ -59,6 +59,7 @@ ALTER TABLE llx_emailcollector_emailcollectoraction ADD COLUMN position integer
-- For v11 -- For v11
ALTER TABLE llx_c_email_senderprofile MODIFY COLUMN active tinyint DEFAULT 1 NOT NULL;
insert into llx_c_type_container (code,label,module,active) values ('menu', 'Menu', 'system', 1); insert into llx_c_type_container (code,label,module,active) values ('menu', 'Menu', 'system', 1);

View File

@ -97,3 +97,6 @@ BoxSuspenseAccount=Count accountancy operation with suspense account
BoxTitleSuspenseAccount=Number of unallocated lines BoxTitleSuspenseAccount=Number of unallocated lines
NumberOfLinesInSuspenseAccount=Number of line in suspense account NumberOfLinesInSuspenseAccount=Number of line in suspense account
SuspenseAccountNotDefined=Suspense account isn't defined SuspenseAccountNotDefined=Suspense account isn't defined
BoxLastCustomerShipments=Last customer shipments
BoxTitleLastCustomerShipments=Latest %s customer shipments
NoRecordedShipments=No recorded customer shipment

View File

@ -1426,10 +1426,6 @@ function top_htmlhead($head, $title = '', $disablejs = 0, $disablehead = 0, $arr
{ {
$tmpplugin=empty($conf->global->MAIN_USE_JQUERY_MULTISELECT)?constant('REQUIRE_JQUERY_MULTISELECT'):$conf->global->MAIN_USE_JQUERY_MULTISELECT; $tmpplugin=empty($conf->global->MAIN_USE_JQUERY_MULTISELECT)?constant('REQUIRE_JQUERY_MULTISELECT'):$conf->global->MAIN_USE_JQUERY_MULTISELECT;
print '<script src="'.DOL_URL_ROOT.'/includes/jquery/plugins/'.$tmpplugin.'/dist/js/'.$tmpplugin.'.full.min.js'.($ext?'?'.$ext:'').'"></script>'."\n"; // We include full because we need the support of containerCssClass print '<script src="'.DOL_URL_ROOT.'/includes/jquery/plugins/'.$tmpplugin.'/dist/js/'.$tmpplugin.'.full.min.js'.($ext?'?'.$ext:'').'"></script>'."\n"; // We include full because we need the support of containerCssClass
}
if (! defined('DISABLE_MULTISELECT')) // jQuery plugin "mutiselect" to select with checkboxes
{
print '<script src="'.DOL_URL_ROOT.'/includes/jquery/plugins/multiselect/jquery.multi-select.js'.($ext?'?'.$ext:'').'"></script>'."\n";
} }
} }

View File

@ -21,6 +21,10 @@ use Luracast\Restler\RestException;
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
/** /**
* API class for products * API class for products
@ -66,15 +70,16 @@ class Products extends DolibarrApi
* *
* @param int $id ID of product * @param int $id ID of product
* @param int $includestockdata Load also information about stock (slower) * @param int $includestockdata Load also information about stock (slower)
* @param bool $includesubproducts Load information about subproducts
* @return array|mixed Data without useless information * @return array|mixed Data without useless information
* *
* @throws 401 * @throws 401
* @throws 403 * @throws 403
* @throws 404 * @throws 404
*/ */
public function get($id, $includestockdata = 0) public function get($id, $includestockdata = 0, $includesubproducts = false)
{ {
return $this->_fetch($id, '', '', '', $includestockdata); return $this->_fetch($id, '', '', '', $includestockdata, $includesubproducts);
} }
/** /**
@ -84,18 +89,19 @@ class Products extends DolibarrApi
* *
* @param string $ref Ref of element * @param string $ref Ref of element
* @param int $includestockdata Load also information about stock (slower) * @param int $includestockdata Load also information about stock (slower)
* @param bool $includesubproducts Load information about subproducts
* *
* @return array|mixed Data without useless information * @return array|mixed Data without useless information
* *
* @url GET byRef/{ref} * @url GET ref/{ref}
* *
* @throws 401 * @throws 401
* @throws 403 * @throws 403
* @throws 404 * @throws 404
*/ */
public function getByRef($ref, $includestockdata = 0) public function getByRef($ref, $includestockdata = 0, $includesubproducts = false)
{ {
return $this->_fetch('', $ref, '', '', $includestockdata); return $this->_fetch('', $ref, '', '', $includestockdata, $includesubproducts);
} }
/** /**
@ -105,18 +111,19 @@ class Products extends DolibarrApi
* *
* @param string $ref_ext Ref_ext of element * @param string $ref_ext Ref_ext of element
* @param int $includestockdata Load also information about stock (slower) * @param int $includestockdata Load also information about stock (slower)
* @param bool $includesubproducts Load information about subproducts
* *
* @return array|mixed Data without useless information * @return array|mixed Data without useless information
* *
* @url GET byRefExt/{ref_ext} * @url GET ref_ext/{ref_ext}
* *
* @throws 401 * @throws 401
* @throws 403 * @throws 403
* @throws 404 * @throws 404
*/ */
public function getByRefExt($ref_ext, $includestockdata = 0) public function getByRefExt($ref_ext, $includestockdata = 0, $includesubproducts = false)
{ {
return $this->_fetch('', '', $ref_ext, '', $includestockdata); return $this->_fetch('', '', $ref_ext, '', $includestockdata, $includesubproducts);
} }
/** /**
@ -126,18 +133,19 @@ class Products extends DolibarrApi
* *
* @param string $barcode Barcode of element * @param string $barcode Barcode of element
* @param int $includestockdata Load also information about stock (slower) * @param int $includestockdata Load also information about stock (slower)
* @param bool $includesubproducts Load information about subproducts
* *
* @return array|mixed Data without useless information * @return array|mixed Data without useless information
* *
* @url GET byBarcode/{barcode} * @url GET barcode/{barcode}
* *
* @throws 401 * @throws 401
* @throws 403 * @throws 403
* @throws 404 * @throws 404
*/ */
public function getByBarcode($barcode, $includestockdata = 0) public function getByBarcode($barcode, $includestockdata = 0, $includesubproducts = false)
{ {
return $this->_fetch('', '', '', $barcode, $includestockdata); return $this->_fetch('', '', '', $barcode, $includestockdata, $includesubproducts);
} }
/** /**
@ -370,6 +378,105 @@ class Products extends DolibarrApi
return $this->product->delete(DolibarrApiAccess::$user); return $this->product->delete(DolibarrApiAccess::$user);
} }
/**
* Get the list of subproducts of the product.
*
* @param int $id Id of parent product/service
* @return array
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url GET {id}/subproducts
*/
public function getSubproducts($id)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
if(! DolibarrApi::_checkAccessToResource('product', $id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$childsArbo = $this->product->getChildsArbo($id, 1);
$keys = ['rowid', 'qty', 'fk_product_type', 'label', 'incdec'];
$childs = [];
foreach ($childsArbo as $values) {
$childs[] = array_combine($keys, $values);
}
return $childs;
}
/**
* Add subproduct.
*
* Link a product/service to a parent product/service
*
* @param int $id Id of parent product/service
* @param int $subproduct_id Id of child product/service
* @param int $qty Quantity
* @param int $incdec 1=Increase/decrease stock of child when parent stock increase/decrease
* @return int
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url POST {id}/subproducts/add
*/
public function addSubproducts($id, $subproduct_id, $qty, $incdec = 1)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
if(! DolibarrApi::_checkAccessToResource('product', $id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$result = $this->product->add_sousproduit($id, $subproduct_id, $qty, $incdec);
if ($result <= 0) {
throw new RestException(500, "Error adding product child");
}
return $result;
}
/**
* Remove subproduct.
*
* Unlink a product/service from a parent product/service
*
* @param int $id Id of parent product/service
* @param int $subproduct_id Id of child product/service
* @return int
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url DELETE {id}/subproducts/remove
*/
public function delSubproducts($id, $subproduct_id)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
if(! DolibarrApi::_checkAccessToResource('product', $id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$result = $this->product->del_sousproduit($id, $subproduct_id);
if ($result <= 0) {
throw new RestException(500, "Error while removing product child");
}
return $result;
}
/** /**
* Get categories for a product * Get categories for a product
@ -696,6 +803,713 @@ class Products extends DolibarrApi
return $this->_cleanObjectDatas($this->productsupplier); return $this->_cleanObjectDatas($this->productsupplier);
} }
/**
* Get attributes.
*
* @return array
*
* @throws RestException
*
* @url GET attributes
*/
public function getAttributes()
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$prodattr = new ProductAttribute($this->db);
return $prodattr->fetchAll();
}
/**
* Get attribute by ID.
*
* @param int $id ID of Attribute
* @return array
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url GET attributes/{id}
*/
public function getAttributeById($id)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$prodattr = new ProductAttribute($this->db);
$result = $prodattr->fetch((int) $id);
if($result < 0) {
throw new RestException(404, "Attribute not found");
}
return $prodattr;
}
/**
* Get attributes by ref.
*
* @param string $ref Reference of Attribute
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET attributes/ref/{ref}
*/
public function getAttributesByRef($ref)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$sql = "SELECT rowid, ref, label, rang FROM ".MAIN_DB_PREFIX."product_attribute WHERE ref LIKE '". trim($ref) ."' AND entity IN (".getEntity('product').")";
$query = $this->db->query($sql);
if (!$this->db->num_rows($query)) {
throw new RestException(404);
}
$result = $this->db->fetch_object($query);
$attr = [];
$attr['id'] = $result->rowid;
$attr['ref'] = $result->ref;
$attr['label'] = $result->label;
$attr['rang'] = $result->rang;
return $attr;
}
/**
* Add attributes.
*
* @param string $ref Reference of Attribute
* @param string $label Label of Attribute
* @return int
*
* @throws RestException
* @throws 401
*
* @url POST attributes
*/
public function addAttributes($ref, $label)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
$prodattr = new ProductAttribute($this->db);
$prodattr->label = $label;
$prodattr->ref = $ref;
$resid = $prodattr->create(DolibarrApiAccess::$user);
if ($resid <= 0) {
throw new RestException(500, "Error creating new attribute");
}
return $resid;
}
/**
* Update attributes by id.
*
* @param int $id ID of Attribute
* @param array $request_data Datas
* @return array
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url PUT attributes/{id}
*/
public function putAttributes($id, $request_data = null)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
$prodattr = new ProductAttribute($this->db);
$result = $prodattr->fetch((int) $id);
if ($result == 0) {
throw new RestException(404, 'Attribute not found');
} elseif ($result < 0) {
throw new RestException(500, "Error fetching attribute");
}
foreach($request_data as $field => $value) {
if ($field == 'rowid') { continue;
}
$prodattr->$field = $value;
}
if ($prodattr->update(DolibarrApiAccess::$user) > 0) {
$result = $prodattr->fetch((int) $id);
if ($result == 0) {
throw new RestException(404, 'Attribute not found');
} elseif ($result < 0) {
throw new RestException(500, "Error fetching attribute");
} else {
return $prodattr;
}
}
throw new RestException(500, "Error updating attribute");
}
/**
* Delete attributes by id.
*
* @param int $id ID of Attribute
* @return int
*
* @throws RestException
* @throws 401
*
* @url DELETE attributes/{id}
*/
public function deleteAttributes($id)
{
if(! DolibarrApiAccess::$user->rights->produit->supprimer) {
throw new RestException(401);
}
$prodattr = new ProductAttribute($this->db);
$prodattr->id = (int) $id;
$result = $prodattr->delete();
if ($result > 0) {
return 1;
}
throw new RestException(500, "Error deleting attribute");
}
/**
* Get attribute value by id.
*
* @param int $id ID of Attribute value
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET attributes/values/{id}
*/
public function getAttributeValueById($id)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE rowid = ".(int) $id." AND entity IN (".getEntity('product').")";
$query = $this->db->query($sql);
if (!$query) {
throw new RestException(401);
}
if (!$this->db->num_rows($query)) {
throw new RestException(404, 'Attribute value not found');
}
$result = $this->db->fetch_object($query);
$attrval = [];
$attrval['id'] = $result->rowid;
$attrval['fk_product_attribute'] = $result->fk_product_attribute;
$attrval['ref'] = $result->ref;
$attrval['value'] = $result->value;
return $attrval;
}
/**
* Get attribute value by ref.
*
* @param int $id ID of Attribute value
* @param string $ref Ref of Attribute value
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET attributes/{id}/values/ref/{ref}
*/
public function getAttributeValueByRef($id, $ref)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE ref LIKE '". trim($ref) ."' AND fk_product_attribute = ". (int) $id ." AND entity IN (".getEntity('product').")";
$query = $this->db->query($sql);
if (!$query) {
throw new RestException(401);
}
if (!$this->db->num_rows($query)) {
throw new RestException(404, 'Attribute value not found');
}
$result = $this->db->fetch_object($query);
$attrval = [];
$attrval['id'] = $result->rowid;
$attrval['fk_product_attribute'] = $result->fk_product_attribute;
$attrval['ref'] = $result->ref;
$attrval['value'] = $result->value;
return $attrval;
}
/**
* Delete attribute value by ref.
*
* @param int $id ID of Attribute
* @param string $ref Ref of Attribute value
* @return int
*
* @throws RestException
* @throws 401
*
* @url DELETE attributes/{id}/values/ref/{ref}
*/
public function deleteAttributeValueByRef($id, $ref)
{
if(! DolibarrApiAccess::$user->rights->produit->supprimer) {
throw new RestException(401);
}
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE ref LIKE '". trim($ref) ."' AND fk_product_attribute = ". (int) $id;
if ($this->db->query($sql)) {
return 1;
}
throw new RestException(500, "Error deleting attribute value");
}
/**
* Get all values for an attribute id.
*
* @param int $id ID of an Attribute
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET attributes/{id}/values
*/
public function getAttributeValues($id)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$objectval = new ProductAttributeValue($this->db);
return $objectval->fetchAllByProductAttribute((int) $id);
}
/**
* Get all values for an attribute ref.
*
* @param string $ref Ref of an Attribute
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET attributes/ref/{ref}/values
*/
public function getAttributeValuesByRef($ref)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$return = array();
$sql = 'SELECT ';
$sql .= 'v.fk_product_attribute, v.rowid, v.ref, v.value FROM '.MAIN_DB_PREFIX.'product_attribute_value v ';
$sql .= "WHERE v.fk_product_attribute = ( SELECT rowid FROM ".MAIN_DB_PREFIX."product_attribute WHERE ref LIKE '". strtoupper(trim($ref)) ."' LIMIT 1)";
$query = $this->db->query($sql);
while ($result = $this->db->fetch_object($query)) {
$tmp = new ProductAttributeValue($this->db);
$tmp->fk_product_attribute = $result->fk_product_attribute;
$tmp->id = $result->rowid;
$tmp->ref = $result->ref;
$tmp->value = $result->value;
$return[] = $tmp;
}
return $return;
}
/**
* Add attribute value.
*
* @param int $id ID of Attribute
* @param string $ref Reference of Attribute value
* @param string $value Value of Attribute value
* @return int
*
* @throws RestException
* @throws 401
*
* @url POST attributes/{id}/values
*/
public function addAttributeValue($id, $ref, $value)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
if (empty($ref) || empty($value)) {
throw new RestException(401);
}
$objectval = new ProductAttributeValue($this->db);
$objectval->fk_product_attribute = $id;
$objectval->ref = $ref;
$objectval->value = $value;
if ($objectval->create(DolibarrApiAccess::$user) > 0) {
return $objectval->id;
}
throw new RestException(500, "Error creating new attribute value");
}
/**
* Update attribute value.
*
* @param int $id ID of Attribute
* @param array $request_data Datas
* @return array
*
* @throws RestException
* @throws 401
*
* @url PUT attributes/values/{id}
*/
public function putAttributeValue($id, $request_data)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
$objectval = new ProductAttributeValue($this->db);
$result = $objectval->fetch((int) $id);
if ($result == 0) {
throw new RestException(404, 'Attribute value not found');
} elseif ($result < 0) {
throw new RestException(500, "Error fetching attribute value");
}
foreach($request_data as $field => $value) {
if ($field == 'rowid') { continue;
}
$objectval->$field = $value;
}
if ($objectval->update(DolibarrApiAccess::$user) > 0) {
$result = $objectval->fetch((int) $id);
if ($result == 0) {
throw new RestException(404, 'Attribute not found');
} elseif ($result < 0) {
throw new RestException(500, "Error fetching attribute");
} else {
return $objectval;
}
}
throw new RestException(500, "Error updating attribute");
}
/**
* Delete attribute value by id.
*
* @param int $id ID of Attribute value
* @return int
*
* @throws RestException
* @throws 401
*
* @url DELETE attributes/values/{id}
*/
public function deleteAttributeValueById($id)
{
if(! DolibarrApiAccess::$user->rights->produit->supprimer) {
throw new RestException(401);
}
$objectval = new ProductAttributeValue($this->db);
$objectval->id = (int) $id;
if ($objectval->delete() > 0) {
return 1;
}
throw new RestException(500, "Error deleting attribute value");
}
/**
* Get product variants.
*
* @param int $id ID of Product
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET {id}/variants
*/
public function getVariants($id)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$prodcomb = new ProductCombination($this->db);
$combinations = $prodcomb->fetchAllByFkProductParent((int) $id);
foreach ($combinations as $key => $combination) {
$prodc2vp = new ProductCombination2ValuePair($this->db);
$combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
}
return $combinations;
}
/**
* Get product variants by Product ref.
*
* @param string $ref Ref of Product
* @return array
*
* @throws RestException
* @throws 401
*
* @url GET ref/{ref}/variants
*/
public function getVariantsByProdRef($ref)
{
if(! DolibarrApiAccess::$user->rights->produit->lire) {
throw new RestException(401);
}
$result = $this->product->fetch('', $ref);
if(! $result ) {
throw new RestException(404, 'Product not found');
}
$prodcomb = new ProductCombination($this->db);
$combinations = $prodcomb->fetchAllByFkProductParent((int) $this->product->id);
foreach ($combinations as $key => $combination) {
$prodc2vp = new ProductCombination2ValuePair($this->db);
$combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
}
return $combinations;
}
/**
* Add variant.
*
* "features" is a list of attributes pairs id_attribute=>id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
*
* @param int $id ID of Product
* @param float $weight_impact Weight impact of variant
* @param float $price_impact Price impact of variant
* @param bool $price_impact_is_percent Price impact in percent (true or false)
* @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
* @return int
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url POST {id}/variants
*/
public function addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
if (empty($id) || empty($features) || !is_array($features)) {
throw new RestException(401);
}
$weight_impact = price2num($weight_impact);
$price_impact = price2num($price_impact);
$prodattr = new ProductAttribute($this->db);
$prodattr_val = new ProductAttributeValue($this->db);
foreach ($features as $id_attr => $id_value) {
if ($prodattr->fetch((int) $id_attr) < 0) {
throw new RestException(401);
}
if ($prodattr_val->fetch((int) $id_value) < 0) {
throw new RestException(401);
}
}
$result = $this->product->fetch((int) $id);
if(! $result ) {
throw new RestException(404, 'Product not found');
}
$prodcomb = new ProductCombination($this->db);
if (! $prodcomb->fetchByProductCombination2ValuePairs($id, $features))
{
$result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact);
if ($result > 0)
{
return $result;
} else {
throw new RestException(500, "Error creating new product variant");
}
} else {
return $prodcomb->id;
}
}
/**
* Add variant by product ref.
*
* "features" is a list of attributes pairs id_attribute=>id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
*
* @param string $ref Ref of Product
* @param float $weight_impact Weight impact of variant
* @param float $price_impact Price impact of variant
* @param bool $price_impact_is_percent Price impact in percent (true or false)
* @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
* @return int
*
* @throws RestException
* @throws 401
* @throws 404
*
* @url POST ref/{ref}/variants
*/
public function addVariantByProductRef($ref, $weight_impact, $price_impact, $price_impact_is_percent, $features)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
if (empty($ref) || empty($features) || !is_array($features)) {
throw new RestException(401);
}
$weight_impact = price2num($weight_impact);
$price_impact = price2num($price_impact);
$prodattr = new ProductAttribute($this->db);
$prodattr_val = new ProductAttributeValue($this->db);
foreach ($features as $id_attr => $id_value) {
if ($prodattr->fetch((int) $id_attr) < 0) {
throw new RestException(404);
}
if ($prodattr_val->fetch((int) $id_value) < 0) {
throw new RestException(404);
}
}
$result = $this->product->fetch('', trim($ref));
if(! $result ) {
throw new RestException(404, 'Product not found');
}
$prodcomb = new ProductCombination($this->db);
if (! $prodcomb->fetchByProductCombination2ValuePairs($this->product->id, $features))
{
$result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact);
if ($result > 0)
{
return $result;
} else {
throw new RestException(500, "Error creating new product variant");
}
} else {
return $prodcomb->id;
}
}
/**
* Put product variants.
*
* @param int $id ID of Variant
* @param array $request_data Datas
* @return int
*
* @throws RestException
* @throws 401
*
* @url PUT variants/{id}
*/
public function putVariant($id, $request_data = null)
{
if(! DolibarrApiAccess::$user->rights->produit->creer) {
throw new RestException(401);
}
$prodcomb = new ProductCombination($this->db);
$prodcomb->fetch((int) $id);
foreach($request_data as $field => $value) {
if ($field == 'rowid') { continue;
}
$prodcomb->$field = $value;
}
$result = $prodcomb->update(DolibarrApiAccess::$user);
if ($result > 0)
{
return 1;
}
throw new RestException(500, "Error editing variant");
}
/**
* Delete product variants.
*
* @param int $id ID of Variant
* @return int
*
* @throws RestException
* @throws 401
*
* @url DELETE variants/{id}
*/
public function deleteVariant($id)
{
if(! DolibarrApiAccess::$user->rights->produit->supprimer) {
throw new RestException(401);
}
$prodcomb = new ProductCombination($this->db);
$prodcomb->id = (int) $id;
$result = $prodcomb->delete(DolibarrApiAccess::$user);
return $result;
if ($result > 0)
{
return 1;
}
throw new RestException(500, "Error deleting variant");
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
/** /**
* Clean sensible object datas * Clean sensible object datas
@ -753,13 +1567,14 @@ class Products extends DolibarrApi
* @param string $ref_ext Ref ext of element * @param string $ref_ext Ref ext of element
* @param string $barcode Barcode of element * @param string $barcode Barcode of element
* @param int $includestockdata Load also information about stock (slower) * @param int $includestockdata Load also information about stock (slower)
* @param bool $includesubproducts Load information about subproducts
* @return array|mixed Data without useless information * @return array|mixed Data without useless information
* *
* @throws 401 * @throws 401
* @throws 403 * @throws 403
* @throws 404 * @throws 404
*/ */
private function _fetch($id, $ref = '', $ref_ext = '', $barcode = '', $includestockdata = 0) private function _fetch($id, $ref = '', $ref_ext = '', $barcode = '', $includestockdata = 0, $includesubproducts = false)
{ {
if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) { if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) {
throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode'); throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode');
@ -784,6 +1599,20 @@ class Products extends DolibarrApi
$this->product->load_stock(); $this->product->load_stock();
} }
if ($includesubproducts) {
$childsArbo = $this->product->getChildsArbo($id, 1);
$keys = ['rowid', 'qty', 'fk_product_type', 'label', 'incdec'];
$childs = [];
foreach ($childsArbo as $values) {
$childs[] = array_combine($keys, $values);
}
$this->product->sousprods = $childs;
}
return $this->_cleanObjectDatas($this->product); return $this->_cleanObjectDatas($this->product);
} }
} }

View File

@ -89,7 +89,9 @@ if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)
{ {
$virtualdiffersfromphysical = 1; // According to increase/decrease stock options, virtual and physical stock may differs. $virtualdiffersfromphysical = 1; // According to increase/decrease stock options, virtual and physical stock may differs.
} }
$usevirtualstock = 0;
$usevirtualstock = !empty($conf->global->STOCK_USE_VIRTUAL_STOCK);
if ($mode == 'physical') $usevirtualstock = 0;
if ($mode == 'virtual') $usevirtualstock = 1; if ($mode == 'virtual') $usevirtualstock = 1;
$parameters = array(); $parameters = array();

View File

@ -1985,7 +1985,7 @@ class Task extends CommonObject
$sql .= " WHERE p.entity IN (".getEntity('project', 0).')'; $sql .= " WHERE p.entity IN (".getEntity('project', 0).')';
$sql .= " AND p.fk_statut = 1"; $sql .= " AND p.fk_statut = 1";
$sql .= " AND t.fk_projet = p.rowid"; $sql .= " AND t.fk_projet = p.rowid";
$sql .= " AND t.progress < 100"; // tasks to do $sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do
if (!$user->rights->projet->all->lire) $sql .= " AND p.rowid IN (".$projectsListId.")"; if (!$user->rights->projet->all->lire) $sql .= " AND p.rowid IN (".$projectsListId.")";
// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
//if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")"; //if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")";

View File

@ -1180,9 +1180,7 @@ if ($action == 'create')
$line->array_options = array_merge($line->array_options, $srcLine->array_options); $line->array_options = array_merge($line->array_options, $srcLine->array_options);
print '<tr class="oddeven">';
print $line->showOptionals($extrafields, 'edit', array('style'=>'class="oddeven"', 'colspan'=>$colspan), $indiceAsked); print $line->showOptionals($extrafields, 'edit', array('style'=>'class="oddeven"', 'colspan'=>$colspan), $indiceAsked);
print '</tr>';
} }
$indiceAsked++; $indiceAsked++;
@ -1989,7 +1987,7 @@ elseif ($id || $ref)
$colspan = empty($conf->productbatch->enabled) ? 8 : 9; $colspan = empty($conf->productbatch->enabled) ? 8 : 9;
$line = new CommandeFournisseurDispatch($db); $line = new CommandeFournisseurDispatch($db);
$line->fetch_optionals($lines[$i]->id); $line->fetch_optionals($lines[$i]->id);
print '<tr class="oddeven">';
if ($action == 'editline' && $lines[$i]->id == $line_id) if ($action == 'editline' && $lines[$i]->id == $line_id)
{ {
print $line->showOptionals($extrafields, 'edit', array('colspan'=>$colspan), $indiceAsked); print $line->showOptionals($extrafields, 'edit', array('colspan'=>$colspan), $indiceAsked);
@ -1998,7 +1996,6 @@ elseif ($id || $ref)
{ {
print $line->showOptionals($extrafields, 'view', array('colspan'=>$colspan), $indiceAsked); print $line->showOptionals($extrafields, 'view', array('colspan'=>$colspan), $indiceAsked);
} }
print '</tr>';
} }
} }

View File

@ -972,6 +972,9 @@ select.flat.selectlimit {
-ms-overflow-style: -ms-autohiding-scrollbar; -ms-overflow-style: -ms-autohiding-scrollbar;
}*/ }*/
/* Style used for most tables */ /* Style used for most tables */
div.fiche>div.tabBar>form>div.div-table-responsive {
min-height: 392px;
}
.div-table-responsive, .div-table-responsive-no-min { .div-table-responsive, .div-table-responsive-no-min {
overflow-x: auto; overflow-x: auto;
min-height: 0.01%; min-height: 0.01%;

View File

@ -495,6 +495,7 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1";
* [...] * [...]
* ) * )
* *
* @param User $user Object user
* @param Product $product Parent product * @param Product $product Parent product
* @param array $combinations Attribute and value combinations. * @param array $combinations Attribute and value combinations.
* @param array $variations Price and weight variations * @param array $variations Price and weight variations
@ -503,9 +504,9 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1";
* @param bool|float $forced_weightvar If the weight variation is forced * @param bool|float $forced_weightvar If the weight variation is forced
* @return int <0 KO, >0 OK * @return int <0 KO, >0 OK
*/ */
public function createProductCombination(Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false) public function createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false)
{ {
global $db, $user, $conf; global $db, $conf;
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php'; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php'; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
@ -660,11 +661,12 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1";
/** /**
* Copies all product combinations from the origin product to the destination product * Copies all product combinations from the origin product to the destination product
* *
* @param User $user Object user
* @param int $origProductId Origin product id * @param int $origProductId Origin product id
* @param Product $destProduct Destination product * @param Product $destProduct Destination product
* @return int >0 OK <0 KO * @return int >0 OK <0 KO
*/ */
public function copyAll($origProductId, Product $destProduct) public function copyAll(User $user, $origProductId, Product $destProduct)
{ {
require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php'; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
@ -686,6 +688,7 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1";
} }
if ($this->createProductCombination( if ($this->createProductCombination(
$user,
$destProduct, $destProduct,
$variations, $variations,
array(), array(),

View File

@ -141,7 +141,7 @@ if ($_POST) {
if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features)) if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features))
{ {
$result = $prodcomb->createProductCombination($object, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact); $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact);
if ($result > 0) if ($result > 0)
{ {
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
@ -268,7 +268,7 @@ if ($action === 'confirm_deletecombination') {
if ($prodstatic->fetch('', $dest_product) > 0) { if ($prodstatic->fetch('', $dest_product) > 0) {
//To prevent from copying to the same product //To prevent from copying to the same product
if ($prodstatic->ref != $object->ref) { if ($prodstatic->ref != $object->ref) {
if ($prodcomb->copyAll($object->id, $prodstatic) > 0) { if ($prodcomb->copyAll($user, $object->id, $prodstatic) > 0) {
header('Location: '.dol_buildpath('/variants/combinations.php?id='.$prodstatic->id, 2)); header('Location: '.dol_buildpath('/variants/combinations.php?id='.$prodstatic->id, 2));
exit(); exit();
} else { } else {

View File

@ -110,7 +110,7 @@ if ($_POST)
$cartesianarray = cartesianArray($adapted_values); $cartesianarray = cartesianArray($adapted_values);
foreach ($cartesianarray as $currcomb) foreach ($cartesianarray as $currcomb)
{ {
$res = $combination->createProductCombination($product, $currcomb, $sanitized_values, $price_var_percent); $res = $combination->createProductCombination($user, $product, $currcomb, $sanitized_values, $price_var_percent);
if ($res < 0) { if ($res < 0) {
$error++; $error++;
setEventMessages($combination->error, $combination->errors, 'errors'); setEventMessages($combination->error, $combination->errors, 'errors');