+ *
+ * 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/card.php b/htdocs/expedition/card.php
index 17d37a295fb..6543705f137 100644
--- a/htdocs/expedition/card.php
+++ b/htdocs/expedition/card.php
@@ -439,7 +439,14 @@ if (empty($reshook)) {
} 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++;
@@ -1252,13 +1259,22 @@ if ($action == 'create') {
print ''."\n";
print ''."\n";
+ $qtyProdCom = $line->qty;
+ $productChildrenNb = 0;
// Product label
if ($line->fk_product > 0) { // If predefined product
$res = $product->fetch($line->fk_product);
if ($res < 0) {
dol_print_error($db, $product->error, $product->errors);
}
- $product->load_stock('warehouseopen'); // Load all $product->stock_warehouse[idwarehouse]->detail_batch
+ if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
+ $productChildrenNb = $product->hasFatherOrChild(1);
+ }
+ if ($productChildrenNb > 0) {
+ $product->loadStockForVirtualProduct('warehouseopen', $qtyProdCom);
+ } else {
+ $product->load_stock('warehouseopen'); // Load all $product->stock_warehouse[idwarehouse]->detail_batch
+ }
//var_dump($product->stock_warehouse[1]);
print '| ';
@@ -1319,7 +1335,6 @@ if ($action == 'create') {
print ' | '.$line->qty;
print '';
print ''.$unit_order.' | ';
- $qtyProdCom = $line->qty;
// Qty already shipped
print '';
@@ -1391,10 +1406,14 @@ if ($action == 'create') {
if (getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) {
$stockMin = 0;
}
- if ($product->stockable_product == Product::ENABLED_STOCK) {
- print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'minwidth200', array(), 1, $stockMin, 'stock DESC, e.ref');
+ if ($productChildrenNb > 0) {
+ print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, 0, '', 0, 0, array(), 'minwidth200', array(), 1, $stockMin, 'stock DESC, e.ref');
} else {
- print img_warning().' '.$langs->trans('StockDisabled');
+ if ($product->stockable_product == Product::ENABLED_STOCK) {
+ print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'minwidth200', array(), 1, $stockMin, 'stock DESC, e.ref');
+ } else {
+ print img_warning().' '.$langs->trans('StockDisabled');
+ }
}
if ($tmpentrepot_id > 0 && $tmpentrepot_id == $warehouse_id) {
@@ -1636,10 +1655,12 @@ if ($action == 'create') {
if (isModEnabled('stock')) {
print ' | ';
if ($line->product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
- if ($product->stockable_product == Product::ENABLED_STOCK) {
+ if ($product->stockable_product == Product::ENABLED_STOCK || $productChildrenNb > 0) {
print $tmpwarehouseObject->getNomUrl(0).' ';
- print '';
- print '('.$stock.')';
+ if ($productChildrenNb <= 0) {
+ print '';
+ print '('.$stock.')';
+ }
} else {
print img_warning().' '.$langs->trans('StockDisabled');
}
@@ -2686,6 +2707,34 @@ if ($action == 'create') {
}
}
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 (!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 (!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];
+ }
+
+ $detail .= $langs->trans('DetailChildrenFormat', $child_product_label, $child_warehouse->label, price2num($total_qty, 'MS')).' ';
+ }
+ }
+ print $form->textwithtooltip(img_picto('', 'object_stock').' '.$langs->trans('DetailWarehouseNumber'), $detail);
}
print ' | ';
}
@@ -2746,9 +2795,25 @@ if ($action == 'create') {
print '
';
print '';
} elseif ($object->status == 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 (!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];
+ }
+
+ if ($product->hasFatherOrChild(1)) {
+ $edit_url = dol_buildpath('/expedition/dispatch.php?id='.$object->id, 1);
+ }
+ }
+
// edit-delete buttons
print '';
- print 'id.'&action=editline&token='.newToken().'&lineid='.$lines[$i]->id.'">'.img_edit().'';
+ print ''.img_edit().'';
print ' | ';
print '';
print 'id.'&action=deleteline&token='.newToken().'&lineid='.$lines[$i]->id.'">'.img_delete().'';
diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php
index b8111939c2f..2350c570fbb 100644
--- a/htdocs/expedition/class/expedition.class.php
+++ b/htdocs/expedition/class/expedition.class.php
@@ -258,7 +258,7 @@ class Expedition extends CommonObject
public $commande;
/**
- * @var ExpeditionLigne[] array of shipping lines
+ * @var array array of shipping lines
*/
public $lines = array();
@@ -496,15 +496,140 @@ class Expedition extends CommonObject
if ($this->db->query($sql)) {
// Insert of lines
$num = count($this->lines);
- 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++;
+ $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 (!is_object($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;
+ }
}
- } else { // with batch management
- if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
- $error++;
+ }
+ }
+ }
+ $kits_id_cached = array();
+ $sub_kits_id_cached = array();
+ for ($i = 0; $i < $num; $i++) {
+ $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 {
+ $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'];
+ $product_child_qty = (float) $product_child_arr['nb_total']; // by default
+ $warehouse_id = $line->entrepot_id; // by default
+ if ($is_kit || !$product_child_incdec) {
+ if (!$product_child_incdec) {
+ $product_child_qty = 0;
+ }
+ $warehouse_id = 0; // no warehouse used for a kit or if stock is not managed (empty incdec)
+ }
+
+ // 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;
}
}
}
@@ -589,9 +714,11 @@ class Expedition extends CommonObject
* @param float $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 = [])
+ public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [], $parent_line_id = 0, $product_id = 0)
{
//phpcs:enable
global $user;
@@ -601,10 +728,18 @@ class Expedition extends CommonObject
$expeditionline->entrepot_id = $entrepot_id;
$expeditionline->fk_elementdet = $origin_line_id;
$expeditionline->element_type = $this->origin;
+ $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_elementdet);
+ $expeditionline->fk_product = $order_line->fk_product;
+ }
+
if (($lineId = $expeditionline->insert($user)) < 0) {
$this->errors[] = $expeditionline->error;
}
@@ -995,9 +1130,11 @@ class Expedition extends CommonObject
* @param int $id Id of source line (order line)
* @param float $qty Quantity
* @param array $array_options extrafields array
+ * @param int $fk_product Id of product
+ * @param int $fk_parent Id of parent line
* @return int Return integer <0 if KO, >0 if OK
*/
- public function addline($entrepot_id, $id, $qty, $array_options = [])
+ public function addline($entrepot_id, $id, $qty, $array_options = [], $fk_product = 0, $fk_parent = 0)
{
global $conf, $langs;
@@ -1008,6 +1145,8 @@ class Expedition extends CommonObject
$line->origin_line_id = $id;
$line->fk_elementdet = $id;
$line->element_type = 'order';
+ $line->fk_parent = $fk_parent;
+ $line->fk_product = $fk_product;
$line->qty = $qty;
$orderline = new OrderLine($this->db);
@@ -1016,6 +1155,9 @@ class Expedition extends CommonObject
// 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)) {
$product = new Product($this->db);
@@ -1028,20 +1170,45 @@ class Expedition extends CommonObject
}
if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
- // Check must be done for stock of product into warehouse if $entrepot_id defined
- if ($entrepot_id > 0) {
- $product->load_stock('warehouseopen');
- $product_stock = $product->stock_warehouse[$entrepot_id]->real;
+ $productChildrenNb = 0;
+ if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
+ $productChildrenNb = $product->hasFatherOrChild(1);
+ }
+ if ($productChildrenNb > 0) {
+ $product_stock = null;
+ $product->loadStockForVirtualProduct('warehouseopen', $line->qty);
+ if ($entrepot_id > 0) {
+ if (isset($product->stock_warehouse[$entrepot_id])) {
+ $product_stock = $product->stock_warehouse[$entrepot_id]->real;
+ }
+ } else {
+ foreach ($product->stock_warehouse as $componentStockWarehouse) {
+ if ($product_stock === null) {
+ $product_stock = $componentStockWarehouse->real;
+ } else {
+ $product_stock = min($product_stock, $componentStockWarehouse->real);
+ }
+ }
+ }
+ if ($product_stock === null) {
+ $product_stock = 0;
+ }
} else {
- $product_stock = $product->stock_reel;
+ // Check must be done for stock of product into warehouse if $entrepot_id defined
+ if ($entrepot_id > 0) {
+ $product->load_stock('warehouseopen');
+ $product_stock = $product->stock_warehouse[$entrepot_id]->real;
+ } else {
+ $product_stock = $product->stock_reel;
+ }
}
$product_type = $product->type;
if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
- $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
+ $isavirtualproduct = ($productChildrenNb > 0);
// The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
if (!$isavirtualproduct || !getDolGlobalString('PRODUIT_SOUSPRODUITS') || ($isavirtualproduct && !getDolGlobalString('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
- if ($product_stock < $qty && $product->stockable_product == Product::ENABLED_STOCK) {
+ if ($product->stockable_product == Product::ENABLED_STOCK && $product_stock < $qty) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
$this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
@@ -1056,8 +1223,8 @@ class Expedition extends CommonObject
// 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;
}
@@ -1077,9 +1244,10 @@ class Expedition extends CommonObject
*
* @param array{detail:array,qty:int|float,ix_l:int} $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 = [])
+ public function addline_batch($dbatch, $array_options = [], $origin_line = null)
{
// phpcs:enable
global $conf, $langs;
@@ -1131,6 +1299,12 @@ class Expedition extends CommonObject
$line->fk_elementdet = $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
@@ -1331,19 +1505,27 @@ class Expedition extends CommonObject
}
// Stock control
- if (!$error && isModEnabled('stock') &&
+ $can_update_stock = isModEnabled('stock') &&
((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
- (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock))) {
+ (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == 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 ".$this->db->prefix()."commandedet as cd,";
- $sql .= " ".$this->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_elementdet";
+ $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);
@@ -1355,45 +1537,68 @@ class Expedition extends CommonObject
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 = '';
- // 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 = '';
+ // 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, (string) $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, (string) $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 in case of error
+ }
}
- if ($error) {
- break; // break for loop in case 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++;
@@ -1401,87 +1606,67 @@ class Expedition extends CommonObject
}
}
- // delete batch expedition line
- if (!$error && isModEnabled('productbatch')) {
- $shipmentlinebatch = new ExpeditionLineBatch($this->db);
- if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
- $error++;
- $this->errors[] = "Error ".$this->db->lasterror();
- }
- }
-
-
if (!$error) {
- $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet";
- $sql .= " WHERE fk_expedition = ".((int) $this->id);
+ // Delete linked object
+ $res = $this->deleteObjectLinked();
+ if ($res < 0) {
+ $error++;
+ }
- if ($this->db->query($sql)) {
- // Delete linked object
- $res = $this->deleteObjectLinked();
- if ($res < 0) {
- $error++;
- }
+ // No delete expedition
+ if (!$error) {
+ $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition";
+ $sql .= " WHERE rowid = ".((int) $this->id);
- // 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_object = $this->origin_object;
+ '@phan-var-force Facture|Commande $origin_object';
+ if ($origin_object->status == 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"
+ $origin_object->loadExpeditions();
+ //var_dump($this->$origin->expeditions);exit;
+ if (count($origin_object->expeditions) <= 0) {
+ $origin_object->setStatut(Commande::STATUS_VALIDATED);
+ }
+ }
+ }
- if ($this->db->query($sql)) {
- if (!empty($this->origin) && $this->origin_id > 0) {
- $this->fetch_origin();
- $origin_object = $this->origin_object;
- '@phan-var-force Facture|Commande $origin_object';
- if ($origin_object->status == 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"
- $origin_object->loadExpeditions();
- //var_dump($this->$origin->expeditions);exit;
- if (count($origin_object->expeditions) <= 0) {
- $origin_object->setStatut(Commande::STATUS_VALIDATED);
+ 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;
+ }
+ }
+ if (file_exists($dir)) {
+ if (!dol_delete_dir_recursive($dir)) {
+ $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
+ return 0;
}
}
}
- 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;
- }
- }
- 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();
@@ -1530,9 +1715,10 @@ class Expedition extends CommonObject
}
// Stock control
- if (!$error && isModEnabled('stock') &&
+ $can_update_stock = isModEnabled('stock') &&
((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
- (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock))) {
+ (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
+ if (!$error) {
require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
$langs->load("agenda");
@@ -1540,12 +1726,19 @@ class Expedition extends CommonObject
// 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 ".$this->db->prefix()."commandedet as cd,";
- $sql .= " ".$this->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_elementdet";
+ $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);
@@ -1554,41 +1747,64 @@ class Expedition extends CommonObject
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 = '';
- // 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 = '';
+ // 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, (string) $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, (string) $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 in case of error
+ }
}
- if ($error) {
- break; // break for loop in case 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++;
@@ -1596,106 +1812,83 @@ class Expedition extends CommonObject
}
}
- // 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 = $this->db->prefix().'expeditiondet';
- $ef = $main."_extrafields";
- $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
+ // delete extrafields
+ $res = $this->deleteExtraFields();
+ if ($res < 0) {
+ $error++;
+ }
- $sql = "DELETE FROM ".$this->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 (!$error) {
+ // Delete linked contacts
+ $res = $this->delete_linked_contact();
if ($res < 0) {
$error++;
}
+ }
+ if (!$error) {
+ $sql = "DELETE FROM ".$this->db->prefix()."expedition";
+ $sql .= " WHERE rowid = ".((int) $this->id);
- // delete extrafields
- $res = $this->deleteExtraFields();
- if ($res < 0) {
- $error++;
- }
-
- if (!$error) {
- // Delete linked contacts
- $res = $this->delete_linked_contact();
- if ($res < 0) {
- $error++;
+ if ($this->db->query($sql)) {
+ if (!empty($this->origin) && $this->origin_id > 0) {
+ $this->fetch_origin();
+ $origin_object = $this->origin_object;
+ '@phan-var-force Facture|Commande $origin_object';
+ if ($origin_object->status == 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"
+ $origin_object->loadExpeditions();
+ //var_dump($this->$origin->expeditions);exit;
+ if (count($origin_object->expeditions) <= 0) {
+ $origin_object->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_object = $this->origin_object;
- '@phan-var-force Facture|Commande $origin_object';
- if ($origin_object->status == 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"
- $origin_object->loadExpeditions();
- //var_dump($this->$origin->expeditions);exit;
- if (count($origin_object->expeditions) <= 0) {
- $origin_object->setStatut(Commande::STATUS_VALIDATED);
+ 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;
+ }
+ }
+ if (file_exists($dir)) {
+ if (!dol_delete_dir_recursive($dir)) {
+ $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
+ return 0;
}
}
}
- 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;
- }
- }
- 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();
@@ -1890,6 +2083,33 @@ class Expedition extends CommonObject
}
}
+ // 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) {
+ 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_elementdet) {
@@ -2479,17 +2699,18 @@ class Expedition extends CommonObject
$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 " . $this->db->prefix() . "commandedet as cd,";
- $sql .= " " . $this->db->prefix() . "expeditiondet as ed";
+ $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_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);
@@ -2505,7 +2726,7 @@ class Expedition extends CommonObject
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;
diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php
index 0ab98517920..2fee97e8d0a 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 ([id_product][id_warehouse] -> qty (int|float))
+ * @var array>
+ */
+ public $detail_children;
+
/** detail of warehouses and qty
* We can use this to know warehouse when there is no lot.
* @var stdClass[]
@@ -361,7 +372,10 @@ class ExpeditionLigne extends CommonObjectLine
$error = 0;
// Check parameters
- if (empty($this->fk_expedition) || empty($this->fk_elementdet) || !is_numeric($this->qty)) {
+ 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;
}
@@ -383,13 +397,17 @@ class ExpeditionLigne extends CommonObjectLine
$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);
@@ -418,7 +436,7 @@ class ExpeditionLigne extends CommonObjectLine
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);
}
}
@@ -435,6 +453,69 @@ class ExpeditionLigne extends CommonObjectLine
}
}
+ /**
+ * Find all children
+ *
+ * @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)
+ {
+ 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 = 0;
+ $line_obj->incdec = 0;
+ $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.
*
@@ -448,41 +529,82 @@ class ExpeditionLigne extends CommonObjectLine
$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
+ $line_id_list = array();
+ $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) {
diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php
index a96f628aeba..fdcadcfe8eb 100644
--- a/htdocs/expedition/dispatch.php
+++ b/htdocs/expedition/dispatch.php
@@ -143,35 +143,36 @@ if (empty($reshook)) {
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|productbatch)([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
$pos++;
$modebatch = null;
- 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 = GETPOSTINT("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, GETPOSTINT('dluo_'.$reg[1].'_'.$reg[2].'month'), GETPOSTINT('dluo_'.$reg[1].'_'.$reg[2].'day'), GETPOSTINT('dluo_'.$reg[1].'_'.$reg[2].'year'));
- $dDLC = dol_mktime(12, 0, 0, GETPOSTINT('dlc_'.$reg[1].'_'.$reg[2].'month'), GETPOSTINT('dlc_'.$reg[1].'_'.$reg[2].'day'), GETPOSTINT('dlc_'.$reg[1].'_'.$reg[2].'year'));
+ $lot = GETPOST('lot_number'.$dispatch_line_suffix);
+ $dDLUO = dol_mktime(12, 0, 0, GETPOSTINT('dluo'.$dispatch_line_suffix.'month'), GETPOSTINT('dluo'.$dispatch_line_suffix.'day'), GETPOSTINT('dluo'.$dispatch_line_suffix.'year'));
+ $dDLC = dol_mktime(12, 0, 0, GETPOSTINT('dlc'.$dispatch_line_suffix.'month'), GETPOSTINT('dlc'.$dispatch_line_suffix.'day'), GETPOSTINT('dlc'.$dispatch_line_suffix.'year'));
}
$newqty = GETPOSTFLOAT($qty, 'MS');
@@ -241,13 +242,12 @@ if (empty($reshook)) {
if (!$error && $modebatch == "batch") {
if ($newqty > 0) {
- $suffixkeyfordate = preg_replace('/^product_batch/', '', $key);
- $sellby = dol_mktime(0, 0, 0, GETPOSTINT('dlc'.$suffixkeyfordate.'month'), GETPOSTINT('dlc'.$suffixkeyfordate.'day'), GETPOSTINT('dlc'.$suffixkeyfordate.'year'), '');
- $eatby = dol_mktime(0, 0, 0, GETPOSTINT('dluo'.$suffixkeyfordate.'month'), GETPOSTINT('dluo'.$suffixkeyfordate.'day'), GETPOSTINT('dluo'.$suffixkeyfordate.'year'));
+ $suffixkeyfordate = preg_replace('/^productbatch/', '', $key);
+ $sellby = dol_mktime(12, 0, 0, GETPOSTINT('dlc'.$suffixkeyfordate.'month'), GETPOSTINT('dlc'.$suffixkeyfordate.'day'), GETPOSTINT('dlc'.$suffixkeyfordate.'year'), '');
+ $eatby = dol_mktime(12, 0, 0, GETPOSTINT('dluo'.$suffixkeyfordate.'month'), GETPOSTINT('dluo'.$suffixkeyfordate.'day'), GETPOSTINT('dluo'.$suffixkeyfordate.'year'));
$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);
$objsearchdet = null;
@@ -259,10 +259,11 @@ if (empty($reshook)) {
if ($objsearchdet) {
$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 .= " batch = '".$db->escape($lot)."'";
+ $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 ".$db->prefix().$expeditionlinebatch->table_element." (";
@@ -271,7 +272,7 @@ if (empty($reshook)) {
$sql .= " '".$db->escape($lot)."', ".((float) $newqty).", 0, ".((int) $warehouse_id).")";
}
} else {
- $sql = " DELETE FROM ".$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)."'";
}
@@ -286,7 +287,11 @@ if (empty($reshook)) {
} else {
$expeditiondispatch->fk_expedition = $object->id;
$expeditiondispatch->entrepot_id = GETPOSTINT($ent);
- $expeditiondispatch->fk_elementdet = GETPOSTINT($fk_commandedet);
+ $expeditiondispatch->fk_parent = GETPOSTINT('fk_parent'.$dispatch_line_suffix);
+ $expeditiondispatch->fk_product = $prod_id;
+ if (!($expeditiondispatch->fk_parent > 0)) {
+ $expeditiondispatch->fk_elementdet = GETPOSTINT($fk_commandedet);
+ }
$expeditiondispatch->qty = $newqty;
if ($newqty > 0) {
@@ -297,8 +302,8 @@ if (empty($reshook)) {
}
if ($modebatch == "batch" && !$error) {
- $expeditionlinebatch->sellby = $dDLUO;
- $expeditionlinebatch->eatby = $dDLC;
+ $expeditionlinebatch->sellby = $dDLC; // DLC is sellByDate
+ $expeditionlinebatch->eatby = $dDLUO; // DLUO is eatByDate
$expeditionlinebatch->batch = $lot;
$expeditionlinebatch->qty = $newqty;
$expeditionlinebatch->fk_origin_stock = 0;
@@ -793,7 +798,7 @@ if ($object->id > 0 || !empty($object->ref)) {
$sql = "SELECT ed.rowid";
$sql .= ", cd.fk_product";
$sql .= ", ".$db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty')." as qty";
- $sql .= ", ed.fk_entrepot";
+ $sql .= ", ".$db->ifsql('eb.rowid IS NULL OR eb.fk_warehouse 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";
@@ -806,167 +811,324 @@ if ($object->id > 0 || !empty($object->ref)) {
$j = 0;
if ($resultsql) {
$numd = $db->num_rows($resultsql);
+ while ($obj_exp = $db->fetch_object($resultsql)) {
+ $suffix = "_" . $j . "_" . $i;
- while ($j < $numd) {
- $suffix = "_".$j."_".$i;
- $objd = $db->fetch_object($resultsql);
+ $productChildrenNb = 0;
+ $expedition_line_child_list = array();
+ if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
+ // virtual product : find all children
+ $productChildrenNb = $tmpproduct->hasFatherOrChild(1);
+ if ($productChildrenNb > 0) {
+ $line_id_list = array();
- if ($is_mod_batch_enabled && (!empty($objd->batch) || (is_null($objd->batch) && $tmpproduct->status_batch > 0))) {
- $type = 'batch';
+ // 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];
+ }
- // 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');
+ // sub-product is a batch and get selected batch from database or all batches for selected warehouse
+ $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)) {
+ // set the selected bath by default
+ if ($obj_batch->batch != '') {
+ $line_obj->batch = $obj_batch->batch;
+ }
+ $obj_batch->eatby = dol_print_date($obj_batch->eatby, 'day');
+ $obj_batch->sellby = dol_print_date($obj_batch->sellby, 'day');
+ $batch_list[] = $obj_batch;
+ }
+ $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($batch_list)) {
+ $batch_sort_field_arr = array();
+ $batch_sort_order_arr = array();
+ if ($is_sell_by_enabled) {
+ $batch_sort_field_arr[] = 'pl.sellby'; // order by sell by (DLC)
+ $batch_sort_order_arr[] = 'ASC';
+ }
+ if ($is_eat_by_enabled) {
+ $batch_sort_field_arr[] = 'pl.eatby'; // order by eat by (DLUO)
+ $batch_sort_order_arr[] = 'ASC';
+ }
+ $batch_sort_field_arr[] = 'pb.qty'; // order by qty
+ $batch_sort_order_arr[] = 'ASC';
+ $batch_sort_field_arr[] = 'pl.rowid'; // order by rowid
+ $batch_sort_order_arr[] = 'ASC';
+ $product_batch = new Productbatch($db);
+ $product_batch_result = $product_batch->findAllForProduct($child_product_id, $line_obj->fk_warehouse, (getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER') ? 0 : null), implode(',', $batch_sort_field_arr), implode(',', $batch_sort_order_arr));
+ 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');
+ $batch_list[] = $batch_current;
+ }
+ }
+ }
+ }
+ $line_obj->batch_list = $batch_list;
+
+ // determine if line is virtual product and stock is managed
+ $line_obj->iskit = 0;
+ if ($child_product->stockable_product == Product::ENABLED_STOCK) {
+ $can_manage_stock = 1;
+ } else {
+ $can_manage_stock = 0; // the value of "incdec" can't be modified
+ }
+ $line_obj->incdec = $can_manage_stock; // set value by default before this request
+ $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 = ".((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) {
+ if ($child_obj = $db->fetch_object($resql_child)) {
+ $line_obj->iskit = (int) $child_obj->iskit;
+ if ($can_manage_stock) {
+ $line_obj->incdec = (int) $child_obj->incdec; // reset value to 0 or 1 if stock can be managed
+ }
+ }
+ $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 ' |
';
-
- print '';
- print '';
- print '| ';
- print '';
- print '';
- print '';
-
- print '';
- print '';
-
- print ' | ';
-
- print '';
- print '';
- //print '';
- print ' | ';
- 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_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, '');
- print ' | ';
- }
- print ' | '; // Supplier ref + Qty ordered + qty already dispatched
- } else {
- $type = 'dispatch';
- $colspan = 6;
- $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
- $colspan = $is_eat_by_enabled ? $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 ' | ';
}
- // Qty to dispatch
- print '';
- print ''.img_picto($langs->trans("Reset"), 'eraser', 'class="pictofixedwidth opacitymedium"').'';
- $suggestedvalue = (GETPOSTISSET('qty'.$suffix) ? GETPOSTFLOAT('qty'.$suffix) : $objd->qty);
- //var_dump($suggestedvalue);exit;
- print '';
- print ' | ';
- print '';
- if ($is_mod_batch_enabled && $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 ($objp->stockable_product == Product::ENABLED_STOCK) {
- 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, array(), '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, array(), 'csswarehouse'.$suffix);
+ if (empty($expedition_line_child_list)) {
+ $obj_exp->iskit = 0; // is not virtual product
+ // manage stock if enabled for product
+ if ($objp->stockable_product == Product::ENABLED_STOCK) {
+ $obj_exp->incdec = 1;
} else {
- $langs->load("errors");
- print $langs->trans("ErrorNoWarehouseDefined");
+ $obj_exp->incdec = 0;
}
- } else {
- // on force l'entrepot pour passer le test d'ajout de ligne dans expedition.class.php
- print '';
- print img_warning().' '.$langs->trans('StockDisabled');
+ $expedition_line_child_list[] = $obj_exp;
}
- 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');
+ $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;
+
+ // set default batch values for this dispatched line (lot/serial number of virtual product)
+ $dispatch_line_batch_current = null;
+ if (!empty($objd->batch_list)) {
+ $dispatch_line_batch_count = count($objd->batch_list);
+ // if only one batch found, this batch is pre-selected
+ if ($dispatch_line_batch_count == 1) {
+ $dispatch_line_batch_current = current($objd->batch_list);
+ }
+ }
+ if (is_object($dispatch_line_batch_current)) {
+ $objd->batch = $dispatch_line_batch_current->batch;
+ $objd->eatby = $dispatch_line_batch_current->eatby;
+ $objd->sellby = $dispatch_line_batch_current->sellby;
+ }
+
+ if ($is_mod_batch_enabled
+ && (
+ !empty($objd->batch)
+ || (is_null($objd->batch) && $tmpproduct->status_batch > 0)
+ || !empty($objd->batch_list)
+ )
+ ) {
+ $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 '';
+ if (!empty($objd->html_label)) {
+ print $objd->html_label;
+ }
+ print ' | ';
+
+ 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_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_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, '');
+ print ' | ';
+ }
+ print ' | '; // Supplier ref + Qty ordered + qty already dispatched
+ } else {
+ $type = 'dispatch';
+ $colspan = 6;
+ $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
+ $colspan = $is_eat_by_enabled ? $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) ? GETPOSTFLOAT('qty'.$suffix) : $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 '';
+ if ($can_update_stock) {
+ print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'-'.$child_line_id.'\')"');
+ }
+ 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, $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, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
+ } else {
+ $langs->load("errors");
+ print $langs->trans("ErrorNoWarehouseDefined");
+ }
+ } else {
+ // on force l'entrepot pour passer le test d'ajout de ligne dans expedition.class.php
+ print '';
+ print img_warning().' '.$langs->trans('StockDisabled');
+ }
+ 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,
+ 'objd' => $objd,
+ );
+ $reshook = $hookmanager->executeHooks(
+ 'printFieldListValue',
+ $parameters,
+ $object,
+ $action
+ );
+ if ($reshook < 0) {
+ setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+ }
+ print $hookmanager->resPrint;
+
+ print "
\n";
}
- print $hookmanager->resPrint;
- print "\n";
$j++;
-
$numline++;
}
- $suffix = "_".$j."_".$i;
+
+ //$suffix = "_".$j."_".$i;
} else {
$errorMsg = 'Shipment dispatch SQL error : '.$db->lasterror();
setEventMessage($errorMsg, 'errors');
dol_syslog($errorMsg, LOG_ERR);
}
+ /*
if ($j == 0) {
if ($is_mod_batch_enabled && !empty($objp->tobatch)) {
$type = 'batch';
@@ -997,7 +1159,7 @@ if ($object->id > 0 || !empty($object->ref)) {
print '';
print '';
print '';
- print '';
+ print '';
print '';
print '';
@@ -1110,10 +1272,128 @@ if ($object->id > 0 || !empty($object->ref)) {
print $hookmanager->resPrint;
print "\n";
}
+ */
}
}
$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(" |