From 5e5b11de7dd192404f8f3e9ca46c3de8921fb876 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 17:02:18 +0100 Subject: [PATCH 01/33] NEW shipment kits with dispatcher v2 --- htdocs/admin/stock.php | 8 +- htdocs/expedition/card.php | 87 +- htdocs/expedition/class/expedition.class.php | 810 ++++++++++++------ .../class/expeditionlinebatch.class.php | 4 +- htdocs/expedition/dispatch.php | 397 +++++---- htdocs/expedition/js/lib_dispatch.js.php | 75 +- htdocs/langs/en_US/products.lang | 1 + htdocs/langs/en_US/sendings.lang | 2 + htdocs/product/class/product.class.php | 15 +- .../stock/class/mouvementstock.class.php | 33 +- 10 files changed, 953 insertions(+), 479 deletions(-) diff --git a/htdocs/admin/stock.php b/htdocs/admin/stock.php index a784792ae029d..7c7be8f97e393 100644 --- a/htdocs/admin/stock.php +++ b/htdocs/admin/stock.php @@ -182,10 +182,16 @@ $disabled = ''; +if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') || isModEnabled('productbatch')) { + $disabled = ' disabled'; +} +if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + $langs->load('products'); + print info_admin($langs->trans('WhenProductVirtualOnOptionAreForced')); +} if (isModEnabled('productbatch')) { // If module lot/serial enabled, we force the inc/dec mode to STOCK_CALCULATE_ON_SHIPMENT_CLOSE and STOCK_CALCULATE_ON_RECEPTION_CLOSE $langs->load("productbatch"); - $disabled = ' disabled'; // STOCK_CALCULATE_ON_SHIPMENT_CLOSE $descmode = $langs->trans('DeStockOnShipmentOnClosing'); diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index d939deac49812..23ef506fdc1c7 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -407,7 +407,14 @@ } else { // batch mode if ($batch_line[$i]['qty'] > 0 || ($batch_line[$i]['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) { - $ret = $object->addline_batch($batch_line[$i], $array_options[$i]); + $origin_line_id = (int) $batch_line[$i]['ix_l']; + $origin_line = new OrderLine($db); + $res = $origin_line->fetch($origin_line_id); + if ($res <= 0) { + $error++; + setEventMessages($origin_line->error, $origin_line->errors, 'errors'); + } + $ret = $object->addline_batch($batch_line[$i], $array_options[$i], $origin_line); if ($ret < 0) { setEventMessages($object->error, $object->errors, 'errors'); $error++; @@ -2277,6 +2284,8 @@ print ''; // Loop on each product to send/sent + $conf->cache['product'] = array(); + $conf->cache['warehouse'] = array(); for ($i = 0; $i < $num_prod; $i++) { $parameters = array('i' => $i, 'line' => $lines[$i], 'line_id' => $line_id, 'num' => $num_prod, 'alreadysent' => $alreadysent, 'editColspan' => !empty($editColspan) ? $editColspan : 0, 'outputlangs' => $outputlangs); $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $object, $action); @@ -2297,8 +2306,13 @@ if ($lines[$i]->fk_product > 0) { // Define output language if (getDolGlobalInt('MAIN_MULTILANGS') && getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE')) { - $prod = new Product($db); - $prod->fetch($lines[$i]->fk_product); + $product_id = $lines[$i]->fk_product; + if (empty($conf->cache['product'][$product_id])) { + $prod = new Product($db); + $prod->fetch($product_id); + } else { + $prod = $conf->cache['product'][$product_id]; + } $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $lines[$i]->product_label; } else { $label = (!empty($lines[$i]->label) ? $lines[$i]->label : $lines[$i]->product_label); @@ -2497,19 +2511,55 @@ if ($lines[$i]->product_type == Product::TYPE_SERVICE && getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { print '('.$langs->trans("Service").')'; } elseif ($lines[$i]->entrepot_id > 0) { - $entrepot = new Entrepot($db); - $entrepot->fetch($lines[$i]->entrepot_id); - print $entrepot->getNomUrl(1); + $warehouse_id = $lines[$i]->entrepot_id; + if (empty($conf->cache['warehouse'][$warehouse_id])) { + $warehouse = new Entrepot($db); + $warehouse->fetch($warehouse_id); + } else { + $warehouse = $conf->cache['warehouse'][$warehouse_id]; + } + print $warehouse->getNomUrl(1); } elseif (count($lines[$i]->details_entrepot) > 1) { $detail = ''; foreach ($lines[$i]->details_entrepot as $detail_entrepot) { - if ($detail_entrepot->entrepot_id > 0) { - $entrepot = new Entrepot($db); - $entrepot->fetch($detail_entrepot->entrepot_id); - $detail .= $langs->trans("DetailWarehouseFormat", $entrepot->label, $detail_entrepot->qty_shipped).'
'; + $warehouse_id = $detail_entrepot->entrepot_id; + if ($warehouse_id > 0) { + if (empty($conf->cache['warehouse'][$warehouse_id])) { + $warehouse = new Entrepot($db); + $warehouse->fetch($warehouse_id); + } else { + $warehouse = $conf->cache['warehouse'][$warehouse_id]; + } + $detail .= $langs->trans("DetailWarehouseFormat", $warehouse->label, $detail_entrepot->qty_shipped).'
'; } } print $form->textwithtooltip(img_picto('', 'object_stock').' '.$langs->trans("DetailWarehouseNumber"), $detail); + } elseif (count($lines[$i]->detail_children) > 1) { + $detail = ''; + foreach ($lines[$i]->detail_children as $child_product_id => $child_stock_list) { + foreach ($child_stock_list as $warehouse_id => $total_qty) { + // get product from cache + $child_product_label = ''; + if (empty($conf->cache['product'][$child_product_id])) { + $child_product = new Product($db); + $child_product->fetch($child_product_id); + } else { + $child_product = $conf->cache['product'][$child_product_id]; + } + $child_product_label = $child_product->ref . ' ' . $child_product->label; + + // get warehouse from cache + if (empty($conf->cache['warehouse'][$warehouse_id])) { + $child_warehouse = new Entrepot($db); + $child_warehouse->fetch($warehouse_id); + } else { + $child_warehouse = $conf->cache['warehouse'][$warehouse_id]; + } + + $detail .= $langs->trans('DetailChildrenFormat', $child_product_label, $child_warehouse->label, $total_qty).'
'; + } + } + print $form->textwithtooltip(img_picto('', 'object_stock').' '.$langs->trans('DetailWarehouseNumber'), $detail); } print ''; } @@ -2570,9 +2620,24 @@ print '
'; print ''; } elseif ($object->statut == Expedition::STATUS_DRAFT) { + $edit_url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=editline&token='.newToken().'&lineid='.$lines[$i]->id; + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + $product_id = $lines[$i]->fk_product; + if (empty($conf->cache['product'][$product_id])) { + $product = new Product($db); + $product->fetch($product_id); + } else { + $product = $conf->cache['product'][$product_id]; + } + + if ($product->hasFatherOrChild(1)) { + $edit_url = dol_buildpath('/expedition/dispatch.php?id='.$object->id, 1); + } + } + // edit-delete buttons print ''; - print 'id.'">'.img_edit().''; + print ''.img_edit().''; print ''; print ''; print 'id.'">'.img_delete().''; diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 076a9317d6ec5..db952e4dbe7b4 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -419,15 +419,143 @@ public function create($user, $notrigger = 0) if ($this->db->query($sql)) { // Insert of lines $num = count($this->lines); + $kits_list = array(); + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + for ($i = 0; $i < $num; $i++) { + if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { + // virtual products + $line = $this->lines[$i]; + if ($line->fk_product > 0) { + if (!isset($kits_list[$line->fk_product])) { + if (!isset($line->product)) { + $line_product = new Product($this->db); + $result = $line_product->fetch($line->fk_product, '', '', '', 1, 1, 1); + if ($result <= 0) { + $error++; + } + } else { + $line_product = $line->product; + } + + // get all children of virtual product + $line_product->get_sousproduits_arbo(); + $prods_arbo = $line_product->get_arbo_each_prod($line->qty); + if (count($prods_arbo) > 0) { + $kits_list[$line->fk_product] = array( + 'arbo' => $prods_arbo, + 'total_qty' => $line->qty, + ); + } + } else { + $kits_list[$line->fk_product]['total_qty'] += $line->qty; + } + } + } + } + } + $kits_id_cached = array(); + $sub_kits_id_cached = array(); for ($i = 0; $i < $num; $i++) { - if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { - if (!isset($this->lines[$i]->detail_batch)) { // no batch management - if ($this->create_line($this->lines[$i]->entrepot_id, $this->lines[$i]->origin_line_id, $this->lines[$i]->qty, $this->lines[$i]->rang, $this->lines[$i]->array_options) <= 0) { - $error++; + $line = $this->lines[$i]; + if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { + $line_id = 0; + if (!isset($kits_id_cached[$line->fk_product])) { + if (!isset($line->detail_batch) || isset($kits_list[$line->fk_product])) { // no batch management or is kit + $qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty; + $warehouse_id = isset($kits_list[$line->fk_product]) ? 0 : $line->entrepot_id; + $line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product); + if ($line_id <= 0) { + $error++; + } + if (isset($kits_list[$line->fk_product])) $kits_id_cached[$line->fk_product] = $line_id; + } else { // with batch management + if ($this->create_line_batch($line, $line->array_options) <= 0) { + $error++; + } } - } else { // with batch management - if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) { - $error++; + } else { + $line_id = $kits_id_cached[$line->fk_product]; + } + + // virtual products + if (isset($kits_list[$line->fk_product])) { + $prods_arbo = $kits_list[$line->fk_product]['arbo']; + $total_qty = $kits_list[$line->fk_product]['total_qty']; + + // get all children of virtual product + $parent_line_id = $line_id; // parent line created + $level_last = 1; + $product_child_id = 0; + foreach ($prods_arbo as $index => $product_child_arr) { + // 'id' => Id product + // 'id_parent' => Id parent product + // 'ref' => Ref product + // 'nb' => Nb of units that compose parent product + // 'nb_total' => // Nb of units for all nb of product + // 'stock' => Stock + // 'stock_alert' => Stock alert + // 'label' => Label + // 'fullpath' => // Full path label + // 'type' => + // 'desiredstock' => Desired stock + // 'level' => Level + // 'incdec' => Need to be incremented or decremented + // 'entity' => Entity + $product_child_level = (int) $product_child_arr['level']; + $product_child_incdec = !empty($product_child_arr['incdec']); + + // detect new level + if ($product_child_level != $level_last) { + $parent_line_id = $line_id; // last line id + $parent_product_id = $product_child_id; // last line id + if (isset($kits_id_cached[$parent_product_id])) { + $parent_line_id = $kits_id_cached[$parent_product_id]; + } else { + $kits_id_cached[$parent_product_id] = $parent_line_id; + } + } + + // determine if it's a kit : check next level + $is_kit = false; + $next_level = $product_child_level; + $next_index = $index + 1; + if (isset($prods_arbo[$next_index])) { + $next_level = (int) $prods_arbo[$next_index]['level']; + } + if ($next_level > $product_child_level) { + $is_kit = true; + } + + // determine quantity of sub-product + $product_child_id = (int) $product_child_arr['id']; + $qty = $line->qty; // by default + $warehouse_id = $line->entrepot_id; // by default + if ($is_kit || !$product_child_incdec) { + if ($is_kit) { + $qty = $total_qty; // insert only one line in expeditiondet table and use "total_qty" + } else { + $qty = 0; + } + $warehouse_id = 0; // no warehouse used for a kit or if stock is not managed (empty incdec) + } + $product_child_qty = (float) $product_child_arr['nb'] * $qty; + + // create line for a child of virtual product + if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) { + $line_id = $this->create_line($warehouse_id, 0, $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id); + if ($line_id <= 0) { + $error++; + dol_syslog(__METHOD__ . ' : ' . $this->errorsToString(), LOG_ERR); + break; + } + + // if kit or not manage stock (empty incdec) + if (empty($warehouse_id)) { + $sub_kits_id_cached[$product_child_id] = $line_id; + } + } + + $level_last = $product_child_level; } } } @@ -495,9 +623,11 @@ public function create($user, $notrigger = 0) * @param int $qty Quantity * @param int $rang Rang * @param array $array_options extrafields array + * @param int $parent_line_id Id of parent line for virtual products + * @param int $product_id Id of product (child of virtual product) * @return int Return integer <0 if KO, line_id if OK */ - public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = null) + public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = null, $parent_line_id = 0, $product_id = 0) { //phpcs:enable global $user; @@ -506,10 +636,18 @@ public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $arr $expeditionline->fk_expedition = $this->id; $expeditionline->entrepot_id = $entrepot_id; $expeditionline->fk_origin_line = $origin_line_id; + $expeditionline->fk_parent = $parent_line_id; + $expeditionline->fk_product = $product_id; $expeditionline->qty = $qty; $expeditionline->rang = $rang; $expeditionline->array_options = $array_options; + if (!($expeditionline->fk_product > 0)) { + $order_line = new OrderLine($this->db); + $order_line->fetch($expeditionline->fk_origin_line); + $expeditionline->fk_product = $order_line->fk_product; + } + if (($lineId = $expeditionline->insert($user)) < 0) { $this->errors[] = $expeditionline->error; } @@ -534,11 +672,11 @@ public function create_line_batch($line_ext, $array_options = 0) $tab = $line_ext->detail_batch; // create stockLocation Qty array foreach ($tab as $detbatch) { - if (!empty($detbatch->entrepot_id)) { - if (empty($stockLocationQty[$detbatch->entrepot_id])) { - $stockLocationQty[$detbatch->entrepot_id] = 0; + if (!empty($detbatch->fk_warehouse)) { + if (empty($stockLocationQty[$detbatch->fk_warehouse])) { + $stockLocationQty[$detbatch->fk_warehouse] = 0; } - $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty; + $stockLocationQty[$detbatch->fk_warehouse] += $detbatch->qty; } } // create shipment lines @@ -549,7 +687,7 @@ public function create_line_batch($line_ext, $array_options = 0) } else { // create shipment batch lines for stockLocation foreach ($tab as $detbatch) { - if ($detbatch->entrepot_id == $stockLocation) { + if ($detbatch->fk_warehouse == $stockLocation) { if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch $this->errors = $detbatch->errors; $error++; @@ -898,7 +1036,7 @@ public function create_delivery($user) * @param array $array_options extrafields array * @return int Return integer <0 if KO, >0 if OK */ - public function addline($entrepot_id, $id, $qty, $array_options = 0) + public function addline($entrepot_id, $id, $qty, $array_options = 0, $fk_product = 0, $fk_parent = 0) { global $conf, $langs; @@ -908,6 +1046,8 @@ public function addline($entrepot_id, $id, $qty, $array_options = 0) $line->entrepot_id = $entrepot_id; $line->origin_line_id = $id; $line->fk_origin_line = $id; + $line->fk_parent = $fk_parent; + $line->fk_product = $fk_product; $line->qty = $qty; $orderline = new OrderLine($this->db); @@ -916,10 +1056,11 @@ public function addline($entrepot_id, $id, $qty, $array_options = 0) // Copy the rang of the order line to the expedition line $line->rang = $orderline->rang; $line->product_type = $orderline->product_type; + if (!($line->fk_product > 0)) { + $line->fk_product = $orderline->fk_product; + } if (isModEnabled('stock') && !empty($orderline->fk_product)) { - $fk_product = $orderline->fk_product; - if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS')) { $langs->load("errors"); $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine"); @@ -928,7 +1069,7 @@ public function addline($entrepot_id, $id, $qty, $array_options = 0) if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) { $product = new Product($this->db); - $product->fetch($fk_product); + $product->fetch($line->fk_product); // Check must be done for stock of product into warehouse if $entrepot_id defined if ($entrepot_id > 0) { @@ -958,8 +1099,8 @@ public function addline($entrepot_id, $id, $qty, $array_options = 0) // If product need a batch number, we should not have called this function but addline_batch instead. // If this happen, we may have a bug in card.php page - if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) { - $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; // + if (isModEnabled('productbatch') && !empty($line->fk_product) && !empty($orderline->product_tobatch)) { + $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$line->fk_product; // return -4; } @@ -979,9 +1120,10 @@ public function addline($entrepot_id, $id, $qty, $array_options = 0) * * @param array $dbatch Array of value (key 'detail' -> Array, key 'qty' total quantity for line, key ix_l : original line index) * @param array $array_options extrafields array + * @param Object $origin_line Origin line (only from OrderLine at this moment) * @return int Return integer <0 if KO, >0 if OK */ - public function addline_batch($dbatch, $array_options = 0) + public function addline_batch($dbatch, $array_options = 0, $origin_line = null) { // phpcs:enable global $conf, $langs; @@ -1030,6 +1172,12 @@ public function addline_batch($dbatch, $array_options = 0) $line->fk_origin_line = $dbatch['ix_l']; $line->qty = $dbatch['qty']; $line->detail_batch = $tab; + if (!($line->rang > 0)) { + $line->rang = $origin_line->rang; + } + if (!($line->fk_product > 0)) { + $line->fk_product = $origin_line->fk_product; + } // extrafields if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used @@ -1215,19 +1363,26 @@ public function cancel($notrigger = 0, $also_update_stock = false) } // Stock control - if (!$error && isModEnabled('stock') && + $can_update_stock = isModEnabled('stock') && (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) || - ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) { + ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock)); + if (!$error) { require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php"; $langs->load("agenda"); - // Loop on each product line to add a stock movement and delete features - $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id"; - $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,"; - $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed"; + // Loop on each product line to add a stock movement (contain sub-products) + $sql = "SELECT "; + $sql .= " ed.fk_product"; + $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id"; + $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", 1, 0).") as iskit"; + $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", 1, "pai.incdec")." as incdec"; + $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed"; + $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product"; + $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent"; + $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product"; $sql .= " WHERE ed.fk_expedition = ".((int) $this->id); - $sql .= " AND cd.rowid = ed.fk_origin_line"; + $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec"; dol_syslog(get_class($this)."::delete select details", LOG_DEBUG); $resql = $this->db->query($sql); @@ -1239,45 +1394,68 @@ public function cancel($notrigger = 0, $also_update_stock = false) for ($i = 0; $i < $cpt; $i++) { dol_syslog(get_class($this)."::delete movement index ".$i); $obj = $this->db->fetch_object($resql); + $line_id = (int) $obj->expeditiondet_id; - $mouvS = new MouvementStock($this->db); - // we do not log origin because it will be deleted - $mouvS->origin = null; - // get lot/serial - $lotArray = null; - if (isModEnabled('productbatch')) { - $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id); - if (!is_array($lotArray)) { - $error++; - $this->errors[] = "Error ".$this->db->lasterror(); + if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) { + $mouvS = new MouvementStock($this->db); + // we do not log origin because it will be deleted + $mouvS->origin = null; + // get lot/serial + $lotArray = null; + if (isModEnabled('productbatch')) { + $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id); + if (!is_array($lotArray)) { + $error++; + $this->errors[] = "Error " . $this->db->lasterror(); + } } - } - if (empty($lotArray)) { - // no lot/serial - // We increment stock of product (and sub-products) - // We use warehouse selected for each line - $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed - if ($result < 0) { - $error++; - $this->errors = array_merge($this->errors, $mouvS->errors); - break; - } - } else { - // We increment stock of batches - // We use warehouse selected for each line - foreach ($lotArray as $lot) { - $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed + if (empty($lotArray)) { + // no lot/serial + // We increment stock of product (and sub-products) + // We use warehouse selected for each line + $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), '', '', '', '', 0, '', 0, 1); // Price is set to 0, because we don't want to see WAP changed if ($result < 0) { $error++; $this->errors = array_merge($this->errors, $mouvS->errors); break; } + } else { + // We increment stock of batches + // We use warehouse selected for each line + foreach ($lotArray as $lot) { + $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch, '', 0, '', 0, 1); // Price is set to 0, because we don't want to see WAP changed + if ($result < 0) { + $error++; + $this->errors = array_merge($this->errors, $mouvS->errors); + break; + } + } + if ($error) { + break; // break for loop incase of error + } } - if ($error) { - break; // break for loop incase of error + } + + if (!$error) { + // delete all children and batches of this shipment line + $shipment_line = new ExpeditionLigne($this->db); + $res = $shipment_line->fetch($line_id); + if ($res > 0) { + $result = $shipment_line->delete($user); + if ($result < 0) { + $error++; + $this->errors[] = "Error ".$shipment_line->errorsToString(); + } + } else { + $error++; + $this->errors[] = "Error ".$shipment_line->errorsToString(); } } + + if ($error) { + break; + } } } else { $error++; @@ -1285,86 +1463,67 @@ public function cancel($notrigger = 0, $also_update_stock = false) } } - // delete batch expedition line - if (!$error && isModEnabled('productbatch')) { - $shipmentlinebatch = new ExpeditionLineBatch($this->db); - if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) { + if (!$error) { + // Delete linked object + $res = $this->deleteObjectLinked(); + if ($res < 0) { $error++; - $this->errors[] = "Error ".$this->db->lasterror(); } - } - - - if (!$error) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet"; - $sql .= " WHERE fk_expedition = ".((int) $this->id); - - if ($this->db->query($sql)) { - // Delete linked object - $res = $this->deleteObjectLinked(); - if ($res < 0) { - $error++; - } - // No delete expedition - if (!$error) { - $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition"; - $sql .= " WHERE rowid = ".((int) $this->id); - - if ($this->db->query($sql)) { - if (!empty($this->origin) && $this->origin_id > 0) { - $this->fetch_origin(); - $origin = $this->origin; - if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" - // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" - $this->$origin->loadExpeditions(); - //var_dump($this->$origin->expeditions);exit; - if (count($this->$origin->expeditions) <= 0) { - $this->$origin->setStatut(Commande::STATUS_VALIDATED); - } + // No delete expedition + if (!$error) { + $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition"; + $sql .= " WHERE rowid = ".((int) $this->id); + + if ($this->db->query($sql)) { + if (!empty($this->origin) && $this->origin_id > 0) { + $this->fetch_origin(); + $origin = $this->origin; + if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" + // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" + $this->$origin->loadExpeditions(); + //var_dump($this->$origin->expeditions);exit; + if (count($this->$origin->expeditions) <= 0) { + $this->$origin->setStatut(Commande::STATUS_VALIDATED); } } + } - if (!$error) { - $this->db->commit(); + if (!$error) { + $this->db->commit(); - // We delete PDFs - $ref = dol_sanitizeFileName($this->ref); - if (!empty($conf->expedition->dir_output)) { - $dir = $conf->expedition->dir_output.'/sending/'.$ref; - $file = $dir.'/'.$ref.'.pdf'; - if (file_exists($file)) { - if (!dol_delete_file($file)) { - return 0; - } + // We delete PDFs + $ref = dol_sanitizeFileName($this->ref); + if (!empty($conf->expedition->dir_output)) { + $dir = $conf->expedition->dir_output.'/sending/'.$ref; + $file = $dir.'/'.$ref.'.pdf'; + if (file_exists($file)) { + if (!dol_delete_file($file)) { + return 0; } - if (file_exists($dir)) { - if (!dol_delete_dir_recursive($dir)) { - $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); - return 0; - } + } + if (file_exists($dir)) { + if (!dol_delete_dir_recursive($dir)) { + $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); + return 0; } } - - return 1; - } else { - $this->db->rollback(); - return -1; } + + return 1; } else { - $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -3; + return -1; } } else { $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -2; - }//*/ + return -3; + } } else { $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -1; + return -2; } } else { $this->db->rollback(); @@ -1408,9 +1567,10 @@ public function delete($notrigger = 0, $also_update_stock = false) } // Stock control - if (!$error && isModEnabled('stock') && - (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) || - ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) { + $can_update_stock = isModEnabled('stock') && + (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) || + ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock)); + if (!$error) { require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php"; $langs->load("agenda"); @@ -1418,12 +1578,18 @@ public function delete($notrigger = 0, $also_update_stock = false) // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously $shipmentlinebatch = new ExpeditionLineBatch($this->db); - // Loop on each product line to add a stock movement - $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id"; - $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,"; - $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed"; + // Loop on each product line to add a stock movement (contain sub-products) + $sql = "SELECT "; + $sql .= " ed.fk_product"; + $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id"; + $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", 1, 0).") as iskit"; + $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", 1, "pai.incdec")." as incdec"; + $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed"; + $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product"; + $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent"; + $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product"; $sql .= " WHERE ed.fk_expedition = ".((int) $this->id); - $sql .= " AND cd.rowid = ed.fk_origin_line"; + $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec"; dol_syslog(get_class($this)."::delete select details", LOG_DEBUG); $resql = $this->db->query($sql); @@ -1432,41 +1598,64 @@ public function delete($notrigger = 0, $also_update_stock = false) for ($i = 0; $i < $cpt; $i++) { dol_syslog(get_class($this)."::delete movement index ".$i); $obj = $this->db->fetch_object($resql); + $line_id = (int) $obj->expeditiondet_id; - $mouvS = new MouvementStock($this->db); - // we do not log origin because it will be deleted - $mouvS->origin = null; - // get lot/serial - $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id); - if (!is_array($lotArray)) { - $error++; - $this->errors[] = "Error ".$this->db->lasterror(); - } - if (empty($lotArray)) { - // no lot/serial - // We increment stock of product (and sub-products) - // We use warehouse selected for each line - $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed - if ($result < 0) { + if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) { + $mouvS = new MouvementStock($this->db); + // we do not log origin because it will be deleted + $mouvS->origin = null; + // get lot/serial + $lotArray = $shipmentlinebatch->fetchAll($line_id); + if (!is_array($lotArray)) { $error++; - $this->errors = array_merge($this->errors, $mouvS->errors); - break; + $this->errors[] = "Error " . $this->db->lasterror(); } - } else { - // We increment stock of batches - // We use warehouse selected for each line - foreach ($lotArray as $lot) { - $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed + if (empty($lotArray)) { + // no lot/serial + // We increment stock of product (disable for sub-products : already in shipment lines) + // We use warehouse selected for each line + $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), '', '', '', '', 0, '', 0, 1); // Price is set to 0, because we don't want to see WAP changed if ($result < 0) { $error++; $this->errors = array_merge($this->errors, $mouvS->errors); break; } + } else { + // We increment stock of batches + // We use warehouse selected for each line + foreach ($lotArray as $lot) { + $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch, '', 0, '', 0, 1); // Price is set to 0, because we don't want to see WAP changed + if ($result < 0) { + $error++; + $this->errors = array_merge($this->errors, $mouvS->errors); + break; + } + } + if ($error) { + break; // break for loop incase of error + } } - if ($error) { - break; // break for loop incase of error + } + + if (!$error) { + // delete all children and batches of this shipment line + $shipment_line = new ExpeditionLigne($this->db); + $res = $shipment_line->fetch($line_id); + if ($res > 0) { + $result = $shipment_line->delete($user); + if ($result < 0) { + $error++; + $this->errors[] = "Error ".$shipment_line->errorsToString(); + } + } else { + $error++; + $this->errors[] = "Error ".$shipment_line->errorsToString(); } } + + if ($error) { + break; + } } } else { $error++; @@ -1474,56 +1663,39 @@ public function delete($notrigger = 0, $also_update_stock = false) } } - // delete batch expedition line if (!$error) { - $shipmentlinebatch = new ExpeditionLineBatch($this->db); - if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) { + // Delete linked object + $res = $this->deleteObjectLinked(); + if ($res < 0) { $error++; - $this->errors[] = "Error ".$this->db->lasterror(); } - } - - if (!$error) { - $main = MAIN_DB_PREFIX.'expeditiondet'; - $ef = $main."_extrafields"; - $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")"; - - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet"; - $sql .= " WHERE fk_expedition = ".((int) $this->id); - - if ($this->db->query($sqlef) && $this->db->query($sql)) { - // Delete linked object - $res = $this->deleteObjectLinked(); - if ($res < 0) { - $error++; - } - // delete extrafields - $res = $this->deleteExtraFields(); - if ($res < 0) { - $error++; - } + // delete extrafields + $res = $this->deleteExtraFields(); + if ($res < 0) { + $error++; + } - if (!$error) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition"; - $sql .= " WHERE rowid = ".((int) $this->id); - - if ($this->db->query($sql)) { - if (!empty($this->origin) && $this->origin_id > 0) { - $this->fetch_origin(); - $origin = $this->origin; - if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" - // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" - $this->$origin->loadExpeditions(); - //var_dump($this->$origin->expeditions);exit; - if (count($this->$origin->expeditions) <= 0) { - $this->$origin->setStatut(Commande::STATUS_VALIDATED); - } + if (!$error) { + $sql = "DELETE FROM ".$this->db->prefix()."expedition"; + $sql .= " WHERE rowid = ".((int) $this->id); + + if ($this->db->query($sql)) { + if (!empty($this->origin) && $this->origin_id > 0) { + $this->fetch_origin(); + $origin = $this->origin; + if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress" + // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress" + $this->$origin->loadExpeditions(); + //var_dump($this->$origin->expeditions);exit; + if (count($this->$origin->expeditions) <= 0) { + $this->$origin->setStatut(Commande::STATUS_VALIDATED); } } + } - if (!$error) { - $this->db->commit(); + if (!$error) { + $this->db->commit(); // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive @@ -1547,25 +1719,20 @@ public function delete($notrigger = 0, $also_update_stock = false) } } - return 1; - } else { - $this->db->rollback(); - return -1; - } + return 1; } else { - $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -3; + return -1; } } else { $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -2; + return -3; } } else { $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); - return -1; + return -2; } } else { $this->db->rollback(); @@ -1738,6 +1905,35 @@ public function fetch_lines() } } + // virtual product : find all children stock (group by product id and warehouse id) + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + $detail_children = array(); // detail by product : array of [warehouse_id => total_qty] + $line_child_list = array(); + $res = $line->findAllChild($line->id, $line_child_list, 1); + if ($res > 0) { + if (!empty($line_child_list)) { + foreach ($line_child_list as $child_line) { + foreach ($child_line as $child_obj) { + $child_product_id = (int) $child_obj->fk_product; + $child_warehouse_id = (int) $child_obj->fk_warehouse; + + if ($child_warehouse_id > 0) { + // child quantities group by warehouses + if (!isset($detail_children[$child_product_id])) { + $detail_children[$child_product_id] = array(); + } + if (!isset($detail_children[$child_product_id][$child_warehouse_id])) { + $detail_children[$child_product_id][$child_warehouse_id] = 0; + } + $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty; + } + } + } + } + } + $line->detail_children = $detail_children; + } + $line->fetch_optionals(); if ($originline != $obj->fk_origin_line) { @@ -2289,17 +2485,17 @@ private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyCl $langs->load("agenda"); // Loop on each product line to add a stock movement - $sql = "SELECT cd.fk_product, cd.subprice,"; - $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,"; - $sql .= " e.ref,"; - $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,"; - $sql .= " cd.rowid as cdid, ed.rowid as edid"; - $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,"; - $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed"; - $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid"; - $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid"; + $sql = "SELECT"; + $sql .= " ed.rowid as edid, ed.fk_product, ed.qty, ed.fk_entrepot"; + $sql .= ", cd.rowid as cdid"; + $sql .= ", cd.subprice"; + $sql .= ", edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock"; + $sql .= ", e.ref"; + $sql .= " FROM " . $this->db->prefix() . "expeditiondet as ed"; + $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_origin_line"; + $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid"; + $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as e ON ed.fk_expedition = e.rowid"; $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id); - $sql .= " AND cd.rowid = ed.fk_origin_line"; dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG); $resql = $this->db->query($sql); @@ -2315,7 +2511,7 @@ private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyCl if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) { continue; } - dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid); + dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->edid . " edb.rowid=" . $obj->edbrowid); $mouvS = new MouvementStock($this->db); $mouvS->origin = &$this; @@ -2347,7 +2543,7 @@ private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyCl // If some stock lines are now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. - $sqldelete = "DELETE FROM ".MAIN_DB_PREFIX."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".MAIN_DB_PREFIX."product_batch as pb)"; + $sqldelete = "DELETE FROM ".$this->db->prefix()."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".$this->db->prefix()."product_batch as pb)"; $resqldelete = $this->db->query($sqldelete); // We do not test error, it can fails if there is child in batch details } @@ -2618,6 +2814,11 @@ class ExpeditionLigne extends CommonObjectLine */ public $origin_line_id; + /** + * @var int Id of parent line for children of virtual product + */ + public $fk_parent; + /** * Code of object line that is origin of the shipment line. * @@ -2659,6 +2860,9 @@ class ExpeditionLigne extends CommonObjectLine // We can use this to know warehouse planned to be used for each lot. public $detail_batch; + // virtual products : array of total of quantities group product id and warehouse id + public $detail_children; + // detail of warehouses and qty // We can use this to know warehouse when there is no lot. public $details_entrepot; @@ -2827,7 +3031,11 @@ public function insert($user, $notrigger = 0) $error = 0; // Check parameters - if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) { + if (empty($this->fk_expedition) + || empty($this->fk_product) // product id is mandatory + || (empty($this->fk_origin_line) && empty($this->fk_parent)) // at least origin line id of parent line id is set + || !is_numeric($this->qty)) + { $this->error = 'ErrorMandatoryParametersNotProvided'; return -1; } @@ -2849,12 +3057,16 @@ public function insert($user, $notrigger = 0) $sql .= "fk_expedition"; $sql .= ", fk_entrepot"; $sql .= ", fk_origin_line"; + $sql .= ", fk_parent"; + $sql .= ", fk_product"; $sql .= ", qty"; $sql .= ", rang"; $sql .= ") VALUES ("; $sql .= $this->fk_expedition; $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id); - $sql .= ", ".((int) $this->fk_origin_line); + $sql .= ", ".(empty($this->fk_origin_line) ? 'NULL' : $this->fk_origin_line); + $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : $this->fk_parent); + $sql .= ", ".(empty($this->fk_product) ? 'NULL' : $this->fk_product); $sql .= ", ".price2num($this->qty, 'MS'); $sql .= ", ".((int) $ranktouse); $sql .= ")"; @@ -2882,7 +3094,7 @@ public function insert($user, $notrigger = 0) if ($error) { foreach ($this->errors as $errmsg) { - dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); + dol_syslog(__METHOD__.' '.$errmsg, LOG_ERR); $this->error .= ($this->error ? ', '.$errmsg : $errmsg); } } @@ -2899,6 +3111,68 @@ public function insert($user, $notrigger = 0) } } + /** + * Find all children + * + * @param int $line_id Line id + * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher + * @return int <0 if KO, >0 if OK + */ + public function findAllChild($line_id, &$list = array(), $mode = 0) + { + if ($line_id > 0) { + // find all child + $sql = "SELECT ed.rowid as child_line_id"; + if ($mode == 1) { + $sql .= ", ed.fk_product"; + $sql .= ", ed.fk_parent"; + $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty') . " as qty"; + $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse') . " as fk_warehouse"; + $sql .= ", eb.batch, eb.eatby, eb.sellby"; + } + $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as ed"; + $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as eb ON eb.fk_expeditiondet = " . ((int) $line_id); + $sql .= " WHERE ed.fk_parent = " . ((int) $line_id); + $sql .= $this->db->order('ed.fk_product,ed.rowid', 'ASC,ASC'); + + $resql = $this->db->query($sql); + if ($resql) { + while ($obj = $this->db->fetch_object($resql)) { + $child_line_id = (int) $obj->child_line_id; + if (!isset($list[$line_id])) { + $list[$line_id] = array(); + } + + if ($mode == 0) { + $list[$line_id][] = $child_line_id; + } elseif ($mode == 1) { + $line_obj = new stdClass(); + $line_obj->rowid = $child_line_id; + $line_obj->fk_product = $obj->fk_product; + $line_obj->fk_parent = $obj->fk_parent; + $line_obj->qty = $obj->qty; + $line_obj->fk_warehouse = $obj->fk_warehouse; + $line_obj->batch = $obj->batch; + $line_obj->eatby = $obj->eatby; + $line_obj->sellby = $obj->sellby; + $line_obj->iskit = $obj->iskit; + $line_obj->incdec = $obj->incdec; + $list[$line_id][] = $line_obj; + } + + $this->findAllChild($child_line_id, $list, $mode); + } + $this->db->free($resql); + } else { + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + dol_syslog(__METHOD__.' '.$this->error, LOG_ERR); + } + } + + return 1; + } + /** * Delete shipment line. * @@ -2912,53 +3186,93 @@ public function delete($user = null, $notrigger = 0) $this->db->begin(); - // delete batch expedition line - if (isModEnabled('productbatch')) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch"; - $sql .= " WHERE fk_expeditiondet = ".((int) $this->id); + // virtual products : delete all children and batch + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) { + // find all children + $result = $this->findAllChild($this->id, $line_id_list); + if ($result) { + $child_line_id_list = array_reverse($line_id_list, true); + foreach ($child_line_id_list as $child_line_id_arr) { + foreach ($child_line_id_arr as $child_line_id) { + // delete batch expedition line + if (isModEnabled('productbatch')) { + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet_batch"; + $sql .= " WHERE fk_expeditiondet = " . ((int) $child_line_id); + if (!$this->db->query($sql)) { + $error++; + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; + } + } - if (!$this->db->query($sql)) { - $this->errors[] = $this->db->lasterror()." - sql=$sql"; + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet"; + $sql .= " WHERE rowid = " . ((int) $child_line_id); + if (!$this->db->query($sql)) { + $error++; + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; + } + + if ($error) { + break; + } + } + if ($error) { + break; + } + } + } else { $error++; } } - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet"; - $sql .= " WHERE rowid = ".((int) $this->id); + if (!$error) { + // delete batch expedition line + if (isModEnabled('productbatch')) { + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet_batch"; + $sql .= " WHERE fk_expeditiondet = " . ((int) $this->id); - if (!$error && $this->db->query($sql)) { - // Remove extrafields - if (!$error) { - $result = $this->deleteExtraFields(); - if ($result < 0) { - $this->errors[] = $this->error; + if (!$this->db->query($sql)) { + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; $error++; } } - if (!$error && !$notrigger) { - // Call trigger - $result = $this->call_trigger('LINESHIPPING_DELETE', $user); - if ($result < 0) { - $this->errors[] = $this->error; - $error++; + + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet"; + $sql .= " WHERE rowid = " . ((int) $this->id); + + if (!$error && $this->db->query($sql)) { + // Remove extrafields + if (!$error) { + $result = $this->deleteExtraFields(); + if ($result < 0) { + $this->errors[] = $this->error; + $error++; + } } - // End call triggers + if (!$error && !$notrigger) { + // Call trigger + $result = $this->call_trigger('LINESHIPPING_DELETE', $user); + if ($result < 0) { + $this->errors[] = $this->error; + $error++; + } + // End call triggers + } + } else { + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; + $error++; } - } else { - $this->errors[] = $this->db->lasterror()." - sql=$sql"; - $error++; - } - if (!$error) { - $this->db->commit(); - return 1; - } else { - foreach ($this->errors as $errmsg) { - dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); - $this->error .= ($this->error ? ', '.$errmsg : $errmsg); + if (!$error) { + $this->db->commit(); + return 1; + } else { + foreach ($this->errors as $errmsg) { + dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR); + $this->error .= ($this->error ? ', ' . $errmsg : $errmsg); + } + $this->db->rollback(); + return -1 * $error; } - $this->db->rollback(); - return -1 * $error; } } @@ -3075,7 +3389,7 @@ public function update($user = null, $notrigger = 0) $shipmentLot->batch = $lot->batch; $shipmentLot->eatby = $lot->eatby; $shipmentLot->sellby = $lot->sellby; - $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id; + $shipmentLot->fk_warehouse = $this->detail_batch->entrepot_id; $shipmentLot->qty = $this->detail_batch->qty; $shipmentLot->fk_origin_stock = $batch_id; if ($shipmentLot->create($this->id) < 0) { diff --git a/htdocs/expedition/class/expeditionlinebatch.class.php b/htdocs/expedition/class/expeditionlinebatch.class.php index 4c8ee08e5a1b9..75d82d06aa5a8 100644 --- a/htdocs/expedition/class/expeditionlinebatch.class.php +++ b/htdocs/expedition/class/expeditionlinebatch.class.php @@ -43,7 +43,7 @@ class ExpeditionLineBatch extends CommonObject public $batch; public $qty; public $dluo_qty; // deprecated, use qty - public $entrepot_id; + public $entrepot_id; // @deprecated, use fk_warehouse public $fk_origin_stock; // rowid in llx_product_batch table (not usefull) public $fk_warehouse; // warehouse ID public $fk_expeditiondet; @@ -87,7 +87,7 @@ public function fetchFromStock($id_stockdluo) $this->sellby = $this->db->jdate($obj->sellby); $this->eatby = $this->db->jdate($obj->eatby); $this->batch = $obj->batch; - $this->entrepot_id = $obj->fk_entrepot; + $this->fk_warehouse = $obj->fk_entrepot; $this->fk_origin_stock = (int) $id_stockdluo; } $this->db->free($resql); diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 8ba6e6d77682b..8da42bd923051 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -130,34 +130,35 @@ foreach ($_POST as $key => $value) { // without batch module enabled $reg = array(); - if (preg_match('/^product_.*([0-9]+)_([0-9]+)$/i', $key, $reg)) { + if (preg_match('/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { $pos++; - if (preg_match('/^product_([0-9]+)_([0-9]+)$/i', $key, $reg)) { + if (preg_match('/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { $modebatch = "barcode"; - } elseif (preg_match('/^product_batch_([0-9]+)_([0-9]+)$/i', $key, $reg)) { // With batchmode enabled + } elseif (preg_match('/^productbatch([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { // With batchmode enabled $modebatch = "batch"; } $numline = $pos; + $dispatch_line_suffix = $reg[1].'_'.$reg[2].'_'.$reg[3]; if ($modebatch == "barcode") { - $prod = "product_".$reg[1].'_'.$reg[2]; + $prod = "product".$dispatch_line_suffix; } else { - $prod = 'product_batch_'.$reg[1].'_'.$reg[2]; + $prod = 'productbatch'.$dispatch_line_suffix; } - $qty = "qty_".$reg[1].'_'.$reg[2]; - $ent = "entrepot_".$reg[1].'_'.$reg[2]; - $fk_commandedet = "fk_commandedet_".$reg[1].'_'.$reg[2]; - $idline = GETPOST("idline_".$reg[1].'_'.$reg[2]); + $qty = "qty".$dispatch_line_suffix; + $ent = "entrepot".$dispatch_line_suffix; + $fk_commandedet = "fk_commandedet".$dispatch_line_suffix; + $idline = GETPOSTINT("idline".$dispatch_line_suffix); $warehouse_id = GETPOSTINT($ent); $prod_id = GETPOSTINT($prod); - //$pu = "pu_".$reg[1].'_'.$reg[2]; // This is unit price including discount + //$pu = "pu".$dispatch_line_suffix; // This is unit price including discount $lot = ''; $dDLUO = ''; $dDLC = ''; if ($modebatch == "batch") { //TODO: Make impossible to input non existing batch code - $lot = GETPOST('lot_number_'.$reg[1].'_'.$reg[2]); - $dDLUO = dol_mktime(12, 0, 0, GETPOST('dluo_'.$reg[1].'_'.$reg[2].'month', 'int'), GETPOST('dluo_'.$reg[1].'_'.$reg[2].'day', 'int'), GETPOST('dluo_'.$reg[1].'_'.$reg[2].'year', 'int')); - $dDLC = dol_mktime(12, 0, 0, GETPOST('dlc_'.$reg[1].'_'.$reg[2].'month', 'int'), GETPOST('dlc_'.$reg[1].'_'.$reg[2].'day', 'int'), GETPOST('dlc_'.$reg[1].'_'.$reg[2].'year', 'int')); + $lot = GETPOST('lot_number'.$dispatch_line_suffix); + $dDLUO = dol_mktime(12, 0, 0, GETPOST('dluo'.$dispatch_line_suffix.'month', 'int'), GETPOST('dluo'.$dispatch_line_suffix.'day', 'int'), GETPOST('dluo'.$dispatch_line_suffix.'year', 'int')); + $dDLC = dol_mktime(12, 0, 0, GETPOST('dlc'.$dispatch_line_suffix.'month', 'int'), GETPOST('dlc'.$dispatch_line_suffix.'day', 'int'), GETPOST('dlc'.$dispatch_line_suffix.'year', 'int')); } $newqty = price2num(GETPOST($qty, 'alpha'), 'MS'); @@ -174,8 +175,8 @@ } if (!$error && $modebatch == "batch") { $sql = "SELECT pb.rowid "; - $sql .= " FROM ".MAIN_DB_PREFIX."product_batch as pb"; - $sql .= " JOIN ".MAIN_DB_PREFIX."product_stock as ps"; + $sql .= " FROM ".$db->prefix()."product_batch as pb"; + $sql .= " JOIN ".$db->prefix()."product_stock as ps"; $sql .= " ON ps.rowid = pb.fk_product_stock"; $sql .= " WHERE pb.batch = '".$db->escape($lot)."'"; $sql .= " AND ps.fk_product = ".((int) $prod_id) ; @@ -228,11 +229,11 @@ if (!$error && $modebatch == "batch") { if ($newqty > 0) { - $suffixkeyfordate = preg_replace('/^product_batch/', '', $key); + $suffixkeyfordate = preg_replace('/^productbatch/', '', $key); $sellby = dol_mktime(0, 0, 0, GETPOST('dlc'.$suffixkeyfordate.'month'), GETPOST('dlc'.$suffixkeyfordate.'day'), GETPOST('dlc'.$suffixkeyfordate.'year'), ''); $eatby = dol_mktime(0, 0, 0, GETPOST('dluo'.$suffixkeyfordate.'month'), GETPOST('dluo'.$suffixkeyfordate.'day'), GETPOST('dluo'.$suffixkeyfordate.'year')); - $sqlsearchdet = "SELECT rowid FROM ".MAIN_DB_PREFIX.$expeditionlinebatch->table_element; + $sqlsearchdet = "SELECT rowid FROM ".$db->prefix().$expeditionlinebatch->table_element; $sqlsearchdet .= " WHERE fk_expeditiondet = ".((int) $idline); $sqlsearchdet .= " AND batch = '".$db->escape($lot)."'"; $resqlsearchdet = $db->query($sqlsearchdet); @@ -244,20 +245,20 @@ } if ($objsearchdet) { - $sql = "UPDATE ".MAIN_DB_PREFIX.$expeditionlinebatch->table_element." SET"; + $sql = "UPDATE ".$db->prefix().$expeditionlinebatch->table_element." SET"; $sql .= " eatby = ".($eatby ? "'".$db->idate($eatby)."'" : "null"); $sql .= " , sellby = ".($sellby ? "'".$db->idate($sellby)."'" : "null"); $sql .= " , qty = ".((float) $newqty); $sql .= " , fk_warehouse = ".((int) $warehouse_id); $sql .= " WHERE rowid = ".((int) $objsearchdet->rowid); } else { - $sql = "INSERT INTO ".MAIN_DB_PREFIX.$expeditionlinebatch->table_element." ("; + $sql = "INSERT INTO ".$db->prefix().$expeditionlinebatch->table_element." ("; $sql .= "fk_expeditiondet, eatby, sellby, batch, qty, fk_origin_stock, fk_warehouse)"; $sql .= " VALUES (".((int) $idline).", ".($eatby ? "'".$db->idate($eatby)."'" : "null").", ".($sellby ? "'".$db->idate($sellby)."'" : "null").", "; $sql .= " '".$db->escape($lot)."', ".((float) $newqty).", 0, ".((int) $warehouse_id).")"; } } else { - $sql = " DELETE FROM ".MAIN_DB_PREFIX.$expeditionlinebatch->table_element; + $sql = " DELETE FROM ".$db->prefix().$expeditionlinebatch->table_element; $sql .= " WHERE fk_expeditiondet = ".((int) $idline); $sql .= " AND batch = '".$db->escape($lot)."'"; } @@ -272,7 +273,11 @@ } else { $expeditiondispatch->fk_expedition = $object->id; $expeditiondispatch->entrepot_id = GETPOST($ent, 'int'); - $expeditiondispatch->fk_origin_line = GETPOST($fk_commandedet, 'int'); + $expeditiondispatch->fk_parent = GETPOST('fk_parent'.$dispatch_line_suffix, 'int'); + $expeditiondispatch->fk_product = $prod_id; + if (!($expeditiondispatch->fk_parent > 0)) { + $expeditiondispatch->fk_origin_line = GETPOST($fk_commandedet, 'int'); + } $expeditiondispatch->qty = $newqty; if ($newqty > 0) { @@ -347,7 +352,7 @@ setEventMessages($error, $errors, 'errors'); } else { $db->commit(); - setEventMessages($langs->trans("ReceptionUpdated"), null); + setEventMessages($langs->trans("ShipmentUpdated"), null); header("Location: ".DOL_URL_ROOT.'/expedition/dispatch.php?id='.$object->id); exit; @@ -551,7 +556,7 @@ // Get list of lines of the shipment $products_dispatched, with qty dispatched for each product id $products_dispatched = array(); $sql = "SELECT ed.fk_origin_line as rowid, sum(ed.qty) as qty"; - $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed"; + $sql .= " FROM ".$db->prefix()."expeditiondet as ed"; $sql .= " WHERE ed.fk_expedition = ".((int) $object->id); $sql .= " GROUP BY ed.fk_origin_line"; @@ -586,8 +591,8 @@ } $sql .= $hookmanager->resPrint; - $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as l"; - $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON l.fk_product=p.rowid"; + $sql .= " FROM ".$db->prefix()."commandedet as l"; + $sql .= " LEFT JOIN ".$db->prefix()."product as p ON l.fk_product=p.rowid"; $sql .= " WHERE l.fk_commande = ".((int) $objectsrc->id); if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) { $sql .= " AND l.product_type = 0"; @@ -774,14 +779,17 @@ print ''; // Warehouse column /*$sql = "SELECT cfd.rowid, cfd.qty, cfd.fk_entrepot, cfd.batch, cfd.eatby, cfd.sellby, cfd.fk_product"; - $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as cfd"; + $sql .= " FROM ".$db->prefix()."commande_fournisseur_dispatch as cfd"; $sql .= " WHERE cfd.fk_commandefourndet = ".(int) $objp->rowid;*/ - $sql = "SELECT ed.rowid, ed.qty, ed.fk_entrepot,"; - $sql .= " eb.batch, eb.eatby, eb.sellby, cd.fk_product"; - $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed"; - $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet"; - $sql .= " JOIN ".MAIN_DB_PREFIX."commandedet as cd on ed.fk_origin_line = cd.rowid"; + $sql = "SELECT ed.rowid"; + $sql .= ", cd.fk_product"; + $sql .= ", ".$db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty')." as qty"; + $sql .= ", ".$db->ifsql('eb.rowid IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse')." as fk_warehouse"; + $sql .= ", eb.batch, eb.eatby, eb.sellby"; + $sql .= " FROM ".$db->prefix()."expeditiondet as ed"; + $sql .= " LEFT JOIN ".$db->prefix()."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet"; + $sql .= " JOIN ".$db->prefix()."commandedet as cd on ed.fk_origin_line = cd.rowid"; $sql .= " WHERE ed.fk_origin_line =".(int) $objp->rowid; $sql .= " AND ed.fk_expedition =".(int) $object->id; $sql .= " ORDER BY ed.rowid, ed.fk_origin_line"; @@ -791,76 +799,204 @@ if ($resultsql) { $numd = $db->num_rows($resultsql); - while ($j < $numd) { - $suffix = "_".$j."_".$i; - $objd = $db->fetch_object($resultsql); - - if (isModEnabled('productbatch') && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) { - $type = 'batch'; - - // Enable hooks to append additional columns - $parameters = array( - // allows hook to distinguish between the rows with information and the rows with dispatch form input - 'is_information_row' => true, - 'j' => $j, - 'suffix' => $suffix, - 'objd' => $objd, - ); - $reshook = $hookmanager->executeHooks( - 'printFieldListValue', - $parameters, - $object, - $action - ); - if ($reshook < 0) { - setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + while ($obj_exp = $db->fetch_object($resultsql)) { + $suffix = "_" . $j . "_" . $i; + + $expedition_line_child_list = array(); + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + // virtual product : find all children + if ($tmpproduct->hasFatherOrChild(1) > 0) { + $line_id_list = array(); + + // load all child as object line + $expeditionLine = new ExpeditionLigne($db); + $result = $expeditionLine->findAllChild($obj_exp->rowid, $line_id_list, 1); + if ($result > 0) { + $child_level = 1; + foreach ($line_id_list as $line_id_arr) { + foreach ($line_id_arr as $line_obj) { + $child_product_id = (int) $line_obj->fk_product; + if (empty($conf->cache['product'][$child_product_id])) { + $child_product = new Product($db); + $child_product->fetch($child_product_id); + $conf->cache['product'][$child_product_id] = $child_product; + } else { + $child_product = $conf->cache['product'][$child_product_id]; + } + + // determine if line is virtual product and stock is managed + $line_obj->iskit = 0; + $line_obj->incdec = 1; + $sql_child = "SELECT "; + $sql_child .= " SUM(".$db->ifsql("pa.rowid IS NOT NULL", 1, 0).") as iskit"; + $sql_child .= ", ".$db->ifsql("pai.incdec IS NULL", 1, "pai.incdec")." as incdec"; + $sql_child .= " FROM ".$db->prefix()."expeditiondet as ed"; + $sql_child .= " LEFT JOIN ".$db->prefix()."expeditiondet as edp ON edp.rowid = ".$line_obj->fk_parent; + $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pa ON pa.fk_product_pere = ".$child_product_id; + $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ".$child_product_id; + $sql_child .= " WHERE ed.rowid = ".$line_obj->rowid; + $sql_child .= " GROUP BY pa.rowid, pai.incdec"; + $resql_child = $db->query($sql_child); + if ($resql_child) { + if ($child_obj = $db->fetch_object($resql_child)) { + $line_obj->iskit = (int) $child_obj->iskit; + $line_obj->incdec = (int) $child_obj->incdec; + } + $db->free($resql_child); + } + $line_obj->html_label = str_repeat("    ", $child_level) . "→" . $child_product->getNomUrl(1); + $expedition_line_child_list[] = $line_obj; + } + $child_level++; + } + } } - print $hookmanager->resPrint; - - print ''; + } + if (empty($expedition_line_child_list)) { + $obj_exp->iskit = 0; // is not virtual product + $obj_exp->incdec = 1; // manage stock + $expedition_line_child_list[] = $obj_exp; + } - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; + $child_suffix = $suffix; + foreach ($expedition_line_child_list as $objd) { + $child_line_id = $objd->rowid; + + $can_update_stock = empty($objd->iskit) && !empty($objd->incdec); + $suffix = $child_line_id.$child_suffix; + + if (isModEnabled('productbatch') && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) { + $type = 'batch'; + + // Enable hooks to append additional columns + $parameters = array( + // allows hook to distinguish between the rows with information and the rows with dispatch form input + 'is_information_row' => true, + 'j' => $j, + 'suffix' => $suffix, + 'objd' => $objd, + ); + $reshook = $hookmanager->executeHooks( + 'printFieldListValue', + $parameters, + $object, + $action + ); + if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + } + print $hookmanager->resPrint; + + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; - print ''; - print ''; + print ''; + print ''; + print ''; + //print ''; + print ''; + if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + print ''; + $dlcdatesuffix = !empty($objd->sellby) ? dol_stringtotime($objd->sellby) : dol_mktime(0, 0, 0, GETPOST('dlc' . $suffix . 'month'), GETPOST('dlc' . $suffix . 'day'), GETPOST('dlc' . $suffix . 'year')); + print $form->selectDate($dlcdatesuffix, 'dlc' . $suffix, '', '', 1, ''); + print ''; + } + if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + print ''; + $dluodatesuffix = !empty($objd->eatby) ? dol_stringtotime($objd->eatby) : dol_mktime(0, 0, 0, GETPOST('dluo' . $suffix . 'month'), GETPOST('dluo' . $suffix . 'day'), GETPOST('dluo' . $suffix . 'year')); + print $form->selectDate($dluodatesuffix, 'dluo' . $suffix, '', '', 1, ''); + print ''; + } + print ' '; // Supplier ref + Qty ordered + qty already dispatched + } else { + $type = 'dispatch'; + $colspan = 6; + $colspan = (getDolGlobalString('PRODUCT_DISABLE_SELLBY')) ? --$colspan : $colspan; + $colspan = (getDolGlobalString('PRODUCT_DISABLE_EATBY')) ? --$colspan : $colspan; + + // Enable hooks to append additional columns + $parameters = array( + // allows hook to distinguish between the rows with information and the rows with dispatch form input + 'is_information_row' => true, + 'j' => $j, + 'suffix' => $suffix, + 'objd' => $objd, + ); + $reshook = $hookmanager->executeHooks( + 'printFieldListValue', + $parameters, + $object, + $action + ); + if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + } + print $hookmanager->resPrint; + + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + if (!empty($objd->html_label)) { + print $objd->html_label; + } + print ''; + } + // Qty to dispatch + print ''; + $suggestedvalue = (GETPOSTISSET('qty' . $suffix) ? GETPOST('qty' . $suffix, 'int') : $objd->qty); + //var_dump($suggestedvalue);exit; + if ($can_update_stock) { + print '' . img_picto($langs->trans("Reset"), 'eraser', 'class="pictofixedwidth opacitymedium"') . ''; + print ''; + } else { + print ''; + } print ''; - print ''; - print ''; - //print ''; - print ''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { - print ''; - $dlcdatesuffix = !empty($objd->sellby) ? dol_stringtotime($objd->sellby) : dol_mktime(0, 0, 0, GETPOST('dlc'.$suffix.'month'), GETPOST('dlc'.$suffix.'day'), GETPOST('dlc'.$suffix.'year')); - print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, '', '', 1, ''); - print ''; + if ($can_update_stock) { + print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" ' . ' onClick="addDispatchLine(' . $i . ', \'' . $type . '-' . $child_line_id . '\')"'); } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { - print ''; - $dluodatesuffix = !empty($objd->eatby) ? dol_stringtotime($objd->eatby) : dol_mktime(0, 0, 0, GETPOST('dluo'.$suffix.'month'), GETPOST('dluo'.$suffix.'day'), GETPOST('dluo'.$suffix.'year')); - print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, '', '', 1, ''); - print ''; + print ''; + + // Warehouse + print ''; + if ($can_update_stock) { + if (count($listwarehouses) > 1) { + print $formproduct->selectWarehouses(GETPOST("entrepot" . $suffix) ? GETPOST("entrepot" . $suffix) : $objd->fk_warehouse, "entrepot" . $suffix, '', 1, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse' . $suffix); + } elseif (count($listwarehouses) == 1) { + print $formproduct->selectWarehouses(GETPOST("entrepot" . $suffix) ? GETPOST("entrepot" . $suffix) : $objd->fk_warehouse, "entrepot" . $suffix, '', 0, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse' . $suffix); + } else { + $langs->load("errors"); + print $langs->trans("ErrorNoWarehouseDefined"); + } } - print ' '; // Supplier ref + Qty ordered + qty already dispatched - } else { - $type = 'dispatch'; - $colspan = 6; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_SELLBY')) ? --$colspan : $colspan; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_EATBY')) ? --$colspan : $colspan; + print "\n"; // Enable hooks to append additional columns $parameters = array( - // allows hook to distinguish between the rows with information and the rows with dispatch form input - 'is_information_row' => true, - 'j' => $j, + 'is_information_row' => false, // this is a dispatch form row + 'i' => $i, 'suffix' => $suffix, - 'objd' => $objd, + 'objp' => $objp, ); $reshook = $hookmanager->executeHooks( 'printFieldListValue', @@ -873,74 +1009,17 @@ } print $hookmanager->resPrint; - print ''; - - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; + print "\n"; } - // Qty to dispatch - print ''; - print ''.img_picto($langs->trans("Reset"), 'eraser', 'class="pictofixedwidth opacitymedium"').''; - $suggestedvalue = (GETPOSTISSET('qty'.$suffix) ? GETPOST('qty'.$suffix, 'int') : $objd->qty); - //var_dump($suggestedvalue);exit; - print ''; - print ''; - print ''; - if (isModEnabled('productbatch') && $objp->tobatch > 0) { - $type = 'batch'; - print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" '.($numd != $j+1 ? 'style="display:none"' : '').' onClick="addDispatchLine('.$i.', \''.$type.'\')"'); - } else { - $type = 'dispatch'; - print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" '.($numd != $j+1 ? 'style="display:none"' : '').' onClick="addDispatchLine('.$i.', \''.$type.'\')"'); - } - - print ''; - // Warehouse - print ''; - if (count($listwarehouses) > 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix); - } elseif (count($listwarehouses) == 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix); - } else { - $langs->load("errors"); - print $langs->trans("ErrorNoWarehouseDefined"); - } - print "\n"; - - // Enable hooks to append additional columns - $parameters = array( - 'is_information_row' => false, // this is a dispatch form row - 'i' => $i, - 'suffix' => $suffix, - 'objp' => $objp, - ); - $reshook = $hookmanager->executeHooks( - 'printFieldListValue', - $parameters, - $object, - $action - ); - if ($reshook < 0) { - setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - } - print $hookmanager->resPrint; - - print "\n"; $j++; - $numline++; } - $suffix = "_".$j."_".$i; + + //$suffix = "_".$j."_".$i; } + /* if ($j == 0) { if (isModEnabled('productbatch') && !empty($objp->tobatch)) { $type = 'batch'; @@ -971,7 +1050,7 @@ print ''; print ''; print ''; - print ''; + print ''; print ''; print ''; @@ -1084,6 +1163,7 @@ print $hookmanager->resPrint; print "\n"; } + */ } } $i++; @@ -1393,7 +1473,7 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware $("select[name=fk_default_warehouse]").change(function() { console.log("warehouse is modified"); var fk_default_warehouse = $("option:selected", this).val(); - $("select[name^=entrepot_]").val(fk_default_warehouse).change(); + $("select[name^=entrepot]").val(fk_default_warehouse).change(); }); $("#autoreset").click(function() { @@ -1404,7 +1484,12 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware console.log("we process line "+id+" "+idtab); if ($(this).data("remove") == "clear") { /* data-remove=clear means that line qty must be cleared but line must not be removed */ console.log("We clear the object to expected value") - $("#qty_"+idtab[1]+"_"+idtab[2]).val(""); + var idlinetab = idtab[0].split("-"); + var idline = ""; + if (idlinetab.length > 0) { + idline = idlinetab[1]; + } + $("#qty"+idline+"_"+idtab[1]+"_"+idtab[2]).val(""); /* qtyexpected = $("#qty_"+idtab[1]+"_"+idtab[2]).data("expected") console.log(qtyexpected); @@ -1432,9 +1517,9 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware $(".resetline").on("click", function(event) { event.preventDefault(); id = $(this).attr("id"); - id = id.split("reset_"); - console.log("Reset trigger for id = qty_"+id[1]); - $("#qty_"+id[1]).val(""); + id = id.split("reset"); + console.log("Reset trigger for id = qty"+id[1]); + $("#qty"+id[1]).val(""); }); }); '; diff --git a/htdocs/expedition/js/lib_dispatch.js.php b/htdocs/expedition/js/lib_dispatch.js.php index dfcfbc54f833d..e2cd1aae7f120 100644 --- a/htdocs/expedition/js/lib_dispatch.js.php +++ b/htdocs/expedition/js/lib_dispatch.js.php @@ -70,18 +70,23 @@ function addDispatchLine(index, type, mode) { console.log("expedition/js/lib_dispatch.js.php addDispatchLine Split line type="+type+" index="+index+" mode="+mode); + var lineId = ''; + var typeArr = type.split('-'); + if (typeArr.length > 0) { + lineId = typeArr[1]; + } var $row0 = $("tr[name='"+type+'_0_'+index+"']"); var $dpopt = $row0.find('.hasDatepicker').first().datepicker('option', 'all'); // get current datepicker options to apply the same to the cloned datepickers var $row = $row0.clone(true); // clone first batch line to jQuery object var nbrTrs = $("tr[name^='"+type+"_'][name$='_"+index+"']").length; // count nb of tr line with attribute name that starts with 'batch_' or 'dispatch_', and end with _index var qtyOrdered = parseFloat($("#qty_ordered_0_"+index).val()); // Qty ordered is same for all rows - var qty = parseFloat($("#qty_"+(nbrTrs - 1)+"_"+index).val()); + var qty = parseFloat($("#qty"+lineId+"_"+(nbrTrs - 1)+"_"+index).val()); if (isNaN(qty)) { qty = ''; } - console.log("expedition/js/lib_dispatch.js.php addDispatchLine Split line nbrTrs="+nbrTrs+" qtyOrdered="+qtyOrdered+" qty="+qty); + console.log("expedition/js/lib_dispatch.js.php addDispatchLine Split line="+lineId+" nbrTrs="+nbrTrs+" qtyOrdered="+qtyOrdered+" qty="+qty); var qtyDispatched; @@ -106,7 +111,7 @@ function addDispatchLine(index, type, mode) { if (newlineqty <= 0) { newlineqty = qty - 1; oldlineqty = 1; - $("#qty_"+(nbrTrs - 1)+"_"+index).val(oldlineqty); + $("#qty"+lineId+"_"+(nbrTrs - 1)+"_"+index).val(oldlineqty); } //replace tr suffix nbr @@ -124,65 +129,65 @@ function addDispatchLine(index, type, mode) { }, 0); //create new select2 to avoid duplicate id of cloned one - $row.find("select[name='" + 'entrepot_' + nbrTrs + '_' + index + "']").select2(); + $row.find("select[name='"+'entrepot'+lineId+'_'+nbrTrs+'_'+index+"']").select2(); // TODO find solution to copy selected option to new select // TODO find solution to keep new tr's after page refresh //clear value $row.find("input[name^='qty']").val(''); //change name of new row - $row.attr('name', type + '_' + nbrTrs + '_' + index); + $row.attr('name', type+'_'+nbrTrs+'_'+index); //insert new row before last row - $("tr[name^='" + type + "_'][name$='_" + index + "']:last").after($row); + $("tr[name^='"+type+"_'][name$='_"+index+"']:last").after($row); //remove cloned select2 with duplicate id. - $("#s2id_entrepot_" + nbrTrs + '_' + index).detach(); // old way to find duplicated select2 component - $(".csswarehouse_" + nbrTrs + "_" + index + ":first-child").parent("span.selection").parent(".select2").detach(); + $("#s2id_entrepot"+lineId+"_"+nbrTrs+'_'+index).detach(); // old way to find duplicated select2 component + $(".csswarehouse"+lineId+"_"+nbrTrs+"_"+index + ":first-child").parent("span.selection").parent(".select2").detach(); /* Suffix of lines are: _ trs.length _ index */ - $("#qty_"+nbrTrs+"_"+index).focus(); + $("#qty"+lineId+"_"+nbrTrs+"_"+index).focus(); $("#qty_dispatched_0_"+index).val(oldlineqty); //hide all buttons then show only the last one - $("tr[name^='" + type + "_'][name$='_" + index + "'] .splitbutton").hide(); - $("tr[name^='" + type + "_'][name$='_" + index + "']:last .splitbutton").show(); + $("tr[name^='"+type+"_'][name$='_"+index+"'] .splitbutton").hide(); + $("tr[name^='"+type+"_'][name$='_"+index+"']:last .splitbutton").show(); - $("#reset_" + (nbrTrs) + "_" + index).click(function (event) { + $("#reset"+lineId+"_"+(nbrTrs)+"_"+index).click(function (event) { event.preventDefault(); id = $(this).attr("id"); - id = id.split("reset_"); + id = id.split("reset"+lineId+"_"); idrow = id[1]; - idlast = $("tr[name^='" + type + "_'][name$='_" + index + "']:last .qtydispatchinput").attr("id"); - if (idlast == $("#qty_" + idrow).attr("id")) { - console.log("expedition/js/lib_dispatch.js.php Remove trigger for tr name = " + type + "_" + idrow); - $('tr[name="' + type + '_' + idrow + '"').remove(); - $("tr[name^='" + type + "_'][name$='_" + index + "']:last .splitbutton").show(); + idlast = $("tr[name^='"+type+"_'][name$='_"+index+"']:last .qtydispatchinput").attr("id"); + if (idlast == $("#qty"+lineId+"_"+idrow).attr("id")) { + console.log("expedition/js/lib_dispatch.js.php Remove trigger for tr name = "+type+"_"+idrow); + $('tr[name="'+type+'_'+idrow+'"').remove(); + $("tr[name^='"+type+"_'][name$='_"+index+"']:last .splitbutton").show(); } else { - console.log("expedition/js/lib_dispatch.js.php Reset trigger for id = qty_" + idrow); - $("#qty_" + idrow).val(""); + console.log("expedition/js/lib_dispatch.js.php Reset trigger for id = qty_"+idrow); + $("#qty"+lineId+"_"+idrow).val(""); } }); if (mode === 'lessone') { qty = 1; // keep 1 in old line - $("#qty_"+(nbrTrs-1)+"_"+index).val(qty); + $("#qty"+lineId+"_"+(nbrTrs-1)+"_"+index).val(qty); } - $("#qty_"+nbrTrs+"_"+index).val(newlineqty); + $("#qty"+lineId+"_"+nbrTrs+"_"+index).val(newlineqty); // Store arbitrary data for dispatch qty input field change event - $("#qty_" + (nbrTrs - 1) + "_" + index).data('qty', qty); - $("#qty_" + (nbrTrs - 1) + "_" + index).data('type', type); - $("#qty_" + (nbrTrs - 1) + "_" + index).data('index', index); + $("#qty"+lineId+"_" + (nbrTrs - 1) + "_" + index).data('qty', qty); + $("#qty"+lineId+"_" + (nbrTrs - 1) + "_" + index).data('type', type); + $("#qty"+lineId+"_" + (nbrTrs - 1) + "_" + index).data('index', index); // Update dispatched qty when value dispatch qty input field changed //$("#qty_" + (nbrTrs - 1) + "_" + index).change(this.onChangeDispatchLineQty); //set focus on lot of new line (if it exists) - $("#lot_number_" + (nbrTrs) + "_" + index).focus(); + $("#lot_number"+lineId+"_"+(nbrTrs)+"_"+index).focus(); //Clean bad values - $("tr[name^='" + type + "_'][name$='_" + index + "']:last").data("remove", "remove"); - $("#lot_number_" + (nbrTrs) + "_" + index).val("") - $("#idline_" + (nbrTrs) + "_" + index).val("-1") - $("#qty_" + (nbrTrs) + "_" + index).data('expected', "0"); + $("tr[name^='"+type+"_'][name$='_"+index + "']:last").data("remove", "remove"); + $("#lot_number_"+(nbrTrs) + "_"+index).val("") + $("#idline"+lineId+"_"+(nbrTrs)+"_"+index).val("-1") + $("#qty"+lineId+"_"+(nbrTrs)+"_"+index).data('expected', "0"); //$("input[type='hidden']#lot_number_" + (nbrTrs) + "_" + index).remove(); - $("#lot_number_" + (nbrTrs) + "_" + index).removeAttr("disabled"); + $("#lot_number"+lineId+"_"+(nbrTrs)+"_"+index).removeAttr("disabled"); } } @@ -204,13 +209,13 @@ function onChangeDispatchLineQty(element) { index = id[2]; if (index >= 0 && type && qty >= 0) { - nbrTrs = $("tr[name^='" + type + "_'][name$='_" + index + "']").length; + nbrTrs = $("tr[name^='"+type+"_'][name$='_"+index+"']").length; qtyChanged = parseFloat($(element).val()) - qty; // qty changed qtyDispatching = parseFloat($(element).val()); // qty currently being dispatched - qtyOrdered = parseFloat($("#qty_ordered_0_" + index).val()); // qty ordered - qtyDispatched = parseFloat($("#qty_dispatched_0_" + index).val()); // qty already dispatched + qtyOrdered = parseFloat($("#qty_ordered_0_"+index).val()); // qty ordered + qtyDispatched = parseFloat($("#qty_dispatched_0_"+index).val()); // qty already dispatched - console.log("onChangeDispatchLineQty qtyChanged: " + qtyChanged + " qtyDispatching: " + qtyDispatching + " qtyOrdered: " + qtyOrdered + " qtyDispatched: " + qtyDispatched); + console.log("onChangeDispatchLineQty qtyChanged: "+qtyChanged+" qtyDispatching: "+qtyDispatching+" qtyOrdered: "+qtyOrdered+" qtyDispatched: "+qtyDispatched); if ((qtyChanged) <= (qtyOrdered - (qtyDispatched + qtyDispatching))) { $("#qty_dispatched_0_" + index).val(qtyDispatched + qtyChanged); diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 898ce26644312..5eb30d6fb439c 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -438,3 +438,4 @@ OrProductsWithCategories=Or products with tags/categories WarningTransferBatchStockMouvToGlobal = If you want to deserialize this product, all its serialized stock will be transformed into global stock WarningConvertFromBatchToSerial=If you currently have a quantity higher or equal to 2 for the product, switching to this choice means you will still have a product with different objects of the same batch (while you want a unique serial number). The duplicate will remain until an inventory or a manual stock movement to fix this is done. ConfirmSetToDraftInventory=Are you sure you want to go back to Draft status?
The quantities currently set in the inventory will be reset. +WhenProductVirtualOnOptionAreForced=When virtual products option is on, automatic stock decrease is forced to 'Decrease real stocks on shipping validation' and automatic increase mode is forced to 'Increase real stocks on manual dispatching into warehouses' and can't be edited. Other options can be defined as you want. diff --git a/htdocs/langs/en_US/sendings.lang b/htdocs/langs/en_US/sendings.lang index 357a27462a32b..225e786f1b3fe 100644 --- a/htdocs/langs/en_US/sendings.lang +++ b/htdocs/langs/en_US/sendings.lang @@ -63,6 +63,7 @@ NoProductToShipFoundIntoStock=No product to ship found in warehouse %s. C WeightVolShort=Weight/Vol. ValidateOrderFirstBeforeShipment=You must first validate the order before being able to make shipments. NoLineGoOnTabToAddSome=No line, go on tab "%s" to add +ShipmentUpdated=Shipment successfully updated # Sending methods # ModelDocument @@ -75,6 +76,7 @@ SumOfProductWeights=Sum of product weights # warehouse details DetailWarehouseNumber= Warehouse details DetailWarehouseFormat= W:%s (Qty: %d) +DetailChildrenFormat=%s : %s (Qty: %d) SHIPPING_DISPLAY_STOCK_ENTRY_DATE=Display last date of entry in stock during shipment creation for serial number or batch CreationOptions=Available options during shipment creation diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 443e315868331..8c85c07a1970c 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -5072,8 +5072,6 @@ public function getFather() */ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array()) { - global $alreadyfound; - if (empty($id)) { return array(); } @@ -5090,9 +5088,6 @@ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = a dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG); - if ($level == 1) { - $alreadyfound = array($id=>1); // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly - } // Protection against infinite loop if ($level > 30) { return array(); @@ -5101,14 +5096,14 @@ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = a $res = $this->db->query($sql); if ($res) { $prods = array(); + if ($this->db->num_rows($sql) > 0) $parents[] = $id; + while ($rec = $this->db->fetch_array($res)) { - if (!empty($alreadyfound[$rec['rowid']])) { + if (in_array($rec['id'], $parents)) { dol_syslog(get_class($this).'::getChildsArbo the product id='.$rec['rowid'].' was already found at a higher level in tree. We discard to avoid infinite loop', LOG_WARNING); - if (in_array($rec['id'], $parents)) { continue; // We discard this child if it is already found at a higher level in tree in the same branch. } - } - $alreadyfound[$rec['rowid']] = 1; + $prods[$rec['rowid']] = array( 0=>$rec['rowid'], 1=>$rec['qty'], @@ -5122,7 +5117,7 @@ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = a //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']); //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']); if (empty($firstlevelonly)) { - $parents[] = $rec['rowid']; + //$parents[] = $rec['rowid']; $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents); foreach ($listofchilds as $keyChild => $valueChild) { $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 6a56ca01a36ea..e0f1263f60ff0 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -833,28 +833,29 @@ public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $l /** * Increase stock for product and subproducts * - * @param User $user Object user - * @param int $fk_product Id product - * @param int $entrepot_id Warehouse id - * @param int $qty Quantity - * @param int $price Price - * @param string $label Label of stock movement - * @param integer|string $eatby eat-by date - * @param integer|string $sellby sell-by date - * @param string $batch batch number - * @param integer|string $datem Force date of movement - * @param int $id_product_batch Id product_batch - * @param string $inventorycode Inventory code - * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) - * @return int Return integer <0 if KO, >0 if OK + * @param User $user Object user + * @param int $fk_product Id product + * @param int $entrepot_id Warehouse id + * @param int $qty Quantity + * @param int $price Price + * @param string $label Label of stock movement + * @param integer|string $eatby eat-by date + * @param integer|string $sellby sell-by date + * @param string $batch batch number + * @param integer|string $datem Force date of movement + * @param int $id_product_batch Id product_batch + * @param string $inventorycode Inventory code + * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) + * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) + * @return int Return integer <0 if KO, >0 if OK */ - public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0) + public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0, $disablestockchangeforsubproduct = 0) { global $conf; $skip_batch = empty($conf->productbatch->enabled); - return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines); + return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, $disablestockchangeforsubproduct, $donotcleanemptylines); } /** From 54454f32b5a95607047a4e3e116fa63602208952 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 17:22:55 +0100 Subject: [PATCH 02/33] Fix PHPCS --- htdocs/expedition/class/expedition.class.php | 4 ++-- htdocs/expedition/dispatch.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index d457c7c0c059b..8b66c0e55094a 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -3034,8 +3034,7 @@ public function insert($user, $notrigger = 0) if (empty($this->fk_expedition) || empty($this->fk_product) // product id is mandatory || (empty($this->fk_origin_line) && empty($this->fk_parent)) // at least origin line id of parent line id is set - || !is_numeric($this->qty)) - { + || !is_numeric($this->qty)) { $this->error = 'ErrorMandatoryParametersNotProvided'; return -1; } @@ -3115,6 +3114,7 @@ public function insert($user, $notrigger = 0) * Find all children * * @param int $line_id Line id + * @param array $list List of sub-lines for a virtual product line * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher * @return int <0 if KO, >0 if OK */ diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 8da42bd923051..57db539fd3d37 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -973,7 +973,7 @@ print ''; print ''; if ($can_update_stock) { - print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" ' . ' onClick="addDispatchLine(' . $i . ', \'' . $type . '-' . $child_line_id . '\')"'); + print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine(' . $i . ', \'' . $type . '-' . $child_line_id . '\')"'); } print ''; From abc81a1267481572aa6e04234ac6100947cb3b56 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 17:30:36 +0100 Subject: [PATCH 03/33] Fix pre-commit --- htdocs/product/stock/class/mouvementstock.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 20a598f77f08c..114a40639d296 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -846,7 +846,7 @@ public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $l * @param int $id_product_batch Id product_batch * @param string $inventorycode Inventory code * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) - * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) + * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (useful only if product is a subproduct) * @return int Return integer <0 if KO, >0 if OK */ public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0, $disablestockchangeforsubproduct = 0) From 5bacbfa176e5d92d5ae52fb838ceb9e70837cc74 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 17:42:21 +0100 Subject: [PATCH 04/33] Fix PHPStan --- htdocs/expedition/class/expedition.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 8b66c0e55094a..66b7171c80231 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -3116,7 +3116,7 @@ public function insert($user, $notrigger = 0) * @param int $line_id Line id * @param array $list List of sub-lines for a virtual product line * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher - * @return int <0 if KO, >0 if OK + * @return int Return integer <0 if KO else >0 if OK */ public function findAllChild($line_id, &$list = array(), $mode = 0) { @@ -3178,7 +3178,7 @@ public function findAllChild($line_id, &$list = array(), $mode = 0) * * @param User $user User that modify * @param int $notrigger 0=launch triggers after, 1=disable triggers - * @return int >0 if OK, <0 if KO + * @return int Return integer < 0 if KO, > 0 if OK */ public function delete($user = null, $notrigger = 0) { From cdbee1e029d52d4f457c34e09328446165daee66 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 17:53:15 +0100 Subject: [PATCH 05/33] Fix PHPStan --- htdocs/expedition/class/expedition.class.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 66b7171c80231..bb23eb6342590 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -3261,18 +3261,18 @@ public function delete($user = null, $notrigger = 0) $this->errors[] = $this->db->lasterror() . " - sql=$sql"; $error++; } + } - if (!$error) { - $this->db->commit(); - return 1; - } else { - foreach ($this->errors as $errmsg) { - dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR); - $this->error .= ($this->error ? ', ' . $errmsg : $errmsg); - } - $this->db->rollback(); - return -1 * $error; + if (!$error) { + $this->db->commit(); + return 1; + } else { + foreach ($this->errors as $errmsg) { + dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR); + $this->error .= ($this->error ? ', ' . $errmsg : $errmsg); } + $this->db->rollback(); + return -1 * $error; } } From 8fa6c76395d6a8ca4132c5c5ba8452cd9aad6e2b Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 18:08:59 +0100 Subject: [PATCH 06/33] Fix travis CI --- htdocs/expedition/class/expedition.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index bb23eb6342590..08c611b3a673b 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1033,6 +1033,8 @@ public function create_delivery($user) * @param int $entrepot_id Id of warehouse * @param int $id Id of source line (order line) * @param int $qty Quantity + * @param int $fk_product Id of product + * @param int $fk_parent Id of parent line * @param array $array_options extrafields array * @return int Return integer <0 if KO, >0 if OK */ From 3409ca852645913b04b7e5d6ac1d955810f5a500 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 16 Jan 2024 18:11:46 +0100 Subject: [PATCH 07/33] Fix travis CI --- htdocs/expedition/class/expedition.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 08c611b3a673b..4078bd43f85b4 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1033,9 +1033,9 @@ public function create_delivery($user) * @param int $entrepot_id Id of warehouse * @param int $id Id of source line (order line) * @param int $qty Quantity + * @param array $array_options extrafields array * @param int $fk_product Id of product * @param int $fk_parent Id of parent line - * @param array $array_options extrafields array * @return int Return integer <0 if KO, >0 if OK */ public function addline($entrepot_id, $id, $qty, $array_options = 0, $fk_product = 0, $fk_parent = 0) From f50cf21a5fe4b3078f05465696428d00aa623923 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 22 Jan 2024 11:17:13 +0100 Subject: [PATCH 08/33] Fix travis CI --- htdocs/expedition/class/expedition.class.php | 4 ++-- htdocs/product/class/product.class.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 4078bd43f85b4..8592a7faddf9a 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1706,8 +1706,8 @@ public function delete($notrigger = 0, $also_update_stock = false) // We delete PDFs $ref = dol_sanitizeFileName($this->ref); if (!empty($conf->expedition->dir_output)) { - $dir = $conf->expedition->dir_output.'/sending/'.$ref; - $file = $dir.'/'.$ref.'.pdf'; + $dir = $conf->expedition->dir_output . '/sending/' . $ref; + $file = $dir . '/' . $ref . '.pdf'; if (file_exists($file)) { if (!dol_delete_file($file)) { return 0; diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 3b6ccb105722a..40f27c679764b 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -5117,9 +5117,9 @@ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = a while ($rec = $this->db->fetch_array($res)) { if (in_array($rec['id'], $parents)) { - dol_syslog(get_class($this).'::getChildsArbo the product id='.$rec['rowid'].' was already found at a higher level in tree. We discard to avoid infinite loop', LOG_WARNING); - continue; // We discard this child if it is already found at a higher level in tree in the same branch. - } + dol_syslog(get_class($this) . '::getChildsArbo the product id=' . $rec['rowid'] . ' was already found at a higher level in tree. We discard to avoid infinite loop', LOG_WARNING); + continue; // We discard this child if it is already found at a higher level in tree in the same branch. + } $prods[$rec['rowid']] = array( 0=>$rec['rowid'], From 3f72007a5d1195da5982245c04ce7e3226e9f4a3 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 22 Jan 2024 11:28:27 +0100 Subject: [PATCH 09/33] Fix travis CI --- htdocs/expedition/class/expedition.class.php | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 8592a7faddf9a..a34ad1d6fb858 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1699,27 +1699,27 @@ public function delete($notrigger = 0, $also_update_stock = false) if (!$error) { $this->db->commit(); - // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive - $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive - $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive - - // We delete PDFs - $ref = dol_sanitizeFileName($this->ref); - if (!empty($conf->expedition->dir_output)) { - $dir = $conf->expedition->dir_output . '/sending/' . $ref; - $file = $dir . '/' . $ref . '.pdf'; - if (file_exists($file)) { - if (!dol_delete_file($file)) { - return 0; - } + // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive + $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive + $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive + + // We delete PDFs + $ref = dol_sanitizeFileName($this->ref); + if (!empty($conf->expedition->dir_output)) { + $dir = $conf->expedition->dir_output . '/sending/' . $ref; + $file = $dir . '/' . $ref . '.pdf'; + if (file_exists($file)) { + if (!dol_delete_file($file)) { + return 0; } - if (file_exists($dir)) { - if (!dol_delete_dir_recursive($dir)) { - $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); - return 0; - } + } + if (file_exists($dir)) { + if (!dol_delete_dir_recursive($dir)) { + $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir); + return 0; } } + } return 1; } else { From f2c0f9ddb079dd9ef1d706d743961012df3eb9c9 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 22 Jan 2024 11:46:54 +0100 Subject: [PATCH 10/33] Fix travis CI --- htdocs/expedition/dispatch.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 57db539fd3d37..851b98c4b40ae 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -831,10 +831,10 @@ $sql_child .= " SUM(".$db->ifsql("pa.rowid IS NOT NULL", 1, 0).") as iskit"; $sql_child .= ", ".$db->ifsql("pai.incdec IS NULL", 1, "pai.incdec")." as incdec"; $sql_child .= " FROM ".$db->prefix()."expeditiondet as ed"; - $sql_child .= " LEFT JOIN ".$db->prefix()."expeditiondet as edp ON edp.rowid = ".$line_obj->fk_parent; - $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pa ON pa.fk_product_pere = ".$child_product_id; - $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ".$child_product_id; - $sql_child .= " WHERE ed.rowid = ".$line_obj->rowid; + $sql_child .= " LEFT JOIN ".$db->prefix()."expeditiondet as edp ON edp.rowid = ".((int) $line_obj->fk_parent); + $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pa ON pa.fk_product_pere = ".((int) $child_product_id); + $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ".((int) $child_product_id); + $sql_child .= " WHERE ed.rowid = ".((int) $line_obj->rowid); $sql_child .= " GROUP BY pa.rowid, pai.incdec"; $resql_child = $db->query($sql_child); if ($resql_child) { From 4a67790eaa1150bf0a1398f1785b55609f1b6316 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 16 Oct 2024 13:34:16 +0200 Subject: [PATCH 11/33] NEW dispatch kit with batches as sub-component --- htdocs/admin/stock.php | 49 +++++-- htdocs/expedition/class/expedition.class.php | 2 + htdocs/expedition/dispatch.php | 128 ++++++++++++++++-- htdocs/expedition/js/lib_dispatch.js.php | 10 +- .../stock/class/mouvementstock.class.php | 29 ++-- 5 files changed, 172 insertions(+), 46 deletions(-) diff --git a/htdocs/admin/stock.php b/htdocs/admin/stock.php index 77fe219e65e58..9ac3a21cb482c 100644 --- a/htdocs/admin/stock.php +++ b/htdocs/admin/stock.php @@ -181,18 +181,19 @@ $formproduct = new FormProduct($db); - -$disabled = ''; -if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') || isModEnabled('productbatch')) { - $disabled = ' disabled'; -} +$disableStockCalculateOn = array(); if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { $langs->load('products'); print info_admin($langs->trans('WhenProductVirtualOnOptionAreForced')); + $disableStockCalculateOn[] = 'BILL'; + $disableStockCalculateOn[] = 'VALIDATE_ORDER'; + $disableStockCalculateOn[] = 'SHIPMENT_CLOSE'; } if (isModEnabled('productbatch')) { // If module lot/serial enabled, we force the inc/dec mode to STOCK_CALCULATE_ON_SHIPMENT_CLOSE and STOCK_CALCULATE_ON_RECEPTION_CLOSE $langs->load("productbatch"); + $disableStockCalculateOn[] = 'BILL'; + $disableStockCalculateOn[] = 'VALIDATE_ORDER'; // STOCK_CALCULATE_ON_SHIPMENT_CLOSE $descmode = $langs->trans('DeStockOnShipmentOnClosing'); @@ -231,7 +232,7 @@ print ''; if (isModEnabled('invoice')) { if ($conf->use_javascript_ajax) { - if ($disabled) { + if (in_array('BILL', $disableStockCalculateOn)) { print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); } else { print ajax_constantonoff('STOCK_CALCULATE_ON_BILL', array(), null, 0, 0, 0, 2, 1, 0, '', '', 'reposition'); @@ -253,7 +254,7 @@ print ''; if (isModEnabled('order')) { if ($conf->use_javascript_ajax) { - if ($disabled) { + if (in_array('VALIDATE_ORDER', $disableStockCalculateOn)) { print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); } else { print ajax_constantonoff('STOCK_CALCULATE_ON_VALIDATE_ORDER', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); @@ -277,7 +278,11 @@ print ''; if (isModEnabled("shipping")) { if ($conf->use_javascript_ajax) { - print ajax_constantonoff('STOCK_CALCULATE_ON_SHIPMENT', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + if (in_array('SHIPMENT', $disableStockCalculateOn)) { + print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); + } else { + print ajax_constantonoff('STOCK_CALCULATE_ON_SHIPMENT', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + } } else { $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); print $form->selectarray("STOCK_CALCULATE_ON_SHIPMENT", $arrval, $conf->global->STOCK_CALCULATE_ON_SHIPMENT); @@ -294,7 +299,11 @@ print ''; if (isModEnabled("shipping")) { if ($conf->use_javascript_ajax) { - print ajax_constantonoff('STOCK_CALCULATE_ON_SHIPMENT_CLOSE', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + if (in_array('SHIPMENT_CLOSE', $disableStockCalculateOn)) { + print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); + } else { + print ajax_constantonoff('STOCK_CALCULATE_ON_SHIPMENT_CLOSE', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + } } else { $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); print $form->selectarray("STOCK_CALCULATE_ON_SHIPMENT_CLOSE", $arrval, $conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE); @@ -327,7 +336,7 @@ print ''; if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) { if ($conf->use_javascript_ajax) { - if ($disabled) { + if (in_array('BILL', $disableStockCalculateOn)) { print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); } else { print ajax_constantonoff('STOCK_CALCULATE_ON_SUPPLIER_BILL', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); @@ -349,7 +358,7 @@ print ''; if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) { if ($conf->use_javascript_ajax) { - if ($disabled) { + if (in_array('VALIDATE_ORDER', $disableStockCalculateOn)) { print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); } else { print ajax_constantonoff('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); @@ -371,7 +380,11 @@ print ''; if ($conf->use_javascript_ajax) { - print ajax_constantonoff('STOCK_CALCULATE_ON_RECEPTION', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + if (in_array('RECEPTION', $disableStockCalculateOn)) { + print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); + } else { + print ajax_constantonoff('STOCK_CALCULATE_ON_RECEPTION', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + } } else { $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); print $form->selectarray("STOCK_CALCULATE_ON_RECEPTION", $arrval, $conf->global->STOCK_CALCULATE_ON_RECEPTION); @@ -386,7 +399,11 @@ print ''; if ($conf->use_javascript_ajax) { - print ajax_constantonoff('STOCK_CALCULATE_ON_RECEPTION_CLOSE', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + if (in_array('RECEPTION_CLOSE', $disableStockCalculateOn)) { + print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); + } else { + print ajax_constantonoff('STOCK_CALCULATE_ON_RECEPTION_CLOSE', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + } } else { $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); print $form->selectarray("STOCK_CALCULATE_ON_RECEPTION_CLOSE", $arrval, $conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE); @@ -400,7 +417,11 @@ print ''; if (isModEnabled("supplier_order")) { if ($conf->use_javascript_ajax) { - print ajax_constantonoff('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + if (in_array('SUPPLIER_DISPATCH_ORDER', $disableStockCalculateOn)) { + print img_picto($langs->trans("Disabled"), 'off', 'class="opacitymedium"'); + } else { + print ajax_constantonoff('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER', array(), null, 0, 0, 0, 2, 1, '', '', 'reposition'); + } } else { $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); print $form->selectarray("STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER", $arrval, $conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER); diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index c1635ee5ba8da..0c5ec61c9e6f1 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1598,6 +1598,7 @@ public function delete($user = null, $notrigger = 0, $also_update_stock = false) $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product"; $sql .= " WHERE ed.fk_expedition = ".((int) $this->id); $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec"; + $sql .= $this->db->order("ed.rowid", "DESC"); dol_syslog(get_class($this)."::delete select details", LOG_DEBUG); $resql = $this->db->query($sql); @@ -3216,6 +3217,7 @@ public function delete($user = null, $notrigger = 0) // virtual products : delete all children and batch if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) { // find all children + $line_id_list = array(); $result = $this->findAllChild($this->id, $line_id_list); if ($result) { $child_line_id_list = array_reverse($line_id_list, true); diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 12d3f108d8574..e4d4e28d0f44b 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -130,7 +130,7 @@ foreach ($_POST as $key => $value) { // without batch module enabled $reg = array(); - if (preg_match('/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { + if (preg_match('/^(?:product|productbatch)([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { $pos++; if (preg_match('/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { $modebatch = "barcode"; @@ -230,8 +230,8 @@ if (!$error && $modebatch == "batch") { if ($newqty > 0) { $suffixkeyfordate = preg_replace('/^productbatch/', '', $key); - $sellby = dol_mktime(0, 0, 0, GETPOST('dlc'.$suffixkeyfordate.'month'), GETPOST('dlc'.$suffixkeyfordate.'day'), GETPOST('dlc'.$suffixkeyfordate.'year'), ''); - $eatby = dol_mktime(0, 0, 0, GETPOST('dluo'.$suffixkeyfordate.'month'), GETPOST('dluo'.$suffixkeyfordate.'day'), GETPOST('dluo'.$suffixkeyfordate.'year')); + $sellby = dol_mktime(12, 0, 0, GETPOST('dlc'.$suffixkeyfordate.'month'), GETPOST('dlc'.$suffixkeyfordate.'day'), GETPOST('dlc'.$suffixkeyfordate.'year'), ''); + $eatby = dol_mktime(12, 0, 0, GETPOST('dluo'.$suffixkeyfordate.'month'), GETPOST('dluo'.$suffixkeyfordate.'day'), GETPOST('dluo'.$suffixkeyfordate.'year')); $sqlsearchdet = "SELECT rowid FROM ".$db->prefix().$expeditionlinebatch->table_element; $sqlsearchdet .= " WHERE fk_expeditiondet = ".((int) $idline); @@ -288,8 +288,8 @@ } if ($modebatch == "batch" && !$error) { - $expeditionlinebatch->sellby = $dDLUO; - $expeditionlinebatch->eatby = $dDLC; + $expeditionlinebatch->sellby = $dDLC; // DLUO is eatByDate + $expeditionlinebatch->eatby = $dDLUO; // DLC is sellByDate $expeditionlinebatch->batch = $lot; $expeditionlinebatch->qty = $newqty; $expeditionlinebatch->fk_origin_stock = 0; @@ -789,7 +789,7 @@ $sql .= ", eb.batch, eb.eatby, eb.sellby"; $sql .= " FROM ".$db->prefix()."expeditiondet as ed"; $sql .= " LEFT JOIN ".$db->prefix()."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet"; - $sql .= " JOIN ".$db->prefix()."commandedet as cd on ed.fk_origin_line = cd.rowid"; + $sql .= " INNER JOIN ".$db->prefix()."commandedet as cd on ed.fk_origin_line = cd.rowid"; $sql .= " WHERE ed.fk_origin_line =".(int) $objp->rowid; $sql .= " AND ed.fk_expedition =".(int) $object->id; $sql .= " ORDER BY ed.rowid, ed.fk_origin_line"; @@ -799,6 +799,9 @@ if ($resultsql) { $numd = $db->num_rows($resultsql); + $is_mod_batch_enabled = isModEnabled('productbatch'); + $is_sell_by_disabled = getDolGlobalInt('PRODUCT_DISABLE_SELLBY'); + $is_eat_by_disabled = getDolGlobalInt('PRODUCT_DISABLE_EATBY'); while ($obj_exp = $db->fetch_object($resultsql)) { $suffix = "_" . $j . "_" . $i; @@ -824,6 +827,70 @@ $child_product = $conf->cache['product'][$child_product_id]; } + // sub-product is a batch + $product_batch_first = null; + $line_obj->batch_list = array(); + if ($is_mod_batch_enabled && $child_product->hasbatch()) { + // search if batch is not exist in shipment lines + $sql_line_batch_search = "SELECT eb.rowid, eb.qty, eb.batch, eb.sellby, eb.eatby"; + $sql_line_batch_search .= " FROM ".$db->prefix()."expeditiondet_batch as eb"; + $sql_line_batch_search .= " WHERE eb.fk_expeditiondet = ".((int) $line_obj->rowid); + $res_line_batch_search = $db->query($sql_line_batch_search); + if ($res_line_batch_search) { + while ($obj_batch = $db->fetch_object($res_line_batch_search)) { + $obj_batch->eatby = dol_print_date($obj_batch->eatby, "day"); + $obj_batch->sellby = dol_print_date($obj_batch->sellby, "day"); + + if ($product_batch_first === null) { + $product_batch_first = $obj_batch; + } + + $product_batch_label = $obj_batch->batch; + $line_obj->batch_list[$obj_batch->batch] = $product_batch_label; + } + $db->free($res_line_batch_search); + } + + // no batch found for this sub-product so retrieve all batch numbers for this sub-product id and warehouse id + if (empty($line_obj->batch_list)) { + $product_batch_sort_field = 'pl.sellby,pl.eatby,pb.qty,pl.rowid'; // order by sell by (DLC), eat by (DLUO), qty and rowid + $product_batch_sort_order = 'ASC,ASC,ASC,ASC'; + $product_batch = new Productbatch($db); + $product_batch_result = $product_batch->findAllForProduct($child_product_id, $line_obj->fk_warehouse, (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER') ? null : 0), $product_batch_sort_field, $product_batch_sort_order); + if (is_array($product_batch_result)) { + foreach ($product_batch_result as $batch_current) { + $batch_current->eatby = dol_print_date($batch_current->eatby, "day"); + $batch_current->sellby = dol_print_date($batch_current->sellby, "day"); + + if ($product_batch_first === null) { + $product_batch_first = $batch_current; + } + + $product_batch_label = $batch_current->batch; + if (!empty($batch_current->sellby) || !empty($batch_current->eatby)) { + $product_batch_label .= ' ('; + $product_batch_label_separate = ''; + if (!empty($batch_current->sellby)) { + $product_batch_label .= $langs->trans('SellByDate').' : '.$batch_current->sellby; + $product_batch_label_separate = ' '; + } + if (!empty($batch_current->eatby)) { + $product_batch_label .= $product_batch_label_separate.$langs->trans('EatByDate').' : '.$batch_current->eatby; + } + $product_batch_label .= ')'; + } + $line_obj->batch_list[$batch_current->batch] = $product_batch_label; + } + } + } + } + if (is_object($product_batch_first)) { + // get first lot / serial of this warehouse + $line_obj->batch = $product_batch_first->batch; + $line_obj->sellby = $product_batch_first->sellby; + $line_obj->eatby = $product_batch_first->eatby; + } + // determine if line is virtual product and stock is managed $line_obj->iskit = 0; $line_obj->incdec = 1; @@ -853,18 +920,22 @@ } } if (empty($expedition_line_child_list)) { + $obj_exp->batch_list = array(); // only used for virtual product with batch in sub-product $obj_exp->iskit = 0; // is not virtual product $obj_exp->incdec = 1; // manage stock $expedition_line_child_list[] = $obj_exp; } $child_suffix = $suffix; + $out_js_line_list = array(); foreach ($expedition_line_child_list as $objd) { $child_line_id = $objd->rowid; $can_update_stock = empty($objd->iskit) && !empty($objd->incdec); $suffix = $child_line_id.$child_suffix; + $out_js_line = ''; + if (isModEnabled('productbatch') && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) { $type = 'batch'; @@ -899,20 +970,55 @@ print ''; print ''; - + if (!empty($objd->html_label)) { + print $objd->html_label; + } print ''; print ''; - print ''; - //print ''; + if (!empty($objd->batch_list) && count($objd->batch_list) > 1) { + print Form::selectarray('lot_number'.$suffix, $objd->batch_list, '', 0, 0, 0, '', 0, 0, '', '', 'minwidth300 csslotnumber'.$suffix); + + $out_js_line .= 'var isSellByDisabled = '.dol_escape_js($is_sell_by_disabled).';'; + $out_js_line .= 'var isEatByDisabled = '.dol_escape_js($is_eat_by_disabled).';'; + $out_js_line .= 'jQuery("#lot_number'.$suffix.'").change(function(event) {'; + $out_js_line .= ' var batch = jQuery(this).val();'; + $out_js_line .= ' jQuery.getJSON("'.DOL_URL_ROOT.'/product/ajax/product_lot.php?action=search&token='.currentToken().'&product_id='.$objd->fk_product.'&batch="+batch, function(data) {'; + $out_js_line .= ' if (data.length > 0) {'; + $out_js_line .= ' var productLot = data[0];'; + $out_js_line .= ' if (isSellByDisabled == 0) {'; + $out_js_line .= ' jQuery("#dlc'.$suffix.'").val(productLot.sellby);'; + $out_js_line .= ' jQuery("#dlc'.$suffix.'").trigger("change");'; // also modify hidden input of date picker + $out_js_line .= ' }'; + $out_js_line .= ' if (isEatByDisabled == 0) {'; + $out_js_line .= ' jQuery("#dluo'.$suffix.'").val(productLot.eatby);'; + $out_js_line .= ' jQuery("#dluo'.$suffix.'").trigger("change");'; // also modify hidden input of date picker + $out_js_line .= ' }'; + $out_js_line .= ' }'; + $out_js_line .= ' });'; + $out_js_line .= '});'; + $out_js_line_list[] = $out_js_line; + + if (!empty($out_js_line_list)) { + $out_js = ''; + print $out_js; + } + } else { + print ''; + } print ''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + + if (!$is_sell_by_disabled) { print ''; $dlcdatesuffix = !empty($objd->sellby) ? dol_stringtotime($objd->sellby) : dol_mktime(0, 0, 0, GETPOST('dlc'.$suffix.'month'), GETPOST('dlc'.$suffix.'day'), GETPOST('dlc'.$suffix.'year')); print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, ''); print ''; } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + if (!$is_eat_by_disabled) { print ''; $dluodatesuffix = !empty($objd->eatby) ? dol_stringtotime($objd->eatby) : dol_mktime(0, 0, 0, GETPOST('dluo'.$suffix.'month'), GETPOST('dluo'.$suffix.'day'), GETPOST('dluo'.$suffix.'year')); print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, ''); diff --git a/htdocs/expedition/js/lib_dispatch.js.php b/htdocs/expedition/js/lib_dispatch.js.php index e2cd1aae7f120..99e8c0f96e3e2 100644 --- a/htdocs/expedition/js/lib_dispatch.js.php +++ b/htdocs/expedition/js/lib_dispatch.js.php @@ -128,8 +128,10 @@ function addDispatchLine(index, type, mode) { }); }, 0); - //create new select2 to avoid duplicate id of cloned one + // create new select2 to avoid duplicate id of cloned one for warehouse $row.find("select[name='"+'entrepot'+lineId+'_'+nbrTrs+'_'+index+"']").select2(); + // create new select2 to avoid duplicate id of cloned one for lot / serial number + $row.find("select[name='"+'lot_number'+lineId+'_'+nbrTrs+'_'+index+"']").select2(); // TODO find solution to copy selected option to new select // TODO find solution to keep new tr's after page refresh //clear value @@ -139,10 +141,14 @@ function addDispatchLine(index, type, mode) { //insert new row before last row $("tr[name^='"+type+"_'][name$='_"+index+"']:last").after($row); - //remove cloned select2 with duplicate id. + // remove cloned select2 with duplicate id for warehouse $("#s2id_entrepot"+lineId+"_"+nbrTrs+'_'+index).detach(); // old way to find duplicated select2 component $(".csswarehouse"+lineId+"_"+nbrTrs+"_"+index + ":first-child").parent("span.selection").parent(".select2").detach(); + // remove cloned select2 with duplicate id for lot / serial number + $("#s2id_lot_number"+lineId+"_"+nbrTrs+'_'+index).detach(); // old way to find duplicated select2 component + $(".csslotnumber"+lineId+"_"+nbrTrs+"_"+index + ":first-child").parent("span.selection").parent(".select2").detach(); + /* Suffix of lines are: _ trs.length _ index */ $("#qty"+lineId+"_"+nbrTrs+"_"+index).focus(); $("#qty_dispatched_0_"+index).val(oldlineqty); diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 73cec7fb71c1e..78aad1715aa87 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -760,44 +760,35 @@ private function _createSubProduct($user, $idProduct, $entrepot_id, $qty, $type, global $langs; $error = 0; - $pids = array(); - $pqtys = array(); $sql = "SELECT fk_product_pere, fk_product_fils, qty"; $sql .= " FROM ".$this->db->prefix()."product_association"; $sql .= " WHERE fk_product_pere = ".((int) $idProduct); $sql .= " AND incdec = 1"; - dol_syslog(get_class($this)."::_createSubProduct for parent product ".$idProduct, LOG_DEBUG); + dol_syslog(__METHOD__.' for parent product '.$idProduct, LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) { - $i = 0; + // Create movement for each sub-product while ($obj = $this->db->fetch_object($resql)) { - $pids[$i] = $obj->fk_product_fils; - $pqtys[$i] = $obj->qty; - $i++; - } - $this->db->free($resql); - } else { - $error = -2; - } - - // Create movement for each subproduct - foreach ($pids as $key => $value) { - if (!$error) { $tmpmove = dol_clone($this, 1); - - $result = $tmpmove->_create($user, $pids[$key], $entrepot_id, ($qty * $pqtys[$key]), $type, 0, $label, $inventorycode, $datem); // This will also call _createSubProduct making this recursive + $result = $tmpmove->_create($user, $obj->fk_product_fils, $entrepot_id, ($qty * $obj->qty), $type, 0, $label, $inventorycode, $datem); // This will also call _createSubProduct making this recursive if ($result < 0) { $this->error = $tmpmove->error; $this->errors = array_merge($this->errors, $tmpmove->errors); if ($result == -2) { - $this->errors[] = $langs->trans("ErrorNoteAlsoThatSubProductCantBeFollowedByLot"); + $this->errors[] = $langs->trans('ErrorNoteAlsoThatSubProductCantBeFollowedByLot'); } $error = $result; + dol_syslog(__METHOD__ . ' Error : ' . $this->errorsToString(), LOG_ERR); + break; } unset($tmpmove); } + + $this->db->free($resql); + } else { + $error = -2; } return $error; From d08044fbf4ad2976ce25ae72d2ab67380653691e Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 16 Oct 2024 15:38:06 +0200 Subject: [PATCH 12/33] Resolve conflicts v2 --- htdocs/expedition/class/expedition.class.php | 4 +- .../class/expeditionligne.class.php | 178 +++++++++++++++--- 2 files changed, 152 insertions(+), 30 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 5eea6af5a1cad..e24ee07d7fa5c 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1459,7 +1459,7 @@ public function cancel($notrigger = 0, $also_update_stock = false) // Stock control $can_update_stock = isModEnabled('stock') && ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) || - (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))); + (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock)); if (!$error) { require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php"; @@ -1669,7 +1669,7 @@ public function delete($user = null, $notrigger = 0, $also_update_stock = false) // Stock control $can_update_stock = isModEnabled('stock') && ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) || - (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) { + (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock)); if (!$error) { require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php"; diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index 888c953f7a318..32df78e81bca7 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -91,6 +91,11 @@ class ExpeditionLigne extends CommonObjectLine */ public $origin_line_id; + /** + * @var int Id of parent line for children of virtual product + */ + public $fk_parent; + /** * @var string Type of object the fk_element refers to. Example: 'order'. */ @@ -137,6 +142,12 @@ class ExpeditionLigne extends CommonObjectLine */ public $detail_batch; + /** + * Virtual products : array of total of quantities group product id and warehouse id + * @var array + */ + public $detail_children; + /** detail of warehouses and qty * We can use this to know warehouse when there is no lot. * @var stdClass[] @@ -352,7 +363,11 @@ public function insert($user, $notrigger = 0) $error = 0; // Check parameters - if (empty($this->fk_expedition) || empty($this->fk_elementdet) || !is_numeric($this->qty)) { + // Check parameters + if (empty($this->fk_expedition) + || empty($this->fk_product) // product id is mandatory + || (empty($this->fk_elementdet) && empty($this->fk_parent)) // at least origin line id of parent line id is set + || !is_numeric($this->qty)) { $this->error = 'ErrorMandatoryParametersNotProvided'; return -1; } @@ -374,13 +389,17 @@ public function insert($user, $notrigger = 0) $sql .= "fk_expedition"; $sql .= ", fk_entrepot"; $sql .= ", fk_elementdet"; + $sql .= ", fk_parent"; + $sql .= ", fk_product"; $sql .= ", element_type"; $sql .= ", qty"; $sql .= ", rang"; $sql .= ") VALUES ("; $sql .= $this->fk_expedition; $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id); - $sql .= ", ".((int) $this->fk_elementdet); + $sql .= ", ".(empty($this->fk_elementdet) ? 'NULL' : $this->fk_elementdet); + $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : $this->fk_parent); + $sql .= ", ".(empty($this->fk_product) ? 'NULL' : $this->fk_product); $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'"; $sql .= ", ".price2num($this->qty, 'MS'); $sql .= ", ".((int) $ranktouse); @@ -409,7 +428,7 @@ public function insert($user, $notrigger = 0) if ($error) { foreach ($this->errors as $errmsg) { - dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); + dol_syslog(__METHOD__.' '.$errmsg, LOG_ERR); $this->error .= ($this->error ? ', '.$errmsg : $errmsg); } } @@ -426,6 +445,69 @@ public function insert($user, $notrigger = 0) } } + /** + * Find all children + * + * @param int $line_id Line id + * @param array $list List of sub-lines for a virtual product line + * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher + * @return int Return integer <0 if KO else >0 if OK + */ + public function findAllChild($line_id, &$list = array(), $mode = 0) + { + if ($line_id > 0) { + // find all child + $sql = "SELECT ed.rowid as child_line_id"; + if ($mode == 1) { + $sql .= ", ed.fk_product"; + $sql .= ", ed.fk_parent"; + $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty') . " as qty"; + $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse') . " as fk_warehouse"; + $sql .= ", eb.batch, eb.eatby, eb.sellby"; + } + $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as ed"; + $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as eb ON eb.fk_expeditiondet = " . ((int) $line_id); + $sql .= " WHERE ed.fk_parent = " . ((int) $line_id); + $sql .= $this->db->order('ed.fk_product,ed.rowid', 'ASC,ASC'); + + $resql = $this->db->query($sql); + if ($resql) { + while ($obj = $this->db->fetch_object($resql)) { + $child_line_id = (int) $obj->child_line_id; + if (!isset($list[$line_id])) { + $list[$line_id] = array(); + } + + if ($mode == 0) { + $list[$line_id][] = $child_line_id; + } elseif ($mode == 1) { + $line_obj = new stdClass(); + $line_obj->rowid = $child_line_id; + $line_obj->fk_product = $obj->fk_product; + $line_obj->fk_parent = $obj->fk_parent; + $line_obj->qty = $obj->qty; + $line_obj->fk_warehouse = $obj->fk_warehouse; + $line_obj->batch = $obj->batch; + $line_obj->eatby = $obj->eatby; + $line_obj->sellby = $obj->sellby; + $line_obj->iskit = $obj->iskit; + $line_obj->incdec = $obj->incdec; + $list[$line_id][] = $line_obj; + } + + $this->findAllChild($child_line_id, $list, $mode); + } + $this->db->free($resql); + } else { + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + dol_syslog(__METHOD__.' '.$this->error, LOG_ERR); + } + } + + return 1; + } + /** * Delete shipment line. * @@ -439,41 +521,81 @@ public function delete($user = null, $notrigger = 0) $this->db->begin(); - // delete batch expedition line - if (isModEnabled('productbatch')) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch"; - $sql .= " WHERE fk_expeditiondet = ".((int) $this->id); + // virtual products : delete all children and batch + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) { + // find all children + $result = $this->findAllChild($this->id, $line_id_list); + if ($result) { + $child_line_id_list = array_reverse($line_id_list, true); + foreach ($child_line_id_list as $child_line_id_arr) { + foreach ($child_line_id_arr as $child_line_id) { + // delete batch expedition line + if (isModEnabled('productbatch')) { + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet_batch"; + $sql .= " WHERE fk_expeditiondet = " . ((int) $child_line_id); + if (!$this->db->query($sql)) { + $error++; + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; + } + } - if (!$this->db->query($sql)) { - $this->errors[] = $this->db->lasterror()." - sql=$sql"; + $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet"; + $sql .= " WHERE rowid = " . ((int) $child_line_id); + if (!$this->db->query($sql)) { + $error++; + $this->errors[] = $this->db->lasterror() . " - sql=$sql"; + } + + if ($error) { + break; + } + } + if ($error) { + break; + } + } + } else { $error++; } } - $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet"; - $sql .= " WHERE rowid = ".((int) $this->id); + if (!$error) { + // delete batch expedition line + if (isModEnabled('productbatch')) { + $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet_batch"; + $sql .= " WHERE fk_expeditiondet = ".((int) $this->id); - if (!$error && $this->db->query($sql)) { - // Remove extrafields - if (!$error) { - $result = $this->deleteExtraFields(); - if ($result < 0) { - $this->errors[] = $this->error; + if (!$this->db->query($sql)) { + $this->errors[] = $this->db->lasterror()." - sql=$sql"; $error++; } } - if (!$error && !$notrigger) { - // Call trigger - $result = $this->call_trigger('LINESHIPPING_DELETE', $user); - if ($result < 0) { - $this->errors[] = $this->error; - $error++; + + $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet"; + $sql .= " WHERE rowid = ".((int) $this->id); + + if (!$error && $this->db->query($sql)) { + // Remove extrafields + if (!$error) { + $result = $this->deleteExtraFields(); + if ($result < 0) { + $this->errors[] = $this->error; + $error++; + } } - // End call triggers + if (!$error && !$notrigger) { + // Call trigger + $result = $this->call_trigger('LINESHIPPING_DELETE', $user); + if ($result < 0) { + $this->errors[] = $this->error; + $error++; + } + // End call triggers + } + } else { + $this->errors[] = $this->db->lasterror()." - sql=$sql"; + $error++; } - } else { - $this->errors[] = $this->db->lasterror()." - sql=$sql"; - $error++; } if (!$error) { @@ -603,7 +725,7 @@ public function update($user = null, $notrigger = 0) $shipmentLot->batch = $lot->batch; $shipmentLot->eatby = $lot->eatby; $shipmentLot->sellby = $lot->sellby; - $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id; + $shipmentLot->fk_warehouse = $this->detail_batch->entrepot_id; $shipmentLot->qty = $this->detail_batch->qty; $shipmentLot->fk_origin_stock = $batch_id; if ($shipmentLot->create($this->id) < 0) { From 489493fc319e9770fe56ac43abb95a0d80340c82 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 16 Oct 2024 17:34:42 +0200 Subject: [PATCH 13/33] Resolve conflicts v3 --- htdocs/expedition/card.php | 18 ++++++++++++------ htdocs/expedition/class/expedition.class.php | 5 +++-- .../expedition/class/expeditionligne.class.php | 1 + htdocs/expedition/dispatch.php | 8 ++++---- htdocs/product/class/product.class.php | 4 +++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index ead59e75efbf7..19d776bdb2285 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -2374,9 +2374,10 @@ // Define output language if (getDolGlobalInt('MAIN_MULTILANGS') && getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE')) { $product_id = $lines[$i]->fk_product; - if (empty($conf->cache['product'][$product_id])) { + if (!isset($conf->cache['product'][$product_id])) { $prod = new Product($db); $prod->fetch($product_id); + $conf->cache['product'][$product_id] = $prod; } else { $prod = $conf->cache['product'][$product_id]; } @@ -2579,9 +2580,10 @@ print '('.$langs->trans("Service").')'; } elseif ($lines[$i]->entrepot_id > 0) { $warehouse_id = $lines[$i]->entrepot_id; - if (empty($conf->cache['warehouse'][$warehouse_id])) { + if (!isset($conf->cache['warehouse'][$warehouse_id])) { $warehouse = new Entrepot($db); $warehouse->fetch($warehouse_id); + $conf->cache['warehouse'][$warehouse_id] = $warehouse; } else { $warehouse = $conf->cache['warehouse'][$warehouse_id]; } @@ -2591,9 +2593,10 @@ foreach ($lines[$i]->details_entrepot as $detail_entrepot) { $warehouse_id = $detail_entrepot->entrepot_id; if ($warehouse_id > 0) { - if (empty($conf->cache['warehouse'][$warehouse_id])) { + if (!isset($conf->cache['warehouse'][$warehouse_id])) { $warehouse = new Entrepot($db); $warehouse->fetch($warehouse_id); + $conf->cache['warehouse'][$warehouse_id] = $warehouse; } else { $warehouse = $conf->cache['warehouse'][$warehouse_id]; } @@ -2607,18 +2610,20 @@ foreach ($child_stock_list as $warehouse_id => $total_qty) { // get product from cache $child_product_label = ''; - if (empty($conf->cache['product'][$child_product_id])) { + if (!isset($conf->cache['product'][$child_product_id])) { $child_product = new Product($db); $child_product->fetch($child_product_id); + $conf->cache['product'][$child_product_id] = $child_product; } else { $child_product = $conf->cache['product'][$child_product_id]; } $child_product_label = $child_product->ref . ' ' . $child_product->label; // get warehouse from cache - if (empty($conf->cache['warehouse'][$warehouse_id])) { + if (!isset($conf->cache['warehouse'][$warehouse_id])) { $child_warehouse = new Entrepot($db); $child_warehouse->fetch($warehouse_id); + $conf->cache['warehouse'][$warehouse_id] = $child_warehouse; } else { $child_warehouse = $conf->cache['warehouse'][$warehouse_id]; } @@ -2690,9 +2695,10 @@ $edit_url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=editline&token='.newToken().'&lineid='.$lines[$i]->id; if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { $product_id = $lines[$i]->fk_product; - if (empty($conf->cache['product'][$product_id])) { + if (!isset($conf->cache['product'][$product_id])) { $product = new Product($db); $product->fetch($product_id); + $conf->cache['product'][$product_id] = $product; } else { $product = $conf->cache['product'][$product_id]; } diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index e24ee07d7fa5c..98ae9b2e5c907 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1477,6 +1477,7 @@ public function cancel($notrigger = 0, $also_update_stock = false) $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product"; $sql .= " WHERE ed.fk_expedition = ".((int) $this->id); $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec"; + $sql .= $this->db->order("ed.rowid", "DESC"); dol_syslog(get_class($this)."::delete select details", LOG_DEBUG); $resql = $this->db->query($sql); @@ -2614,11 +2615,11 @@ private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyCl $sql .= ", edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock"; $sql .= ", e.ref"; $sql .= " FROM " . $this->db->prefix() . "expeditiondet as ed"; - $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_origin_line"; + $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_elementdet"; $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid"; $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as e ON ed.fk_expedition = e.rowid"; $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id); - $sql .= " AND cd.rowid = ed.fk_elementdet"; + //$sql .= " AND cd.rowid = ed.fk_elementdet"; dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG); $resql = $this->db->query($sql); diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index 32df78e81bca7..8d2e1c52740bd 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -524,6 +524,7 @@ public function delete($user = null, $notrigger = 0) // virtual products : delete all children and batch if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) { // find all children + $line_id_list = array(); $result = $this->findAllChild($this->id, $line_id_list); if ($result) { $child_line_id_list = array_reverse($line_id_list, true); diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index c51b1893454be..1e35a4850cd2a 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -785,7 +785,7 @@ $sql .= ", eb.batch, eb.eatby, eb.sellby"; $sql .= " FROM ".$db->prefix()."expeditiondet as ed"; $sql .= " LEFT JOIN ".$db->prefix()."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet"; - $sql .= " INNER JOIN ".$db->prefix()."commandedet as cd on ed.fk_origin_line = cd.rowid"; + $sql .= " INNER JOIN ".$db->prefix()."commandedet as cd on ed.fk_elementdet = cd.rowid"; $sql .= " WHERE ed.fk_elementdet =".(int) $objp->rowid; $sql .= " AND ed.fk_expedition =".(int) $object->id; $sql .= " ORDER BY ed.rowid, ed.fk_elementdet"; @@ -973,10 +973,10 @@ print ''; if (!empty($objd->batch_list) && count($objd->batch_list) > 1) { - print Form::selectarray('lot_number'.$suffix, $objd->batch_list, '', 0, 0, 0, '', 0, 0, '', '', 'minwidth300 csslotnumber'.$suffix); + print Form::selectarray('lot_number'.$suffix, $objd->batch_list, '', 0, 0, 0, '', 0, 0, 0, '', 'minwidth300 csslotnumber'.$suffix); - $out_js_line .= 'var isSellByDisabled = '.dol_escape_js($is_sell_by_disabled).';'; - $out_js_line .= 'var isEatByDisabled = '.dol_escape_js($is_eat_by_disabled).';'; + $out_js_line .= 'var isSellByDisabled = '.dol_escape_js((string) $is_sell_by_disabled).';'; + $out_js_line .= 'var isEatByDisabled = '.dol_escape_js((string) $is_eat_by_disabled).';'; $out_js_line .= 'jQuery("#lot_number'.$suffix.'").change(function(event) {'; $out_js_line .= ' var batch = jQuery(this).val();'; $out_js_line .= ' jQuery.getJSON("'.DOL_URL_ROOT.'/product/ajax/product_lot.php?action=search&token='.currentToken().'&product_id='.$objd->fk_product.'&batch="+batch, function(data) {'; diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 43dd701bbd388..2e13cd57970d2 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -5495,7 +5495,9 @@ public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = a $res = $this->db->query($sql); if ($res) { $prods = array(); - if ($this->db->num_rows($sql) > 0) $parents[] = $id; + if ($this->db->num_rows($res) > 0) { + $parents[] = $id; + } while ($rec = $this->db->fetch_array($res)) { if (in_array($rec['id'], $parents)) { From 56c29c58300e22e9d4cb9e1afc937021740115b1 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Thu, 17 Oct 2024 10:23:20 +0200 Subject: [PATCH 14/33] Fix phan errors --- htdocs/expedition/class/expedition.class.php | 2 -- htdocs/expedition/class/expeditionligne.class.php | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 98ae9b2e5c907..e7694922d0425 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1833,8 +1833,6 @@ public function delete($user = null, $notrigger = 0, $also_update_stock = false) return -3; } } else { - $this->error = $this->db->lasterror()." - sql=$sql"; - $this->db->rollback(); return -2; } } else { diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index 8d2e1c52740bd..c9edd76876cb1 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -143,8 +143,8 @@ class ExpeditionLigne extends CommonObjectLine public $detail_batch; /** - * Virtual products : array of total of quantities group product id and warehouse id - * @var array + * Virtual products : array of total of quantities group product id and warehouse id ([id_product][id_warehouse] -> qty (int|float)) + * @var array> */ public $detail_children; @@ -448,9 +448,9 @@ public function insert($user, $notrigger = 0) /** * Find all children * - * @param int $line_id Line id - * @param array $list List of sub-lines for a virtual product line - * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher + * @param int $line_id Line id + * @param stdClass[] $list List of sub-lines for a virtual product line (array of object with attributes : rowid, fk_product, fk_parent, qty, fk_warehouse, batch, eatby, sellby, iskit, incdec) + * @param int $mode [=0] array of lines ids, 1 array of line object for dispatcher * @return int Return integer <0 if KO else >0 if OK */ public function findAllChild($line_id, &$list = array(), $mode = 0) From 35a1668795474e957a5650d25f55e00c8f71dab8 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Thu, 17 Oct 2024 10:41:04 +0200 Subject: [PATCH 15/33] Fix variable not declared --- htdocs/expedition/class/expedition.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index e7694922d0425..df41866b99ae9 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1617,7 +1617,6 @@ public function cancel($notrigger = 0, $also_update_stock = false) return -3; } } else { - $this->error = $this->db->lasterror()." - sql=$sql"; $this->db->rollback(); return -2; } @@ -1833,6 +1832,7 @@ public function delete($user = null, $notrigger = 0, $also_update_stock = false) return -3; } } else { + $this->db->rollback(); return -2; } } else { From f70cecec911525c43dba7537cc42addb77610a47 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 28 Oct 2024 10:44:57 +0100 Subject: [PATCH 16/33] Fix precommit --- htdocs/expedition/dispatch.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 29d6483e54c81..0cd0d375daf32 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -1079,19 +1079,19 @@ } print ''; - // Warehouse - print ''; - if ($can_update_stock) { - if (count($listwarehouses) > 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); - } elseif (count($listwarehouses) == 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); - } else { - $langs->load("errors"); - print $langs->trans("ErrorNoWarehouseDefined"); + // Warehouse + print ''; + if ($can_update_stock) { + if (count($listwarehouses) > 1) { + print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); + } elseif (count($listwarehouses) == 1) { + print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); + } else { + $langs->load("errors"); + print $langs->trans("ErrorNoWarehouseDefined"); + } } - } - print "\n"; + print "\n"; // Enable hooks to append additional columns $parameters = array( From 854430edc07a0243d0c07db781cc0e5202621c6e Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 8 Nov 2024 09:54:08 +0100 Subject: [PATCH 17/33] Fix out js line not empty always true --- htdocs/expedition/dispatch.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 3ff4414c4fa37..3bbf1b1b380d8 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -1003,14 +1003,12 @@ $out_js_line .= '});'; $out_js_line_list[] = $out_js_line; - if (!empty($out_js_line_list)) { - $out_js = ''; - print $out_js; - } + $out_js = ''; + print $out_js; } else { print ''; } From 3108fdcfc171dbba2f8dcbd774ad22eb5597caa1 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 8 Nov 2024 13:44:19 +0100 Subject: [PATCH 18/33] Fix CI errors --- htdocs/expedition/class/expedition.class.php | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index c66f1a87bf401..216c9b11d74df 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -509,7 +509,7 @@ public function create($user, $notrigger = 0) $line = $this->lines[$i]; if ($line->fk_product > 0) { if (!isset($kits_list[$line->fk_product])) { - if (!isset($line->product)) { + if (!is_object($line->product)) { $line_product = new Product($this->db); $result = $line_product->fetch($line->fk_product, '', '', '', 1, 1, 1); if ($result <= 0) { @@ -1899,8 +1899,10 @@ public function fetch_lines() if ($originline > 0 && $originline == $obj->fk_elementdet) { '@phan-var-force ExpeditionLigne $line'; // $line from previous loop - $line->entrepot_id = 0; // entrepod_id in details_entrepot - $line->qty_shipped += $obj->qty_shipped; + if (isset($line)) { + $line->entrepot_id = 0; // entrepod_id in details_entrepot + $line->qty_shipped += $obj->qty_shipped; + } } else { $line = new ExpeditionLigne($this->db); // new group to start $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line @@ -2027,22 +2029,20 @@ public function fetch_lines() $line_child_list = array(); $res = $line->findAllChild($line->id, $line_child_list, 1); if ($res > 0) { - if (!empty($line_child_list)) { - foreach ($line_child_list as $child_line) { - foreach ($child_line as $child_obj) { - $child_product_id = (int) $child_obj->fk_product; - $child_warehouse_id = (int) $child_obj->fk_warehouse; - - if ($child_warehouse_id > 0) { - // child quantities group by warehouses - if (!isset($detail_children[$child_product_id])) { - $detail_children[$child_product_id] = array(); - } - if (!isset($detail_children[$child_product_id][$child_warehouse_id])) { - $detail_children[$child_product_id][$child_warehouse_id] = 0; - } - $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty; + foreach ($line_child_list as $child_line) { + foreach ($child_line as $child_obj) { + $child_product_id = (int) $child_obj->fk_product; + $child_warehouse_id = (int) $child_obj->fk_warehouse; + + if ($child_warehouse_id > 0) { + // child quantities group by warehouses + if (!isset($detail_children[$child_product_id])) { + $detail_children[$child_product_id] = array(); + } + if (!isset($detail_children[$child_product_id][$child_warehouse_id])) { + $detail_children[$child_product_id][$child_warehouse_id] = 0; } + $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty; } } } From 6bf21604baa996ae8ba468e2a7356010ca49291b Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 8 Nov 2024 14:17:57 +0100 Subject: [PATCH 19/33] Fix CI errors --- htdocs/expedition/class/expedition.class.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 216c9b11d74df..f1ef9c61d6687 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -263,7 +263,7 @@ class Expedition extends CommonObject public $commande; /** - * @var ExpeditionLigne[] array of shipping lines + * @var array array of shipping lines */ public $lines = array(); @@ -1892,17 +1892,14 @@ public function fetch_lines() $this->multicurrency_total_ttc = 0; $shipmentlinebatch = new ExpeditionLineBatch($this->db); - + $line = new ExpeditionLigne($this->db); // initialize while ($i < $num) { $obj = $this->db->fetch_object($resql); - if ($originline > 0 && $originline == $obj->fk_elementdet) { '@phan-var-force ExpeditionLigne $line'; // $line from previous loop - if (isset($line)) { - $line->entrepot_id = 0; // entrepod_id in details_entrepot - $line->qty_shipped += $obj->qty_shipped; - } + $line->entrepot_id = 0; // entrepod_id in details_entrepot + $line->qty_shipped += $obj->qty_shipped; } else { $line = new ExpeditionLigne($this->db); // new group to start $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line From 49ae2153e692b011cee4fcd259e6cb1e432d5512 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 8 Nov 2024 16:15:19 +0100 Subject: [PATCH 20/33] FIX batch number in expedition create line --- htdocs/expedition/class/expeditionlinebatch.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/expedition/class/expeditionlinebatch.class.php b/htdocs/expedition/class/expeditionlinebatch.class.php index 75d69bccbb6e2..968fab33642f9 100644 --- a/htdocs/expedition/class/expeditionlinebatch.class.php +++ b/htdocs/expedition/class/expeditionlinebatch.class.php @@ -161,7 +161,7 @@ public function create($id_line_expdet, $f_user = null, $notrigger = 0) $sql .= $id_line_expdet; $sql .= ", ".(!isset($this->sellby) || dol_strlen($this->sellby) == 0 ? 'NULL' : ("'".$this->db->idate($this->sellby))."'"); $sql .= ", ".(!isset($this->eatby) || dol_strlen($this->eatby) == 0 ? 'NULL' : ("'".$this->db->idate($this->eatby))."'"); - $sql .= ", ".(!empty($this->batch) ? 'NULL' : ("'".$this->db->escape($this->batch)."'")); + $sql .= ", ".($this->batch == '' ? 'NULL' : ("'".$this->db->escape($this->batch)."'")); $sql .= ", ".(!isset($this->qty) ? ((!isset($this->dluo_qty)) ? 'NULL' : $this->dluo_qty) : $this->qty); // dluo_qty deprecated, use qty $sql .= ", ".((int) $this->fk_origin_stock); $sql .= ", ".(empty($this->fk_warehouse) ? 'NULL' : $this->fk_warehouse); From fa5ddc74d5b5c65324cf337eda3b529ec51ef712 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Sat, 16 Nov 2024 18:39:45 +0100 Subject: [PATCH 21/33] Fix initilize kit and incdec for find all children --- htdocs/expedition/class/expeditionligne.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index e1659c1ad1a79..66f06382987f4 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -490,8 +490,8 @@ public function findAllChild($line_id, &$list = array(), $mode = 0) $line_obj->batch = $obj->batch; $line_obj->eatby = $obj->eatby; $line_obj->sellby = $obj->sellby; - $line_obj->iskit = $obj->iskit; - $line_obj->incdec = $obj->incdec; + $line_obj->iskit = 0; + $line_obj->incdec = 0; $list[$line_id][] = $line_obj; } From aaa73328146af3407ce4500b2215d1a644d002b0 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 20 Nov 2024 10:08:28 +0100 Subject: [PATCH 22/33] NEW update batch from warehouse and warehouse from batch (ajax) --- htdocs/expedition/ajax/interface.php | 149 ++++++++++++ htdocs/expedition/dispatch.php | 223 +++++++++++------- htdocs/langs/fr_FR/stocks.lang | 1 + .../product/class/html.formproduct.class.php | 31 ++- 4 files changed, 321 insertions(+), 83 deletions(-) create mode 100644 htdocs/expedition/ajax/interface.php diff --git a/htdocs/expedition/ajax/interface.php b/htdocs/expedition/ajax/interface.php new file mode 100644 index 0000000000000..94aa3f60fc40d --- /dev/null +++ b/htdocs/expedition/ajax/interface.php @@ -0,0 +1,149 @@ + + * Copyright (C) 2024 Lionel Vessiller + * + * 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 . + */ + +/** + * \file htdocs/expedition/ajax/interface.php + * \brief Ajax search component for Shipment. + */ + +if (!defined('NOREQUIRESOC')) { + define('NOREQUIRESOC', '1'); +} +if (!defined('NOCSRFCHECK')) { + define('NOCSRFCHECK', '1'); +} +if (!defined('NOTOKENRENEWAL')) { + define('NOTOKENRENEWAL', '1'); +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} + +require '../../main.inc.php'; // Load $user and permissions +/** + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +$warehouse_id = GETPOSTINT('warehouse_id'); +$batch = GETPOST('batch', 'alphanohtml'); +$product_id = GETPOSTINT('product_id'); +$action = GETPOST('action', 'alphanohtml'); + +$result = restrictedArea($user, 'expedition'); + +$permissiontowrite = $user->hasRight('expedition', 'write'); + +$is_eat_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_EATBY'); +$is_sell_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_SELLBY'); + + +/* + * View + */ + +top_httphead("application/json"); + +if ($action == 'updateselectbatchbywarehouse' && $permissiontowrite) { + $resArr = array(); + + $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product"; + if ($is_eat_by_enabled) { + $sql .= ", pl.eatby"; + } + if ($is_sell_by_enabled) { + $sql .= ", pl.sellby"; + } + $sql .= " FROM ".$db->prefix()."product_batch as pb"; + $sql .= " LEFT JOIN ".$db->prefix()."product_stock as ps on ps.rowid = pb.fk_product_stock"; + $sql .= " LEFT JOIN ".$db->prefix()."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")"; + if ($is_eat_by_enabled || $is_sell_by_enabled) { + $sql .= " LEFT JOIN ".$db->prefix()."product_lot as pl on ps.fk_product = pl.fk_product AND pb.batch = pl.batch"; + } + $sql .= " WHERE ps.fk_product = ".((int) $product_id); + if ($warehouse_id > 0) { + $sql .= " AND fk_entrepot = '".((int) $warehouse_id)."'"; + } + $sql .= " ORDER BY e.ref, pb.batch"; + + $resql = $db->query($sql); + + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $eat_by_date_formatted = ''; + if ($is_eat_by_enabled && !empty($obj->eatby)) { + $eat_by_date_formatted = dol_print_date($db->jdate($obj->eatby), 'day'); + } + $sell_by_date_formatted = ''; + if ($is_sell_by_enabled && !empty($obj->sellby)) { + $sell_by_date_formatted = dol_print_date($db->jdate($obj->sellby), 'day'); + } + + // set qty + if (!isset($resArr[$obj->batch])) { + $resArr[$obj->batch] = array( + 'qty' => (float) $obj->qty, + ); + } else { + $resArr[$obj->batch]['qty'] += $obj->qty; + } + + // set eat-by date + if (!isset($resArr[$obj->batch]['eatbydate'])) { + $resArr[$obj->batch]['eatbydate'] = $eat_by_date_formatted; + } + + // set sell-by date + if (!isset($resArr[$obj->batch]['sellbydate'])) { + $resArr[$obj->batch]['sellbydate'] = $sell_by_date_formatted; + } + } + } + + echo json_encode($resArr); +} elseif ($action == 'updateselectwarehousebybatch' && $permissiontowrite) { + $res = 0; + + $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, e.ref, pb.qty"; + $sql .= " FROM ".$db->prefix()."product_batch as pb"; + $sql .= " JOIN ".$db->prefix()."product_stock as ps on ps.rowid = pb.fk_product_stock"; + $sql .= " JOIN ".$db->prefix()."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")"; + $sql .= " WHERE ps.fk_product = ".((int) $product_id); + if ($batch) { + $sql .= " AND pb.batch = '".$db->escape($batch)."'"; + } + $sql .= " ORDER BY e.ref, pb.batch"; + + $resql = $db->query($sql); + + if ($resql) { + if ($db->num_rows($resql) == 1) { + $obj = $db->fetch_object($resql); + $res = $obj->fk_entrepot; + } + } + + echo json_encode($res); +} diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index 565bb36af6a29..e382375e760e7 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -56,7 +56,10 @@ // Load translation files required by the page $langs->loadLangs(array("sendings", "companies", "bills", 'deliveries', 'orders', 'stocks', 'other', 'propal', 'receptions')); -if (isModEnabled('productbatch')) { +$is_mod_batch_enabled = isModEnabled('productbatch'); +$is_eat_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_EATBY'); +$is_sell_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_SELLBY'); +if ($is_mod_batch_enabled) { $langs->load('productbatch'); } @@ -533,7 +536,7 @@ print ''; print '
'; - if (isModEnabled('barcode') || isModEnabled('productbatch')) { + if (isModEnabled('barcode') || $is_mod_batch_enabled) { print ''.img_picto('', 'barcode', 'class="paddingrightonly"').$langs->trans("UpdateByScaning").''; } print ''.img_picto("", 'autofill', 'class="pictofixedwidth"').$langs->trans("RestoreWithCurrentQtySaved").''; @@ -631,12 +634,12 @@ print ''; print ''.$langs->trans("Description").''; - if (isModEnabled('productbatch')) { + if ($is_mod_batch_enabled) { print ''.$langs->trans("batch_number").''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + if ($is_sell_by_enabled) { print ''.$langs->trans("SellByDate").''; } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + if ($is_eat_by_enabled) { print ''.$langs->trans("EatByDate").''; } } else { @@ -734,17 +737,17 @@ $linktoprod = $tmpproduct->getNomUrl(1); $linktoprod .= ' - '.$objp->label."\n"; - if (isModEnabled('productbatch')) { + if ($is_mod_batch_enabled) { if ($objp->tobatch) { // Product print ''; print $linktoprod; print ""; print ''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + if ($is_sell_by_enabled) { print ''; } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + if ($is_eat_by_enabled) { print ''; } } else { @@ -755,10 +758,10 @@ print ''; print ''.$langs->trans("ProductDoesNotUseBatchSerial").''; print ''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + if ($is_sell_by_enabled) { print ''; } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + if ($is_eat_by_enabled) { print ''; } } @@ -802,10 +805,6 @@ $j = 0; if ($resultsql) { $numd = $db->num_rows($resultsql); - - $is_mod_batch_enabled = isModEnabled('productbatch'); - $is_sell_by_disabled = getDolGlobalInt('PRODUCT_DISABLE_SELLBY'); - $is_eat_by_disabled = getDolGlobalInt('PRODUCT_DISABLE_EATBY'); while ($obj_exp = $db->fetch_object($resultsql)) { $suffix = "_" . $j . "_" . $i; @@ -833,7 +832,6 @@ // sub-product is a batch $product_batch_first = null; - $line_obj->batch_list = array(); if ($is_mod_batch_enabled && $child_product->hasbatch()) { // search if batch is not exist in shipment lines $sql_line_batch_search = "SELECT eb.rowid, eb.qty, eb.batch, eb.sellby, eb.eatby"; @@ -847,16 +845,15 @@ if ($product_batch_first === null) { $product_batch_first = $obj_batch; + } else { + break; } - - $product_batch_label = $obj_batch->batch; - $line_obj->batch_list[$obj_batch->batch] = $product_batch_label; } $db->free($res_line_batch_search); } // no batch found for this sub-product so retrieve all batch numbers for this sub-product id and warehouse id - if (empty($line_obj->batch_list)) { + if ($product_batch_first === null) { $product_batch_sort_field = 'pl.sellby,pl.eatby,pb.qty,pl.rowid'; // order by sell by (DLC), eat by (DLUO), qty and rowid $product_batch_sort_order = 'ASC,ASC,ASC,ASC'; $product_batch = new Productbatch($db); @@ -868,22 +865,9 @@ if ($product_batch_first === null) { $product_batch_first = $batch_current; + } else { + break; } - - $product_batch_label = $batch_current->batch; - if (!empty($batch_current->sellby) || !empty($batch_current->eatby)) { - $product_batch_label .= ' ('; - $product_batch_label_separate = ''; - if (!empty($batch_current->sellby)) { - $product_batch_label .= $langs->trans('SellByDate').' : '.$batch_current->sellby; - $product_batch_label_separate = ' '; - } - if (!empty($batch_current->eatby)) { - $product_batch_label .= $product_batch_label_separate.$langs->trans('EatByDate').' : '.$batch_current->eatby; - } - $product_batch_label .= ')'; - } - $line_obj->batch_list[$batch_current->batch] = $product_batch_label; } } } @@ -924,23 +908,19 @@ } } if (empty($expedition_line_child_list)) { - $obj_exp->batch_list = array(); // only used for virtual product with batch in sub-product $obj_exp->iskit = 0; // is not virtual product $obj_exp->incdec = 1; // manage stock $expedition_line_child_list[] = $obj_exp; } $child_suffix = $suffix; - $out_js_line_list = array(); foreach ($expedition_line_child_list as $objd) { $child_line_id = $objd->rowid; $can_update_stock = empty($objd->iskit) && !empty($objd->incdec); $suffix = $child_line_id.$child_suffix; - $out_js_line = ''; - - if (isModEnabled('productbatch') && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) { + if ($is_mod_batch_enabled && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) { $type = 'batch'; // Enable hooks to append additional columns @@ -980,47 +960,17 @@ print ''; print ''; - if (!empty($objd->batch_list) && count($objd->batch_list) > 1) { - print Form::selectarray('lot_number'.$suffix, $objd->batch_list, '', 0, 0, 0, '', 0, 0, 0, '', 'minwidth300 csslotnumber'.$suffix); - - $out_js_line .= 'var isSellByDisabled = '.dol_escape_js((string) $is_sell_by_disabled).';'; - $out_js_line .= 'var isEatByDisabled = '.dol_escape_js((string) $is_eat_by_disabled).';'; - $out_js_line .= 'jQuery("#lot_number'.$suffix.'").change(function(event) {'; - $out_js_line .= ' var batch = jQuery(this).val();'; - $out_js_line .= ' jQuery.getJSON("'.DOL_URL_ROOT.'/product/ajax/product_lot.php?action=search&token='.currentToken().'&product_id='.$objd->fk_product.'&batch="+batch, function(data) {'; - $out_js_line .= ' if (data.length > 0) {'; - $out_js_line .= ' var productLot = data[0];'; - $out_js_line .= ' if (isSellByDisabled == 0) {'; - $out_js_line .= ' jQuery("#dlc'.$suffix.'").val(productLot.sellby);'; - $out_js_line .= ' jQuery("#dlc'.$suffix.'").trigger("change");'; // also modify hidden input of date picker - $out_js_line .= ' }'; - $out_js_line .= ' if (isEatByDisabled == 0) {'; - $out_js_line .= ' jQuery("#dluo'.$suffix.'").val(productLot.eatby);'; - $out_js_line .= ' jQuery("#dluo'.$suffix.'").trigger("change");'; // also modify hidden input of date picker - $out_js_line .= ' }'; - $out_js_line .= ' }'; - $out_js_line .= ' });'; - $out_js_line .= '});'; - $out_js_line_list[] = $out_js_line; - - $out_js = ''; - print $out_js; - } else { - print ''; - } + print ''; + print $formproduct->selectLotDataList('lot_number'.$suffix, 0, $objd->fk_product, GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, array()); print ''; - if (!$is_sell_by_disabled) { + if ($is_sell_by_enabled) { print ''; $dlcdatesuffix = !empty($objd->sellby) ? dol_stringtotime($objd->sellby) : dol_mktime(0, 0, 0, GETPOSTINT('dlc'.$suffix.'month'), GETPOSTINT('dlc'.$suffix.'day'), GETPOSTINT('dlc'.$suffix.'year')); print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, ''); print ''; } - if (!$is_eat_by_disabled) { + if ($is_eat_by_enabled) { print ''; $dluodatesuffix = !empty($objd->eatby) ? dol_stringtotime($objd->eatby) : dol_mktime(0, 0, 0, GETPOSTINT('dluo'.$suffix.'month'), GETPOSTINT('dluo'.$suffix.'day'), GETPOSTINT('dluo'.$suffix.'year')); print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, ''); @@ -1030,8 +980,8 @@ } else { $type = 'dispatch'; $colspan = 6; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_SELLBY')) ? --$colspan : $colspan; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_EATBY')) ? --$colspan : $colspan; + $colspan = $is_sell_by_enabled ? $colspan : --$colspan; + $colspan = $is_eat_by_enabled ? $colspan : --$colspan; // Enable hooks to append additional columns $parameters = array( @@ -1089,9 +1039,9 @@ print ''; if ($can_update_stock) { if (count($listwarehouses) > 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); + print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 1, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); } elseif (count($listwarehouses) == 1) { - print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); + print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 0, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix); } else { $langs->load("errors"); print $langs->trans("ErrorNoWarehouseDefined"); @@ -1167,13 +1117,13 @@ print ''; print ''; print ''; - if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY')) { + if ($is_sell_by_enabled) { print ''; $dlcdatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dlc'.$suffix.'month'), GETPOSTINT('dlc'.$suffix.'day'), GETPOSTINT('dlc'.$suffix.'year')); print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, ''); print ''; } - if (!getDolGlobalString('PRODUCT_DISABLE_EATBY')) { + if ($is_eat_by_enabled) { print ''; $dluodatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dluo'.$suffix.'month'), GETPOSTINT('dluo'.$suffix.'day'), GETPOSTINT('dluo'.$suffix.'year')); print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, ''); @@ -1183,8 +1133,8 @@ } else { $type = 'dispatch'; $colspan = 6; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_SELLBY')) ? --$colspan : $colspan; - $colspan = (getDolGlobalString('PRODUCT_DISABLE_EATBY')) ? --$colspan : $colspan; + $colspan = $is_sell_by_enabled ? $colspan : --$colspan; + $colspan = $is_eat_by_enabled ? $colspan : --$colspan; // Enable hooks to append additional columns $parameters = array( @@ -1230,7 +1180,7 @@ print ''; print ''; print ''; - if (isModEnabled('productbatch') && $objp->tobatch > 0) { + if ($is_mod_batch_enabled && $objp->tobatch > 0) { $type = 'batch'; print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"'); } else { @@ -1276,6 +1226,115 @@ } $i++; } + + // reload batch select and warehouse select on change (Ajax) + $out_js_line_list = array(); + $out_js_line = 'function updateselectbatchbywarehouse() {'; + $out_js_line .= ' jQuery(document).on("change", "select[name*=\"entrepot\"]", function() {'; + $out_js_line .= ' var selectwarehouse = jQuery(this);'; + $out_js_line .= ' var selectbatch_name = selectwarehouse.attr("name").replace("entrepot", "lot_number");'; + $out_js_line .= ' var selectbatch = jQuery("datalist[id*=\""+selectbatch_name+"\"]");'; + $out_js_line .= ' var selectedbatch = selectbatch.val();'; + $out_js_line .= ' var product_element_name = selectwarehouse.attr("name").replace("entrepot", "productbatch");'; + $out_js_line .= ' jQuery.ajax({'; + $out_js_line .= ' type: "POST",'; + $out_js_line .= ' url: "'.dol_escape_js(dol_buildpath('/expedition/ajax/interface.php', 1)).'",'; + $out_js_line .= ' data: {'; + $out_js_line .= ' action: "updateselectbatchbywarehouse",'; + $out_js_line .= ' warehouse_id: jQuery(this).val(),'; + $out_js_line .= ' token: "'.currentToken().'",'; + $out_js_line .= ' product_id: jQuery("input[name=\""+product_element_name+"\"]").val()'; + $out_js_line .= ' }'; + $out_js_line .= ' }).done(function(data) {'; + $out_js_line .= ' selectbatch.empty();'; + $out_js_line .= ' if (typeof data == "object") {'; + $out_js_line .= ' console.log("data is already type object, no need to parse it");'; + $out_js_line .= ' } else {'; + $out_js_line .= ' console.log("data is type "+(typeof data));'; + $out_js_line .= ' data = JSON.parse(data);'; + $out_js_line .= ' }'; + $out_js_line .= ' selectbatch.append(jQuery("";'; + $out_js_line .= ' } else {'; + $out_js_line .= ' var option = "";'; + $out_js_line .= ' }'; + $out_js_line .= ' selectbatch.append(option);'; + $out_js_line .= ' });'; + $out_js_line .= ' });'; + $out_js_line .= ' });'; + $out_js_line .= '}'; + + $out_js_line .= 'function updateselectwarehousebybatch() {'; + $out_js_line .= ' jQuery(document).on("change", "input[name*=lot_number]", function() {'; + $out_js_line .= ' var selectbatch = jQuery(this);'; + $out_js_line .= ' var selectwarehouse_name = selectbatch.attr("name").replace("lot_number", "entrepot");'; + $out_js_line .= ' var selectwarehouse = jQuery("select[name*=\""+selectwarehouse_name+"\"]");'; + $out_js_line .= ' var selectedwarehouse = selectwarehouse.val();'; + $out_js_line .= ' var inputbatchdlc_name = selectbatch.attr("name").replace("lot_number", "dlc");'; + $out_js_line .= ' var inputbatchdlc = jQuery("input[name*=\""+inputbatchdlc_name+"\"]");'; + $out_js_line .= ' var inputbatchdluo_name = selectbatch.attr("name").replace("lot_number", "dluo");'; + $out_js_line .= ' var inputbatchdluo = jQuery("input[name*=\""+inputbatchdluo_name+"\"]");'; + $out_js_line .= ' var datalistselectedbatch = jQuery("#"+selectbatch.attr("name")+" option[value=\""+selectbatch.val()+"\"]");'; + $out_js_line .= ' var selectedbatch_dlc = datalistselectedbatch.data("sellbydate");'; + $out_js_line .= ' var selectedbatch_dluo = datalistselectedbatch.data("eatbydate");'; + $out_js_line .= ' if (typeof selectedbatch_dlc === "undefined") {'; + $out_js_line .= ' selectedbatch_dlc = "";'; + $out_js_line .= ' }'; + $out_js_line .= ' if (typeof selectedbatch_dluo === "undefined") {'; + $out_js_line .= ' selectedbatch_dluo = "";'; + $out_js_line .= ' }'; + $out_js_line .= ' inputbatchdlc.val(selectedbatch_dlc).trigger("change");'; + $out_js_line .= ' inputbatchdluo.val(selectedbatch_dluo).trigger("change");'; + $out_js_line .= ' if (selectedwarehouse != -1) {'; + $out_js_line .= ' return;'; + $out_js_line .= ' }'; + $out_js_line .= ' var product_element_name = selectbatch.attr("name").replace("lot_number", "productbatch");'; + $out_js_line .= ' jQuery.ajax({'; + $out_js_line .= ' type: "POST",'; + $out_js_line .= ' url: "'.dol_escape_js(dol_buildpath('/expedition/ajax/interface.php', 1)).'",'; + $out_js_line .= ' data: {'; + $out_js_line .= ' action: "updateselectwarehousebybatch",'; + $out_js_line .= ' batch: jQuery(this).val(),'; + $out_js_line .= ' token: "'.currentToken().'",'; + $out_js_line .= ' product_id: jQuery("input[name=\""+product_element_name+"\"]").val()'; + $out_js_line .= ' }'; + $out_js_line .= ' }).done(function(data) {'; + $out_js_line .= ' if (typeof data == "object") {'; + $out_js_line .= ' console.log("data is already type object, no need to parse it");'; + $out_js_line .= ' } else {'; + $out_js_line .= ' console.log("data is type "+(typeof data));'; + $out_js_line .= ' data = JSON.parse(data);'; + $out_js_line .= ' }'; + $out_js_line .= ' if (data != 0) {'; + $out_js_line .= ' selectwarehouse.val(data).change();'; + $out_js_line .= ' }'; + $out_js_line .= ' });'; + $out_js_line .= ' });'; + $out_js_line .= '}'; + $out_js_line_list[] = $out_js_line; + + $out_js = ''; + print $out_js; + $db->free($resql); } else { dol_print_error($db); diff --git a/htdocs/langs/fr_FR/stocks.lang b/htdocs/langs/fr_FR/stocks.lang index 8b9e7b2c5b482..78e721e28260d 100644 --- a/htdocs/langs/fr_FR/stocks.lang +++ b/htdocs/langs/fr_FR/stocks.lang @@ -347,3 +347,4 @@ QtyViewed=Quantité vue QtyStock=Quantité en stock QtyRegulated=Quantité de correction de stock InventoryEntrepot=Id entrepôt +StockTotal=Stock total diff --git a/htdocs/product/class/html.formproduct.class.php b/htdocs/product/class/html.formproduct.class.php index 8176e2296628f..dc5dfb5c26bed 100644 --- a/htdocs/product/class/html.formproduct.class.php +++ b/htdocs/product/class/html.formproduct.class.php @@ -859,7 +859,16 @@ public function selectLotDataList($htmlname = 'batch_id', $empty = 0, $fk_produc $label = $arraytypes['entrepot_label'] . ' - '; $label .= $arraytypes['batch']; // Notice: Chrome show 1 line with value and 1 for label. Firefox show only 1 line with label - $out .= ''; + $optionLabel = ($conf->browser->name === 'chrome' ? '' : $arraytypes['batch']); + $optionLabel .= ' ('.$langs->trans('StockTotal').': '.$arraytypes['qty']; + if (!empty($arraytypes['sellbydate'])) { + $optionLabel .= ' - '.$langs->trans('printSellby', $arraytypes['sellbydate']); + } + if (!empty($arraytypes['eatbydate'])) { + $optionLabel .= ' - '.$langs->trans('printEatby', $arraytypes['eatbydate']); + } + $optionLabel .= ')'; + $out .= ''; } } } @@ -921,10 +930,22 @@ private function loadLotStock($productIdArray = array()) return $batch_count; } + $is_eat_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_EATBY'); + $is_sell_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_SELLBY'); + $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product"; + if ($is_eat_by_enabled) { + $sql .= ", pl.eatby"; + } + if ($is_sell_by_enabled) { + $sql .= ", pl.sellby"; + } $sql .= " FROM ".$this->db->prefix()."product_batch as pb"; $sql .= " LEFT JOIN ".$this->db->prefix()."product_stock as ps on ps.rowid = pb.fk_product_stock"; $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")"; + if ($is_eat_by_enabled || $is_sell_by_enabled) { + $sql .= " LEFT JOIN ".$this->db->prefix()."product_lot as pl on ps.fk_product = pl.fk_product AND pb.batch = pl.batch"; + } if (!empty($productIdList)) { $sql .= " WHERE ps.fk_product IN (".$this->db->sanitize($productIdList).")"; } @@ -942,6 +963,14 @@ private function loadLotStock($productIdArray = array()) $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_id'] = $obj->fk_entrepot; $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_label'] = $obj->label; $this->cache_lot[$obj->fk_product][$obj->rowid]['qty'] = $obj->qty; + $this->cache_lot[$obj->fk_product][$obj->rowid]['eatbydate'] = ''; + if (!empty($obj->eatby)) { + $this->cache_lot[$obj->fk_product][$obj->rowid]['eatbydate'] = dol_print_date($this->db->jdate($obj->eatby), 'day'); + } + $this->cache_lot[$obj->fk_product][$obj->rowid]['sellbydate'] = ''; + if (!empty($obj->sellby)) { + $this->cache_lot[$obj->fk_product][$obj->rowid]['sellbydate'] = dol_print_date($this->db->jdate($obj->sellby), 'day'); + } $i++; } From 30515539cd5b8ab74f07e27fd0516345a1e61591 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 20 Nov 2024 10:13:01 +0100 Subject: [PATCH 23/33] Add key StockTotal in en langs file --- htdocs/langs/en_US/stocks.lang | 1 + htdocs/langs/fr_FR/stocks.lang | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index 400a39ec95175..abcdf0343b2fb 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -348,3 +348,4 @@ QtyViewed=Quantity viewed QtyStock=Quantity on stock QtyRegulated=Quantity on stock correction InventoryEntrepot=Warehouse identity +StockTotal=Total stock diff --git a/htdocs/langs/fr_FR/stocks.lang b/htdocs/langs/fr_FR/stocks.lang index 78e721e28260d..8b9e7b2c5b482 100644 --- a/htdocs/langs/fr_FR/stocks.lang +++ b/htdocs/langs/fr_FR/stocks.lang @@ -347,4 +347,3 @@ QtyViewed=Quantité vue QtyStock=Quantité en stock QtyRegulated=Quantité de correction de stock InventoryEntrepot=Id entrepôt -StockTotal=Stock total From d63f54e3d18d15b573ed7df3d5e202ec39315b8d Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 20 Nov 2024 10:41:37 +0100 Subject: [PATCH 24/33] Fix CI errors on lot_cache definition --- htdocs/product/class/html.formproduct.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/class/html.formproduct.class.php b/htdocs/product/class/html.formproduct.class.php index c3247dff21cc3..a1ea39e9ab04b 100644 --- a/htdocs/product/class/html.formproduct.class.php +++ b/htdocs/product/class/html.formproduct.class.php @@ -47,7 +47,7 @@ class FormProduct */ public $cache_warehouses = array(); /** - * @var array> + * @var array> */ public $cache_lot = array(); /** From fe1ca54b337a34136b6501317f56ecd7a0b2bbaa Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Wed, 20 Nov 2024 12:20:56 +0100 Subject: [PATCH 25/33] Remove one check parameters in comment --- htdocs/expedition/class/expeditionligne.class.php | 1 - 1 file changed, 1 deletion(-) diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index 66f06382987f4..ca03cf93ca19b 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -362,7 +362,6 @@ public function insert($user, $notrigger = 0) { $error = 0; - // Check parameters // Check parameters if (empty($this->fk_expedition) || empty($this->fk_product) // product id is mandatory From 6b353a5375fce47137020663338a06a11cf13c01 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 22 Nov 2024 14:21:37 +0100 Subject: [PATCH 26/33] Show eat by and sell by date on update select batch by warehouse --- htdocs/expedition/dispatch.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index e382375e760e7..1ac8ee123d68d 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -1259,16 +1259,24 @@ $out_js_line .= ' jQuery.each(data, function(key, objBatch) {'; $out_js_line .= ' var dataEatByDate = objBatch.eatbydate;'; $out_js_line .= ' var dataSellByDate = objBatch.sellbydate;'; + $out_js_line .= ' var optionLabel = key+" (";'; $out_js_line .= ' if (selectwarehouse.val() == -1) {'; - $out_js_line .= ' var label = key+" ('.dol_escape_js($langs->trans('StockTotal')).' : "+objBatch.qty+")";'; + $out_js_line .= ' optionLabel += "'.dol_escape_js($langs->trans('StockTotal')).': "+objBatch.qty;'; $out_js_line .= ' } else {'; - $out_js_line .= ' var label = key+" ('.dol_escape_js($langs->trans('Stock')).' : "+objBatch.qty+")";'; + $out_js_line .= ' optionLabel += "'.dol_escape_js($langs->trans('Stock')).': "+objBatch.qty;'; $out_js_line .= ' }'; + $out_js_line .= ' if (dataEatByDate != "") {'; + $out_js_line .= ' optionLabel += " - '.dol_escape_js($langs->trans('EatByDate')).': "+dataEatByDate;'; + $out_js_line .= ' }'; + $out_js_line .= ' if (dataSellByDate != "") {'; + $out_js_line .= ' optionLabel += " - '.dol_escape_js($langs->trans('SellByDate')).': "+dataSellByDate;'; + $out_js_line .= ' }'; + $out_js_line .= ' optionLabel += ")";'; + $out_js_line .= ' var option = "