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