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