36require
'../main.inc.php';
44require_once DOL_DOCUMENT_ROOT.
'/core/modules/supplier_order/modules_commandefournisseur.php';
45require_once DOL_DOCUMENT_ROOT.
'/product/stock/class/entrepot.class.php';
46require_once DOL_DOCUMENT_ROOT.
'/core/lib/fourn.lib.php';
47require_once DOL_DOCUMENT_ROOT.
'/fourn/class/fournisseur.commande.class.php';
48require_once DOL_DOCUMENT_ROOT.
'/fourn/class/fournisseur.commande.dispatch.class.php';
49require_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
50require_once DOL_DOCUMENT_ROOT.
'/product/stock/class/mouvementstock.class.php';
51require_once DOL_DOCUMENT_ROOT.
'/core/lib/sendings.lib.php';
53 require_once DOL_DOCUMENT_ROOT.
'/projet/class/project.class.php';
57$langs->loadLangs(array(
"sendings",
"companies",
"bills",
'orders',
'stocks',
'other',
'propal',
'receptions'));
62if ($is_mod_batch_enabled) {
63 $langs->load(
'productbatch');
70$action =
GETPOST(
'action',
'aZ09');
71$fk_default_warehouse =
GETPOSTINT(
'fk_default_warehouse');
72$cancel =
GETPOST(
'cancel',
'alpha');
73$confirm =
GETPOST(
'confirm',
'alpha');
79 $socid = $user->socid;
82$hookmanager->initHooks(array(
'expeditiondispatch'));
94if ($id > 0 || !empty($ref)) {
95 $result =
$object->fetch($id, $ref);
99 $result =
$object->fetch_thirdparty();
118$usercancreate = $user->hasRight(
'expedition',
'creer');
119$permissiontoadd = $usercancreate;
126$parameters = array();
127$reshook = $hookmanager->executeHooks(
'doActions', $parameters, $object, $action);
129 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
133if (empty($reshook)) {
138 if ($action ==
'updatelines' && $usercancreate) {
144 $standaloneWarehouses = array();
145 if ($isStandaloneShipment) {
147 $standaloneWarehouses = $warehouse->list_array(1);
152 foreach ($_POST as $key => $value) {
155 if (preg_match(
'/^(?:product|productbatch)([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
158 if (preg_match(
'/^product([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
159 $modebatch =
"barcode";
160 } elseif (preg_match(
'/^productbatch([0-9]+)_([0-9]+)_([0-9]+)$/i', $key, $reg)) {
161 $modebatch =
"batch";
165 $dispatch_line_suffix = $reg[1].
'_'.$reg[2].
'_'.$reg[3];
166 if ($modebatch ==
"barcode") {
167 $prod =
"product".$dispatch_line_suffix;
169 $prod =
'productbatch'.$dispatch_line_suffix;
171 $qty =
"qty".$dispatch_line_suffix;
172 $ent =
"entrepot".$dispatch_line_suffix;
173 $fk_commandedet =
"fk_commandedet".$dispatch_line_suffix;
174 $idline =
GETPOSTINT(
"idline".$dispatch_line_suffix);
181 if ($modebatch ==
"batch") {
182 $lot =
GETPOST(
'lot_number'.$dispatch_line_suffix);
189 if ($isStandaloneShipment) {
190 if (
GETPOST($qty,
'alphanohtml') ===
'') {
194 if ($source_line ===
null || $newqty < 0 || ($newqty > 0 && !isset($standaloneWarehouses[$warehouse_id]))) {
195 setEventMessages($langs->trans(
'ErrorBadParameters'),
null,
'errors');
203 if (($isStandaloneShipment && $newqty >= 0) || ($modebatch ==
"batch" && $newqty >= 0) || ($modebatch ==
"barcode" && $newqty != 0)) {
205 if (!($warehouse_id > 0)) {
206 dol_syslog(
'No dispatch for line '.$key.
' as no warehouse was chosen.');
207 $text = $langs->transnoentities(
'Warehouse').
', '.$langs->transnoentities(
'Line').
' '.($numline);
208 setEventMessages($langs->trans(
'ErrorFieldRequired', $text),
null,
'errors');
211 if (!$error && $modebatch ==
"batch") {
212 $sql =
"SELECT pb.rowid ";
213 $sql .=
" FROM ".$db->prefix().
"product_batch as pb";
214 $sql .=
" JOIN ".$db->prefix().
"product_stock as ps";
215 $sql .=
" ON ps.rowid = pb.fk_product_stock";
216 $sql .=
" WHERE pb.batch = '".$db->escape($lot).
"'";
217 $sql .=
" AND ps.fk_product = ".((int) $prod_id) ;
218 $sql .=
" AND ps.fk_entrepot = ".((int) $warehouse_id) ;
220 $resql = $db->query($sql);
222 $num = $db->num_rows($resql);
224 dol_syslog(
'No dispatch for line '.$key.
' as too many combination warehouse, product, batch code was found ('.$num.
').');
225 setEventMessages($langs->trans(
'ErrorTooManyCombinationBatchcode', $numline, $num),
null,
'errors');
227 } elseif ($num < 1) {
229 $tmpwarehouse->fetch($warehouse_id);
231 $tmpprod->fetch($prod_id);
232 dol_syslog(
'No dispatch for line '.$key.
' as no combination warehouse, product, batch code was found.');
233 setEventMessages($langs->trans(
'ErrorNoCombinationBatchcode', $numline, $tmpwarehouse->ref, $tmpprod->ref, $lot),
null,
'errors');
245 $result = $expeditiondispatch->fetch($idline);
246 if ($result > 0 && (
int) $expeditiondispatch->fk_expedition !== (
int)
$object->id) {
248 $expeditiondispatch->error = $langs->trans(
'ErrorRecordNotFound');
252 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors,
'errors');
255 $qtystart = $expeditiondispatch->qty;
256 $expeditiondispatch->qty = $newqty;
257 $expeditiondispatch->entrepot_id =
GETPOSTINT($ent);
260 if ($newqty > 0 || ($isStandaloneShipment && !($expeditiondispatch->fk_parent > 0))) {
261 $result = $expeditiondispatch->update($user);
263 $result = $expeditiondispatch->delete($user);
266 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors,
'errors');
270 if (!$error && $modebatch ==
"batch") {
272 $suffixkeyfordate = preg_replace(
'/^productbatch/',
'', $key);
276 $sqlsearchdet =
"SELECT rowid FROM ".$db->prefix().$expeditionlinebatch->table_element;
277 $sqlsearchdet .=
" WHERE fk_expeditiondet = ".((int) $idline);
278 $resqlsearchdet = $db->query($sqlsearchdet);
280 $objsearchdet =
null;
281 if ($resqlsearchdet) {
282 $objsearchdet = $db->fetch_object($resqlsearchdet);
284 dol_print_error($db);
288 $sql =
"UPDATE ".$db->prefix().$expeditionlinebatch->table_element.
" SET";
289 $sql .=
" batch = '".$db->escape($lot).
"'";
290 $sql .=
", eatby = ".($eatby ?
"'".$db->idate($eatby).
"'" :
"null");
291 $sql .=
", sellby = ".($sellby ?
"'".$db->idate($sellby).
"'" :
"null");
292 $sql .=
", qty = ".((float) $newqty);
293 $sql .=
", fk_warehouse = ".((int) $warehouse_id);
294 $sql .=
" WHERE rowid = ".((int) $objsearchdet->rowid);
296 $sql =
"INSERT INTO ".$db->prefix().$expeditionlinebatch->table_element.
" (";
297 $sql .=
"fk_expeditiondet, eatby, sellby, batch, qty, fk_origin_stock, fk_warehouse)";
298 $sql .=
" VALUES (".((int) $idline).
", ".($eatby ?
"'".$db->idate($eatby).
"'" :
"null").
", ".($sellby ?
"'".$db->idate($sellby).
"'" :
"null").
", ";
299 $sql .=
" '".$db->escape($lot).
"', ".((float) $newqty).
", 0, ".((int) $warehouse_id).
")";
302 $sql =
"DELETE FROM ".$db->prefix().$expeditionlinebatch->table_element;
303 $sql .=
" WHERE fk_expeditiondet = ".((int) $idline);
304 $sql .=
" AND batch = '".$db->escape($lot).
"'";
307 $resql = $db->query($sql);
309 dol_print_error($db);
317 $expeditiondispatch->fk_expedition =
$object->id;
318 $expeditiondispatch->entrepot_id =
GETPOSTINT($ent);
319 $expeditiondispatch->fk_parent =
GETPOSTINT(
'fk_parent'.$dispatch_line_suffix);
320 $expeditiondispatch->fk_product = $prod_id;
321 if ($isStandaloneShipment) {
322 $expeditiondispatch->element_type =
'shipping';
323 if ($source_line !==
null) {
324 $expeditiondispatch->fk_parent = $source_line->id;
325 $expeditiondispatch->fk_unit = $source_line->fk_unit;
326 $expeditiondispatch->description = $source_line->description;
327 $expeditiondispatch->rang = $source_line->rang;
329 } elseif (!($expeditiondispatch->fk_parent > 0)) {
330 $expeditiondispatch->fk_elementdet =
GETPOSTINT($fk_commandedet);
332 $expeditiondispatch->qty = $newqty;
335 $idline = $expeditiondispatch->insert($user);
337 setEventMessages($expeditiondispatch->error, $expeditiondispatch->errors,
'errors');
341 if ($modebatch ==
"batch" && !$error) {
342 $expeditionlinebatch->sellby = $dDLC;
343 $expeditionlinebatch->eatby = $dDLUO;
344 $expeditionlinebatch->batch = $lot;
345 $expeditionlinebatch->qty = $newqty;
346 $expeditionlinebatch->fk_origin_stock = 0;
347 $expeditionlinebatch->fk_warehouse =
GETPOSTINT($ent);
349 $result = $expeditionlinebatch->create($idline);
351 setEventMessages($expeditionlinebatch->error, $expeditionlinebatch->errors,
'errors');
403 setEventMessages($langs->trans(
"Error"), $errors,
'errors');
406 setEventMessages($langs->trans(
"ShipmentUpdated"),
null);
408 header(
"Location: ".DOL_URL_ROOT.
'/expedition/dispatch.php?id='.
$object->id);
411 } elseif ($action ==
'setdate_livraison' && $usercancreate) {
415 $result =
$object->setDeliveryDate($user, $datedelivery);
429$form =
new Form($db);
431$warehouse_static =
new Entrepot($db);
433$title =
$object->ref.
" - ".$langs->trans(
'ShipmentDistribution');
434$help_url =
'EN:Module_Shipments|FR:Module_Expéditions|ES:Módulo_Expediciones|DE:Modul_Lieferungen';
435$morejs = array(
'/expedition/js/lib_dispatch.js.php');
438llxHeader(
'', $title, $help_url,
'', 0, 0, $morejs,
'',
'',
'mod-expedition page-card_dispatch');
444 $num_prod = count($lines);
456 $author =
new User($db);
457 $author->fetch(
$object->user_author_id);
467 if ($action ==
'ask_deleteline') {
468 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&lineid='.$lineid, $langs->trans(
'DeleteLine'), $langs->trans(
'ConfirmDeleteLine'),
'confirm_deleteline',
'', 0, 1);
472 $parameters = array(
'lineid' => $lineid);
474 $reshook = $hookmanager->executeHooks(
'formConfirm', $parameters, $object, $action);
475 if (empty($reshook)) {
476 $formconfirm .= $hookmanager->resPrint;
477 } elseif ($reshook > 0) {
478 $formconfirm = $hookmanager->resPrint;
487 $objectsrc->fetch(
$object->origin_object->id);
490 $objectsrc =
new Propal($db);
491 $objectsrc->fetch(
$object->origin_object->id);
495 $linkback =
'<a href="'.DOL_URL_ROOT.
'/expedition/list.php?restore_lastsearch_values=1'.(!empty($socid) ?
'&socid='.$socid :
'').
'">'.$langs->trans(
"BackToList").
'</a>';
496 $morehtmlref =
'<div class="refidno">';
499 $morehtmlref .= $form->editfieldkey(
"RefCustomer",
'ref_customer',
$object->ref_customer, $object, $user->hasRight(
'expedition',
'creer'),
'string',
'', 0, 1);
500 $morehtmlref .= $form->editfieldval(
"RefCustomer",
'ref_customer',
$object->ref_customer, $object, $user->hasRight(
'expedition',
'creer'),
'string'.(isset(
$conf->global->THIRDPARTY_REF_INPUT_SIZE) ?
':' .
getDolGlobalString(
'THIRDPARTY_REF_INPUT_SIZE') :
''),
'', null, null,
'', 1);
503 $morehtmlref .=
'<br>'.$object->thirdparty->getNomUrl(1);
506 $langs->load(
"projects");
507 $morehtmlref .=
'<br>';
509 $morehtmlref .= img_picto($langs->trans(
"Project"),
'project',
'class="pictofixedwidth"');
510 if ($action !=
'classify' && $permissiontoadd) {
511 $morehtmlref .=
'<a class="editfielda" href="'.dolBuildUrl($_SERVER[
'PHP_SELF'], [
'action' =>
'classify',
'id' =>
$object->id],
true).
'">'.img_edit($langs->transnoentitiesnoconv(
'SetProject')).
'</a> ';
513 $morehtmlref .= $form->form_project($_SERVER[
'PHP_SELF'].
'?id='.
$object->id, (!
getDolGlobalString(
'PROJECT_CAN_ALWAYS_LINK_TO_ALL_SUPPLIERS') ?
$object->socid : -1), (string)
$object->fk_project, ($action ==
'classify' ?
'projectid' :
'none'), 0, 0, 0, 1,
'',
'maxwidth300');
515 if (!empty($objectsrc) && !empty($objectsrc->fk_project)) {
517 $proj->fetch($objectsrc->fk_project);
518 $morehtmlref .= $proj->getNomUrl(1);
520 $morehtmlref .=
'<span class="opacitymedium"> - '.dol_escape_htmltag($proj->title).
'</span>';
525 $morehtmlref .=
'</div>';
527 dol_banner_tab($object,
'ref', $linkback, 1,
'ref',
'ref', $morehtmlref);
530 print
'<div class="fichecenter">';
531 print
'<div class="underbanner clearboth"></div>';
533 print
'<table class="border tableforfield centpercent">';
540 print $langs->trans(
"RefOrder").
'</td>';
541 print
'<td colspan="3">';
542 print $objectsrc->getNomUrl(1,
'commande');
549 print $langs->trans(
"RefProposal").
'</td>';
550 print
'<td colspan="3">';
551 print $objectsrc->getNomUrl(1,
'expedition');
557 print
'<tr><td class="titlefield">'.$langs->trans(
"DateCreation").
'</td>';
558 print
'<td colspan="3">'.dol_print_date(
$object->date_creation,
"dayhour",
"tzuserrel").
"</td>\n";
562 print
'<tr><td height="10">';
563 print
'<table class="nobordernopadding" width="100%"><tr><td>';
564 print $langs->trans(
'DateDeliveryPlanned');
566 if ($action !=
'editdate_livraison') {
567 print
'<td class="right"><a class="editfielda" href="'.$_SERVER[
"PHP_SELF"].
'?action=editdate_livraison&token='.
newToken().
'&id='.
$object->id.
'">'.img_edit($langs->trans(
'SetDeliveryDate'), 1).
'</a></td>';
569 print
'</tr></table>';
570 print
'</td><td colspan="2">';
571 if ($action ==
'editdate_livraison') {
572 print
'<form name="setdate_livraison" action="'.$_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'" method="post">';
573 print
'<input type="hidden" name="token" value="'.newToken().
'">';
574 print
'<input type="hidden" name="action" value="setdate_livraison">';
575 print $form->selectDate(
$object->date_delivery ?
$object->date_delivery : -1,
'liv_', 1, 1, 0,
"setdate_livraison", 1, 0);
576 print
'<input type="submit" class="button button-edit smallpaddingimp" value="'.$langs->trans(
'Modify').
'">';
582 print
'</tr></table>';
584 print
'<br><br><center>';
586 print
'<a href="'.$_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&action=updatebyscaning&token='.
currentToken().
'" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
'',
'barcode',
'class="paddingrightonly"').$langs->trans(
"UpdateByScaning").
'</a>';
588 print
'<a href="#" id="resetalltoexpected" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
"",
'autofill',
'class="pictofixedwidth"').$langs->trans(
"RestoreWithCurrentQtySaved").
'</a></td>';
590 print
'<a href="#" id="autoreset" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
"",
'eraser',
'class="pictofixedwidth"').$langs->trans(
"ClearQtys").
'</a></td>';
597 require_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
599 $formproduct->loadWarehouses();
601 $listwarehouses = $entrepot->list_array(1);
609 print
'<form method="post" action="'.dolBuildUrl($_SERVER[
"PHP_SELF"]).
'">';
611 print
'<input type="hidden" name="token" value="'.newToken().
'">';
612 print
'<input type="hidden" name="action" value="updatelines">';
613 print
'<input type="hidden" name="id" value="'.$object->id.
'">';
615 print
'<div class="div-table-responsive-no-min">';
616 print
'<table class="noborder centpercent">';
619 $products_dispatched = array();
620 $standalone_dispatched_qty_sql =
'';
621 if ($isStandaloneShipment) {
622 $standalone_dispatched_qty_sql =
"l.qty + COALESCE((SELECT SUM(child.qty)";
623 $standalone_dispatched_qty_sql .=
" FROM ".$db->prefix().
"expeditiondet as child";
624 $standalone_dispatched_qty_sql .=
" WHERE child.fk_parent = l.rowid";
625 $standalone_dispatched_qty_sql .=
" AND child.fk_expedition = l.fk_expedition";
626 $standalone_dispatched_qty_sql .=
" AND child.fk_product = l.fk_product";
627 $standalone_dispatched_qty_sql .=
"), 0)";
629 $sql =
"SELECT l.rowid, (".$standalone_dispatched_qty_sql.
") as qty";
630 $sql .=
" FROM ".$db->prefix().
"expeditiondet as l";
631 $sql .=
" WHERE l.fk_expedition = ".((int)
$object->id);
632 $sql .=
" AND (l.fk_parent IS NULL OR l.fk_parent = 0)";
634 $sql =
"SELECT ed.fk_elementdet as rowid, sum(ed.qty) as qty";
635 $sql .=
" FROM ".$db->prefix().
"expeditiondet as ed";
636 $sql .=
" WHERE ed.fk_expedition = ".((int)
$object->id);
637 $sql .=
" GROUP BY ed.fk_elementdet";
640 $resql = $db->query($sql);
642 $num = $db->num_rows($resql);
647 $objd = $db->fetch_object($resql);
648 $products_dispatched[$objd->rowid] =
price2num($objd->qty,
'MS');
655 if ($isStandaloneShipment || $objectsrc instanceof
Commande) {
657 if ($isStandaloneShipment) {
658 $sql =
"SELECT l.rowid, l.fk_product, 0 as subprice, 0 as remise_percent, '' AS sref, (".$standalone_dispatched_qty_sql.
") as qty,";
660 $sql =
"SELECT l.rowid, l.fk_product, l.subprice, l.remise_percent, '' AS sref, l.qty as qty,";
662 $sql .=
" p.ref, p.label, p.tobatch, p.fk_default_warehouse, p.barcode, p.stockable_product";
664 $parameters = array();
665 $reshook = $hookmanager->executeHooks(
666 'printFieldListSelect',
672 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
674 $sql .= $hookmanager->resPrint;
676 if ($isStandaloneShipment) {
677 $sql .=
" FROM ".$db->prefix().
"expeditiondet as l";
678 $sql .=
" LEFT JOIN ".$db->prefix().
"product as p ON l.fk_product=p.rowid";
679 $sql .=
" WHERE l.fk_expedition = ".((int)
$object->id);
680 $sql .=
" AND (l.fk_parent IS NULL OR l.fk_parent = 0)";
682 $sql .=
" AND p.fk_product_type = 0";
685 $sql .=
" FROM ".$db->prefix().
"commandedet as l";
686 $sql .=
" LEFT JOIN ".$db->prefix().
"product as p ON l.fk_product=p.rowid";
687 $sql .=
" WHERE l.fk_commande = ".((int) $objectsrc->id);
689 $sql .=
" AND l.product_type = 0";
693 $parameters = array();
694 $reshook = $hookmanager->executeHooks(
695 'printFieldListWhere',
701 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
703 $sql .= $hookmanager->resPrint;
706 $sql .=
" ORDER BY l.rang, p.ref, p.label";
708 $resql = $db->query($sql);
710 $num = $db->num_rows($resql);
715 print
'<tr class="liste_titre">';
717 print
'<td>'.$langs->trans(
"Description").
'</td>';
718 if ($is_mod_batch_enabled) {
719 print
'<td class="dispatch_batch_number_title">'.$langs->trans(
"batch_number").
'</td>';
720 if ($is_sell_by_enabled) {
721 print
'<td class="dispatch_dlc_title">'.$langs->trans(
"SellByDate").
'</td>';
723 if ($is_eat_by_enabled) {
724 print
'<td class="dispatch_dluo_title">'.$langs->trans(
"EatByDate").
'</td>';
731 print
'<td class="right">'.$langs->trans(
"QtyOrdered").
'</td>';
733 print
'<td class="right">'.$langs->trans(
"QtyToShip");
735 print
'<td class="right">'.$langs->trans(
"QtyDispatchedShort").
'</td>';
737 print
'<td class="right">'.$langs->trans(
"Details");
738 print
'<td width="32"></td>';
740 print
'<td class="right">'.$langs->trans(
"Warehouse");
743 if (count($listwarehouses) > 1) {
744 print
'<br><span class="opacitymedium">'.$langs->trans(
"ForceTo").
'</span> '.$form->selectarray(
'fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 1, 0, 0,
'', 0, 0, $disabled,
'',
'minwidth100 maxwidth300', 1);
745 } elseif (count($listwarehouses) == 1) {
746 print
'<br><span class="opacitymedium">'.$langs->trans(
"ForceTo").
'</span> '.$form->selectarray(
'fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0,
'', 0, 0, $disabled,
'',
'minwidth100 maxwidth300', 1);
752 $parameters = array();
753 $reshook = $hookmanager->executeHooks(
754 'printFieldListTitle',
760 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
762 print $hookmanager->resPrint;
767 $conf->cache[
'product'] = array();
771 $objp = $db->fetch_object($resql);
774 if (!$objp->fk_product > 0) {
777 $alreadydispatched = isset($products_dispatched[$objp->rowid]) ? $products_dispatched[$objp->rowid] : 0;
778 $remaintodispatch =
price2num($objp->qty, 5);
779 if ($remaintodispatch < 0 && !
getDolGlobalString(
'SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN')) {
780 $remaintodispatch = 0;
783 if ($remaintodispatch || !
getDolGlobalString(
'SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED')) {
792 print
'<!-- Line to dispatch '.$suffix.
' -->'.
"\n";
794 print
'<input id="qty_ordered'.$suffix.
'" type="hidden" value="'.$objp->qty.
'">';
795 print
'<input id="qty_dispatched'.$suffix.
'" type="hidden" data-dispatched="'.((float) $alreadydispatched).
'" value="'.(float) $alreadydispatched.
'">';
796 print
'<tr class="oddeven">';
798 if (empty(
$conf->cache[
'product'][$objp->fk_product])) {
799 $tmpproduct =
new Product($db);
800 $tmpproduct->fetch($objp->fk_product);
801 $conf->cache[
'product'][$objp->fk_product] = $tmpproduct;
803 $tmpproduct =
$conf->cache[
'product'][$objp->fk_product];
806 $linktoprod =
'<div class="twolinesmax lineheightsmall">';
807 $linktoprod .= $tmpproduct->getNomUrl(1);
808 $linktoprod .=
'<br><span class="opacitymedium small">'.dolPrintHTML($objp->label).
"</span>\n";
809 $linktoprod .=
'</div>';
811 if ($is_mod_batch_enabled) {
812 if ($objp->tobatch) {
814 print
'<td class="tdoverflowbydiv nopaddingtopimp nopaddingbottomimp" id="product_'.$i.
'" data-idproduct="'.$objp->fk_product.
'" data-barcode="'.$objp->barcode.
'">';
817 print
'<td class="dispatch_batch_number"></td>';
818 if ($is_sell_by_enabled) {
819 print
'<td class="dispatch_dlc"></td>';
821 if ($is_eat_by_enabled) {
822 print
'<td class="dispatch_dluo"></td>';
826 print
'<td id="product_'.$i.
'" data-idproduct="'.$objp->fk_product.
'" data-barcode="'.$objp->barcode.
'">';
829 print
'<td class="dispatch_batch_number">';
830 print
'<span class="opacitymedium small">'.$langs->trans(
"ProductDoesNotUseBatchSerial").
'</span>';
832 if ($is_sell_by_enabled) {
833 print
'<td class="dispatch_dlc"></td>';
835 if ($is_eat_by_enabled) {
836 print
'<td class="dispatch_dluo"></td>';
840 print
'<td colspan="4">';
846 $up_ht_disc = $objp->subprice;
847 if (!empty($objp->remise_percent) && !
getDolGlobalString(
'STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
848 $up_ht_disc =
price2num($up_ht_disc * (100 - $objp->remise_percent) / 100,
'MU');
852 print
'<td class="right">'.$objp->qty.
'</td>';
855 print
'<td class="right">'.$alreadydispatched.
'</td>';
857 print
'<td class="right">';
863 $sql =
"SELECT ed.rowid, ed.fk_parent";
864 if ($isStandaloneShipment) {
865 $sql .=
", ed.fk_product";
867 $sql .=
", cd.fk_product";
869 $sql .=
", ".$db->ifsql(
'eb.rowid IS NULL',
'ed.qty',
'eb.qty').
" as qty";
870 $sql .=
", ".$db->ifsql(
'eb.rowid IS NULL OR eb.fk_warehouse IS NULL',
'ed.fk_entrepot',
'eb.fk_warehouse').
" as fk_warehouse";
871 $sql .=
", eb.batch, eb.eatby, eb.sellby";
872 $sql .=
" FROM ".$db->prefix().
"expeditiondet as ed";
873 $sql .=
" LEFT JOIN ".$db->prefix().
"expeditiondet_batch as eb on ed.rowid = eb.fk_expeditiondet";
874 if ($isStandaloneShipment) {
875 $sql .=
" WHERE ed.fk_expedition = ".(int)
$object->id;
876 $sql .=
" AND (ed.rowid = ".(int) $objp->rowid.
" OR (ed.fk_parent = ".(
int) $objp->rowid.
" AND ed.fk_product = ".(int) $objp->fk_product.
"))";
877 $sql .=
" ORDER BY CASE WHEN ed.fk_parent IS NULL OR ed.fk_parent = 0 THEN 0 ELSE 1 END, ed.rowid";
879 $sql .=
" INNER JOIN ".$db->prefix().
"commandedet as cd on ed.fk_elementdet = cd.rowid";
880 $sql .=
" WHERE ed.fk_elementdet = ".(int) $objp->rowid;
881 $sql .=
" AND ed.fk_expedition = ".(int)
$object->id;
882 $sql .=
" ORDER BY ed.rowid, ed.fk_elementdet";
885 $resultsql = $db->query($sql);
888 $numd = $db->num_rows($resultsql);
889 while ($obj_exp = $db->fetch_object($resultsql)) {
890 $suffix =
"_" . $j .
"_" . $i;
892 $productChildrenNb = 0;
893 $expedition_line_child_list = array();
894 if (!$isStandaloneShipment &&
getDolGlobalInt(
'PRODUIT_SOUSPRODUITS')) {
896 $productChildrenNb = $tmpproduct->hasFatherOrChild(1);
897 if ($productChildrenNb > 0) {
898 $line_id_list = array();
902 $result = $expeditionLine->findAllChild($obj_exp->rowid, $line_id_list, 1);
905 foreach ($line_id_list as $line_id_arr) {
906 foreach ($line_id_arr as $line_obj) {
907 $child_product_id = (int) $line_obj->fk_product;
908 if (empty(
$conf->cache[
'product'][$child_product_id])) {
909 $child_product =
new Product($db);
910 $child_product->fetch($child_product_id);
911 $conf->cache[
'product'][$child_product_id] = $child_product;
913 $child_product =
$conf->cache[
'product'][$child_product_id];
917 $batch_list = array();
918 if ($is_mod_batch_enabled && $child_product->hasbatch()) {
920 $sql_line_batch_search =
"SELECT eb.rowid, eb.qty, eb.batch, eb.sellby, eb.eatby";
921 $sql_line_batch_search .=
" FROM ".$db->prefix().
"expeditiondet_batch as eb";
922 $sql_line_batch_search .=
" WHERE eb.fk_expeditiondet = ".((int) $line_obj->rowid);
923 $res_line_batch_search = $db->query($sql_line_batch_search);
924 if ($res_line_batch_search) {
925 while ($obj_batch = $db->fetch_object($res_line_batch_search)) {
927 if ($obj_batch->batch !=
'') {
928 $line_obj->batch = $obj_batch->batch;
932 $batch_list[] = $obj_batch;
934 $db->free($res_line_batch_search);
938 if (empty($batch_list)) {
939 $batch_sort_field_arr = array();
940 $batch_sort_order_arr = array();
941 if ($is_sell_by_enabled) {
942 $batch_sort_field_arr[] =
'pl.sellby';
943 $batch_sort_order_arr[] =
'ASC';
945 if ($is_eat_by_enabled) {
946 $batch_sort_field_arr[] =
'pl.eatby';
947 $batch_sort_order_arr[] =
'ASC';
949 $batch_sort_field_arr[] =
'pb.qty';
950 $batch_sort_order_arr[] =
'ASC';
951 $batch_sort_field_arr[] =
'pl.rowid';
952 $batch_sort_order_arr[] =
'ASC';
954 $product_batch_result = $product_batch->findAllForProduct($child_product_id, $line_obj->fk_warehouse, (
getDolGlobalInt(
'STOCK_DISALLOW_NEGATIVE_TRANSFER') ? 0 : null), implode(
',', $batch_sort_field_arr), implode(
',', $batch_sort_order_arr));
955 if (is_array($product_batch_result)) {
956 foreach ($product_batch_result as $batch_current) {
957 $batch_current->eatby =
dol_print_date($batch_current->eatby,
'day');
958 $batch_current->sellby =
dol_print_date($batch_current->sellby,
'day');
959 $batch_list[] = $batch_current;
964 $line_obj->batch_list = $batch_list;
967 $line_obj->iskit = 0;
968 if ($child_product->stockable_product == Product::ENABLED_STOCK) {
969 $can_manage_stock = 1;
971 $can_manage_stock = 0;
973 $line_obj->incdec = $can_manage_stock;
974 $sql_child =
"SELECT ";
975 $sql_child .=
" SUM(".$db->ifsql(
"pa.rowid IS NOT NULL",
"1",
"0").
") as iskit";
976 $sql_child .=
", ".$db->ifsql(
"pai.incdec IS NULL",
"1",
"pai.incdec").
" as incdec";
977 $sql_child .=
" FROM ".$db->prefix().
"expeditiondet as ed";
978 $sql_child .=
" LEFT JOIN ".$db->prefix().
"expeditiondet as edp ON edp.rowid = ".((int) $line_obj->fk_parent);
979 $sql_child .=
" LEFT JOIN ".$db->prefix().
"product_association as pa ON pa.fk_product_pere = ".((int) $child_product_id);
980 $sql_child .=
" LEFT JOIN ".$db->prefix().
"product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ".((int) $child_product_id);
981 $sql_child .=
" WHERE ed.rowid = ".((int) $line_obj->rowid);
982 $sql_child .=
" GROUP BY pa.rowid, pai.incdec";
983 $resql_child = $db->query($sql_child);
985 if ($child_obj = $db->fetch_object($resql_child)) {
986 if (!
getDolGlobalInt(
'PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
987 $line_obj->iskit = (int) $child_obj->iskit;
989 if ($can_manage_stock) {
990 $line_obj->incdec = (int) $child_obj->incdec;
993 $db->free($resql_child);
995 $line_obj->html_label = str_repeat(
" ", $child_level) .
"→" . $child_product->getNomUrl(1);
996 $expedition_line_child_list[] = $line_obj;
1003 if (empty($expedition_line_child_list)) {
1004 $obj_exp->iskit = 0;
1006 if ($objp->stockable_product == Product::ENABLED_STOCK) {
1007 $obj_exp->incdec = 1;
1009 $obj_exp->incdec = 0;
1011 $expedition_line_child_list[] = $obj_exp;
1014 $child_suffix = $suffix;
1015 foreach ($expedition_line_child_list as $objd) {
1016 $child_line_id = $isStandaloneShipment ? $objp->rowid : $objd->rowid;
1018 $can_update_stock = empty($objd->iskit) && !empty($objd->incdec);
1019 $suffix = $child_line_id.$child_suffix;
1022 $dispatch_line_batch_current =
null;
1023 if (!empty($objd->batch_list)) {
1024 $dispatch_line_batch_count = count($objd->batch_list);
1026 if ($dispatch_line_batch_count == 1) {
1027 $dispatch_line_batch_current = current($objd->batch_list);
1030 if (is_object($dispatch_line_batch_current)) {
1031 $objd->batch = $dispatch_line_batch_current->batch;
1032 $objd->eatby = $dispatch_line_batch_current->eatby;
1033 $objd->sellby = $dispatch_line_batch_current->sellby;
1036 if ($is_mod_batch_enabled
1038 !empty($objd->batch)
1039 || (is_null($objd->batch) && $tmpproduct->status_batch > 0)
1040 || !empty($objd->batch_list)
1046 $parameters = array(
1048 'is_information_row' =>
true,
1050 'suffix' => $suffix,
1053 $reshook = $hookmanager->executeHooks(
1054 'printFieldListValue',
1060 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
1062 print $hookmanager->resPrint;
1066 print
'<!-- line for batch ' . $numline .
' -->';
1067 print
'<tr class="oddeven autoresettr" name="' . $type .
'-' . $suffix .
'" data-remove="clear">';
1069 print
'<input id="fk_commandedet' . $suffix .
'" name="fk_commandedet' . $suffix .
'" type="hidden" value="' . $objp->rowid .
'">';
1070 print
'<input id="idline' . $suffix .
'" name="idline' . $suffix .
'" type="hidden" value="' . $objd->rowid .
'">';
1071 print
'<input id="fk_parent' . $suffix .
'" name="fk_parent' . $suffix .
'" type="hidden" value="' . $objd->fk_parent .
'">';
1072 print
'<input name="productbatch' . $suffix .
'" type="hidden" value="' . $objd->fk_product .
'">';
1074 print
'<!-- This is a U.P. (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1075 print
'<input class="maxwidth75" name="pu' . $suffix .
'" type="hidden" value="' .
price2num($up_ht_disc,
'MU') .
'">';
1076 if (!empty($objd->html_label)) {
1077 print $objd->html_label;
1082 print
'<input type="text" class="inputlotnumber quatrevingtquinzepercent csslotnumber" name="lot_number'.$suffix.
'" value="'.(
GETPOSTISSET(
'lot_number'.$suffix) ?
GETPOST(
'lot_number'.$suffix) : $objd->batch).
'" list="lot_number'.$suffix.
'">';
1083 print $formproduct->selectLotDataList(
'lot_number'.$suffix, 0, $objd->fk_product,
GETPOST(
"entrepot".$suffix) ?
GETPOST(
"entrepot".$suffix) : $objd->fk_warehouse, array());
1086 if ($is_sell_by_enabled) {
1087 print
'<td class="nowraponall">';
1089 print $form->selectDate($dlcdatesuffix,
'dlc'.$suffix, 0, 0, 1,
'');
1092 if ($is_eat_by_enabled) {
1093 print
'<td class="nowraponall">';
1095 print $form->selectDate($dluodatesuffix,
'dluo'.$suffix, 0, 0, 1,
'');
1098 print
'<td colspan="2"> </td>';
1102 $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
1103 $colspan = $is_eat_by_enabled ? $colspan : --$colspan;
1106 $parameters = array(
1108 'is_information_row' =>
true,
1110 'suffix' => $suffix,
1113 $reshook = $hookmanager->executeHooks(
1114 'printFieldListValue',
1120 setEventMessages($hookmanager->error, $hookmanager->errors,
'errors');
1122 print $hookmanager->resPrint;
1126 print
'<!-- line no batch '.$numline.
' -->';
1127 print
'<tr class="oddeven autoresettr" name="'.$type.
'-'.$suffix.
'" data-remove="clear">';
1128 print
'<td colspan="'.$colspan.
'">';
1129 print
'<input id="fk_commandedet'.$suffix.
'" name="fk_commandedet'.$suffix.
'" type="hidden" value="'.$objp->rowid.
'">';
1130 print
'<input id="idline'.$suffix.
'" name="idline'.$suffix.
'" type="hidden" value="'.$objd->rowid.
'">';
1131 print
'<input id="fk_parent'.$suffix.
'" name="fk_parent'.$suffix.
'" type="hidden" value="'.$objd->fk_parent.
'">';
1132 print
'<input name="product'.$suffix.
'" type="hidden" value="'.$objd->fk_product.
'">';
1133 print
'<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1134 print
'<input class="maxwidth75" name="pu'.$suffix.
'" type="hidden" value="'.
price2num($up_ht_disc,
'MU').
'">';
1135 if (!empty($objd->html_label)) {
1136 print $objd->html_label;
1141 print
'<td class="right nowraponall">';
1144 if ($can_update_stock) {
1145 print
'<a href="" id="reset'.$suffix.
'" class="resetline">'.img_picto($langs->trans(
"Reset"),
'eraser',
'class="pictofixedwidth opacitymedium"').
'</a>';
1146 print
'<input id="qty'.$suffix.
'" onchange="onChangeDispatchLineQty($(this))" name="qty'.$suffix.
'" data-type="'.$type.
'" data-index="'.$i.
'" class="width50 right qtydispatchinput" value="'.$suggestedvalue.
'" data-expected="'.$objd->qty.
'">';
1148 print
'<input type="hidden" id="qty'.$suffix.
'" name="qty'.$suffix.
'" value="">';
1152 if ($can_update_stock) {
1153 print img_picto($langs->trans(
'AddStockLocationLine'),
'split',
'class="splitbutton" onClick="addDispatchLine('.$i.
', \''.$type.
'-'.$child_line_id.
'\')
"');
1158 print '<td class="right
">';
1159 if ($can_update_stock) {
1160 if (count($listwarehouses) > 1) {
1161 print $formproduct->selectWarehouses(GETPOST("entrepot
".$suffix) ? GETPOST("entrepot
".$suffix) : $objd->fk_warehouse, "entrepot
".$suffix, '', 1, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1162 } elseif (count($listwarehouses) == 1) {
1163 print $formproduct->selectWarehouses(GETPOST("entrepot
".$suffix) ? GETPOST("entrepot
".$suffix) : $objd->fk_warehouse, "entrepot
".$suffix, '', 0, 0, $objd->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1165 $langs->load("errors
");
1166 print $langs->trans("ErrorNoWarehouseDefined
");
1169 // we force the warehouse so we will pass the test to add a line into expedition.class.php
1170 print '<input id="entrepot
'.$suffix.'" name="entrepot
'.$suffix.'" type="hidden
" value="'.$objd->fk_warehouse.'">';
1171 print img_warning().' '.$langs->trans('StockDisabled');
1175 // Enable hooks to append additional columns
1176 $parameters = array(
1177 'is_information_row' => false, // this is a dispatch form row
1179 'suffix' => $suffix,
1183 $reshook = $hookmanager->executeHooks(
1184 'printFieldListValue',
1190 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1192 print $hookmanager->resPrint;
1201 //$suffix = "_
".$j."_
".$i;
1203 $errorMsg = 'Shipment dispatch SQL error : '.$db->lasterror();
1204 setEventMessage($errorMsg, 'errors');
1205 dol_syslog($errorMsg, LOG_ERR);
1210 if ($is_mod_batch_enabled && !empty($objp->tobatch)) {
1213 // Enable hooks to append additional columns
1214 $parameters = array(
1215 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1216 'is_information_row' => true,
1218 'suffix' => $suffix,
1221 $reshook = $hookmanager->executeHooks(
1222 'printFieldListValue',
1228 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1230 print $hookmanager->resPrint;
1234 print '<!-- line for batch '.$numline.' (not dispatched line yet for this order line) -->';
1235 print '<tr class="oddeven autoresettr
" name="'.$type.$suffix.'" data-remove="clear
">';
1237 print '<input id="fk_commandedet
'.$suffix.'" name="fk_commandedet
'.$suffix.'" type="hidden
" value="'.$objp->rowid.'">';
1238 print '<input id="idline
'.$suffix.'" name="idline
'.$suffix.'" type="hidden
" value="-1
">';
1239 print '<input name="productbatch
'.$suffix.'" type="hidden
" value="'.$objp->fk_product.'">';
1241 print '<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1242 print '<input class="maxwidth75
" name="pu
'.$suffix.'" type="hidden
" value="'.price2num($up_ht_disc, 'MU
').'">';
1246 print '<input type="text
" class="inputlotnumber quatrevingtquinzepercent
" id="lot_number
'.$suffix.'" name="lot_number
'.$suffix.'" value="'.GETPOST('lot_number
'.$suffix).'">';
1248 if ($is_sell_by_enabled) {
1249 print '<td class="nowraponall
">';
1250 $dlcdatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dlc'.$suffix.'month'), GETPOSTINT('dlc'.$suffix.'day'), GETPOSTINT('dlc'.$suffix.'year'));
1251 print $form->selectDate($dlcdatesuffix, 'dlc'.$suffix, 0, 0, 1, '');
1254 if ($is_eat_by_enabled) {
1255 print '<td class="nowraponall
">';
1256 $dluodatesuffix = dol_mktime(0, 0, 0, GETPOSTINT('dluo'.$suffix.'month'), GETPOSTINT('dluo'.$suffix.'day'), GETPOSTINT('dluo'.$suffix.'year'));
1257 print $form->selectDate($dluodatesuffix, 'dluo'.$suffix, 0, 0, 1, '');
1260 print '<td colspan="2
"> </td>'; // Supplier ref + Qty ordered + qty already dispatched
1264 $colspan = $is_sell_by_enabled ? $colspan : --$colspan;
1265 $colspan = $is_eat_by_enabled ? $colspan : --$colspan;
1267 // Enable hooks to append additional columns
1268 $parameters = array(
1269 // allows hook to distinguish between the rows with information and the rows with dispatch form input
1270 'is_information_row' => true,
1272 'suffix' => $suffix,
1275 $reshook = $hookmanager->executeHooks(
1276 'printFieldListValue',
1282 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1284 print $hookmanager->resPrint;
1288 print '<!-- line no batch '.$numline.' (not dispatched line yet for this order line) -->';
1289 print '<tr class="oddeven autoresettr
" name="'.$type.$suffix.'" data-remove="clear
">';
1290 print '<td colspan="'.$colspan.'">';
1291 print '<input id="fk_commandedet
'.$suffix.'" name="fk_commandedet
'.$suffix.'" type="hidden
" value="'.$objp->rowid.'">';
1292 print '<input id="idline
'.$suffix.'" name="idline
'.$suffix.'" type="hidden
" value="-1
">';
1293 print '<input name="product
'.$suffix.'" type="hidden
" value="'.$objp->fk_product.'">';
1295 print '<!-- This is a up (may include discount or not depending on STOCK_EXCLUDE_DISCOUNT_FOR_PMP. will be used for PMP calculation) -->';
1296 print '<input class="maxwidth75
" name="pu
'.$suffix.'" type="hidden
" value="'.price2num($up_ht_disc, 'MU
').'">';
1300 print '<td class="right
">';
1301 print '<a href="" id="reset
'.$suffix.'" class="resetline
">'.img_picto($langs->trans("Reset
"), 'eraser', 'class="pictofixedwidth opacitymedium
"').'</a>';
1302 $amounttosuggest = (GETPOSTISSET('qty'.$suffix) ? GETPOSTINT('qty'.$suffix) : (!getDolGlobalString('SUPPLIER_ORDER_DISPATCH_FORCE_QTY_INPUT_TO_ZERO') ? $remaintodispatch : 0));
1303 if (count($products_dispatched)) {
1304 // There is already existing lines into llx_expeditiondet, this means a plan for the shipment has already been started.
1305 // In such a case, we do not suggest new values, we suggest the value known.
1306 $amounttosuggest = (GETPOSTISSET('qty'.$suffix) ? GETPOSTINT('qty'.$suffix) : (isset($products_dispatched[$objp->rowid]) ? $products_dispatched[$objp->rowid] : ''));
1308 print '<input id="qty
'.$suffix.'" onchange="onChangeDispatchLineQty($(
this))
" name="qty
'.$suffix.'" data-index="'.$i.'" data-type="text
" class="width50 right qtydispatchinput
" value="'.$amounttosuggest.'" data-expected="'.$amounttosuggest.'">';
1311 if ($is_mod_batch_enabled && $objp->tobatch > 0) {
1313 print img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton
" onClick="addDispatchLine(
'.$i.', \
''.$type.
'\')
"');
1316 print img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton
" onClick="addDispatchLine(
'.$i.', \
''.$type.
'\')
"');
1322 print '<td class="right
">';
1323 if (count($listwarehouses) > 1) {
1324 print $formproduct->selectWarehouses(GETPOST("entrepot
".$suffix) ? GETPOST("entrepot
".$suffix) : ($objp->fk_default_warehouse ? $objp->fk_default_warehouse : ''), "entrepot
".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1325 } elseif (count($listwarehouses) == 1) {
1326 print $formproduct->selectWarehouses(GETPOST("entrepot
".$suffix) ? GETPOST("entrepot
".$suffix) : ($objp->fk_default_warehouse ? $objp->fk_default_warehouse : ''), "entrepot
".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, array(), 'csswarehouse'.$suffix);
1328 $langs->load("errors
");
1329 print $langs->trans("ErrorNoWarehouseDefined
");
1333 // Enable hooks to append additional columns
1334 $parameters = array(
1335 'is_information_row' => false, // this is a dispatch form row
1337 'suffix' => $suffix,
1340 $reshook = $hookmanager->executeHooks(
1341 'printFieldListValue',
1347 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1349 print $hookmanager->resPrint;
1358 // reload batch select and warehouse select on change (Ajax)
1359 $out_js_line_list = array();
1360 $out_js_line = 'function updateselectbatchbywarehouse() {';
1361 $out_js_line .= ' jQuery(document).on("change
", "select[
name*=\
"entrepot\"]",
function() {
';
1362 $out_js_line .= ' var selectwarehouse = jQuery(
this);
';
1363 $out_js_line .= ' var selectbatch_name = selectwarehouse.attr(
"name").replace(
"entrepot",
"lot_number");
';
1364 $out_js_line .= ' var selectbatch = jQuery(
"datalist[id*=\""+selectbatch_name+
"\"]");
';
1365 $out_js_line .= ' var selectedbatch = selectbatch.val();
';
1366 $out_js_line .= ' var product_element_name = selectwarehouse.attr(
"name").replace(
"entrepot",
"productbatch");
';
1367 $out_js_line .= ' jQuery.ajax({
';
1368 $out_js_line .= ' type:
"POST",
';
1369 $out_js_line .= ' url: \
''.dol_escape_js(
dol_buildpath(
'/expedition/ajax/interface.php', 1)).
'\',
';
1370 $out_js_line .= ' data: {
';
1371 $out_js_line .= ' action:
"updateselectbatchbywarehouse",
';
1372 $out_js_line .= ' warehouse_id: jQuery(
this).val(),
';
1373 $out_js_line .= ' token:
"'.currentToken().'",
';
1374 $out_js_line .= ' product_id: jQuery(
"input[name=\""+product_element_name+
"\"]").val()
';
1375 $out_js_line .= ' }
';
1376 $out_js_line .= ' }).done(
function(data) {
';
1377 $out_js_line .= ' selectbatch.empty();
';
1378 $out_js_line .= ' if (typeof data ==
"object") {
';
1379 $out_js_line .= ' console.log(
"data is already type object, no need to parse it");
';
1380 $out_js_line .= ' }
else {
';
1381 $out_js_line .= ' console.log(
"data is type "+(typeof data));
';
1382 $out_js_line .= ' data = JSON.parse(data);
';
1383 $out_js_line .= ' }
';
1384 $out_js_line .= ' selectbatch.append(jQuery(
"<option>", {
';
1385 $out_js_line .= ' value:
"",
';
1386 $out_js_line .= ' }));
';
1387 $out_js_line .= ' jQuery.each(data,
function(key, objBatch) {
';
1388 $out_js_line .= ' var dataEatByDate = objBatch.eatbydate;
';
1389 $out_js_line .= ' var dataSellByDate = objBatch.sellbydate;
';
1390 $out_js_line .= ' var optionLabel = key+
" (";
';
1391 $out_js_line .= ' if (selectwarehouse.val() == -1) {
';
1392 $out_js_line .= ' optionLabel += \
''.dol_escape_js($langs->trans(
'TotalStock')).
': \'+objBatch.qty;';
1393 $out_js_line .=
' } else {';
1394 $out_js_line .=
' optionLabel += \''.dol_escape_js($langs->trans(
'Stock')).
': \'+objBatch.qty;';
1395 $out_js_line .=
' }';
1396 $out_js_line .=
' if (dataEatByDate != "") {';
1397 $out_js_line .=
' optionLabel += \' - '.dol_escape_js($langs->trans(
'EatByDate')).
': \'+dataEatByDate;';
1398 $out_js_line .=
' }';
1399 $out_js_line .=
' if (dataSellByDate != "") {';
1400 $out_js_line .=
' optionLabel += \' - '.dol_escape_js($langs->trans(
'SellByDate')).
': \'+dataSellByDate;';
1401 $out_js_line .=
' }';
1402 $out_js_line .=
' optionLabel += ")";';
1403 $out_js_line .=
' var option = "<option data-eatbydate=\""+dataEatByDate+"\" data-sellbydate=\""+dataSellByDate+"\" value=\""+key+"\"";';
1404 $out_js_line .=
' if (key === selectedbatch) {';
1405 $out_js_line .=
' option += " selected";';
1406 $out_js_line .=
' }';
1407 $out_js_line .=
' option += ">"+optionLabel+"</option>";';
1408 $out_js_line .=
' selectbatch.append(option);';
1409 $out_js_line .=
' });';
1410 $out_js_line .=
' });';
1411 $out_js_line .=
' });';
1412 $out_js_line .=
'}';
1414 $out_js_line .=
'function updateselectwarehousebybatch() {';
1415 $out_js_line .=
' jQuery(document).on("change", "input[name*=lot_number]", function() {';
1416 $out_js_line .=
' var selectbatch = jQuery(this);';
1417 $out_js_line .=
' var selectwarehouse_name = selectbatch.attr("name").replace("lot_number", "entrepot");';
1418 $out_js_line .=
' var selectwarehouse = jQuery("select[name*=\""+selectwarehouse_name+"\"]");';
1419 $out_js_line .=
' var selectedwarehouse = selectwarehouse.val();';
1420 $out_js_line .=
' var inputbatchdlc_name = selectbatch.attr("name").replace("lot_number", "dlc");';
1421 $out_js_line .=
' var inputbatchdlc = jQuery("input[name*=\""+inputbatchdlc_name+"\"]");';
1422 $out_js_line .=
' var inputbatchdluo_name = selectbatch.attr("name").replace("lot_number", "dluo");';
1423 $out_js_line .=
' var inputbatchdluo = jQuery("input[name*=\""+inputbatchdluo_name+"\"]");';
1424 $out_js_line .=
' var datalistselectedbatch = jQuery("#"+selectbatch.attr("name")+" option[value=\""+selectbatch.val()+"\"]");';
1425 $out_js_line .=
' var selectedbatch_dlc = datalistselectedbatch.data("sellbydate");';
1426 $out_js_line .=
' var selectedbatch_dluo = datalistselectedbatch.data("eatbydate");';
1427 $out_js_line .=
' if (typeof selectedbatch_dlc === "undefined") {';
1428 $out_js_line .=
' selectedbatch_dlc = "";';
1429 $out_js_line .=
' }';
1430 $out_js_line .=
' if (typeof selectedbatch_dluo === "undefined") {';
1431 $out_js_line .=
' selectedbatch_dluo = "";';
1432 $out_js_line .=
' }';
1433 $out_js_line .=
' inputbatchdlc.val(selectedbatch_dlc).trigger("change");';
1434 $out_js_line .=
' inputbatchdluo.val(selectedbatch_dluo).trigger("change");';
1435 $out_js_line .=
' if (selectedwarehouse != -1) {';
1436 $out_js_line .=
' return;';
1437 $out_js_line .=
' }';
1438 $out_js_line .=
' var product_element_name = selectbatch.attr("name").replace("lot_number", "productbatch");';
1439 $out_js_line .=
' jQuery.ajax({';
1440 $out_js_line .=
' type: "POST",';
1441 $out_js_line .=
' url: \''.dol_escape_js(dol_buildpath(
'/expedition/ajax/interface.php', 1)).
'\',';
1442 $out_js_line .=
' data: {';
1443 $out_js_line .=
' action: "updateselectwarehousebybatch",';
1444 $out_js_line .=
' batch: jQuery(this).val(),';
1445 $out_js_line .=
' token: "'.currentToken().
'",';
1446 $out_js_line .=
' product_id: jQuery("input[name=\""+product_element_name+"\"]").val()';
1447 $out_js_line .=
' }';
1448 $out_js_line .=
' }).done(function(data) {';
1449 $out_js_line .=
' if (typeof data == "object") {';
1450 $out_js_line .=
' console.log("data is already type object, no need to parse it");';
1451 $out_js_line .=
' } else {';
1452 $out_js_line .=
' console.log("data is type "+(typeof data));';
1453 $out_js_line .=
' data = JSON.parse(data);';
1454 $out_js_line .=
' }';
1455 $out_js_line .=
' if (data != 0) {';
1456 $out_js_line .=
' selectwarehouse.val(data).change();';
1457 $out_js_line .=
' }';
1458 $out_js_line .=
' });';
1459 $out_js_line .=
' });';
1460 $out_js_line .=
'}';
1461 $out_js_line_list[] = $out_js_line;
1463 $out_js =
'<script type="text/javascript">';
1464 $out_js .=
'jQuery(document).ready(function() {';
1466 $out_js .=
'updateselectbatchbywarehouse();';
1468 $out_js .=
'updateselectwarehousebybatch();';
1469 $out_js .= implode(
'', $out_js_line_list);
1471 $out_js .=
'</script>';
1476 dol_print_error($db);
1486 print
'<div class="center">';
1487 $parameters = array();
1488 $reshook = $hookmanager->executeHooks(
'addMoreActionsButtons', $parameters, $object, $action);
1490 if (empty($reshook)) {
1506 print
'<input type="submit" id="submitform" class="button" name="dispatch" value="'.$langs->trans(
"Save").
'"';
1508 if (!$usercancreate) {
1511 if (count($listwarehouses) <= 0) {
1526 if (!
getDolGlobalString(
'SUPPLIER_ORDER_DISABLE_STOCK_DISPATCH_WHEN_TOTAL_REACHED')) {
1527 print
'<div class="opacitymedium">'.$langs->trans(
"NoPredefinedProductToDispatch").
'</div>';
1529 print
'<div class="opacitymedium">'.$langs->trans(
"NoMorePredefinedProductToDispatch").
'</div>';
1539 if ($action ==
'updatebyscaning') {
1540 if ($permissiontoadd) {
1545 var duplicatedbatchcode = [];
1551 function barcodescannerjs() {
1552 console.log("We catch inputs in scanner box");
1553 jQuery("#scantoolmessage").text();
1555 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
1556 var barcodemode = $("input[name=barcodemode]:checked").val();
1557 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
1558 var warehousetouse = $("select[name=warehousenew]").val();
1559 var textarea = $("textarea[name=barcodelist]").val();
1560 var textarray = textarea.split(/[\s,;]+/);
1561 var tabproduct = [];
1562 duplicatedbatchcode = [];
1568 textarray = textarray.filter(function(value) {
1571 if (textarray.some((element) => element != "")) {
1572 $(".qtydispatchinput").each(function() {
1573 id = $(this).attr(\'id\');
1574 idarray = id.split(\'_\');
1575 idproduct = idarray[2];
1576 id = idarray[1] + \'_\' + idarray[2];
1577 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
1578 warehouse = $("#entrepot_"+id).val();
1579 console.log(warehouse);
1580 productbarcode = $("#product_"+idproduct).attr(\'data-barcode\');
1581 console.log(productbarcode);
1582 productbatchcode = $("#lot_number_"+id).val();
1583 if (productbatchcode == undefined) {
1584 productbatchcode = "";
1586 console.log(productbatchcode);
1588 if (barcodemode != "barcodeforproduct") {
1589 tabproduct.forEach(product=>{
1590 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
1591 if (product.Batch != "" && product.Batch == productbatchcode) {
1592 console.log("duplicate batch code found for batch code "+productbatchcode);
1593 duplicatedbatchcode.push(productbatchcode);
1597 productinput = $("#qty_"+id).val();
1598 if (productinput == "") {
1601 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
1603 console.log("Loop on each record entered in the textarea");
1605 textarray.forEach(function(element,index) {
1606 console.log("Process record element="+element+" id="+id);
1607 var verify_batch = false;
1608 var verify_barcode = false;
1609 switch(barcodemode) {
1610 case "barcodeforautodetect":
1611 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode",true);
1612 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial",true);
1614 case "barcodeforproduct":
1615 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode");
1617 case "barcodeforlotserial":
1618 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial");
1621 alert(\''.dol_escape_js($langs->trans(
"ErrorWrongBarcodemode")).
' "\'+barcodemode+\'"\');
1622 throw \''.
dol_escape_js($langs->trans(
'ErrorWrongBarcodemode')).
' "\'+barcodemode+\'"\';
1625 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
1626 errortab2.push(element);
1627 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
1628 errortab3.push(element);
1629 } else if (verify_batch == true) {
1630 console.log("element="+element);
1631 console.log(duplicatedbatchcode);
1632 if (duplicatedbatchcode.includes(element)) {
1633 errortab1.push(element);
1638 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
1639 tabproduct.forEach(product => {
1640 if (product.Qty!=0) {
1641 if (product.hasOwnProperty("reelqty")) {
1642 idprod = $("td[data-idproduct=\'"+product.fk_product+"\']").attr("id");
1643 idproduct = idprod.split("_")[1];
1644 console.log("We create a new line for product_"+idproduct);
1645 if (product.Barcode != null) {
1646 modedispatch = "dispatch";
1648 modedispatch = "batch";
1650 addDispatchLine(idproduct,modedispatch);
1651 console.log($("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']"));
1652 nbrTrs = $("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']").length;
1654 $("#qty_"+(nbrTrs-1)+"_"+idproduct).val(product.Qty);
1655 $("#entrepot_"+(nbrTrs-1)+"_"+idproduct).val(product.Warehouse);
1657 if (modedispatch == "batch") {
1658 $("#lot_number_"+(nbrTrs-1)+"_"+idproduct).val(product.Batch);
1662 console.log("We change #qty_"+product.Id +" to match input in scanner box");
1663 $("#qty_"+product.Id).val(product.Qty);
1667 jQuery("#scantoolmessage").text(\''.
dol_escape_js($langs->transnoentities(
"QtyWasAddedToTheScannedBarcode")).
'\'+
"\n");
1670 let stringerror =
"";
1671 if (Object.keys(errortab1).length > 0) {
1672 stringerror += \
'<br>'.dol_escape_js($langs->transnoentities(
'ErrorSameBatchNumber')).
': \';
1673 errortab1.forEach(element => {
1674 stringerror += (element + ", ")
1676 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1678 if (Object.keys(errortab2).length > 0) {
1679 stringerror += \'<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCantFindCodeInInventory')).
': \';
1680 errortab2.forEach(element => {
1681 stringerror += (element + ", ")
1683 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1685 if (Object.keys(errortab3).length > 0) {
1686 stringerror += \'<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCodeScannedIsBothProductAndSerial')).
': \';
1687 errortab3.forEach(element => {
1688 stringerror += (element + ", ")
1690 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1692 if (Object.keys(errortab4).length > 0) {
1693 stringerror += \'<br>'.
dol_escape_js($langs->transnoentities(
'ErrorBarcodeNotFoundForProductWarehouse')).
': \';
1694 errortab4.forEach(element => {
1695 stringerror += (element + ", ")
1697 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
1700 jQuery("#scantoolmessage").html(\''.
dol_escape_js($langs->transnoentities(
"ErrorOnElementsInventory")).
'\' + stringerror);
1708 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,mode,autodetect=
false) {
1709 BarcodeIsInProduct=0;
1712 tabproduct.forEach(product => {
1713 $.ajax({ url: \
''.DOL_URL_ROOT.
'/expedition/ajax/searchfrombarcode.php\',
1714 data: { "token":"'.
currentToken().
'", "action":"existbarcode","fk_entrepot": warehousetouse, "barcode":element, "mode":mode},
1717 success: function(response) {
1718 if (response.status == "success") {
1719 console.log(response.message);
1720 if (!newproductrow) {
1721 newproductrow = response.object;
1724 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)) {
1725 errortab4.push(element);
1726 console.error(response.message);
1730 error : function(output) {
1731 console.error("Error on barcodeserialforproduct function");
1734 console.log("Product "+(index+=1)+": "+element);
1735 if (mode == "barcode") {
1736 testonproduct = product.Barcode
1737 }else if (mode == "lotserial") {
1738 testonproduct = product.Batch
1740 testonwarehouse = product.Warehouse;
1741 if (testonproduct == element && testonwarehouse == warehousetouse) {
1742 if (selectaddorreplace == "add") {
1743 productqty = parseInt(product.Qty,10);
1744 product.Qty = productqty + parseInt(barcodeproductqty,10);
1745 }else if (selectaddorreplace == "replace") {
1746 if (product.fetched == false) {
1747 product.Qty = barcodeproductqty
1748 product.fetched=true
1750 productqty = parseInt(product.Qty,10);
1751 product.Qty = productqty + parseInt(barcodeproductqty,10);
1754 BarcodeIsInProduct+=1;
1757 if (BarcodeIsInProduct==0 && newproductrow!=0) {
1758 tabproduct.push({\'Id\':tabproduct.length-1,\'Warehouse\':newproductrow.fk_warehouse,\'Barcode\':mode=="barcode"?element:null,\'Batch\':mode=="lotserial"?element:null,\'Qty\':barcodeproductqty,\'fetched\':true,\'reelqty\':newproductrow.reelqty,\'fk_product\':newproductrow.fk_product,\'mode\':mode});
1761 if (BarcodeIsInProduct > 0) {
1769 include DOL_DOCUMENT_ROOT.
'/core/class/html.formother.class.php';
1771 print $formother->getHTMLScannerForm(
"barcodescannerjs",
'all', 1);
1775 print
'<script type="text/javascript">
1776 $(document).ready(function () {
1777 $("select[name=fk_default_warehouse]").change(function() {
1778 console.log("warehouse is modified");
1779 var fk_default_warehouse = $("option:selected", this).val();
1780 $("select[name^=entrepot]").val(fk_default_warehouse).change();
1783 $("#autoreset").click(function() {
1784 console.log("we click on autoreset");
1785 $(".autoresettr").each(function() {
1786 id = $(this).attr("name");
1787 idtab = id.split("_");
1788 console.log("we process line "+id+" "+idtab);
1789 if ($(this).data("remove") == "clear") { /* data-remove=clear means that line qty must be cleared but line must not be removed */
1790 console.log("We clear the object to expected value")
1791 var idlinetab = idtab[0].split("-");
1793 if (idlinetab.length > 0) {
1794 idline = idlinetab[1];
1796 $("#qty"+idline+"_"+idtab[1]+"_"+idtab[2]).val("");
1798 qtyexpected = $("#qty_"+idtab[1]+"_"+idtab[2]).data("expected")
1799 console.log(qtyexpected);
1800 $("#qty_"+idtab[1]+"_"+idtab[2]).val(qtyexpected);
1801 qtydispatched = $("#qty_dispatched_0_"+idtab[2]).data("dispatched")
1802 $("#qty_dispatched_0_"+idtab[2]).val(qtydispatched);
1804 } else { /* data-remove=remove means that line must be removed */
1805 console.log("We remove the object")
1807 $("tr[name^=\'"+idtab[0]+"_\'][name$=\'_"+idtab[2]+"\']:last .splitbutton").show();
1813 $("#resetalltoexpected").click(function() {
1814 $(".qtydispatchinput").each(function() {
1815 console.log("We reset to expected "+$(this).attr("id")+" qty to dispatch");
1816 $(this).val($(this).data("expected"));
1821 $(".resetline").on("click", function(event) {
1822 event.preventDefault();
1823 id = $(this).attr("id");
1824 id = id.split("reset");
1825 console.log("Reset trigger for id = qty"+id[1]);
1826 $("#qty"+id[1]).val("");
$id
Support class for third parties, contacts, members, users or resources.
if(! $sortfield) if(! $sortorder) $object
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
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.
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 products or services.
Manage record for batch number management.
Class to manage projects.
Class to manage proposals.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
dol_stringtotime($string, $gm=1, $processnotimeasnoon=0)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
currentToken()
Return the value of token currently saved into session with name 'token'.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTFLOAT($paramname, $rounding='', $option=2)
Return the value of a $_GET or $_POST supervariable, converted into float.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
$conf db name
Only used if Module[ID]Name translation string is not found.
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $nodie=0, $mode='')
Check permissions of a user to show a page and an object.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.
shipping_prepare_head($object)
Prepare array with list of tabs.
shippingGetStandaloneDispatchSourceLine($object, $sourceLineId, $lineId, $productId)
Resolve a standalone dispatch group from the loaded shipment lines.