dolibarr 25.0.0-alpha
lib_dispatch.js.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
3 */
4// Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
5// Copyright (C) 2017 Francis Appels <francis.appels@z-application.com>
6// Copyright (C) 2026 Lenin Rivas <lenin.rivas777@gmail.com>
7//
8// This program is free software; you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by
10// the Free Software Foundation; either version 3 of the License, or
11// (at your option) any later version.
12//
13// This program is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16// GNU General Public License for more details.
17//
18// You should have received a copy of the GNU General Public License
19// along with this program. If not, see <https://www.gnu.org/licenses/>.
20// or see https://www.gnu.org/
21
27if (!defined('NOREQUIRESOC')) {
28 define('NOREQUIRESOC', '1');
29}
30if (!defined('NOCSRFCHECK')) {
31 define('NOCSRFCHECK', 1);
32}
33if (!defined('NOTOKENRENEWAL')) {
34 define('NOTOKENRENEWAL', 1);
35}
36if (!defined('NOLOGIN')) {
37 define('NOLOGIN', 1);
38}
39if (!defined('NOREQUIREMENU')) {
40 define('NOREQUIREMENU', 1);
41}
42if (!defined('NOREQUIREHTML')) {
43 define('NOREQUIREHTML', 1);
44}
45if (!defined('NOREQUIREAJAX')) {
46 define('NOREQUIREAJAX', '1');
47}
48
49session_cache_limiter('public');
50
51require_once '../../main.inc.php';
56// Load translation files required by the page
57$langs->loadLangs(array("productbatch"));
58
59// Define javascript type
60top_httphead('text/javascript; charset=UTF-8');
61// Important: Following code is to avoid page request by browser and PHP CPU at each Dolibarr page access.
62header('Cache-Control: max-age=10800, public, must-revalidate');
63
64?>
74function addDispatchLine(index, type, mode, lotnumber) {
75 mode = mode || 'qtymissing'
76 lotnumber = lotnumber || ''
77
78 console.log("fourn/js/lib_dispatch.js.php addDispatchLine Split line type="+type+" index="+index+" mode="+mode);
79
80 var $row0 = $("tr[name='"+type+'_0_'+index+"']");
81 var $dpopt = $row0.find('.hasDatepicker').first().datepicker('option', 'all'); // get current datepicker options to apply the same to the cloned datepickers
82 var $row = $row0.clone(true); // clone first batch line to jQuery object
83 var nbrTrs = $("tr[name^='"+type+"_'][name$='_"+index+"']").length; // count nb of tr line with attribute name that starts with 'batch_' or 'dispatch_', and end with _index
84 var qtyOrdered = parseFloat($("#qty_ordered_0_"+index).val()); // Qty ordered is same for all rows
85
86 var qty = parseFloat($("#qty_"+(nbrTrs - 1)+"_"+index).val());
87 if (isNaN(qty)) {
88 qty = '';
89 }
90
91 console.log("fourn/js/lib_dispatch.js.php addDispatchLine Split line nbrTrs="+nbrTrs+" qtyOrdered="+qtyOrdered+" qty="+qty);
92
93 var qtyDispatched;
94
95 if (mode === 'lessone') {
96 qtyDispatched = parseFloat($("#qty_dispatched_0_" + index).val()) + 1;
97 }
98 else {
99 qtyDispatched = parseFloat($("#qty_dispatched_0_" + index).val()) + qty;
100 // If user did not reduced the qty to dispatch on old line, we keep only 1 on old line and the rest on new line
101 if (lotnumber === '' && qtyDispatched == qtyOrdered && qtyDispatched > 1) {
102 qtyDispatched = parseFloat($("#qty_dispatched_0_" + index).val()) + 1;
103 mode = 'lessone';
104 }
105 }
106 console.log("qtyDispatched=" + qtyDispatched + " qtyOrdered=" + qtyOrdered+ " qty=" + qty);
107
108 if (qty <= 1 && lotnumber === '') {
109 window.alert("Remain quantity to dispatch is too low to be split");
110 } else {
111 var oldlineqty = qtyDispatched;
112 var newlineqty = qtyOrdered - qtyDispatched;
113 if (lotnumber !== '') {
114 newlineqty = 1; // One serial number = one unit, previous line is kept unchanged
115 } else if (newlineqty <= 0) {
116 newlineqty = qty - 1;
117 oldlineqty = 1;
118 $("#qty_"+(nbrTrs - 1)+"_"+index).val(oldlineqty);
119 }
120
121 //replace tr suffix nbr
122 $row.html($row.html().replace(/_0_/g, "_" + nbrTrs + "_"));
123
124 // jquery's deep clone is incompatible with date pickers (the clone shares data with the original)
125 // so we destroy and rebuild the new date pickers
126 setTimeout(() => {
127 $row.find('.hasDatepicker').each((i, dp) => {
128 $(dp).removeData()
129 .removeClass('hasDatepicker');
130 $(dp).next('img.ui-datepicker-trigger').remove();
131 $(dp).datepicker($dpopt);
132 });
133 }, 0);
134
135 //create new select2 to avoid duplicate id of cloned one
136 $row.find("select[name='" + 'entrepot_' + nbrTrs + '_' + index + "']").select2();
137 // Copy selected option to new select
138 let $prevRow = $("tr[name^='" + type + "_'][name$='_" + index + "']:last")
139 let $prevEntr = Number($prevRow.find("select[name='" + 'entrepot_' + (nbrTrs-1) + '_' + index + "']").val())
140 $row.find("select[name='" + 'entrepot_' + nbrTrs + '_' + index + "']").val($prevEntr)
141 // TODO find solution to keep new tr's after page refresh
142 //clear value
143 $row.find("input[name^='qty']").val('');
144 //change name of new row
145 $row.attr('name', type + '_' + nbrTrs + '_' + index);
146 //insert new row before last row
147 $("tr[name^='" + type + "_'][name$='_" + index + "']:last").after($row);
148
149 //remove cloned select2 with duplicate id.
150 $("#s2id_entrepot_" + nbrTrs + '_' + index).detach(); // old way to find duplicated select2 component
151 $(".csswarehouse_" + nbrTrs + "_" + index + ":first-child").parent("span.selection").parent(".select2").detach();
152
153 /* Suffix of lines are: _ trs.length _ index */
154 $("#qty_"+nbrTrs+"_"+index).focus();
155 $("#qty_dispatched_0_"+index).val(oldlineqty);
156
157 //hide all buttons then show only the last one
158 $("tr[name^='" + type + "_'][name$='_" + index + "'] .splitbutton").hide();
159 $("tr[name^='" + type + "_'][name$='_" + index + "']:last .splitbutton").show();
160
161 $("#reset_" + (nbrTrs) + "_" + index).click(function (event) {
162 event.preventDefault();
163 id = $(this).attr("id");
164 id = id.split("reset_");
165 idrow = id[1];
166 idlast = $("tr[name^='" + type + "_'][name$='_" + index + "']:last .qtydispatchinput").attr("id");
167 if (idlast == $("#qty_" + idrow).attr("id")) {
168 console.log("Remove trigger for tr name = " + type + "_" + idrow);
169 $('tr[name="' + type + '_' + idrow + '"').remove();
170 $("tr[name^='" + type + "_'][name$='_" + index + "']:last .splitbutton").show();
171 } else {
172 console.log("fourn/js/lib_dispatch.js.php Reset trigger for id = qty_" + idrow);
173 $("#qty_" + idrow).val("");
174 }
175 });
176
177 if (mode === 'lessone')
178 {
179 qty = 1; // keep 1 in old line
180 $("#qty_"+(nbrTrs-1)+"_"+index).val(qty);
181 }
182 $("#qty_"+nbrTrs+"_"+index).val(newlineqty);
183 // Store arbitrary data for dispatch qty input field change event
184 $("#qty_" + (nbrTrs - 1) + "_" + index).data('qty', qty);
185 $("#qty_" + (nbrTrs - 1) + "_" + index).data('type', type);
186 $("#qty_" + (nbrTrs - 1) + "_" + index).data('index', index);
187 // Update dispatched qty when value dispatch qty input field changed
188 //$("#qty_" + (nbrTrs - 1) + "_" + index).change(this.onChangeDispatchLineQty);
189 //Clean bad values
190 $("tr[name^='" + type + "_'][name$='_" + index + "']:last").data("remove", "remove");
191 $("#lot_number_" + (nbrTrs) + "_" + index).val(lotnumber)
192 $("#idline_" + (nbrTrs) + "_" + index).val("-1")
193 $("#qty_" + (nbrTrs) + "_" + index).data('expected', "0");
194 $("#lot_number_" + (nbrTrs) + "_" + index).removeAttr("disabled");
195 //set focus on lot of new line (if it exists)
196 if ($("#lot_number_" + (nbrTrs) + "_" + index).length) {
197 $("#lot_number_" + (nbrTrs) + "_" + index).focus().select();
198 } else {
199 $("#qty_" + (nbrTrs) + "_" + index).focus().select();
200 }
201 }
202}
203
213function addDispatchLinesFromSerialList(index, type) {
214 console.log("fourn/js/lib_dispatch.js.php addDispatchLinesFromSerialList type="+type+" index="+index);
215
216 var $dialog = jQuery("#dialogforpopup");
217 var html = '<textarea id="seriallist" class="centpercent" rows="10" placeholder=<?php echo "'".dol_escape_js(dol_escape_htmltag($langs->transnoentitiesnoconv("EnterOneSerialNumberPerLine")))."'" ; ?>></textarea>';
218 html += '<div id="seriallistmessage" class="opacitymedium paddingtop"></div>';
219 $dialog.html(html);
220
221 // Return the list of non empty trimmed lines
222 var getSerialList = function() {
223 return jQuery("#seriallist").val().split(/\r\n|\r|\n/).map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; });
224 };
225
226 jQuery("#seriallist").on("input", function() {
227 jQuery("#seriallistmessage").removeClass("error").text(<?php echo "'".dol_escape_js($langs->transnoentitiesnoconv("NbOfSerialNumbersDetected", "%s"))."'" ; ?>.replace('%s', getSerialList().length));
228 });
229
230 $dialog.dialog({
231 title: <?php echo "'".dol_escape_js($langs->transnoentitiesnoconv("EnterMultipleSerialNumbers"))."'" ; ?>,
232 modal: true,
233 resizable: false,
234 width: 'auto',
235 buttons: [
236 {
237 text: <?php echo "'".dol_escape_js($langs->transnoentitiesnoconv("Apply"))."'" ; ?>,
238 click: function() {
239 var serials = getSerialList();
240 if (serials.length == 0) {
241 return;
242 }
243 // Cheap early check of duplicates inside the list. Uniqueness in stock is still checked by MouvementStock.
244 var seen = {};
245 for (var n = 0; n < serials.length; n++) {
246 if (seen[serials[n]]) {
247 jQuery("#seriallistmessage").addClass("error").text(<?php echo "'".dol_escape_js($langs->transnoentitiesnoconv("ErrorDuplicateSerialNumberInList", "%s"))."'" ; ?>.replace('%s', serials[n]));
248 return;
249 }
250 seen[serials[n]] = true;
251 }
252
253 var nbrTrs = $("tr[name^='"+type+"_'][name$='_"+index+"']").length;
254 var $lastlot = $("#lot_number_"+(nbrTrs - 1)+"_"+index);
255 var k = 0;
256 // Reuse the last line if it is still empty (not a saved line and no lot/serial entered yet)
257 if (!$lastlot.prop('disabled') && $lastlot.val().trim() === '') {
258 $lastlot.val(serials[0]);
259 $("#qty_"+(nbrTrs - 1)+"_"+index).val(1);
260 k = 1;
261 }
262 for (; k < serials.length; k++) {
263 addDispatchLine(index, type, 'qtymissing', serials[k]);
264 }
265
266 jQuery(this).dialog("close");
267 }
268 },
269 {
270 text: <?php echo "'".dol_escape_js($langs->transnoentitiesnoconv("Cancel"))."'" ; ?>,
271 click: function() {
272 jQuery(this).dialog("close");
273 }
274 }
275 ]
276 });
277 jQuery("#seriallist").focus();
278}
279
289function onChangeDispatchLineQty(element) {
290 var type = $(element).data('type'),
291 qty = parseFloat($(element).data('expected')),
292 changedQty, nbrTrs, dispatchingQty, qtyOrdered, qtyDispatched;
293 id = $(element).attr("id");
294 id = id.split("_");
295 index = id[2];
296
297 if (index >= 0 && type && qty >= 0) {
298 nbrTrs = $("tr[name^='" + type + "_'][name$='_" + index + "']").length;
299 qtyChanged = parseFloat($(element).val()) - qty; // qty changed
300 qtyDispatching = parseFloat($(element).val()); // qty currently being dispatched
301 qtyOrdered = parseFloat($("#qty_ordered_0_" + index).val()); // qty ordered
302 qtyDispatched = parseFloat($("#qty_dispatched_0_" + index).val()); // qty already dispatched
303
304 console.log("onChangeDispatchLineQty qtyChanged: " + qtyChanged + " qtyDispatching: " + qtyDispatching + " qtyOrdered: " + qtyOrdered + " qtyDispatched: " + qtyDispatched);
305
306 if ((qtyChanged) <= (qtyOrdered - (qtyDispatched + qtyDispatching))) {
307 $("#qty_dispatched_0_" + index).val(qtyDispatched + qtyChanged);
308 } else {
309 /*console.log("eee");
310 $(element).val($(element).data('expected'));*/
311 }
312 $(element).data('expected', $(element).val());
313 }
314}
315
316$(document).ready(function () {
317 $(document).on('keydown', '.inputlotnumber', function (e) {
318 if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
319 e.preventDefault();
320
321 var $input = $(this);
322 var $tr = $input.closest('tr');
323 var trName = $tr.attr('name');
324 if (!trName) {
325 return;
326 }
327
328 var parts = trName.split('_');
329 if (parts.length < 3) {
330 return;
331 }
332
333 var type = parts[0];
334 var rowIdx = parseInt(parts[1], 10);
335 var prodIdx = parseInt(parts[2], 10);
336
337 var $rows = $("tr[name^='" + type + "_'][name$='_" + prodIdx + "']");
338 var nbrTrs = $rows.length;
339
340 // If user is on the last row of this product line
341 if (rowIdx === nbrTrs - 1) {
342 var qty = parseFloat($("#qty_" + rowIdx + "_" + prodIdx).val());
343 if (qty > 1) {
344 addDispatchLine(prodIdx, type);
345 } else {
346 // Last line for this product reached, try focusing next product line if available
347 var nextProdIdx = prodIdx + 1;
348 var $nextLot = $("#lot_number_0_" + nextProdIdx);
349 if ($nextLot.length && !$nextLot.prop('disabled')) {
350 $nextLot.focus().select();
351 }
352 }
353 } else {
354 // Move focus to next row's lot input if it exists
355 var $nextRowLot = $("#lot_number_" + (rowIdx + 1) + "_" + prodIdx);
356 if ($nextRowLot.length) {
357 $nextRowLot.focus().select();
358 }
359 }
360 }
361 });
362});
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140