diff --git a/htdocs/commande/card.php b/htdocs/commande/card.php index 5eb1ccc9a57..422f38d5ae7 100644 --- a/htdocs/commande/card.php +++ b/htdocs/commande/card.php @@ -3117,6 +3117,20 @@ if ($action == 'create' && $usercancreate) { print ' ' . img_picto($langs->trans("Late") . ' : ' . $object->showDelay(), "warning"); } } + // --- SHIPPABLE icon --- + if (isModEnabled('stock') && isModEnabled('shipping') && !getDolGlobalString('ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD') && !empty($object->delivery_date)) { + $shippableInfos = $object->getShippableInfos(); + + if (!empty($shippableInfos['has_product'])) { + print ' '; + print $form->textwithtooltip('', $shippableInfos['textinfo'], 2, 1, $shippableInfos['texticon'], '', 2); + + if (!empty($shippableInfos['warning'])) { + print ' '; + print $form->textwithtooltip('', $langs->trans("NotEnoughForAllOrders"), 2, 1, img_picto('', 'error', '', 0, 0, 0, '', '2'), '', 2); + } + } + } print ''; print ''; diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php index 2436944576d..5760dc26048 100644 --- a/htdocs/commande/class/commande.class.php +++ b/htdocs/commande/class/commande.class.php @@ -4320,4 +4320,164 @@ class Commande extends CommonOrder { return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode); } + + /** + * Compute shippable status and tooltip/icon for the order. + * + * @param array $options Extra options (reserved for future use) + * @return array Array with keys: has_product, shippable, texticon, textinfo, warning + * / + */ + public function getShippableInfos(array $options = array()) : array + { + global $conf, $langs; + + $langs->loadLangs(array('orders', 'sendings', 'stocks', 'products')); + + $result = array( + 'has_product' => false, + 'shippable' => false, + 'texticon' => '', + 'textinfo' => '', + 'warning' => false, + ); + + // Requested naming for statuses + if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_CLOSED) { + return $result; + } + + + $genericCommande = $this; + $genericProduct = new Product($this->db); + + + $productstatcache = array(); + $productstatcachevirtual = array(); + + + $genericCommande->getLinesArray(); // Load array ->lines + $genericCommande->loadExpeditions(); // Load array ->expeditions + + $notshippable = 0; + $has_reliquat = 0; + $warning = 0; + $textinfo = ''; + $textwarning = ''; + $nbprod = 0; + + $genericProduct = new Product($this->db); + + $numlines = count($genericCommande->lines); + for ($lig = 0; $lig < $numlines; $lig++) { + $orderLine = $genericCommande->lines[$lig]; // @phan-var-force OrderLine $orderLine + + if (isset($genericCommande->expeditions[$orderLine->id])) { + $reliquat = $orderLine->qty - $genericCommande->expeditions[$orderLine->id]; + } else { + $reliquat = $orderLine->qty; + } + + if ($orderLine->product_type == 0 && $orderLine->fk_product > 0) { // product, not service + $nbprod = 1; + + if (empty($productstatcache[$orderLine->fk_product])) { + $genericProduct->fetch($orderLine->fk_product); + $genericProduct->load_stock('nobatch,warehouseopen'); // loadvirtualstock included + + $productstatcache[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_reel; + $productstatcachevirtual[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_theorique; + } + + $genericProduct->stock_reel = $productstatcache[$orderLine->fk_product]['stockreel']; + $genericProduct->stock_theorique = $productstatcachevirtual[$orderLine->fk_product]['stockreel']; + + if ($reliquat > 0) { + $has_reliquat = 1; + if (!getDolGlobalString('SHIPPABLE_ORDER_ICON_IN_LIST')) { + $textinfo .= $reliquat . ' x ' . $orderLine->product_ref . ' ' . dol_trunc($orderLine->product_label, 20); + $textinfo .= ' - ' . $langs->trans("Stock") . ': ' . $genericProduct->stock_reel . ''; + $textinfo .= ' - ' . $langs->trans("VirtualStock") . ': ' . $genericProduct->stock_theorique . ''; + if ($reliquat != $orderLine->qty) { + $textinfo .= ' ' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . ''; + } + $textinfo .= '
'; + } else { + // BUGGED CODE (kept for backward compatibility and hidden conf) + $stockorder = 0; + $stockordersupplier = 0; + + if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) { + if (isModEnabled('order')) { + if (empty($productstatcache[$orderLine->fk_product]['statsordercustomer'])) { + $genericProduct->fetch($orderLine->fk_product); + $genericProduct->load_stats_commande(0, '1,2'); + $productstatcache[$orderLine->fk_product]['statsordercustomer'] = $genericProduct->stats_commande['qty']; + } + $genericProduct->stats_commande['qty'] = $productstatcache[$orderLine->fk_product]['statsordercustomer']; + $stockorder = $genericProduct->stats_commande['qty']; + + if (isModEnabled('supplier_order')) { + if (empty($productstatcache[$orderLine->fk_product]['statsordersupplier'])) { + $genericProduct->load_stats_commande_fournisseur(0, '3'); + $productstatcache[$orderLine->fk_product]['statsordersupplier'] = $genericProduct->stats_commande_fournisseur['qty']; + } + $genericProduct->stats_commande_fournisseur['qty'] = $productstatcache[$orderLine->fk_product]['statsordersupplier']; + $stockordersupplier = $genericProduct->stats_commande_fournisseur['qty']; + } + } + } + + $textinfo .= $reliquat . ' x ' . $orderLine->ref . ' ' . dol_trunc($orderLine->product_label, 20); + $textinfo .= ' ' . $langs->trans("Available") . '  ' . $genericProduct->stock_reel . '..' . $stockorder; + + if ($stockorder && $genericProduct->stock_reel < ($genericProduct->stock_reel - $stockorder + $reliquat)) { + $warning++; + $textwarning .= '' . $langs->trans("Available") . '  ' . $genericProduct->stock_reel . '..' . $stockorder . ''; + } else { + if ($reliquat > $genericProduct->stock_reel) { + $textinfo .= ' ' . $langs->trans("Available") . '  ' . $genericProduct->stock_reel . ''; + } else { + $textinfo .= ' ' . $langs->trans("Available") . '  ' . $genericProduct->stock_reel . ''; + } + } + + if (isModEnabled('supplier_order')) { + $textinfo .= ' ' . $langs->trans("SupplierOrder") . '  ' . $stockordersupplier; + } + if ($reliquat != $orderLine->qty) { + $textinfo .= ' ' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . ''; + } + $textinfo .= '
'; + } + + if ($reliquat > $genericProduct->stock_reel) { + $notshippable++; + } + } + } + } + + if ($nbprod) { + if (!$has_reliquat) { + $texticon = img_picto('', 'statut5', '', 0, 0, 0, '', 'paddingleft'); + $textinfo = $texticon . ' ' . $langs->trans("Shipped"); + $result['shippable'] = true; + } elseif ($notshippable) { + $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'error paddingleft'); + $textinfo = $texticon . ' ' . $langs->trans("NonShippable") . '
' . $textinfo; + $result['shippable'] = false; + } else { + $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'green paddingleft'); + $textinfo = $texticon . ' ' . $langs->trans("Shippable") . '
' . $textinfo; + $result['shippable'] = true; + } + $result['has_product'] = true; + $result['texticon'] = $texticon; + $result['textinfo'] = $textinfo; + $result['warning'] = !empty($warning); + } + + return $result; + } } diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php index 632706cbfe6..b86c4406119 100644 --- a/htdocs/commande/list.php +++ b/htdocs/commande/list.php @@ -2999,108 +2999,22 @@ while ($i < $imaxinloop) { if (!empty($arrayfields['shippable']['checked'])) { print ''; if (!empty($show_shippable_command) && isModEnabled('stock')) { - $text_icon = ''; - if (($obj->fk_statut > $generic_commande::STATUS_DRAFT) && ($obj->fk_statut < $generic_commande::STATUS_CLOSED)) { - $generic_commande->getLinesArray(); // Load array ->lines - $generic_commande->loadExpeditions(); // Load array ->expeditions + $commande = new Commande($db); + $commande->id = (int) $obj->rowid; + $commande->status = (int) $obj->fk_statut; + $commande->statut = (int) $obj->fk_statut; - $numlines = count($generic_commande->lines); // Loop on each line of order - for ($lig = 0; $lig < $numlines; $lig++) { - $orderLine = $generic_commande->lines[$lig]; - '@phan-var-force OrderLine $orderLine'; - if (isset($generic_commande->expeditions[$orderLine->id])) { - $reliquat = $orderLine->qty - $generic_commande->expeditions[$orderLine->id]; - } else { - $reliquat = $orderLine->qty; - } - if ($orderLine->product_type == 0 && $orderLine->fk_product > 0) { // If line is a product and not a service - $nbprod++; // order contains real products - $generic_product->id = $orderLine->fk_product; + $shippableInfos = $commande->getShippableInfos(); - // Get local and virtual stock and store it into cache - if (empty($productstat_cache[$orderLine->fk_product])) { - $generic_product->load_stock('nobatch,warehouseopen'); // ->load_virtual_stock() is already included into load_stock() - $productstat_cache[$orderLine->fk_product]['stock_reel'] = $generic_product->stock_reel; - $productstat_cachevirtual[$orderLine->fk_product]['stock_reel'] = $generic_product->stock_theorique; - } else { - $generic_product->stock_reel = $productstat_cache[$orderLine->fk_product]['stock_reel']; - // @phan-suppress-next-line PhanTypeInvalidDimOffset - $generic_product->stock_theorique = $productstat_cachevirtual[$orderLine->fk_product]['stock_reel']; - } - - if ($reliquat > $generic_product->stock_reel) { - $notshippable++; - } - if (!getDolGlobalString('SHIPPABLE_ORDER_ICON_IN_LIST')) { // Default code. Default should be this case. - $text_info .= $reliquat.' x '.$orderLine->product_ref.' '.dol_trunc($orderLine->product_label, 20); - $text_info .= ' - '.$langs->trans("Stock").': '.$generic_product->stock_reel.''; - $text_info .= ' - '.$langs->trans("VirtualStock").': '.$generic_product->stock_theorique.''; - $text_info .= ($reliquat != $orderLine->qty ? ' ('.$langs->trans("QtyInOtherShipments").' '.($orderLine->qty - $reliquat).')' : ''); - $text_info .= '
'; - } else { // BUGGED CODE. - // DOES NOT TAKE INTO ACCOUNT MANUFACTURING. THIS CODE SHOULD BE USELESS. PREVIOUS CODE SEEMS COMPLETE. - // COUNT STOCK WHEN WE SHOULD ALREADY HAVE VALUE - // Detailed virtual stock, looks bugged, incomplete and need heavy load. - // stock order and stock order_supplier - $stock_order = 0; - $stock_order_supplier = 0; - if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) { // What about other options ? - if (isModEnabled('order')) { - if (empty($productstat_cache[$orderLine->fk_product]['stats_order_customer'])) { - $generic_product->load_stats_commande(0, '1,2'); - $productstat_cache[$orderLine->fk_product]['stats_order_customer'] = $generic_product->stats_commande['qty']; - } else { - // @phan-suppress-next-line PhanTypeInvalidDimOffset - $generic_product->stats_commande['qty'] = $productstat_cache[$orderLine->fk_product]['stats_order_customer']; - } - $stock_order = $generic_product->stats_commande['qty']; - } - if (isModEnabled("supplier_order")) { - if (empty($productstat_cache[$orderLine->fk_product]['stats_order_supplier'])) { - $generic_product->load_stats_commande_fournisseur(0, '3'); - $productstat_cache[$orderLine->fk_product]['stats_order_supplier'] = $generic_product->stats_commande_fournisseur['qty']; - } else { - // @phan-suppress-next-line PhanTypeInvalidDimOffset - $generic_product->stats_commande_fournisseur['qty'] = $productstat_cache[$orderLine->fk_product]['stats_order_supplier']; - } - $stock_order_supplier = $generic_product->stats_commande_fournisseur['qty']; - } - } - $text_info .= $reliquat.' x '.$orderLine->ref.' '.dol_trunc($orderLine->product_label, 20); - $text_stock_reel = $generic_product->stock_reel.'/'.$stock_order; - if ($stock_order > $generic_product->stock_reel && !($generic_product->stock_reel < $orderLine->qty)) { - $warning++; - $text_warning .= ''.$langs->trans('Available').' : '.$text_stock_reel.''; - } - if ($reliquat > $generic_product->stock_reel) { - $text_info .= ''.$langs->trans('Available').' : '.$text_stock_reel.''; - } else { - $text_info .= ''.$langs->trans('Available').' : '.$text_stock_reel.''; - } - if (isModEnabled("supplier_order")) { - $text_info .= ' '.$langs->trans('SupplierOrder').' : '.$stock_order_supplier; - } - $text_info .= ($reliquat != $orderLine->qty ? ' ('.$langs->trans("QtyInOtherShipments").' '.($orderLine->qty - $reliquat).')' : ''); - $text_info .= '
'; - } - } - } - if ($notshippable == 0) { - $text_icon = img_picto('', 'dolly', '', 0, 0, 0, '', 'green paddingleft'); - $text_info = $text_icon.' '.$langs->trans('Shippable').'
'.$text_info; - } else { - $text_icon = img_picto('', 'dolly', '', 0, 0, 0, '', 'error paddingleft'); - $text_info = $text_icon.' '.$langs->trans('NonShippable').'
'.$text_info; - } - } - - if ($nbprod) { // If there is at least one product to ship, we show the shippable icon - print ''; - print $form->textwithtooltip('', $text_info, 2, 1, $text_icon, '', 2); + if ($shippableInfos['has_product']) { + print ''; + print $form->textwithtooltip('', $shippableInfos['textinfo'], 2, 1, $shippableInfos['texticon'], '', 2); print ''; - } - if ($warning) { // Always false in default mode - print $form->textwithtooltip('', $langs->trans('NotEnoughForAllOrders').'
'.$text_warning, 2, 1, img_picto('', 'error'), '', 2); + + if (!empty($shippableInfos['warning'])) { + // On ne remonte plus le détail textwarning, mais on garde l’icône d’avertissement + print $form->textwithtooltip('', $langs->trans("NotEnoughForAllOrders"), 2, 1, img_picto('', 'error', '', 0, 0, 0, '', '2'), '', 2); + } } } print ''; diff --git a/htdocs/core/tpl/objectline_edit.tpl.php b/htdocs/core/tpl/objectline_edit.tpl.php index 2291df4273f..5ba85d81bfe 100644 --- a/htdocs/core/tpl/objectline_edit.tpl.php +++ b/htdocs/core/tpl/objectline_edit.tpl.php @@ -58,7 +58,6 @@ if (empty($object) || !is_object($object)) { print "Error, template page can't be called as URL"; exit(1); } - ' @phan-var-force Propal|Contrat|Commande|Facture|Expedition|Delivery|CommandeFournisseur|FactureFournisseur|SupplierProposal $object @phan-var-force PropaleLigne|ContratLigne|CommonObjectLine|CommonInvoiceLine|CommonOrderLine|ExpeditionLigne|DeliveryLine|FactureFournisseurLigneRec|SupplierInvoiceLine|SupplierProposalLine $line @@ -117,7 +116,7 @@ $coldisplay = 0; ?> - + - + element == 'commande' && isModEnabled('stock') && isModEnabled('shipping') && !getDolGlobalString('ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD') && ($object->status > 0 && $object->status < 3)) { + print ''; + print ' '; + print ''; + } + ?> lines) && $i > 0) { + // @phan-suppress-next-line PhanUndeclaredGlobalVariable for ($j = $i - 1; $j >= 0; $j--) { $lastline = $object->lines[$j]; if ($lastline->product_type == Product::TYPE_SERVICE && (!empty($lastline->date_start) || !empty($lastline->date_end))) { diff --git a/htdocs/core/tpl/objectline_title.tpl.php b/htdocs/core/tpl/objectline_title.tpl.php index 0e454d8866b..b74717a5119 100644 --- a/htdocs/core/tpl/objectline_title.tpl.php +++ b/htdocs/core/tpl/objectline_title.tpl.php @@ -137,6 +137,11 @@ if (isModEnabled("multicurrency") && $this->multicurrency_code && $this->multicu // Qty print ''.$langs->trans('Qty').''; +//ShippableStatus +if ($object->element == 'commande' && isModEnabled('stock') && isModEnabled('shipping') && !getDolGlobalString('ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD') && ($object->status > 0 && $object->status < 3)) { + print ''.$langs->trans("ShippableStatus").''; +} + // Unit if (getDolGlobalString('PRODUCT_USE_UNITS')) { print ''.$langs->trans('Unit').''; diff --git a/htdocs/core/tpl/objectline_view.tpl.php b/htdocs/core/tpl/objectline_view.tpl.php index dd51d9b7df0..e4fe0d3a485 100644 --- a/htdocs/core/tpl/objectline_view.tpl.php +++ b/htdocs/core/tpl/objectline_view.tpl.php @@ -83,7 +83,7 @@ if (defined('SUBTOTALS_SPECIAL_CODE') && $line->special_code == SUBTOTALS_SPECIA return require DOL_DOCUMENT_ROOT.'/core/tpl/subtotal_view.tpl.php'; } -global $mysoc; +global $mysoc, $db; global $forceall, $senderissupplier, $inputalsopricewithtax, $outputalsopricetotalwithtax; $usemargins = 0; @@ -462,7 +462,40 @@ if ((($line->info_bits & 2) != 2) && $line->special_code != 3) { print ' '; } print ''; +//Shippable Status +if ($object->element == 'commande' && isModEnabled('stock') && isModEnabled('shipping') && !getDolGlobalString('ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD') && ($object->status > 0 && $object->status < 3)) { + $coldisplay++; + print ''; + + if ($line->fk_product > 0 && $line->product_type == 0) { + static $productstatcache = array(); + + if (empty($productstatcache[$line->fk_product])) { + $prod = new Product($this->db); + $prod->fetch($line->fk_product); + $prod->load_stock('nobatch,warehouseopen'); + $productstatcache[$line->fk_product]['stockreel'] = $prod->stock_reel; + } + $stock = $productstatcache[$line->fk_product]['stockreel']; + $reliquat = $line->qty; + if (!empty($object->expeditions[$line->id])) { + $reliquat -= $object->expeditions[$line->id]; + } + if ($reliquat > 0) { + if ($stock >= $reliquat) { + print img_picto($langs->trans("Stock").': '.$stock, 'dolly', '', 0, 0, 0, '', 'green'); + } else { + print img_picto($langs->trans("Stock").': '.$stock, 'dolly', '', 0, 0, 0, '', 'error'); + } + } else { + print img_picto($langs->trans("Shipped"), 'statut5'); + } + } else { + print ' '; + } + print ''; +} if (getDolGlobalString('PRODUCT_USE_UNITS')) { print ''; $label = $line->getLabelOfUnit('short', $langs); diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 0309619949b..5c3cd8c98f2 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -1551,6 +1551,7 @@ InvoiceOptionCategoryOfOperationsYes1=Yes, below the address block InvoiceOptionCategoryOfOperationsYes2=Yes, in the lower left-hand corner InvoiceClassifyBilledSupplierOrderWithoutInvoice=Disallow the classification of an order as billed without invoice. InvoiceClassifyBilledSupplierOrderWithoutInvoiceHelp=An order can be classified as billed by default. If this conf is set to true, it will be not. +EnableShippableIconOnCard=Enable shippable icon on order card ##### Supplier Orders ##### SupplierOrderClassifyBilledWithoutInvoice=Disallow the classification of a purchase order as billed without invoice. SupplierOrderClassifyBilledWithoutInvoiceHelp=A supplier order can be classified as billed by default. If this conf is set to true, it will be not. diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index ecc7286b13d..3b593212db0 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -398,6 +398,8 @@ PriceUHT=U.P. (net) PriceUHTCurrency=U.P (net) (currency) PriceUTTC=U.P. (inc. tax) PriceUTTCCurrency=U.P (inc. tax) (currency) +ShippableStatus=Shippable +Shipped=Shipped Amount=Amount Amounts=Amounts AmountInvoice=Invoice amount