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