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