diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php index 72eee9c22df..b5f05e72795 100644 --- a/htdocs/comm/propal/card.php +++ b/htdocs/comm/propal/card.php @@ -1974,6 +1974,21 @@ if ($action == 'create') { $text .= $notify->confirmMessage('PROPAL_VALIDATE', $object->socid, $object); } + // mandatoryPeriod + $nbMandated = 0; + foreach ($object->lines as $line) { + $res = $line->fetch_product(); + if ($res > 0 ) { + if ($line->product->isService() && $line->product->isMandatoryPeriod() && (empty($line->date_start) || empty($line->date_end) )) { + $nbMandated++; + break; + } + } + } + if ($nbMandated > 0) { + $text .= '
'.$langs->trans("mandatoryPeriodNeedTobeSetMsgValidate").'
'; + } + if (!$error) { $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ValidateProp'), $text, 'confirm_validate', '', 0, 1); } diff --git a/htdocs/commande/card.php b/htdocs/commande/card.php index e1896cc48b6..decabedc2e8 100644 --- a/htdocs/commande/card.php +++ b/htdocs/commande/card.php @@ -1924,6 +1924,21 @@ if ($action == 'create' && $usercancreate) { ); } + // mandatoryPeriod + $nbMandated = 0; + foreach ($object->lines as $line) { + $res = $line->fetch_product(); + if ($res > 0 ) { + if ($line->product->isService() && $line->product->isMandatoryPeriod() && (empty($line->date_start) || empty($line->date_end) )) { + $nbMandated++; + break; + } + } + } + if ($nbMandated > 0 ) $text .= '
'.$langs->trans("mandatoryPeriodNeedTobeSetMsgValidate").'
'; + + + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ValidateOrder'), $text, 'confirm_validate', $formquestion, 0, 1, 220); } diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index d4cf0a68441..7ddb90cd4da 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -4013,6 +4013,21 @@ if ($action == 'create') { if ($object->type != Facture::TYPE_CREDIT_NOTE && $object->total_ttc < 0) { // Can happen only if $conf->global->FACTURE_ENABLE_NEGATIVE is on $text .= '
'.img_warning().' '.$langs->trans("ErrorInvoiceOfThisTypeMustBePositive"); } + + // mandatoryPeriod + $nbMandated = 0; + foreach ($object->lines as $line) { + $res = $line->fetch_product(); + if ($res > 0 ) { + if ($line->product->isService() && $line->product->isMandatoryPeriod() && (empty($line->date_start) || empty($line->date_end) )) { + $nbMandated++; + break; + } + } + } + if ($nbMandated > 0 ) $text .= '
'.$langs->trans("mandatoryPeriodNeedTobeSetMsgValidate").'
'; + + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?facid='.$object->id, $langs->trans('ValidateBill'), $text, 'confirm_valid', $formquestion, (($object->type != Facture::TYPE_CREDIT_NOTE && $object->total_ttc < 0) ? "no" : "yes"), 2); } diff --git a/htdocs/core/tpl/objectline_create.tpl.php b/htdocs/core/tpl/objectline_create.tpl.php index f978a436354..d9ca8291066 100644 --- a/htdocs/core/tpl/objectline_create.tpl.php +++ b/htdocs/core/tpl/objectline_create.tpl.php @@ -68,6 +68,7 @@ if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf- if (in_array($object->element, array('propal', 'commande', 'order', 'facture', 'facturerec', 'invoice', 'supplier_proposal', 'order_supplier', 'invoice_supplier'))) { $colspan++; // With this, there is a column move button } + //print $object->element; // Lines for extrafield $objectline = null; @@ -675,13 +676,26 @@ if (!empty($usemargins) && $user->rights->margins->creer) { } ?> +table_element_line != 'commande_fournisseurdet') { ?> + $("#date_start, #date_end").focusout(function() + { + let type = $(this).attr('type'); + let mandatoryP = $(this).attr('mandatoryperiod'); + if (type == 1 && mandatoryP == 1 ){ + if ( $(this).val() == '' && !$(this).hasClass("error") ) { + $(this).addClass('error'); + }else{ + $(this).removeClass('error'); + } + } + }); + /* When changing predefined product, we reload list of supplier prices required for margin combo */ $("#idprod, #idprodfournprice").change(function() { console.log("Call method change() after change on #idprod or #idprodfournprice (senderissupplier=). this.val = "+$(this).val()); setforpredef(); // TODO Keep vat combo visible and set it to first entry into list that match result of get_default_tva - jQuery('#trlinefordates').show(); rights->margins->creer) { { 'id': $(this).val(), 'socid': socid; ?> }, function(data) { console.log("Load unit price end, we got value "+data.price_ht); + + $( '#date_start').removeAttr( "type" ); + $( '#date_end' ).removeAttr( "type" ); + $('#date_start').attr('type', data.type); + $('#date_end').attr('type', data.type); + + $( '#date_start').removeAttr( "mandatoryperiod" ); + $( '#date_end' ).removeAttr( "mandatoryperiod" ); + $('#date_start').attr('mandatoryperiod', data.mandatory_period); + $('#date_end').attr('mandatoryperiod', data.mandatory_period); + + // service and we setted mandatory_period to true + if (data.mandatory_period == 1 && data.type == 1 ) { + jQuery("#date_start").addClass("error"); + jQuery("#date_end").addClass("error"); + }else{ + jQuery("#date_start").removeClass("error"); + jQuery("#date_end").removeClass("error"); + } + jQuery("#price_ht").val(data.price_ht); global->PRODUIT_AUTOFILL_DESC) && $conf->global->PRODUIT_AUTOFILL_DESC == 1) { @@ -984,6 +1018,7 @@ if (!empty($usemargins) && $user->rights->margins->creer) { jQuery("#np_marginRate, #np_markRate, .np_marginRate, .np_markRate, #units, #title_units").show(); jQuery("#fournprice_predef").hide(); } + function setforpredef() { console.log("Call setforpredef. We hide some fields and show dates"); jQuery("#select_type").val(-1); diff --git a/htdocs/core/tpl/objectline_edit.tpl.php b/htdocs/core/tpl/objectline_edit.tpl.php index 9094daaa2d1..e15c489a5b6 100644 --- a/htdocs/core/tpl/objectline_edit.tpl.php +++ b/htdocs/core/tpl/objectline_edit.tpl.php @@ -333,9 +333,18 @@ $coldisplay++; if (isset($conf->global->MAIN_DEFAULT_DATE_START_HOUR)) { print 'jQuery("#date_starthour").val("'.$conf->global->MAIN_DEFAULT_DATE_START_HOUR.'");'; } + + if (isset($conf->global->MAIN_DEFAULT_DATE_START_MIN)) { print 'jQuery("#date_startmin").val("'.$conf->global->MAIN_DEFAULT_DATE_START_MIN.'");'; } + + $res = $line->fetch_product(); + if ($res > 0 ) { + if ( $line->product->isMandatoryPeriod() && $line->product->isService()) { + print 'jQuery("#date_start").addClass("error");'; + } + } } if (!$line->date_end) { if (isset($conf->global->MAIN_DEFAULT_DATE_END_HOUR)) { @@ -344,6 +353,14 @@ $coldisplay++; if (isset($conf->global->MAIN_DEFAULT_DATE_END_MIN)) { print 'jQuery("#date_endmin").val("'.$conf->global->MAIN_DEFAULT_DATE_END_MIN.'");'; } + + $res = $line->fetch_product(); + // on doit fetch le product là !!! pour connaître le type + if ($res > 0 ) { + if ($line->product->isMandatoryperiod() && $line->product->isService()) { + print 'jQuery("#date_end").addClass("error");'; + } + } } print '' ?> @@ -451,6 +468,15 @@ jQuery(document).ready(function() } }); + $("#date_start, #date_end").focusout(function() + { + + if ( $(this).val() == '' && !$(this).hasClass("error") ) { + $(this).addClass('error'); + }else{ + $(this).removeClass('error'); + } + }); margin->enabled)) { ?> diff --git a/htdocs/core/tpl/objectline_view.tpl.php b/htdocs/core/tpl/objectline_view.tpl.php index ad27dae210c..8ebb767a4d8 100644 --- a/htdocs/core/tpl/objectline_view.tpl.php +++ b/htdocs/core/tpl/objectline_view.tpl.php @@ -182,6 +182,22 @@ if (($line->info_bits & 2) == 2) { if ($line->date_start || $line->date_end) { print '
'.get_date_range($line->date_start, $line->date_end, $format).'
'; } + if (!$line->date_start || !$line->date_end) { + // show warning under line + // we need to fetch product associated to line for some test + if ($object->element == 'propal' || $object->element == 'order' || $object->element == 'propal_supplier' || $object->element == 'supplier_proposal' || $object->element == 'commande') { + $res = $line->fetch_product(); + if ($res > 0 ) { + if ($line->product->isService() && $line->product->isMandatoryPeriod()) { + print '
'.$langs->trans("mandatoryPeriodNeedTobeSet").'
'; + } + } + } + } + + + + //print get_date_range($line->date_start, $line->date_end, $format); } diff --git a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql index fb0969dc7ef..f4f6e9e349f 100644 --- a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql +++ b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql @@ -57,6 +57,7 @@ INSERT INTO llx_c_email_templates (entity, module, type_template, lang, private, -- v15 +ALTER TABLE llx_product ADD COLUMN mandatory_period tinyint NULL DEFAULT 0; ALTER TABLE llx_holiday ADD COLUMN date_approve DATETIME DEFAULT NULL; ALTER TABLE llx_holiday ADD COLUMN fk_user_approve integer DEFAULT NULL; diff --git a/htdocs/install/mysql/tables/llx_product.sql b/htdocs/install/mysql/tables/llx_product.sql index e89a7658e5a..463ed33a86e 100644 --- a/htdocs/install/mysql/tables/llx_product.sql +++ b/htdocs/install/mysql/tables/llx_product.sql @@ -103,6 +103,9 @@ create table llx_product desiredstock float DEFAULT 0, fk_unit integer DEFAULT NULL, price_autogen tinyint DEFAULT 0, + fk_project integer DEFAULT NULL, -- Used when product was generated by a project or is specifif to a project + mandatory_period tinyint DEFAULT 0 -- is used to signal to the user that the start and end dates are mandatory for this type of product the fk_product_type == 1 (service) (non-blocking action) + fk_default_bom integer DEFAULT NULL, fk_project integer DEFAULT NULL -- Used when the product was generated by a project or is specific to a project )ENGINE=innodb; diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 294e2afbc03..128eb120e0b 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -401,5 +401,11 @@ DeleteLinkedProduct=Delete the child product linked to the combination AmountUsedToUpdateWAP=Amount to use to update the Weighted Average Price PMPValue=Weighted average price PMPValueShort=WAP + +mandatoryperiod=Mandatory periods +mandatoryPeriodNeedTobeSet=Attention periods not entered and mandatory +mandatoryPeriodNeedTobeSetMsgValidate=A service requires a start and end period +mandatoryHelper=Message to the user on the need to enter a start date and an end date on a service when creating / validating an invoice, commercial proposal, sales order.
This action is not blocking in the process of confirmation DefaultBOM=Default BOM -DefaultBOMDesc=The default BOM recommended to use to manufacture this product. This field can be set only if nature of product is '%s'. \ No newline at end of file +DefaultBOMDesc=The default BOM recommended to use to manufacture this product. This field can be set only if nature of product is '%s'. + diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang index 013d75d2ec0..83514bd95ff 100644 --- a/htdocs/langs/fr_FR/products.lang +++ b/htdocs/langs/fr_FR/products.lang @@ -399,3 +399,7 @@ ProductSupplierExtraFields=Attributs supplémentaires (Prix fournisseur) DeleteLinkedProduct=Supprimer le produit enfant lié à la combinaison PMPValue=Prix moyen pondéré (PMP) PMPValueShort=PMP +mandatoryperiod=Périodes obligatoires +mandatoryPeriodNeedTobeSet=Attention périodes non saisies et obligatoires +mandatoryPeriodNeedTobeSetMsgValidate=Un service nécessite une période de début et de fin +mandatoryHelper=Message à l'utilisateur sur la necessité de saisir une date de début et une date de fin sur un service lors de la création/validation de facture, proposition commerciale , commande client.
Cette action n'est pas bloquante dans le processus de validation diff --git a/htdocs/product/ajax/products.php b/htdocs/product/ajax/products.php index 77acafbbd55..9ba8d9cec09 100644 --- a/htdocs/product/ajax/products.php +++ b/htdocs/product/ajax/products.php @@ -91,7 +91,7 @@ if (!empty($action) && $action == 'fetch' && !empty($id)) { $outtype = $object->type; $outqty = 1; $outdiscount = 0; - + $mandatory_period = $object->mandatory_period; $found = false; $price_level = 1; @@ -203,6 +203,7 @@ if (!empty($action) && $action == 'fetch' && !empty($id)) { 'tva_tx' => $outtva_tx, 'qty' => $outqty, 'discount' => $outdiscount, + 'mandatory_period' => $mandatory_period, 'array_options'=>$object->array_options); } diff --git a/htdocs/product/card.php b/htdocs/product/card.php index b6820bddddd..9e7a9b6d52d 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -105,6 +105,7 @@ $accountancy_code_buy = GETPOST('accountancy_code_buy', 'alpha'); $accountancy_code_buy_intra = GETPOST('accountancy_code_buy_intra', 'alpha'); $accountancy_code_buy_export = GETPOST('accountancy_code_buy_export', 'alpha'); +$checkmandatory = GETPOST('accountancy_code_buy_export', 'alpha'); // by default 'alphanohtml' (better security); hidden conf MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML allows basic html $label_security_check = empty($conf->global->MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML) ? 'alphanohtml' : 'restricthtml'; @@ -298,7 +299,7 @@ if (empty($reshook)) { $object->ref = $ref; $object->label = GETPOST('label', $label_security_check); $object->price_base_type = GETPOST('price_base_type', 'aZ09'); - + $object->mandatory_period = !empty(GETPOST("mandatoryperiod", 'alpha')) ? 1 : 0; if ($object->price_base_type == 'TTC') { $object->price_ttc = GETPOST('price'); } else { @@ -599,7 +600,7 @@ if (empty($reshook)) { $accountancy_code_buy = GETPOST('accountancy_code_buy', 'alpha'); $accountancy_code_buy_intra = GETPOST('accountancy_code_buy_intra', 'alpha'); $accountancy_code_buy_export = GETPOST('accountancy_code_buy_export', 'alpha'); - + $checkmandatory = GETPOST('mandatoryperiod', 'alpha'); if (empty($accountancy_code_sell) || $accountancy_code_sell == '-1') { $object->accountancy_code_sell = ''; } else { @@ -630,6 +631,11 @@ if (empty($reshook)) { } else { $object->accountancy_code_buy_export = $accountancy_code_buy_export; } + if ($object->isService()) { + $object->mandatory_period = (!empty($checkmandatory)) ? 1 : 0 ; + } + + // Fill array 'array_options' with data from add form $ret = $extrafields->setOptionalsFromPost(null, $object); @@ -1446,6 +1452,14 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print $form->load_tva("tva_tx", $defaultva, $mysoc, $mysoc, 0, 0, '', false, 1); print ''; + if (!empty($conf->service->enabled)) { + if ($object->isService()) { + // Mandatory period + print ''.$langs->trans("mandatoryperiod").''; + print ' '; + print ''; + } + } print ''; print '
'; @@ -2036,6 +2050,13 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print ''.$langs->trans("ProductAccountancyBuyExportCode").''; print ''; print ''; + + if ($object->isService()) { + // Mandatory period + print ''.$langs->trans("mandatoryperiod").''; + print 'mandatory_period == 1 ? ' checked="checked"' : '').' /> '; + print ''; + } } } print ''; @@ -2310,6 +2331,13 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print (!empty($object->duration_unit) && isset($dur[$object->duration_unit]) ? $langs->trans($dur[$object->duration_unit]) : '')." "; print ''; + + // Mandatory period + $htmltooltip = '
'.$langs->trans("mandatoryHelper"); + print ''.$langs->trans("mandatoryperiod"); + print $form->textwithpicto('', $htmltooltip, 1, 0).''; + print 'mandatory_period == 1 ? ' checked="checked"' : '').' disabled/> '; + print ''; } else { if (empty($conf->global->PRODUCT_DISABLE_NATURE)) { // Nature diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index b624c0f231e..fd5d8ecf1db 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -434,6 +434,13 @@ class Product extends CommonObject public $is_object_used; + /** + * + * + * + */ + public $mandatory_period; + /** * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password') * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)" @@ -484,6 +491,8 @@ class Product extends CommonObject 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000), //'tosell' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')), //'tobuy' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')), + 'mandatory_period' =>array('type'=>'integer', 'label'=>'mandatory_period', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000), + ); /** @@ -635,7 +644,7 @@ class Product extends CommonObject // Barcode value $this->barcode = trim($this->barcode); - + $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period; // Check parameters if (empty($this->label)) { $this->error = 'ErrorMandatoryParametersNotProvided'; @@ -717,6 +726,7 @@ class Product extends CommonObject $sql .= ", tobatch"; $sql .= ", batch_mask"; $sql .= ", fk_unit"; + $sql .= ", mandatory_period"; $sql .= ") VALUES ("; $sql .= "'".$this->db->idate($now)."'"; $sql .= ", ".((int) $conf->entity); @@ -745,6 +755,7 @@ class Product extends CommonObject $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch)); $sql .= ", '".$this->db->escape($this->batch_mask)."'"; $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL'); + $sql .= ", '".$this->db->escape($this->mandatory_period)."'"; $sql .= ")"; dol_syslog(get_class($this)."::Create", LOG_DEBUG); @@ -1149,7 +1160,7 @@ class Product extends CommonObject $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1); $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL'); $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL'); - + $sql .= ", mandatory_period = ".($this->mandatory_period ); // stock field is not here because it is a denormalized value from product_stock. $sql .= " WHERE rowid = ".((int) $id); @@ -2252,7 +2263,7 @@ class Product extends CommonObject $sql .= " p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell,"; $sql .= " p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units,"; $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,"; - $sql .= " p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom,"; + $sql .= " p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period,"; if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) { $sql .= " p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export,"; } else { @@ -2427,6 +2438,8 @@ class Product extends CommonObject $this->price_autogen = $obj->price_autogen; $this->model_pdf = $obj->model_pdf; + $this->mandatory_period = $obj->mandatory_period; + $this->db->free($resql); // fetch optionals attributes and labels @@ -5642,6 +5655,16 @@ class Product extends CommonObject return ($this->type == Product::TYPE_SERVICE ? true : false); } + + /** + * Return if object have a constraint on mandatory_period + * + * @return boolean True if mandatory_period setted to 1 + */ + public function isMandatoryPeriod() + { + return ($this->mandatory_period == 1 ? true : false); + } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** * Get a barcode from the module to generate barcode values.