dolibarr 25.0.0-alpha
dispatch.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2004-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2016 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005 Eric Seigne <eric.seigne@ryxeo.com>
5 * Copyright (C) 2005-2009 Regis Houssin <regis.houssin@inodbox.com>
6 * Copyright (C) 2010-2021 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2014 Cedric Gross <c.gross@kreiz-it.fr>
8 * Copyright (C) 2016 Florian Henry <florian.henry@atm-consulting.fr>
9 * Copyright (C) 2017-2022 Ferran Marcet <fmarcet@2byte.es>
10 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
11 * Copyright (C) 2019-2020 Christophe Battarel <christophe@altairis.fr>
12 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
13 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program. If not, see <https://www.gnu.org/licenses/>.
27 */
28
35// Load Dolibarr environment
36require '../main.inc.php';
44require_once DOL_DOCUMENT_ROOT.'/core/modules/supplier_order/modules_commandefournisseur.php';
45require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
46require_once DOL_DOCUMENT_ROOT.'/core/lib/fourn.lib.php';
47require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
48require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
49require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
50require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
51require_once DOL_DOCUMENT_ROOT.'/core/lib/sendings.lib.php';
52if (isModEnabled('project')) {
53 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
54}
55
56// Load translation files required by the page
57$langs->loadLangs(array("sendings", "companies", "bills", 'orders', 'stocks', 'other', 'propal', 'receptions'));
58
59$is_mod_batch_enabled = isModEnabled('productbatch');
60$is_eat_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_EATBY');
61$is_sell_by_enabled = !getDolGlobalInt('PRODUCT_DISABLE_SELLBY');
62if ($is_mod_batch_enabled) {
63 $langs->load('productbatch');
64}
65
66// Security check
67$id = GETPOSTINT("id");
68$ref = GETPOST('ref');
69$lineid = GETPOSTINT('lineid');
70$action = GETPOST('action', 'aZ09');
71$fk_default_warehouse = GETPOSTINT('fk_default_warehouse');
72$cancel = GETPOST('cancel', 'alpha');
73$confirm = GETPOST('confirm', 'alpha');
74
75$error = 0;
76$errors = array();
77
78if ($user->socid) {
79 $socid = $user->socid;
80}
81
82$hookmanager->initHooks(array('expeditiondispatch'));
83
84// Retrieve the project id
85$projectid = 0;
86if (GETPOSTISSET("projectid")) {
87 $projectid = GETPOSTINT("projectid");
88}
89
90$object = new Expedition($db);
91$objectorder = new Commande($db);
92
93
94if ($id > 0 || !empty($ref)) {
95 $result = $object->fetch($id, $ref);
96 if ($result <= 0) {
97 setEventMessages($object->error, $object->errors, 'errors');
98 }
99 $result = $object->fetch_thirdparty();
100 if ($result < 0) {
101 setEventMessages($object->error, $object->errors, 'errors');
102 }
103 if (!empty($object->origin) && $object->origin_id > 0) {
104 $origin = $object->origin;
105 $typeobject = $object->origin;
106
107 $object->fetch_origin();
108 }
109}
110
111// $id is id of a purchase order.
112$result = restrictedArea($user, 'expedition', $object, '');
113
114if (!isModEnabled('stock')) {
115 accessforbidden('Module stock disabled');
116}
117
118$usercancreate = $user->hasRight('expedition', 'creer');
119$permissiontoadd = $usercancreate; // Used by the include of actions_addupdatedelete.inc.php
120
121
122/*
123 * Actions
124 */
125
126$parameters = array();
127$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
128if ($reshook < 0) {
129 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
130}
131$isStandaloneShipment = !empty($object->id) && !($object->origin_id > 0) && getDolGlobalString('SHIPMENT_STANDALONE');
132
133if (empty($reshook)) {
134 if ($action == 'updatelines' && (!$usercancreate || $object->status != Expedition::STATUS_DRAFT || (!$isStandaloneShipment && !($object->origin_id > 0)))) {
136 }
137 // Update a dispatched line
138 if ($action == 'updatelines' && $usercancreate) {
139 $db->begin();
140 $error = 0;
141
142 $expeditiondispatch = new ExpeditionLigne($db);
143 $expeditionlinebatch = new ExpeditionLineBatch($db);
144 $standaloneWarehouses = array();
145 if ($isStandaloneShipment) {
146 $warehouse = new Entrepot($db);
147 $standaloneWarehouses = $warehouse->list_array(1);
148 }
149
150 $pos = 0;
151
152 foreach ($_POST as $key => $value) {
153 // without batch module enabled
154 $reg = array();
155 if (preg_match('/^(?:product|productbatch)([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
156 $pos++;
157 $modebatch = null;
158 if (preg_match('/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
159 $modebatch = "barcode";
160 } elseif (preg_match('/^productbatch([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) { // With batchmode enabled
161 $modebatch = "batch";
162 }
163
164 $numline = $pos;
165 $dispatch_line_suffix = $reg[1].'_'.$reg[2].'_'.$reg[3];
166 if ($modebatch == "barcode") {
167 $prod = "product".$dispatch_line_suffix;
168 } else {
169 $prod = 'productbatch'.$dispatch_line_suffix;
170 }
171 $qty = "qty".$dispatch_line_suffix;
172 $ent = "entrepot".$dispatch_line_suffix;
173 $fk_commandedet = "fk_commandedet".$dispatch_line_suffix;
174 $idline = GETPOSTINT("idline".$dispatch_line_suffix);
175 $warehouse_id = GETPOSTINT($ent);
176 $prod_id = GETPOSTINT($prod);
177 //$pu = "pu".$dispatch_line_suffix; // This is unit price including discount
178 $lot = '';
179 $dDLUO = '';
180 $dDLC = '';
181 if ($modebatch == "batch") { //TODO: Make impossible to input non existing batch code
182 $lot = GETPOST('lot_number'.$dispatch_line_suffix);
183 $dDLUO = dol_mktime(12, 0, 0, GETPOSTINT('dluo'.$dispatch_line_suffix.'month'), GETPOSTINT('dluo'.$dispatch_line_suffix.'day'), GETPOSTINT('dluo'.$dispatch_line_suffix.'year'));
184 $dDLC = dol_mktime(12, 0, 0, GETPOSTINT('dlc'.$dispatch_line_suffix.'month'), GETPOSTINT('dlc'.$dispatch_line_suffix.'day'), GETPOSTINT('dlc'.$dispatch_line_suffix.'year'));
185 }
186
187 $newqty = GETPOSTFLOAT($qty, 'MS');
188 $source_line = null;
189 if ($isStandaloneShipment) {
190 if (GETPOST($qty, 'alphanohtml') === '') {
191 continue;
192 }
193 $source_line = shippingGetStandaloneDispatchSourceLine($object, GETPOSTINT($fk_commandedet), $idline, $prod_id);
194 if ($source_line === null || $newqty < 0 || ($newqty > 0 && !isset($standaloneWarehouses[$warehouse_id]))) {
195 setEventMessages($langs->trans('ErrorBadParameters'), null, 'errors');
196 $error++;
197 continue;
198 }
199 }
200 //var_dump("modebatch=".$modebatch." newqty=".$newqty." ent=".$ent." idline=".$idline);
201
202 // We ask to move a qty
203 if (($isStandaloneShipment && $newqty >= 0) || ($modebatch == "batch" && $newqty >= 0) || ($modebatch == "barcode" && $newqty != 0)) {
204 if ($newqty > 0) { // If we want a qty, we make test on input data
205 if (!($warehouse_id > 0)) {
206 dol_syslog('No dispatch for line '.$key.' as no warehouse was chosen.');
207 $text = $langs->transnoentities('Warehouse').', '.$langs->transnoentities('Line').' '.($numline);
208 setEventMessages($langs->trans('ErrorFieldRequired', $text), null, 'errors');
209 $error++;
210 }
211 if (!$error && $modebatch == "batch") {
212 $sql = "SELECT pb.rowid ";
213 $sql .= " FROM ".$db->prefix()."product_batch as pb";
214 $sql .= " JOIN ".$db->prefix()."product_stock as ps";
215 $sql .= " ON ps.rowid = pb.fk_product_stock";
216 $sql .= " WHERE pb.batch = '".$db->escape($lot)."'";
217 $sql .= " AND ps.fk_product = ".((int) $prod_id) ;
218 $sql .= " AND ps.fk_entrepot = ".((int) $warehouse_id) ;
219
220 $resql = $db->query($sql);
221 if ($resql) {
222 $num = $db->num_rows($resql);
223 if ($num > 1) {
224 dol_syslog('No dispatch for line '.$key.' as too many combination warehouse, product, batch code was found ('.$num.').');
225 setEventMessages($langs->trans('ErrorTooManyCombinationBatchcode', $numline, $num), null, 'errors');
226 $error++;
227 } elseif ($num < 1) {
228 $tmpwarehouse = new Entrepot($db);
229 $tmpwarehouse->fetch($warehouse_id);
230 $tmpprod = new Product($db);
231 $tmpprod->fetch($prod_id);
232 dol_syslog('No dispatch for line '.$key.' as no combination warehouse, product, batch code was found.');
233 setEventMessages($langs->trans('ErrorNoCombinationBatchcode', $numline, $tmpwarehouse->ref, $tmpprod->ref, $lot), null, 'errors');
234 $error++;
235 }
236 $db->free($resql);
237 }
238 }
239 }
240
241 if (!$error) {
242 $qtystart = 0;
243
244 if ($idline > 0) {
245 $result = $expeditiondispatch->fetch($idline); // get line from llx_expeditiondet
246 if ($result > 0 && (int) $expeditiondispatch->fk_expedition !== (int) $object->id) {
247 // The line must be a line of the shipment of the page
248 $expeditiondispatch->error = $langs->trans('ErrorRecordNotFound');
249 $result = -1;
250 }
251 if ($result < 0) {
252 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors, 'errors');
253 $error++;
254 } else {
255 $qtystart = $expeditiondispatch->qty;
256 $expeditiondispatch->qty = $newqty;
257 $expeditiondispatch->entrepot_id = GETPOSTINT($ent);
258
259 // A standalone root remains the source of its warehouse allocations, even at zero quantity.
260 if ($newqty > 0 || ($isStandaloneShipment && !($expeditiondispatch->fk_parent > 0))) {
261 $result = $expeditiondispatch->update($user);
262 } else {
263 $result = $expeditiondispatch->delete($user);
264 }
265 if ($result < 0) {
266 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors, 'errors');
267 $error++;
268 }
269
270 if (!$error && $modebatch == "batch") {
271 if ($newqty > 0) {
272 $suffixkeyfordate = preg_replace('/^productbatch/', '', $key);
273 $sellby = dol_mktime(12, 0, 0, GETPOSTINT('dlc'.$suffixkeyfordate.'month'), GETPOSTINT('dlc'.$suffixkeyfordate.'day'), GETPOSTINT('dlc'.$suffixkeyfordate.'year'), '');
274 $eatby = dol_mktime(12, 0, 0, GETPOSTINT('dluo'.$suffixkeyfordate.'month'), GETPOSTINT('dluo'.$suffixkeyfordate.'day'), GETPOSTINT('dluo'.$suffixkeyfordate.'year'));
275
276 $sqlsearchdet = "SELECT rowid FROM ".$db->prefix().$expeditionlinebatch->table_element;
277 $sqlsearchdet .= " WHERE fk_expeditiondet = ".((int) $idline);
278 $resqlsearchdet = $db->query($sqlsearchdet);
279
280 $objsearchdet = null;
281 if ($resqlsearchdet) {
282 $objsearchdet = $db->fetch_object($resqlsearchdet);
283 } else {
284 dol_print_error($db);
285 }
286
287 if ($objsearchdet) {
288 $sql = "UPDATE ".$db->prefix().$expeditionlinebatch->table_element." SET";
289 $sql .= " batch = '".$db->escape($lot)."'";
290 $sql .= ", eatby = ".($eatby ? "'".$db->idate($eatby)."'" : "null");
291 $sql .= ", sellby = ".($sellby ? "'".$db->idate($sellby)."'" : "null");
292 $sql .= ", qty = ".((float) $newqty);
293 $sql .= ", fk_warehouse = ".((int) $warehouse_id);
294 $sql .= " WHERE rowid = ".((int) $objsearchdet->rowid);
295 } else {
296 $sql = "INSERT INTO ".$db->prefix().$expeditionlinebatch->table_element." (";
297 $sql .= "fk_expeditiondet, eatby, sellby, batch, qty, fk_origin_stock, fk_warehouse)";
298 $sql .= " VALUES (".((int) $idline).", ".($eatby ? "'".$db->idate($eatby)."'" : "null").", ".($sellby ? "'".$db->idate($sellby)."'" : "null").", ";
299 $sql .= " '".$db->escape($lot)."', ".((float) $newqty).", 0, ".((int) $warehouse_id).")";
300 }
301 } else {
302 $sql = "DELETE FROM ".$db->prefix().$expeditionlinebatch->table_element;
303 $sql .= " WHERE fk_expeditiondet = ".((int) $idline);
304 $sql .= " AND batch = '".$db->escape($lot)."'";
305 }
306
307 $resql = $db->query($sql);
308 if (!$resql) {
309 dol_print_error($db);
310 $error++;
311 }
312 }
313 }
314 } else {
315 // Do not reuse product, origin or batch data from the previously updated line.
316 $expeditiondispatch = new ExpeditionLigne($db);
317 $expeditiondispatch->fk_expedition = $object->id;
318 $expeditiondispatch->entrepot_id = GETPOSTINT($ent);
319 $expeditiondispatch->fk_parent = GETPOSTINT('fk_parent'.$dispatch_line_suffix);
320 $expeditiondispatch->fk_product = $prod_id;
321 if ($isStandaloneShipment) {
322 $expeditiondispatch->element_type = 'shipping';
323 if ($source_line !== null) {
324 $expeditiondispatch->fk_parent = $source_line->id;
325 $expeditiondispatch->fk_unit = $source_line->fk_unit;
326 $expeditiondispatch->description = $source_line->description;
327 $expeditiondispatch->rang = $source_line->rang;
328 }
329 } elseif (!($expeditiondispatch->fk_parent > 0)) {
330 $expeditiondispatch->fk_elementdet = GETPOSTINT($fk_commandedet);
331 }
332 $expeditiondispatch->qty = $newqty;
333
334 if ($newqty > 0) {
335 $idline = $expeditiondispatch->insert($user);
336 if ($idline < 0) {
337 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors, 'errors');
338 $error++;
339 }
340
341 if ($modebatch == "batch" && !$error) {
342 $expeditionlinebatch->sellby = $dDLC; // DLC is sellByDate
343 $expeditionlinebatch->eatby = $dDLUO; // DLUO is eatByDate
344 $expeditionlinebatch->batch = $lot;
345 $expeditionlinebatch->qty = $newqty;
346 $expeditionlinebatch->fk_origin_stock = 0;
347 $expeditionlinebatch->fk_warehouse = GETPOSTINT($ent);
348
349 $result = $expeditionlinebatch->create($idline);
350 if ($result < 0) {
351 setEventMessages($expeditionlinebatch->error, $expeditionlinebatch->errors, 'errors');
352 $error++;
353 }
354 }
355 }
356 }
357
358 // If module stock is enabled and the stock decrease is done on edition of this page
359 /*
360 if (!$error && GETPOST($ent, 'int') > 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_DISPATCH_ORDER)) {
361 $mouv = new MouvementStock($db);
362 $product = GETPOST($prod, 'int');
363 $entrepot = GETPOST($ent, 'int');
364 $qtymouv = price2num(GETPOST($qty, 'alpha'), 'MS') - $qtystart;
365 $price = price2num(GETPOST($pu), 'MU');
366 $comment = GETPOST('comment');
367 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
368 $now = dol_now();
369 $eatby = '';
370 $sellby = '';
371 $batch = '';
372 if ($modebatch == "batch") {
373 $eatby = $dDLUO;
374 $sellby = $dDLC;
375 $batch = $lot ;
376 }
377 if ($product > 0 && $qtymouv != 0) {
378 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
379 $mouv->origin = $objectorder;
380 $mouv->setOrigin($objectorder->element, $objectorder->id);
381
382 // Method change if qty < 0
383 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qtymouv < 0) {
384 $result = $mouv->reception($user, $product, $entrepot, $qtymouv*(-1), $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
385 } else {
386 $result = $mouv->livraison($user, $product, $entrepot, $qtymouv, $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
387 }
388
389 if ($result < 0) {
390 setEventMessages($mouv->error, $mouv->errors, 'errors');
391 $error++;
392 }
393 }
394 }
395 */
396 }
397 }
398 }
399 }
400
401 if ($error > 0) {
402 $db->rollback();
403 setEventMessages($langs->trans("Error"), $errors, 'errors');
404 } else {
405 $db->commit();
406 setEventMessages($langs->trans("ShipmentUpdated"), null);
407
408 header("Location: ".DOL_URL_ROOT.'/expedition/dispatch.php?id='.$object->id);
409 exit;
410 }
411 } elseif ($action == 'setdate_livraison' && $usercancreate) {
412 $datedelivery = dol_mktime(GETPOSTINT('liv_hour'), GETPOSTINT('liv_min'), 0, GETPOSTINT('liv_month'), GETPOSTINT('liv_day'), GETPOSTINT('liv_year'));
413
414 $object->fetch($id);
415 $result = $object->setDeliveryDate($user, $datedelivery);
416 if ($result < 0) {
417 setEventMessages($object->error, $object->errors, 'errors');
418 }
419 }
420}
421
422
423/*
424 * View
425 */
426
427$now = dol_now();
428
429$form = new Form($db);
430$formproduct = new FormProduct($db);
431$warehouse_static = new Entrepot($db);
432
433$title = $object->ref." - ".$langs->trans('ShipmentDistribution');
434$help_url = 'EN:Module_Shipments|FR:Module_Expéditions|ES:M&oacute;dulo_Expediciones|DE:Modul_Lieferungen';
435$morejs = array('/expedition/js/lib_dispatch.js.php');
436$typeobject = null;
437
438llxHeader('', $title, $help_url, '', 0, 0, $morejs, '', '', 'mod-expedition page-card_dispatch');
439
440if ($object->id > 0 || !empty($object->ref)) {
441 $lines = $object->lines; // This is an array of detail of line, on line per source order line found intolines[]->fk_elementdet, then each line may have sub data
442 //var_dump($lines[0]->fk_elementdet); exit;
443
444 $num_prod = count($lines);
445
446 if (!empty($object->origin) && $object->origin_id > 0) {
447 $object->origin = 'commande';
448 $typeobject = $object->origin;
449 $origin = $object->origin;
450
451 $object->fetch_origin(); // Load property $object->origin_object, $object->commande, $object->propal, ...
452 }
453 $soc = new Societe($db);
454 $soc->fetch($object->socid);
455
456 $author = new User($db);
457 $author->fetch($object->user_author_id);
458
459 $head = shipping_prepare_head($object);
460
461 print dol_get_fiche_head($head, 'dispatch', $langs->trans("Shipment"), -1, $object->picto);
462
463
464 $formconfirm = '';
465
466 // Confirmation to delete line
467 if ($action == 'ask_deleteline') {
468 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
469 }
470
471 // Call Hook formConfirm
472 $parameters = array('lineid' => $lineid);
473 // Note that $action and $object may be modified by hook
474 $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action);
475 if (empty($reshook)) {
476 $formconfirm .= $hookmanager->resPrint;
477 } elseif ($reshook > 0) {
478 $formconfirm = $hookmanager->resPrint;
479 }
480
481 // Print form confirm
482 print $formconfirm;
483
484 $objectsrc = null;
485 if ($typeobject == 'commande' && $object->origin_object->id && isModEnabled('order')) {
486 $objectsrc = new Commande($db);
487 $objectsrc->fetch($object->origin_object->id);
488 }
489 if ($typeobject == 'propal' && $object->origin_object->id && isModEnabled("propal")) {
490 $objectsrc = new Propal($db);
491 $objectsrc->fetch($object->origin_object->id);
492 }
493
494 // Shipment card
495 $linkback = '<a href="'.DOL_URL_ROOT.'/expedition/list.php?restore_lastsearch_values=1'.(!empty($socid) ? '&socid='.$socid : '').'">'.$langs->trans("BackToList").'</a>';
496 $morehtmlref = '<div class="refidno">';
497
498 // Ref customer shipment
499 $morehtmlref .= $form->editfieldkey("RefCustomer", 'ref_customer', $object->ref_customer, $object, $user->hasRight('expedition', 'creer'), 'string', '', 0, 1);
500 $morehtmlref .= $form->editfieldval("RefCustomer", 'ref_customer', $object->ref_customer, $object, $user->hasRight('expedition', 'creer'), 'string'.(isset($conf->global->THIRDPARTY_REF_INPUT_SIZE) ? ':' . getDolGlobalString('THIRDPARTY_REF_INPUT_SIZE') : ''), '', null, null, '', 1);
501
502 // Thirdparty
503 $morehtmlref .= '<br>'.$object->thirdparty->getNomUrl(1);
504 // Project
505 if (isModEnabled('project')) {
506 $langs->load("projects");
507 $morehtmlref .= '<br>';
508 if (0) { // @phpstan-ignore-line Do not change on reception
509 $morehtmlref .= img_picto($langs->trans("Project"), 'project', 'class="pictofixedwidth"');
510 if ($action != 'classify' && $permissiontoadd) {
511 $morehtmlref .= '<a class="editfielda" href="'.dolBuildUrl($_SERVER['PHP_SELF'], ['action' => 'classify', 'id' => $object->id], true).'">'.img_edit($langs->transnoentitiesnoconv('SetProject')).'</a> ';
512 }
513 $morehtmlref .= $form->form_project($_SERVER['PHP_SELF'].'?id='.$object->id, (!getDolGlobalString('PROJECT_CAN_ALWAYS_LINK_TO_ALL_SUPPLIERS') ? $object->socid : -1), (string) $object->fk_project, ($action == 'classify' ? 'projectid' : 'none'), 0, 0, 0, 1, '', 'maxwidth300');
514 } else {
515 if (!empty($objectsrc) && !empty($objectsrc->fk_project)) {
516 $proj = new Project($db);
517 $proj->fetch($objectsrc->fk_project);
518 $morehtmlref .= $proj->getNomUrl(1);
519 if ($proj->title) {
520 $morehtmlref .= '<span class="opacitymedium"> - '.dol_escape_htmltag($proj->title).'</span>';
521 }
522 }
523 }
524 }
525 $morehtmlref .= '</div>';
526
527 dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
528
529
530 print '<div class="fichecenter">';
531 print '<div class="underbanner clearboth"></div>';
532
533 print '<table class="border tableforfield centpercent">';
534
535 // Linked documents
536
537 // @phpstan-ignore-next-line
538 if (($objectsrc instanceof Commande) && $object->origin_object->id && isModEnabled('order')) {
539 print '<tr><td>';
540 print $langs->trans("RefOrder").'</td>';
541 print '<td colspan="3">';
542 print $objectsrc->getNomUrl(1, 'commande');
543 print "</td>\n";
544 print '</tr>';
545 }
546 // @phpstan-ignore-next-line
547 if (($objectsrc instanceof Propal) && $object->origin_object->id && isModEnabled("propal")) {
548 print '<tr><td>';
549 print $langs->trans("RefProposal").'</td>';
550 print '<td colspan="3">';
551 print $objectsrc->getNomUrl(1, 'expedition');
552 print "</td>\n";
553 print '</tr>';
554 }
555
556 // Date creation
557 print '<tr><td class="titlefield">'.$langs->trans("DateCreation").'</td>';
558 print '<td colspan="3">'.dol_print_date($object->date_creation, "dayhour", "tzuserrel")."</td>\n";
559 print '</tr>';
560
561 // Delivery date planned
562 print '<tr><td height="10">';
563 print '<table class="nobordernopadding" width="100%"><tr><td>';
564 print $langs->trans('DateDeliveryPlanned');
565 print '</td>';
566 if ($action != 'editdate_livraison') {
567 print '<td class="right"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=editdate_livraison&token='.newToken().'&id='.$object->id.'">'.img_edit($langs->trans('SetDeliveryDate'), 1).'</a></td>';
568 }
569 print '</tr></table>';
570 print '</td><td colspan="2">';
571 if ($action == 'editdate_livraison') {
572 print '<form name="setdate_livraison" action="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'" method="post">';
573 print '<input type="hidden" name="token" value="'.newToken().'">';
574 print '<input type="hidden" name="action" value="setdate_livraison">';
575 print $form->selectDate($object->date_delivery ? $object->date_delivery : -1, 'liv_', 1, 1, 0, "setdate_livraison", 1, 0);
576 print '<input type="submit" class="button button-edit smallpaddingimp" value="'.$langs->trans('Modify').'">';
577 print '</form>';
578 } else {
579 print $object->date_delivery ? dol_print_date($object->date_delivery, 'dayhour') : '&nbsp;';
580 }
581 print '</td>';
582 print '</tr></table>';
583
584 print '<br><br><center>';
585 if (isModEnabled('barcode') || $is_mod_batch_enabled) {
586 print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=updatebyscaning&token='.currentToken().'" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'barcode', 'class="paddingrightonly"').$langs->trans("UpdateByScaning").'</a>';
587 }
588 print '<a href="#" id="resetalltoexpected" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto("", 'autofill', 'class="pictofixedwidth"').$langs->trans("RestoreWithCurrentQtySaved").'</a></td>';
589 // Link to clear qty
590 print '<a href="#" id="autoreset" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto("", 'eraser', 'class="pictofixedwidth"').$langs->trans("ClearQtys").'</a></td>';
591 print '<center>';
592
593 print '<br>';
594 $disabled = 0; // This is used to disable or not the bulk selection of target warehouse. No reason to have it disabled so forced to 0.
595
596 if ($object->status == Expedition::STATUS_DRAFT) {
597 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
598 $formproduct = new FormProduct($db);
599 $formproduct->loadWarehouses();
600 $entrepot = new Entrepot($db);
601 $listwarehouses = $entrepot->list_array(1);
602
603 $nbfreeproduct = 0; // Nb of lines of free products/services
604 $nbproduct = 0; // Nb of predefined product lines to dispatch (already done or not) if SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED is off (default)
605 // or nb of line that remain to dispatch if SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED is on.
606
607
608
609 print '<form method="post" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'">';
610
611 print '<input type="hidden" name="token" value="'.newToken().'">';
612 print '<input type="hidden" name="action" value="updatelines">';
613 print '<input type="hidden" name="id" value="'.$object->id.'">';
614
615 print '<div class="div-table-responsive-no-min">';
616 print '<table class="noborder centpercent">';
617
618 // Get list of lines of the shipment $products_dispatched, with qty dispatched for each source line
619 $products_dispatched = array();
620 $standalone_dispatched_qty_sql = '';
621 if ($isStandaloneShipment) {
622 $standalone_dispatched_qty_sql = "l.qty + COALESCE((SELECT SUM(child.qty)";
623 $standalone_dispatched_qty_sql .= " FROM ".$db->prefix()."expeditiondet as child";
624 $standalone_dispatched_qty_sql .= " WHERE child.fk_parent = l.rowid";
625 $standalone_dispatched_qty_sql .= " AND child.fk_expedition = l.fk_expedition";
626 $standalone_dispatched_qty_sql .= " AND child.fk_product = l.fk_product";
627 $standalone_dispatched_qty_sql .= "), 0)";
628
629 $sql = "SELECT l.rowid, (".$standalone_dispatched_qty_sql.") as qty";
630 $sql .= " FROM ".$db->prefix()."expeditiondet as l";
631 $sql .= " WHERE l.fk_expedition = ".((int) $object->id);
632 $sql .= " AND (l.fk_parent IS NULL OR l.fk_parent = 0)";
633 } else {
634 $sql = "SELECT ed.fk_elementdet as rowid, sum(ed.qty) as qty";
635 $sql .= " FROM ".$db->prefix()."expeditiondet as ed";
636 $sql .= " WHERE ed.fk_expedition = ".((int) $object->id);
637 $sql .= " GROUP BY ed.fk_elementdet";
638 }
639
640 $resql = $db->query($sql);
641 if ($resql) {
642 $num = $db->num_rows($resql);
643 $i = 0;
644
645 if ($num) {
646 while ($i < $num) {
647 $objd = $db->fetch_object($resql);
648 $products_dispatched[$objd->rowid] = price2num($objd->qty, 'MS');
649 $i++;
650 }
651 }
652 $db->free($resql);
653 }
654
655 if ($isStandaloneShipment || $objectsrc instanceof Commande) {
656 //$sql = "SELECT l.rowid, l.fk_product, l.subprice, l.remise_percent, l.ref AS sref, SUM(l.qty) as qty,";
657 if ($isStandaloneShipment) {
658 $sql = "SELECT l.rowid, l.fk_product, 0 as subprice, 0 as remise_percent, '' AS sref, (".$standalone_dispatched_qty_sql.") as qty,";
659 } else {
660 $sql = "SELECT l.rowid, l.fk_product, l.subprice, l.remise_percent, '' AS sref, l.qty as qty,";
661 }
662 $sql .= " p.ref, p.label, p.tobatch, p.fk_default_warehouse, p.barcode, p.stockable_product";
663 // Enable hooks to alter the SQL query (SELECT)
664 $parameters = array();
665 $reshook = $hookmanager->executeHooks(
666 'printFieldListSelect',
667 $parameters,
668 $object,
669 $action
670 );
671 if ($reshook < 0) {
672 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
673 }
674 $sql .= $hookmanager->resPrint;
675
676 if ($isStandaloneShipment) {
677 $sql .= " FROM ".$db->prefix()."expeditiondet as l";
678 $sql .= " LEFT JOIN ".$db->prefix()."product as p ON l.fk_product=p.rowid";
679 $sql .= " WHERE l.fk_expedition = ".((int) $object->id);
680 $sql .= " AND (l.fk_parent IS NULL OR l.fk_parent = 0)";
681 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
682 $sql .= " AND p.fk_product_type = 0";
683 }
684 } else {
685 $sql .= " FROM ".$db->prefix()."commandedet as l";
686 $sql .= " LEFT JOIN ".$db->prefix()."product as p ON l.fk_product=p.rowid";
687 $sql .= " WHERE l.fk_commande = ".((int) $objectsrc->id);
688 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
689 $sql .= " AND l.product_type = 0";
690 }
691 }
692 // Enable hooks to alter the SQL query (WHERE)
693 $parameters = array();
694 $reshook = $hookmanager->executeHooks(
695 'printFieldListWhere',
696 $parameters,
697 $object,
698 $action
699 );
700 if ($reshook < 0) {
701 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
702 }
703 $sql .= $hookmanager->resPrint;
704
705 //$sql .= " GROUP BY p.ref, p.label, p.tobatch, p.fk_default_warehouse, l.rowid, l.fk_product, l.subprice, l.remise_percent, l.ref"; // Calculation of amount dispatched is done per fk_product so we must group by fk_product
706 $sql .= " ORDER BY l.rang, p.ref, p.label";
707
708 $resql = $db->query($sql);
709 if ($resql) {
710 $num = $db->num_rows($resql);
711 $i = 0;
712 $numline = 1;
713
714 if ($num) {
715 print '<tr class="liste_titre">';
716
717 print '<td>'.$langs->trans("Description").'</td>';
718 if ($is_mod_batch_enabled) {
719 print '<td class="dispatch_batch_number_title">'.$langs->trans("batch_number").'</td>';
720 if ($is_sell_by_enabled) {
721 print '<td class="dispatch_dlc_title">'.$langs->trans("SellByDate").'</td>';
722 }
723 if ($is_eat_by_enabled) {
724 print '<td class="dispatch_dluo_title">'.$langs->trans("EatByDate").'</td>';
725 }
726 } else {
727 print '<td></td>';
728 print '<td></td>';
729 print '<td></td>';
730 }
731 print '<td class="right">'.$langs->trans("QtyOrdered").'</td>';
732 if ($object->status == Expedition::STATUS_DRAFT) {
733 print '<td class="right">'.$langs->trans("QtyToShip"); // Qty to dispatch (sum for all lines of batch detail if there is)
734 } else {
735 print '<td class="right">'.$langs->trans("QtyDispatchedShort").'</td>';
736 }
737 print '<td class="right">'.$langs->trans("Details");
738 print '<td width="32"></td>';
739
740 print '<td class="right">'.$langs->trans("Warehouse");
741
742 // Select warehouse to force it everywhere
743 if (count($listwarehouses) > 1) {
744 print '<br><span class="opacitymedium">'.$langs->trans("ForceTo").'</span> '.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 1, 0, 0, '', 0, 0, $disabled, '', 'minwidth100 maxwidth300', 1);
745 } elseif (count($listwarehouses) == 1) {
746 print '<br><span class="opacitymedium">'.$langs->trans("ForceTo").'</span> '.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0, '', 0, 0, $disabled, '', 'minwidth100 maxwidth300', 1);
747 }
748
749 print '</td>';
750
751 // Enable hooks to append additional columns
752 $parameters = array();
753 $reshook = $hookmanager->executeHooks(
754 'printFieldListTitle',
755 $parameters,
756 $object,
757 $action
758 );
759 if ($reshook < 0) {
760 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
761 }
762 print $hookmanager->resPrint;
763
764 print "</tr>\n";
765 }
766
767 $conf->cache['product'] = array();
768
769 // Loop on each line of origin order
770 while ($i < $num) {
771 $objp = $db->fetch_object($resql);
772
774 if (!$objp->fk_product > 0) {
775 $nbfreeproduct++;
776 } else {
777 $alreadydispatched = isset($products_dispatched[$objp->rowid]) ? $products_dispatched[$objp->rowid] : 0;
778 $remaintodispatch = price2num($objp->qty, 5); // Calculation of dispatched
779 if ($remaintodispatch < 0 && !getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN')) {
780 $remaintodispatch = 0;
781 }
782
783 if ($remaintodispatch || !getDolGlobalString('SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED')) {
784 $nbproduct++;
785
786 // To show detail cref and description value, we must make calculation by cref
787 // print ($objp->cref?' ('.$objp->cref.')':'');
788 // if ($objp->description) print '<br>'.nl2br($objp->description);
789 $suffix = '_0_'.$i;
790
791 print "\n";
792 print '<!-- Line to dispatch '.$suffix.' -->'."\n";
793 // hidden fields for js function
794 print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$objp->qty.'">';
795 print '<input id="qty_dispatched'.$suffix.'" type="hidden" data-dispatched="'.((float) $alreadydispatched).'" value="'.(float) $alreadydispatched.'">';
796 print '<tr class="oddeven">';
797
798 if (empty($conf->cache['product'][$objp->fk_product])) {
799 $tmpproduct = new Product($db);
800 $tmpproduct->fetch($objp->fk_product);
801 $conf->cache['product'][$objp->fk_product] = $tmpproduct;
802 } else {
803 $tmpproduct = $conf->cache['product'][$objp->fk_product];
804 }
805
806 $linktoprod = '<div class="twolinesmax lineheightsmall">';
807 $linktoprod .= $tmpproduct->getNomUrl(1);
808 $linktoprod .= '<br><span class="opacitymedium small">'.dolPrintHTML($objp->label)."</span>\n";
809 $linktoprod .= '</div>';
810
811 if ($is_mod_batch_enabled) {
812 if ($objp->tobatch) {
813 // Product
814 print '<td class="tdoverflowbydiv nopaddingtopimp nopaddingbottomimp" id="product_'.$i.'" data-idproduct="'.$objp->fk_product.'" data-barcode="'.$objp->barcode.'">';
815 print $linktoprod;
816 print "</td>";
817 print '<td class="dispatch_batch_number"></td>';
818 if ($is_sell_by_enabled) {
819 print '<td class="dispatch_dlc"></td>';
820 }
821 if ($is_eat_by_enabled) {
822 print '<td class="dispatch_dluo"></td>';
823 }
824 } else {
825 // Product
826 print '<td id="product_'.$i.'" data-idproduct="'.$objp->fk_product.'" data-barcode="'.$objp->barcode.'">';
827 print $linktoprod;
828 print "</td>";
829 print '<td class="dispatch_batch_number">';
830 print '<span class="opacitymedium small">'.$langs->trans("ProductDoesNotUseBatchSerial").'</span>';
831 print '</td>';
832 if ($is_sell_by_enabled) {
833 print '<td class="dispatch_dlc"></td>';
834 }
835 if ($is_eat_by_enabled) {
836 print '<td class="dispatch_dluo"></td>';
837 }
838 }
839 } else {
840 print '<td colspan="4">';
841 print $linktoprod;
842 print "</td>";
843 }
844
845 // Define unit price for PMP calculation
846 $up_ht_disc = $objp->subprice;
847 if (!empty($objp->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
848 $up_ht_disc = price2num($up_ht_disc * (100 - $objp->remise_percent) / 100, 'MU');
849 }
850
851 // Qty ordered
852 print '<td class="right">'.$objp->qty.'</td>';
853
854 // Already dispatched
855 print '<td class="right">'.$alreadydispatched.'</td>';
856
857 print '<td class="right">';
858 print '</td>'; // Qty to dispatch
859 print '<td>';
860 print '</td>'; // Dispatch column
861 print '<td></td>'; // Warehouse column
862
863 $sql = "SELECT ed.rowid, ed.fk_parent";
864 if ($isStandaloneShipment) {
865 $sql .= ", ed.fk_product";
866 } else {
867 $sql .= ", cd.fk_product";
868 }
869 $sql .= ", ".$db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty')." as qty";
870 $sql .= ", ".$db->ifsql('eb.rowid IS NULL OR eb.fk_warehouse IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse')." as fk_warehouse";
871 $sql .= ", eb.batch, eb.eatby, eb.sellby";
872 $sql .= " FROM ".$db->prefix()."expeditiondet as ed";
873 $sql .= " LEFT JOIN ".$db->prefix()."expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet";
874 if ($isStandaloneShipment) {
875 $sql .= " WHERE ed.fk_expedition = ".(int) $object->id;
876 $sql .= " AND (ed.rowid = ".(int) $objp->rowid." OR (ed.fk_parent = ".(int) $objp->rowid." AND ed.fk_product = ".(int) $objp->fk_product."))";
877 $sql .= " ORDER BY CASE WHEN ed.fk_parent IS NULL OR ed.fk_parent = 0 THEN 0 ELSE 1 END, ed.rowid";
878 } else {
879 $sql .= " INNER JOIN ".$db->prefix()."commandedet as cd on ed.fk_elementdet = cd.rowid";
880 $sql .= " WHERE ed.fk_elementdet = ".(int) $objp->rowid;
881 $sql .= " AND ed.fk_expedition = ".(int) $object->id;
882 $sql .= " ORDER BY ed.rowid, ed.fk_elementdet";
883 }
884
885 $resultsql = $db->query($sql);
886 $j = 0;
887 if ($resultsql) {
888 $numd = $db->num_rows($resultsql);
889 while ($obj_exp = $db->fetch_object($resultsql)) {
890 $suffix = "_" . $j . "_" . $i;
891
892 $productChildrenNb = 0;
893 $expedition_line_child_list = array();
894 if (!$isStandaloneShipment && getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
895 // virtual product : find all children
896 $productChildrenNb = $tmpproduct->hasFatherOrChild(1);
897 if ($productChildrenNb > 0) {
898 $line_id_list = array();
899
900 // load all child as object line
901 $expeditionLine = new ExpeditionLigne($db);
902 $result = $expeditionLine->findAllChild($obj_exp->rowid, $line_id_list, 1);
903 if ($result > 0) {
904 $child_level = 1;
905 foreach ($line_id_list as $line_id_arr) {
906 foreach ($line_id_arr as $line_obj) {
907 $child_product_id = (int) $line_obj->fk_product;
908 if (empty($conf->cache['product'][$child_product_id])) {
909 $child_product = new Product($db);
910 $child_product->fetch($child_product_id);
911 $conf->cache['product'][$child_product_id] = $child_product;
912 } else {
913 $child_product = $conf->cache['product'][$child_product_id];
914 }
915
916 // sub-product is a batch and get selected batch from database or all batches for selected warehouse
917 $batch_list = array();
918 if ($is_mod_batch_enabled && $child_product->hasbatch()) {
919 // search if batch is not exist in shipment lines
920 $sql_line_batch_search = "SELECT eb.rowid, eb.qty, eb.batch, eb.sellby, eb.eatby";
921 $sql_line_batch_search .= " FROM ".$db->prefix()."expeditiondet_batch as eb";
922 $sql_line_batch_search .= " WHERE eb.fk_expeditiondet = ".((int) $line_obj->rowid);
923 $res_line_batch_search = $db->query($sql_line_batch_search);
924 if ($res_line_batch_search) {
925 while ($obj_batch = $db->fetch_object($res_line_batch_search)) {
926 // set the selected bath by default
927 if ($obj_batch->batch != '') {
928 $line_obj->batch = $obj_batch->batch;
929 }
930 $obj_batch->eatby = dol_print_date($obj_batch->eatby, 'day');
931 $obj_batch->sellby = dol_print_date($obj_batch->sellby, 'day');
932 $batch_list[] = $obj_batch;
933 }
934 $db->free($res_line_batch_search);
935 }
936
937 // no batch found for this sub-product so retrieve all batch numbers for this sub-product id and warehouse id
938 if (empty($batch_list)) {
939 $batch_sort_field_arr = array();
940 $batch_sort_order_arr = array();
941 if ($is_sell_by_enabled) {
942 $batch_sort_field_arr[] = 'pl.sellby'; // order by sell by (DLC)
943 $batch_sort_order_arr[] = 'ASC';
944 }
945 if ($is_eat_by_enabled) {
946 $batch_sort_field_arr[] = 'pl.eatby'; // order by eat by (DLUO)
947 $batch_sort_order_arr[] = 'ASC';
948 }
949 $batch_sort_field_arr[] = 'pb.qty'; // order by qty
950 $batch_sort_order_arr[] = 'ASC';
951 $batch_sort_field_arr[] = 'pl.rowid'; // order by rowid
952 $batch_sort_order_arr[] = 'ASC';
953 $product_batch = new Productbatch($db);
954 $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));
955 if (is_array($product_batch_result)) {
956 foreach ($product_batch_result as $batch_current) {
957 $batch_current->eatby = dol_print_date($batch_current->eatby, 'day');
958 $batch_current->sellby = dol_print_date($batch_current->sellby, 'day');
959 $batch_list[] = $batch_current;
960 }
961 }
962 }
963 }
964 $line_obj->batch_list = $batch_list;
965
966 // determine if line is virtual product and stock is managed
967 $line_obj->iskit = 0;
968 if ($child_product->stockable_product == Product::ENABLED_STOCK) {
969 $can_manage_stock = 1;
970 } else {
971 $can_manage_stock = 0; // the value of "incdec" can't be modified
972 }
973 $line_obj->incdec = $can_manage_stock; // set value by default before this request
974 $sql_child = "SELECT ";
975 $sql_child .= " SUM(".$db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
976 $sql_child .= ", ".$db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
977 $sql_child .= " FROM ".$db->prefix()."expeditiondet as ed";
978 $sql_child .= " LEFT JOIN ".$db->prefix()."expeditiondet as edp ON edp.rowid = ".((int) $line_obj->fk_parent);
979 $sql_child .= " LEFT JOIN ".$db->prefix()."product_association as pa ON pa.fk_product_pere = ".((int) $child_product_id);
980 $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);
981 $sql_child .= " WHERE ed.rowid = ".((int) $line_obj->rowid);
982 $sql_child .= " GROUP BY pa.rowid, pai.incdec";
983 $resql_child = $db->query($sql_child);
984 if ($resql_child) {
985 if ($child_obj = $db->fetch_object($resql_child)) {
986 if (!getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
987 $line_obj->iskit = (int) $child_obj->iskit;
988 }
989 if ($can_manage_stock) {
990 $line_obj->incdec = (int) $child_obj->incdec; // reset value to 0 or 1 if stock can be managed
991 }
992 }
993 $db->free($resql_child);
994 }
995 $line_obj->html_label = str_repeat("&nbsp;&nbsp;&nbsp;&nbsp;", $child_level) . "&rarr;" . $child_product->getNomUrl(1);
996 $expedition_line_child_list[] = $line_obj;
997 }
998 $child_level++;
999 }
1000 }
1001 }
1002 }
1003 if (empty($expedition_line_child_list)) {
1004 $obj_exp->iskit = 0; // is not virtual product
1005 // manage stock if enabled for product
1006 if ($objp->stockable_product == Product::ENABLED_STOCK) {
1007 $obj_exp->incdec = 1;
1008 } else {
1009 $obj_exp->incdec = 0;
1010 }
1011 $expedition_line_child_list[] = $obj_exp;
1012 }
1013
1014 $child_suffix = $suffix;
1015 foreach ($expedition_line_child_list as $objd) {
1016 $child_line_id = $isStandaloneShipment ? $objp->rowid : $objd->rowid;
1017
1018 $can_update_stock = empty($objd->iskit) && !empty($objd->incdec);
1019 $suffix = $child_line_id.$child_suffix;
1020
1021 // set default batch values for this dispatched line (lot/serial number of virtual product)
1022 $dispatch_line_batch_current = null;
1023 if (!empty($objd->batch_list)) {
1024 $dispatch_line_batch_count = count($objd->batch_list);
1025 // if only one batch found, this batch is preselected
1026 if ($dispatch_line_batch_count == 1) {
1027 $dispatch_line_batch_current = current($objd->batch_list);
1028 }
1029 }
1030 if (is_object($dispatch_line_batch_current)) {
1031 $objd->batch = $dispatch_line_batch_current->batch;
1032 $objd->eatby = $dispatch_line_batch_current->eatby;
1033 $objd->sellby = $dispatch_line_batch_current->sellby;
1034 }
1035
1036 if ($is_mod_batch_enabled
1037 && (
1038 !empty($objd->batch)
1039 || (is_null($objd->batch) && $tmpproduct->status_batch > 0)
1040 || !empty($objd->batch_list)
1041 )
1042 ) {
1043 $type = 'batch';
1044
1045 // Enable hooks to append additional columns
1046 $parameters = array(
1047 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1048 'is_information_row' => true,
1049 'j' => $j,
1050 'suffix' => $suffix,
1051 'objd' => $objd,
1052 );
1053 $reshook = $hookmanager->executeHooks(
1054 'printFieldListValue',
1055 $parameters,
1056 $object,
1057 $action
1058 );
1059 if ($reshook < 0) {
1060 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1061 }
1062 print $hookmanager->resPrint;
1063
1064 print '</tr>';
1065
1066 print '<!-- line for batch ' . $numline . ' -->';
1067 print '<tr class="oddeven autoresettr" name="' . $type . '-' . $suffix . '" data-remove="clear">';
1068 print '<td>';
1069 print '<input id="fk_commandedet' . $suffix . '" name="fk_commandedet' . $suffix . '" type="hidden" value="' . $objp->rowid . '">';
1070 print '<input id="idline' . $suffix . '" name="idline' . $suffix . '" type="hidden" value="' . $objd->rowid . '">';
1071 print '<input id="fk_parent' . $suffix . '" name="fk_parent' . $suffix . '" type="hidden" value="' . $objd->fk_parent . '">';
1072 print '<input name="productbatch' . $suffix . '" type="hidden" value="' . $objd->fk_product . '">';
1073
1074 print '<!-- This is a U.P. (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1075 print '<input class="maxwidth75" name="pu' . $suffix . '" type="hidden" value="' . price2num($up_ht_disc, 'MU') . '">';
1076 if (!empty($objd->html_label)) {
1077 print $objd->html_label;
1078 }
1079 print '</td>';
1080
1081 print '<td>';
1082 print '<input type="text" class="inputlotnumber quatrevingtquinzepercent csslotnumber" name="lot_number'.$suffix.'" value="'.(GETPOSTISSET('lot_number'.$suffix) ? GETPOST('lot_number'.$suffix) : $objd->batch).'" list="lot_number'.$suffix.'">';
1083 print $formproduct->selectLotDataList('lot_number'.$suffix, 0, $objd->fk_product, GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, array());
1084 print '</td>';
1085
1086 if ($is_sell_by_enabled) {
1087 print '<td class="nowraponall">';
1088 $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'));
1089 print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, '');
1090 print '</td>';
1091 }
1092 if ($is_eat_by_enabled) {
1093 print '<td class="nowraponall">';
1094 $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'));
1095 print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, '');
1096 print '</td>';
1097 }
1098 print '<td colspan="2">&nbsp;</td>'; // Supplier ref + Qty ordered + qty already dispatched
1099 } else {
1100 $type = 'dispatch';
1101 $colspan = 6;
1102 $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
1103 $colspan = $is_eat_by_enabled ? $colspan : --$colspan;
1104
1105 // Enable hooks to append additional columns
1106 $parameters = array(
1107 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1108 'is_information_row' => true,
1109 'j' => $j,
1110 'suffix' => $suffix,
1111 'objd' => $objd,
1112 );
1113 $reshook = $hookmanager->executeHooks(
1114 'printFieldListValue',
1115 $parameters,
1116 $object,
1117 $action
1118 );
1119 if ($reshook < 0) {
1120 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1121 }
1122 print $hookmanager->resPrint;
1123
1124 print '</tr>';
1125
1126 print '<!-- line no batch '.$numline.' -->';
1127 print '<tr class="oddeven autoresettr" name="'.$type.'-'.$suffix.'" data-remove="clear">';
1128 print '<td colspan="'.$colspan.'">';
1129 print '<input id="fk_commandedet'.$suffix.'" name="fk_commandedet'.$suffix.'" type="hidden" value="'.$objp->rowid.'">';
1130 print '<input id="idline'.$suffix.'" name="idline'.$suffix.'" type="hidden" value="'.$objd->rowid.'">';
1131 print '<input id="fk_parent'.$suffix.'" name="fk_parent'.$suffix.'" type="hidden" value="'.$objd->fk_parent.'">';
1132 print '<input name="product'.$suffix.'" type="hidden" value="'.$objd->fk_product.'">';
1133 print '<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1134 print '<input class="maxwidth75" name="pu'.$suffix.'" type="hidden" value="'.price2num($up_ht_disc, 'MU').'">';
1135 if (!empty($objd->html_label)) {
1136 print $objd->html_label;
1137 }
1138 print '</td>';
1139 }
1140 // Qty to dispatch
1141 print '<td class="right nowraponall">';
1142 $suggestedvalue = (GETPOSTISSET('qty'.$suffix) ? GETPOSTFLOAT('qty'.$suffix) : $objd->qty);
1143 //var_dump($suggestedvalue);exit;
1144 if ($can_update_stock) {
1145 print '<a href="" id="reset'.$suffix.'" class="resetline">'.img_picto($langs->trans("Reset"), 'eraser', 'class="pictofixedwidth opacitymedium"').'</a>';
1146 print '<input id="qty'.$suffix.'" onchange="onChangeDispatchLineQty($(this))" name="qty'.$suffix.'" data-type="'.$type.'" data-index="'.$i.'" class="width50 right qtydispatchinput" value="'.$suggestedvalue.'" data-expected="'.$objd->qty.'">';
1147 } else {
1148 print '<input type="hidden" id="qty'.$suffix.'" name="qty'.$suffix.'" value="">';
1149 }
1150 print '</td>';
1151 print '<td>';
1152 if ($can_update_stock) {
1153 print img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'-'.$child_line_id.'\')"');
1154 }
1155 print '</td>';
1156
1157 // Warehouse
1158 print '<td class="right">';
1159 if ($can_update_stock) {
1160 if (count($listwarehouses) > 1) {
1161 print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 1, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1162 } elseif (count($listwarehouses) == 1) {
1163 print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_warehouse, "entrepot".$suffix, '', 0, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1164 } else {
1165 $langs->load("errors");
1166 print $langs->trans("ErrorNoWarehouseDefined");
1167 }
1168 } else {
1169 // we force the warehouse so we will pass the test to add a line into expedition.class.php
1170 print '<input id="entrepot'.$suffix.'" name="entrepot'.$suffix.'" type="hidden" value="'.$objd->fk_warehouse.'">';
1171 print img_warning().' '.$langs->trans('StockDisabled');
1172 }
1173 print "</td>\n";
1174
1175 // Enable hooks to append additional columns
1176 $parameters = array(
1177 'is_information_row' => false, // this is a dispatch form row
1178 'i' => $i,
1179 'suffix' => $suffix,
1180 'objp' => $objp,
1181 'objd' => $objd,
1182 );
1183 $reshook = $hookmanager->executeHooks(
1184 'printFieldListValue',
1185 $parameters,
1186 $object,
1187 $action
1188 );
1189 if ($reshook < 0) {
1190 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1191 }
1192 print $hookmanager->resPrint;
1193
1194 print "</tr>\n";
1195 }
1196
1197 $j++;
1198 $numline++;
1199 }
1200
1201 //$suffix = "_".$j."_".$i;
1202 } else {
1203 $errorMsg = 'Shipment dispatch SQL error : '.$db->lasterror();
1204 setEventMessage($errorMsg, 'errors');
1205 dol_syslog($errorMsg, LOG_ERR);
1206 }
1207
1208 /*
1209 if ($j == 0) {
1210 if ($is_mod_batch_enabled && !empty($objp->tobatch)) {
1211 $type = 'batch';
1212
1213 // Enable hooks to append additional columns
1214 $parameters = array(
1215 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1216 'is_information_row' => true,
1217 'j' => $j,
1218 'suffix' => $suffix,
1219 'objp' => $objp,
1220 );
1221 $reshook = $hookmanager->executeHooks(
1222 'printFieldListValue',
1223 $parameters,
1224 $object,
1225 $action
1226 );
1227 if ($reshook < 0) {
1228 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1229 }
1230 print $hookmanager->resPrint;
1231
1232 print '</tr>';
1233
1234 print '<!-- line for batch '.$numline.' (not dispatched line yet for this order line) -->';
1235 print '<tr class="oddeven autoresettr" name="'.$type.$suffix.'" data-remove="clear">';
1236 print '<td>';
1237 print '<input id="fk_commandedet'.$suffix.'" name="fk_commandedet'.$suffix.'" type="hidden" value="'.$objp->rowid.'">';
1238 print '<input id="idline'.$suffix.'" name="idline'.$suffix.'" type="hidden" value="-1">';
1239 print '<input name="productbatch'.$suffix.'" type="hidden" value="'.$objp->fk_product.'">';
1240
1241 print '<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1242 print '<input class="maxwidth75" name="pu'.$suffix.'" type="hidden" value="'.price2num($up_ht_disc, 'MU').'">';
1243 print '</td>';
1244
1245 print '<td>';
1246 print '<input type="text" class="inputlotnumber quatrevingtquinzepercent" id="lot_number'.$suffix.'" name="lot_number'.$suffix.'" value="'.GETPOST('lot_number'.$suffix).'">';
1247 print '</td>';
1248 if ($is_sell_by_enabled) {
1249 print '<td class="nowraponall">';
1250 $dlcdatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dlc'.$suffix.'month'), GETPOSTINT('dlc'.$suffix.'day'), GETPOSTINT('dlc'.$suffix.'year'));
1251 print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, '');
1252 print '</td>';
1253 }
1254 if ($is_eat_by_enabled) {
1255 print '<td class="nowraponall">';
1256 $dluodatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dluo'.$suffix.'month'), GETPOSTINT('dluo'.$suffix.'day'), GETPOSTINT('dluo'.$suffix.'year'));
1257 print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, '');
1258 print '</td>';
1259 }
1260 print '<td colspan="2">&nbsp;</td>'; // Supplier ref + Qty ordered + qty already dispatched
1261 } else {
1262 $type = 'dispatch';
1263 $colspan = 6;
1264 $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
1265 $colspan = $is_eat_by_enabled ? $colspan : --$colspan;
1266
1267 // Enable hooks to append additional columns
1268 $parameters = array(
1269 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1270 'is_information_row' => true,
1271 'j' => $j,
1272 'suffix' => $suffix,
1273 'objp' => $objp,
1274 );
1275 $reshook = $hookmanager->executeHooks(
1276 'printFieldListValue',
1277 $parameters,
1278 $object,
1279 $action
1280 );
1281 if ($reshook < 0) {
1282 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1283 }
1284 print $hookmanager->resPrint;
1285
1286 print '</tr>';
1287
1288 print '<!-- line no batch '.$numline.' (not dispatched line yet for this order line) -->';
1289 print '<tr class="oddeven autoresettr" name="'.$type.$suffix.'" data-remove="clear">';
1290 print '<td colspan="'.$colspan.'">';
1291 print '<input id="fk_commandedet'.$suffix.'" name="fk_commandedet'.$suffix.'" type="hidden" value="'.$objp->rowid.'">';
1292 print '<input id="idline'.$suffix.'" name="idline'.$suffix.'" type="hidden" value="-1">';
1293 print '<input name="product'.$suffix.'" type="hidden" value="'.$objp->fk_product.'">';
1294
1295 print '<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1296 print '<input class="maxwidth75" name="pu'.$suffix.'" type="hidden" value="'.price2num($up_ht_disc, 'MU').'">';
1297 print '</td>';
1298 }
1299 // Qty to dispatch
1300 print '<td class="right">';
1301 print '<a href="" id="reset'.$suffix.'" class="resetline">'.img_picto($langs->trans("Reset"), 'eraser', 'class="pictofixedwidth opacitymedium"').'</a>';
1302 $amounttosuggest = (GETPOSTISSET('qty'.$suffix) ? GETPOSTINT('qty'.$suffix) : (!getDolGlobalString('SUPPLIER_ORDER_DISPATCH_FORCE_QTY_INPUT_TO_ZERO') ? $remaintodispatch : 0));
1303 if (count($products_dispatched)) {
1304 // There is already existing lines into llx_expeditiondet, this means a plan for the shipment has already been started.
1305 // In such a case, we do not suggest new values, we suggest the value known.
1306 $amounttosuggest = (GETPOSTISSET('qty'.$suffix) ? GETPOSTINT('qty'.$suffix) : (isset($products_dispatched[$objp->rowid]) ? $products_dispatched[$objp->rowid] : ''));
1307 }
1308 print '<input id="qty'.$suffix.'" onchange="onChangeDispatchLineQty($(this))" name="qty'.$suffix.'" data-index="'.$i.'" data-type="text" class="width50 right qtydispatchinput" value="'.$amounttosuggest.'" data-expected="'.$amounttosuggest.'">';
1309 print '</td>';
1310 print '<td>';
1311 if ($is_mod_batch_enabled && $objp->tobatch > 0) {
1312 $type = 'batch';
1313 print img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"');
1314 } else {
1315 $type = 'dispatch';
1316 print img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"');
1317 }
1318
1319 print '</td>';
1320
1321 // Warehouse
1322 print '<td class="right">';
1323 if (count($listwarehouses) > 1) {
1324 print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : ($objp->fk_default_warehouse ? $objp->fk_default_warehouse : ''), "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1325 } elseif (count($listwarehouses) == 1) {
1326 print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : ($objp->fk_default_warehouse ? $objp->fk_default_warehouse : ''), "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1327 } else {
1328 $langs->load("errors");
1329 print $langs->trans("ErrorNoWarehouseDefined");
1330 }
1331 print "</td>\n";
1332
1333 // Enable hooks to append additional columns
1334 $parameters = array(
1335 'is_information_row' => false, // this is a dispatch form row
1336 'i' => $i,
1337 'suffix' => $suffix,
1338 'objp' => $objp,
1339 );
1340 $reshook = $hookmanager->executeHooks(
1341 'printFieldListValue',
1342 $parameters,
1343 $object,
1344 $action
1345 );
1346 if ($reshook < 0) {
1347 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1348 }
1349 print $hookmanager->resPrint;
1350 print "</tr>\n";
1351 }
1352 */
1353 }
1354 }
1355 $i++;
1356 }
1357
1358 // reload batch select and warehouse select on change (Ajax)
1359 $out_js_line_list = array();
1360 $out_js_line = 'function updateselectbatchbywarehouse() {';
1361 $out_js_line .= ' jQuery(document).on("change", "select[name*=\"entrepot\"]", function() {';
1362 $out_js_line .= ' var selectwarehouse = jQuery(this);';
1363 $out_js_line .= ' var selectbatch_name = selectwarehouse.attr("name").replace("entrepot", "lot_number");';
1364 $out_js_line .= ' var selectbatch = jQuery("datalist[id*=\""+selectbatch_name+"\"]");';
1365 $out_js_line .= ' var selectedbatch = selectbatch.val();';
1366 $out_js_line .= ' var product_element_name = selectwarehouse.attr("name").replace("entrepot", "productbatch");';
1367 $out_js_line .= ' jQuery.ajax({';
1368 $out_js_line .= ' type: "POST",';
1369 $out_js_line .= ' url: \''.dol_escape_js(dol_buildpath('/expedition/ajax/interface.php', 1)).'\',';
1370 $out_js_line .= ' data: {';
1371 $out_js_line .= ' action: "updateselectbatchbywarehouse",';
1372 $out_js_line .= ' warehouse_id: jQuery(this).val(),';
1373 $out_js_line .= ' token: "'.currentToken().'",';
1374 $out_js_line .= ' product_id: jQuery("input[name=\""+product_element_name+"\"]").val()';
1375 $out_js_line .= ' }';
1376 $out_js_line .= ' }).done(function(data) {';
1377 $out_js_line .= ' selectbatch.empty();';
1378 $out_js_line .= ' if (typeof data == "object") {';
1379 $out_js_line .= ' console.log("data is already type object, no need to parse it");';
1380 $out_js_line .= ' } else {';
1381 $out_js_line .= ' console.log("data is type "+(typeof data));';
1382 $out_js_line .= ' data = JSON.parse(data);';
1383 $out_js_line .= ' }';
1384 $out_js_line .= ' selectbatch.append(jQuery("<option>", {';
1385 $out_js_line .= ' value: "",';
1386 $out_js_line .= ' }));';
1387 $out_js_line .= ' jQuery.each(data, function(key, objBatch) {';
1388 $out_js_line .= ' var dataEatByDate = objBatch.eatbydate;';
1389 $out_js_line .= ' var dataSellByDate = objBatch.sellbydate;';
1390 $out_js_line .= ' var optionLabel = key+" (";';
1391 $out_js_line .= ' if (selectwarehouse.val() == -1) {';
1392 $out_js_line .= ' optionLabel += \''.dol_escape_js($langs->trans('TotalStock')).': \'+objBatch.qty;';
1393 $out_js_line .= ' } else {';
1394 $out_js_line .= ' optionLabel += \''.dol_escape_js($langs->trans('Stock')).': \'+objBatch.qty;';
1395 $out_js_line .= ' }';
1396 $out_js_line .= ' if (dataEatByDate != "") {';
1397 $out_js_line .= ' optionLabel += \' - '.dol_escape_js($langs->trans('EatByDate')).': \'+dataEatByDate;';
1398 $out_js_line .= ' }';
1399 $out_js_line .= ' if (dataSellByDate != "") {';
1400 $out_js_line .= ' optionLabel += \' - '.dol_escape_js($langs->trans('SellByDate')).': \'+dataSellByDate;';
1401 $out_js_line .= ' }';
1402 $out_js_line .= ' optionLabel += ")";';
1403 $out_js_line .= ' var option = "<option data-eatbydate=\""+dataEatByDate+"\" data-sellbydate=\""+dataSellByDate+"\" value=\""+key+"\"";';
1404 $out_js_line .= ' if (key === selectedbatch) {';
1405 $out_js_line .= ' option += " selected";';
1406 $out_js_line .= ' }';
1407 $out_js_line .= ' option += ">"+optionLabel+"</option>";';
1408 $out_js_line .= ' selectbatch.append(option);';
1409 $out_js_line .= ' });';
1410 $out_js_line .= ' });';
1411 $out_js_line .= ' });';
1412 $out_js_line .= '}';
1413
1414 $out_js_line .= 'function updateselectwarehousebybatch() {';
1415 $out_js_line .= ' jQuery(document).on("change", "input[name*=lot_number]", function() {';
1416 $out_js_line .= ' var selectbatch = jQuery(this);';
1417 $out_js_line .= ' var selectwarehouse_name = selectbatch.attr("name").replace("lot_number", "entrepot");';
1418 $out_js_line .= ' var selectwarehouse = jQuery("select[name*=\""+selectwarehouse_name+"\"]");';
1419 $out_js_line .= ' var selectedwarehouse = selectwarehouse.val();';
1420 $out_js_line .= ' var inputbatchdlc_name = selectbatch.attr("name").replace("lot_number", "dlc");';
1421 $out_js_line .= ' var inputbatchdlc = jQuery("input[name*=\""+inputbatchdlc_name+"\"]");';
1422 $out_js_line .= ' var inputbatchdluo_name = selectbatch.attr("name").replace("lot_number", "dluo");';
1423 $out_js_line .= ' var inputbatchdluo = jQuery("input[name*=\""+inputbatchdluo_name+"\"]");';
1424 $out_js_line .= ' var datalistselectedbatch = jQuery("#"+selectbatch.attr("name")+" option[value=\""+selectbatch.val()+"\"]");';
1425 $out_js_line .= ' var selectedbatch_dlc = datalistselectedbatch.data("sellbydate");';
1426 $out_js_line .= ' var selectedbatch_dluo = datalistselectedbatch.data("eatbydate");';
1427 $out_js_line .= ' if (typeof selectedbatch_dlc === "undefined") {';
1428 $out_js_line .= ' selectedbatch_dlc = "";';
1429 $out_js_line .= ' }';
1430 $out_js_line .= ' if (typeof selectedbatch_dluo === "undefined") {';
1431 $out_js_line .= ' selectedbatch_dluo = "";';
1432 $out_js_line .= ' }';
1433 $out_js_line .= ' inputbatchdlc.val(selectedbatch_dlc).trigger("change");';
1434 $out_js_line .= ' inputbatchdluo.val(selectedbatch_dluo).trigger("change");';
1435 $out_js_line .= ' if (selectedwarehouse != -1) {';
1436 $out_js_line .= ' return;';
1437 $out_js_line .= ' }';
1438 $out_js_line .= ' var product_element_name = selectbatch.attr("name").replace("lot_number", "productbatch");';
1439 $out_js_line .= ' jQuery.ajax({';
1440 $out_js_line .= ' type: "POST",';
1441 $out_js_line .= ' url: \''.dol_escape_js(dol_buildpath('/expedition/ajax/interface.php', 1)).'\',';
1442 $out_js_line .= ' data: {';
1443 $out_js_line .= ' action: "updateselectwarehousebybatch",';
1444 $out_js_line .= ' batch: jQuery(this).val(),';
1445 $out_js_line .= ' token: "'.currentToken().'",';
1446 $out_js_line .= ' product_id: jQuery("input[name=\""+product_element_name+"\"]").val()';
1447 $out_js_line .= ' }';
1448 $out_js_line .= ' }).done(function(data) {';
1449 $out_js_line .= ' if (typeof data == "object") {';
1450 $out_js_line .= ' console.log("data is already type object, no need to parse it");';
1451 $out_js_line .= ' } else {';
1452 $out_js_line .= ' console.log("data is type "+(typeof data));';
1453 $out_js_line .= ' data = JSON.parse(data);';
1454 $out_js_line .= ' }';
1455 $out_js_line .= ' if (data != 0) {';
1456 $out_js_line .= ' selectwarehouse.val(data).change();';
1457 $out_js_line .= ' }';
1458 $out_js_line .= ' });';
1459 $out_js_line .= ' });';
1460 $out_js_line .= '}';
1461 $out_js_line_list[] = $out_js_line;
1462
1463 $out_js = '<script type="text/javascript">';
1464 $out_js .= 'jQuery(document).ready(function() {';
1465 // when a warehouse is selected, only the lot/serial numbers that are available in it are offered
1466 $out_js .= 'updateselectbatchbywarehouse();';
1467 // when a lot/serial number is selected and it is only available in one warehouse, the warehouse is automatically selected
1468 $out_js .= 'updateselectwarehousebybatch();';
1469 $out_js .= implode('', $out_js_line_list);
1470 $out_js .= '});';
1471 $out_js .= '</script>';
1472 print $out_js;
1473
1474 $db->free($resql);
1475 } else {
1476 dol_print_error($db);
1477 }
1478 }
1479
1480 print "</table>\n";
1481 print '</div>';
1482
1483 if ($nbproduct) {
1484 //$checkboxlabel = $langs->trans("CloseReceivedSupplierOrdersAutomatically", $langs->transnoentitiesnoconv('StatusOrderReceivedAll'));
1485
1486 print '<div class="center">';
1487 $parameters = array();
1488 $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been
1489 // modified by hook
1490 if (empty($reshook)) {
1491 /*if (empty($conf->reception->enabled)) {
1492 print $langs->trans("Comment").' : ';
1493 print '<input type="text" class="minwidth400" maxlength="128" name="comment" value="';
1494 print GETPOSTISSET("comment") ? GETPOST("comment") : $langs->trans("DispatchSupplierOrder", $object->ref);
1495 // print ' / '.$object->ref_supplier; // Not yet available
1496 print '" class="flat"><br>';
1497
1498 print '<input type="checkbox" checked="checked" name="closeopenorder"> '.$checkboxlabel;
1499 }
1500
1501 $dispatchBt = empty($conf->reception->enabled) ? $langs->trans("Receive") : $langs->trans("CreateReception");
1502
1503 print '<br>';
1504 */
1505
1506 print '<input type="submit" id="submitform" class="button" name="dispatch" value="'.$langs->trans("Save").'"';
1507 $disabled = 0;
1508 if (!$usercancreate) {
1509 $disabled = 1;
1510 }
1511 if (count($listwarehouses) <= 0) {
1512 $disabled = 1;
1513 }
1514 if ($disabled) {
1515 print ' disabled';
1516 }
1517
1518 print '>';
1519 }
1520 print '</div>';
1521 }
1522
1523 // Message if nothing to dispatch
1524 if (!$nbproduct) {
1525 print "<br>\n";
1526 if (!getDolGlobalString('SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED')) {
1527 print '<div class="opacitymedium">'.$langs->trans("NoPredefinedProductToDispatch").'</div>'; // No predefined line at all
1528 } else {
1529 print '<div class="opacitymedium">'.$langs->trans("NoMorePredefinedProductToDispatch").'</div>'; // No predefined line that remain to be dispatched.
1530 }
1531 }
1532
1533 print '</form>';
1534 }
1535
1536 print dol_get_fiche_end();
1537
1538 // Popup for mass barcode scanning
1539 if ($action == 'updatebyscaning') {
1540 if ($permissiontoadd) {
1541 // Output the javascript to manage the scanner tool.
1542 print '<script>';
1543
1544 print '
1545 var duplicatedbatchcode = [];
1546 var errortab1 = [];
1547 var errortab2 = [];
1548 var errortab3 = [];
1549 var errortab4 = [];
1550
1551 function barcodescannerjs() {
1552 console.log("We catch inputs in scanner box");
1553 jQuery("#scantoolmessage").text();
1554
1555 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
1556 var barcodemode = $("input[name=barcodemode]:checked").val();
1557 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
1558 var warehousetouse = $("select[name=warehousenew]").val();
1559 var textarea = $("textarea[name=barcodelist]").val();
1560 var textarray = textarea.split(/[\s,;]+/);
1561 var tabproduct = [];
1562 duplicatedbatchcode = [];
1563 errortab1 = [];
1564 errortab2 = [];
1565 errortab3 = [];
1566 errortab4 = [];
1567
1568 textarray = textarray.filter(function(value) {
1569 return value != "";
1570 });
1571 if (textarray.some((element) => element != "")) {
1572 $(".qtydispatchinput").each(function() {
1573 id = $(this).attr(\'id\');
1574 idarray = id.split(\'_\');
1575 idproduct = idarray[2];
1576 id = idarray[1] + \'_\' + idarray[2];
1577 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
1578 warehouse = $("#entrepot_"+id).val();
1579 console.log(warehouse);
1580 productbarcode = $("#product_"+idproduct).attr(\'data-barcode\');
1581 console.log(productbarcode);
1582 productbatchcode = $("#lot_number_"+id).val();
1583 if (productbatchcode == undefined) {
1584 productbatchcode = "";
1585 }
1586 console.log(productbatchcode);
1587
1588 if (barcodemode != "barcodeforproduct") {
1589 tabproduct.forEach(product=>{
1590 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
1591 if (product.Batch != "" && product.Batch == productbatchcode) {
1592 console.log("duplicate batch code found for batch code "+productbatchcode);
1593 duplicatedbatchcode.push(productbatchcode);
1594 }
1595 })
1596 }
1597 productinput = $("#qty_"+id).val();
1598 if (productinput == "") {
1599 productinput = 0
1600 }
1601 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
1602 });
1603 console.log("Loop on each record entered in the textarea");
1604
1605 textarray.forEach(function(element,index) {
1606 console.log("Process record element="+element+" id="+id);
1607 var verify_batch = false;
1608 var verify_barcode = false;
1609 switch(barcodemode) {
1610 case "barcodeforautodetect":
1611 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode",true);
1612 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial",true);
1613 break;
1614 case "barcodeforproduct":
1615 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode");
1616 break;
1617 case "barcodeforlotserial":
1618 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial");
1619 break;
1620 default:
1621 alert(\''.dol_escape_js($langs->trans("ErrorWrongBarcodemode")).' "\'+barcodemode+\'"\');
1622 throw \''.dol_escape_js($langs->trans('ErrorWrongBarcodemode')).' "\'+barcodemode+\'"\';
1623 }
1624
1625 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
1626 errortab2.push(element);
1627 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
1628 errortab3.push(element);
1629 } else if (verify_batch == true) {
1630 console.log("element="+element);
1631 console.log(duplicatedbatchcode);
1632 if (duplicatedbatchcode.includes(element)) {
1633 errortab1.push(element);
1634 }
1635 }
1636 });
1637
1638 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
1639 tabproduct.forEach(product => {
1640 if (product.Qty!=0) {
1641 if (product.hasOwnProperty("reelqty")) {
1642 idprod = $("td[data-idproduct=\'"+product.fk_product+"\']").attr("id");
1643 idproduct = idprod.split("_")[1];
1644 console.log("We create a new line for product_"+idproduct);
1645 if (product.Barcode != null) {
1646 modedispatch = "dispatch";
1647 } else {
1648 modedispatch = "batch";
1649 }
1650 addDispatchLine(idproduct,modedispatch);
1651 console.log($("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']"));
1652 nbrTrs = $("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']").length;
1653
1654 $("#qty_"+(nbrTrs-1)+"_"+idproduct).val(product.Qty);
1655 $("#entrepot_"+(nbrTrs-1)+"_"+idproduct).val(product.Warehouse);
1656
1657 if (modedispatch == "batch") {
1658 $("#lot_number_"+(nbrTrs-1)+"_"+idproduct).val(product.Batch);
1659 }
1660
1661 } else {
1662 console.log("We change #qty_"+product.Id +" to match input in scanner box");
1663 $("#qty_"+product.Id).val(product.Qty);
1664 }
1665 }
1666 });
1667 jQuery("#scantoolmessage").text(\''.dol_escape_js($langs->transnoentities("QtyWasAddedToTheScannedBarcode")).'\'+"\n");
1668 /* document.forms["formrecord"].submit(); */
1669 } else {
1670 let stringerror = "";
1671 if (Object.keys(errortab1).length > 0) {
1672 stringerror += \'<br>'.dol_escape_js($langs->transnoentities('ErrorSameBatchNumber')).': \';
1673 errortab1.forEach(element => {
1674 stringerror += (element + ", ")
1675 });
1676 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1677 }
1678 if (Object.keys(errortab2).length > 0) {
1679 stringerror += \'<br>'.dol_escape_js($langs->transnoentities('ErrorCantFindCodeInInventory')).': \';
1680 errortab2.forEach(element => {
1681 stringerror += (element + ", ")
1682 });
1683 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1684 }
1685 if (Object.keys(errortab3).length > 0) {
1686 stringerror += \'<br>'.dol_escape_js($langs->transnoentities('ErrorCodeScannedIsBothProductAndSerial')).': \';
1687 errortab3.forEach(element => {
1688 stringerror += (element + ", ")
1689 });
1690 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1691 }
1692 if (Object.keys(errortab4).length > 0) {
1693 stringerror += \'<br>'.dol_escape_js($langs->transnoentities('ErrorBarcodeNotFoundForProductWarehouse')).': \';
1694 errortab4.forEach(element => {
1695 stringerror += (element + ", ")
1696 });
1697 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1698 }
1699
1700 jQuery("#scantoolmessage").html(\''.dol_escape_js($langs->transnoentities("ErrorOnElementsInventory")).'\' + stringerror);
1701 //alert(\''.dol_escape_js($langs->trans("ErrorOnElementsInventory")).'\'+" :\n" + stringerror);
1702 }
1703 }
1704
1705 }
1706
1707 /* This methode is called by parent barcodescannerjs() */
1708 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,mode,autodetect=false) {
1709 BarcodeIsInProduct=0;
1710 newproductrow=0
1711 result=false;
1712 tabproduct.forEach(product => {
1713 $.ajax({ url: \''.DOL_URL_ROOT.'/expedition/ajax/searchfrombarcode.php\',
1714 data: { "token":"'.currentToken().'", "action":"existbarcode","fk_entrepot": warehousetouse, "barcode":element, "mode":mode},
1715 type: \'POST\',
1716 async: false,
1717 success: function(response) {
1718 if (response.status == "success") {
1719 console.log(response.message);
1720 if (!newproductrow) {
1721 newproductrow = response.object;
1722 }
1723 }else{
1724 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)) {
1725 errortab4.push(element);
1726 console.error(response.message);
1727 }
1728 }
1729 },
1730 error : function(output) {
1731 console.error("Error on barcodeserialforproduct function");
1732 },
1733 });
1734 console.log("Product "+(index+=1)+": "+element);
1735 if (mode == "barcode") {
1736 testonproduct = product.Barcode
1737 }else if (mode == "lotserial") {
1738 testonproduct = product.Batch
1739 }
1740 testonwarehouse = product.Warehouse;
1741 if (testonproduct == element && testonwarehouse == warehousetouse) {
1742 if (selectaddorreplace == "add") {
1743 productqty = parseInt(product.Qty,10);
1744 product.Qty = productqty + parseInt(barcodeproductqty,10);
1745 }else if (selectaddorreplace == "replace") {
1746 if (product.fetched == false) {
1747 product.Qty = barcodeproductqty
1748 product.fetched=true
1749 }else{
1750 productqty = parseInt(product.Qty,10);
1751 product.Qty = productqty + parseInt(barcodeproductqty,10);
1752 }
1753 }
1754 BarcodeIsInProduct+=1;
1755 }
1756 })
1757 if (BarcodeIsInProduct==0 && newproductrow!=0) {
1758 tabproduct.push({\'Id\':tabproduct.length-1,\'Warehouse\':newproductrow.fk_warehouse,\'Barcode\':mode=="barcode"?element:null,\'Batch\':mode=="lotserial"?element:null,\'Qty\':barcodeproductqty,\'fetched\':true,\'reelqty\':newproductrow.reelqty,\'fk_product\':newproductrow.fk_product,\'mode\':mode});
1759 result = true;
1760 }
1761 if (BarcodeIsInProduct > 0) {
1762 result = true;
1763 }
1764 return result;
1765 }
1766 ';
1767 print '</script>';
1768 }
1769 include DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
1770 $formother = new FormOther($db);
1771 print $formother->getHTMLScannerForm("barcodescannerjs", 'all', 1);
1772 }
1773
1774 // default warehouse handling
1775 print '<script type="text/javascript">
1776 $(document).ready(function () {
1777 $("select[name=fk_default_warehouse]").change(function() {
1778 console.log("warehouse is modified");
1779 var fk_default_warehouse = $("option:selected", this).val();
1780 $("select[name^=entrepot]").val(fk_default_warehouse).change();
1781 });
1782
1783 $("#autoreset").click(function() {
1784 console.log("we click on autoreset");
1785 $(".autoresettr").each(function() {
1786 id = $(this).attr("name");
1787 idtab = id.split("_");
1788 console.log("we process line "+id+" "+idtab);
1789 if ($(this).data("remove") == "clear") { /* data-remove=clear means that line qty must be cleared but line must not be removed */
1790 console.log("We clear the object to expected value")
1791 var idlinetab = idtab[0].split("-");
1792 var idline = "";
1793 if (idlinetab.length > 0) {
1794 idline = idlinetab[1];
1795 }
1796 $("#qty"+idline+"_"+idtab[1]+"_"+idtab[2]).val("");
1797 /*
1798 qtyexpected = $("#qty_"+idtab[1]+"_"+idtab[2]).data("expected")
1799 console.log(qtyexpected);
1800 $("#qty_"+idtab[1]+"_"+idtab[2]).val(qtyexpected);
1801 qtydispatched = $("#qty_dispatched_0_"+idtab[2]).data("dispatched")
1802 $("#qty_dispatched_0_"+idtab[2]).val(qtydispatched);
1803 */
1804 } else { /* data-remove=remove means that line must be removed */
1805 console.log("We remove the object")
1806 $(this).remove();
1807 $("tr[name^=\'"+idtab[0]+"_\'][name$=\'_"+idtab[2]+"\']:last .splitbutton").show();
1808 }
1809 });
1810 return false;
1811 });
1812
1813 $("#resetalltoexpected").click(function() {
1814 $(".qtydispatchinput").each(function() {
1815 console.log("We reset to expected "+$(this).attr("id")+" qty to dispatch");
1816 $(this).val($(this).data("expected"));
1817 });
1818 return false;
1819 });
1820
1821 $(".resetline").on("click", function(event) {
1822 event.preventDefault();
1823 id = $(this).attr("id");
1824 id = id.split("reset");
1825 console.log("Reset trigger for id = qty"+id[1]);
1826 $("#qty"+id[1]).val("");
1827 });
1828 });
1829 </script>';
1830}
1831
1832// End of page
1833llxFooter();
1834$db->close();
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage customers orders.
Class to manage warehouses.
const STATUS_DRAFT
Draft status.
Class to manage lines of shipment.
CRUD class for batch number management within shipment.
Class to manage generation of HTML components Only common components must be here.
Class to help generate other html components Only common components are here.
Class with static methods for building HTML components related to products Only components common to ...
Class to manage products or services.
Manage record for batch number management.
Class to manage projects.
Class to manage proposals.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
dol_stringtotime($string, $gm=1, $processnotimeasnoon=0)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
Definition date.lib.php:442
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
currentToken()
Return the value of token currently saved into session with name 'token'.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTFLOAT($paramname, $rounding='', $option=2)
Return the value of a $_GET or $_POST supervariable, converted into float.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:738
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $nodie=0, $mode='')
Check permissions of a user to show a page and an object.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.
shipping_prepare_head($object)
Prepare array with list of tabs.
shippingGetStandaloneDispatchSourceLine($object, $sourceLineId, $lineId, $productId)
Resolve a standalone dispatch group from the loaded shipment lines.