dolibarr 21.0.0-alpha
mo_production.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2019-2020 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2023 Christian Humpel <christian.humpel@gmail.com>
4 * Copyright (C) 2023 Vincent de Grandpré <vincent@de-grandpre.quebec>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
7 * Copyright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
29// Load Dolibarr environment
30require '../main.inc.php';
31require_once DOL_DOCUMENT_ROOT.'/bom/class/bom.class.php';
32require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
33require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
34require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
35require_once DOL_DOCUMENT_ROOT.'/mrp/class/mo.class.php';
36require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
37require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
38require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
39require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
40require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
42require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
43require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
44
45
46// Load translation files required by the page
47$langs->loadLangs(array("mrp", "stocks", "other", "product", "productbatch"));
48
49// Get parameters
50$id = GETPOSTINT('id');
51$ref = GETPOST('ref', 'alpha');
52$action = GETPOST('action', 'aZ09');
53$confirm = GETPOST('confirm', 'alpha');
54$cancel = GETPOST('cancel', 'aZ09');
55$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'mocard'; // To manage different context of search
56$backtopage = GETPOST('backtopage', 'alpha');
57$lineid = GETPOSTINT('lineid');
58$fk_movement = GETPOSTINT('fk_movement');
59$fk_default_warehouse = GETPOSTINT('fk_default_warehouse');
60
61$collapse = GETPOST('collapse', 'aZ09comma');
62
63// Initialize a technical objects
64$object = new Mo($db);
65$extrafields = new ExtraFields($db);
66$diroutputmassaction = $conf->mrp->dir_output.'/temp/massgeneration/'.$user->id;
67$objectline = new MoLine($db);
68
69$hookmanager->initHooks(array('moproduction', 'globalcard')); // Note that conf->hooks_modules contains array
70
71// Fetch optionals attributes and labels
72$extrafields->fetch_name_optionals_label($object->table_element);
73
74$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
75
76// Initialize array of search criteria
77$search_all = GETPOST("search_all", 'alpha');
78$search = array();
79foreach ($object->fields as $key => $val) {
80 if (GETPOST('search_'.$key, 'alpha')) {
81 $search[$key] = GETPOST('search_'.$key, 'alpha');
82 }
83}
84
85if (empty($action) && empty($id) && empty($ref)) {
86 $action = 'view';
87}
88
89// Load object
90include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be 'include', not 'include_once'.
91
92// Security check - Protection if external user
93//if ($user->socid > 0) accessforbidden();
94//if ($user->socid > 0) $socid = $user->socid;
95$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0);
96$result = restrictedArea($user, 'mrp', $object->id, 'mrp_mo', '', 'fk_soc', 'rowid', $isdraft);
97
98// Permissions
99$permissionnote = $user->hasRight('mrp', 'write'); // Used by the include of actions_setnotes.inc.php
100$permissiondellink = $user->hasRight('mrp', 'write'); // Used by the include of actions_dellink.inc.php
101$permissiontoadd = $user->hasRight('mrp', 'write'); // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php
102$permissiontodelete = $user->hasRight('mrp', 'delete') || ($permissiontoadd && isset($object->status) && $object->status == $object::STATUS_DRAFT);
103
104$permissiontoproduce = $permissiontoadd;
105$permissiontoupdatecost = $user->hasRight('bom', 'read'); // User who can define cost must have knowledge of pricing
106
107$upload_dir = $conf->mrp->multidir_output[isset($object->entity) ? $object->entity : 1];
108
109
110/*
111 * Actions
112 */
113
114$parameters = array();
115$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
116if ($reshook < 0) {
117 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
118}
119
120if (empty($reshook)) {
121 $error = 0;
122
123 $backurlforlist = DOL_URL_ROOT.'/mrp/mo_list.php';
124
125 if (empty($backtopage) || ($cancel && empty($id))) {
126 //var_dump($backurlforlist);exit;
127 if (empty($id) && (($action != 'add' && $action != 'create') || $cancel)) {
128 $backtopage = $backurlforlist;
129 } else {
130 $backtopage = DOL_URL_ROOT.'/mrp/mo_production.php?id='.($id > 0 ? $id : '__ID__');
131 }
132 }
133 $triggermodname = 'MO_MODIFY'; // Name of trigger action code to execute when we modify record
134
135 if ($action == 'confirm_cancel' && $confirm == 'yes' && !empty($permissiontoadd)) {
136 $also_cancel_consumed_and_produced_lines = (GETPOST('alsoCancelConsumedAndProducedLines', 'alpha') ? 1 : 0);
137 $result = $object->cancel($user, 0, $also_cancel_consumed_and_produced_lines);
138 if ($result > 0) {
139 header("Location: " . DOL_URL_ROOT.'/mrp/mo_card.php?id=' . $object->id);
140 exit;
141 } else {
142 $action = '';
143 setEventMessages($object->error, $object->errors, 'errors');
144 }
145 } elseif ($action == 'confirm_delete' && $confirm == 'yes' && !empty($permissiontodelete)) {
146 $also_cancel_consumed_and_produced_lines = (GETPOST('alsoCancelConsumedAndProducedLines', 'alpha') ? 1 : 0);
147 $result = $object->delete($user, 0, $also_cancel_consumed_and_produced_lines);
148 if ($result > 0) {
149 header("Location: " . $backurlforlist);
150 exit;
151 } else {
152 $action = '';
153 setEventMessages($object->error, $object->errors, 'errors');
154 }
155 }
156
157 // Actions cancel, add, update, delete or clone
158 include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php';
159
160 // Actions when linking object each other
161 include DOL_DOCUMENT_ROOT.'/core/actions_dellink.inc.php';
162
163 // Actions when printing a doc from card
164 include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
165
166 // Actions to send emails
167 $triggersendname = 'MO_SENTBYMAIL';
168 $autocopy = 'MAIN_MAIL_AUTOCOPY_MO_TO';
169 $trackid = 'mo'.$object->id;
170 include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';
171
172 // Action to move up and down lines of object
173 //include DOL_DOCUMENT_ROOT.'/core/actions_lineupdown.inc.php'; // Must be 'include', not 'include_once'
174
175 if ($action == 'set_thirdparty' && $permissiontoadd) {
176 $object->setValueFrom('fk_soc', GETPOSTINT('fk_soc'), '', null, 'date', '', $user, $triggermodname);
177 }
178 if ($action == 'classin' && $permissiontoadd) {
179 $object->setProject(GETPOSTINT('projectid'));
180 }
181
182 if ($action == 'confirm_reopen' && $permissiontoadd) {
183 $result = $object->setStatut($object::STATUS_INPROGRESS, 0, '', 'MRP_REOPEN');
184 }
185
186 if (($action == 'confirm_addconsumeline' && GETPOST('addconsumelinebutton') && $permissiontoadd)
187 || ($action == 'confirm_addproduceline' && GETPOST('addproducelinebutton') && $permissiontoadd)) {
188 $moline = new MoLine($db);
189
190 // Line to produce
191 $moline->fk_mo = $object->id;
192 $moline->qty = GETPOSTINT('qtytoadd');
193 $moline->fk_product = GETPOSTINT('productidtoadd');
194 if (GETPOST('addconsumelinebutton')) {
195 $moline->role = 'toconsume';
196 } else {
197 $moline->role = 'toproduce';
198 }
199 $moline->origin_type = 'free'; // free consume line
200 $moline->position = 0;
201
202 // Is it a product or a service ?
203 if (!empty($moline->fk_product)) {
204 $tmpproduct = new Product($db);
205 $tmpproduct->fetch($moline->fk_product);
206 if ($tmpproduct->type == Product::TYPE_SERVICE) {
207 $moline->fk_default_workstation = $tmpproduct->fk_default_workstation;
208 }
209 $moline->disable_stock_change = ($tmpproduct->type == Product::TYPE_SERVICE ? 1 : 0);
210 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
211 $moline->fk_unit = $tmpproduct->fk_unit;
212 }
213 }
214 // Extrafields
215 $extralabelsline = $extrafields->fetch_name_optionals_label($object->table_element_line);
216 $array_options = $extrafields->getOptionalsFromPost($object->table_element_line);
217 // Unset extrafield
218 if (is_array($extralabelsline)) {
219 // Get extra fields
220 foreach ($extralabelsline as $key => $value) {
221 unset($_POST["options_".$key]);
222 }
223 }
224 if (is_array($array_options) && count($array_options) > 0) {
225 $moline->array_options = $array_options;
226 }
227
228 $resultline = $moline->create($user, false); // Never use triggers here
229 if ($resultline <= 0) {
230 $error++;
231 setEventMessages($moline->error, $moline->errors, 'errors');
232 }
233
234 $action = '';
235 // Redirect to refresh the tab information
236 header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
237 exit;
238 }
239
240 if (in_array($action, array('confirm_consumeorproduce', 'confirm_consumeandproduceall')) && $permissiontoproduce) {
241 $stockmove = new MouvementStock($db);
242
243 $labelmovement = GETPOST('inventorylabel', 'alphanohtml');
244 $codemovement = GETPOST('inventorycode', 'alphanohtml');
245
246 $db->begin();
247 $pos = 0;
248 // Process line to consume
249 foreach ($object->lines as $line) {
250 if ($line->role == 'toconsume') {
251 $tmpproduct = new Product($db);
252 $tmpproduct->fetch($line->fk_product);
253
254 $i = 1;
255 while (GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
256 $qtytoprocess = (float) price2num(GETPOST('qty-'.$line->id.'-'.$i));
257
258 if ($qtytoprocess != 0) {
259 // Check warehouse is set if we should have to
260 if (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i)) { // If there is a warehouse to set
261 if (!(GETPOST('idwarehouse-'.$line->id.'-'.$i) > 0)) { // If there is no warehouse set.
262 $langs->load("errors");
263 setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), $tmpproduct->ref), null, 'errors');
264 $error++;
265 }
266 if ($tmpproduct->status_batch && (!GETPOST('batch-'.$line->id.'-'.$i))) {
267 $langs->load("errors");
268 setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), $tmpproduct->ref), null, 'errors');
269 $error++;
270 }
271 }
272
273 $idstockmove = 0;
274 if (!$error && GETPOST('idwarehouse-'.$line->id.'-'.$i) > 0) {
275 // Record stock movement
276 $id_product_batch = 0;
277 $stockmove->setOrigin($object->element, $object->id);
278 $stockmove->context['mrp_role'] = 'toconsume';
279
280 if ($qtytoprocess >= 0) {
281 $idstockmove = $stockmove->livraison($user, $line->fk_product, GETPOST('idwarehouse-'.$line->id.'-'.$i), $qtytoprocess, 0, $labelmovement, dol_now(), '', '', GETPOST('batch-'.$line->id.'-'.$i), $id_product_batch, $codemovement);
282 } else {
283 $idstockmove = $stockmove->reception($user, $line->fk_product, GETPOST('idwarehouse-'.$line->id.'-'.$i), $qtytoprocess * -1, 0, $labelmovement, dol_now(), '', '', GETPOST('batch-'.$line->id.'-'.$i), $id_product_batch, $codemovement);
284 }
285 if ($idstockmove < 0) {
286 $error++;
287 setEventMessages($stockmove->error, $stockmove->errors, 'errors');
288 }
289 }
290
291 if (!$error) {
292 // Record consumption
293 $moline = new MoLine($db);
294 $moline->fk_mo = $object->id;
295 $moline->position = $pos;
296 $moline->fk_product = $line->fk_product;
297 $moline->fk_warehouse = GETPOSTINT('idwarehouse-'.$line->id.'-'.$i);
298 $moline->qty = $qtytoprocess;
299 $moline->batch = GETPOST('batch-'.$line->id.'-'.$i);
300 $moline->role = 'consumed';
301 $moline->fk_mrp_production = $line->id;
302 $moline->fk_stock_movement = $idstockmove == 0 ? null : $idstockmove;
303 $moline->fk_user_creat = $user->id;
304
305 $resultmoline = $moline->create($user);
306 if ($resultmoline <= 0) {
307 $error++;
308 setEventMessages($moline->error, $moline->errors, 'errors');
309 }
310
311 $pos++;
312 }
313 }
314
315 $i++;
316 }
317 }
318 }
319
320 // Process line to produce
321 $pos = 0;
322
323 foreach ($object->lines as $line) {
324 if ($line->role == 'toproduce') {
325 $tmpproduct = new Product($db);
326 $tmpproduct->fetch($line->fk_product);
327
328 $i = 1;
329 while (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
330 $qtytoprocess = (float) price2num(GETPOST('qtytoproduce-'.$line->id.'-'.$i));
331 $pricetoprocess = GETPOST('pricetoproduce-'.$line->id.'-'.$i) ? price2num(GETPOST('pricetoproduce-'.$line->id.'-'.$i)) : 0;
332
333 if ($qtytoprocess != 0) {
334 // Check warehouse is set if we should have to
335 if (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i)) { // If there is a warehouse to set
336 if (!(GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) > 0)) { // If there is no warehouse set.
337 $langs->load("errors");
338 setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), $tmpproduct->ref), null, 'errors');
339 $error++;
340 }
341 if (isModEnabled('productbatch') && $tmpproduct->status_batch && (!GETPOST('batchtoproduce-'.$line->id.'-'.$i))) {
342 $langs->load("errors");
343 setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), $tmpproduct->ref), null, 'errors');
344 $error++;
345 }
346 }
347
348 $idstockmove = 0;
349 if (!$error && GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) > 0) {
350 // Record stock movement
351 $id_product_batch = 0;
352 $stockmove->origin_type = $object->element;
353 $stockmove->origin_id = $object->id;
354 $stockmove->context['mrp_role'] = 'toproduce';
355
356 $idstockmove = $stockmove->reception($user, $line->fk_product, GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i), $qtytoprocess, $pricetoprocess, $labelmovement, '', '', GETPOST('batchtoproduce-'.$line->id.'-'.$i), dol_now(), $id_product_batch, $codemovement);
357 if ($idstockmove < 0) {
358 $error++;
359 setEventMessages($stockmove->error, $stockmove->errors, 'errors');
360 }
361 }
362
363 if (!$error) {
364 // Record production
365 $moline = new MoLine($db);
366 $moline->fk_mo = $object->id;
367 $moline->position = $pos;
368 $moline->fk_product = $line->fk_product;
369 $moline->fk_warehouse = GETPOSTINT('idwarehousetoproduce-'.$line->id.'-'.$i);
370 $moline->qty = $qtytoprocess;
371 $moline->batch = GETPOST('batchtoproduce-'.$line->id.'-'.$i);
372 $moline->role = 'produced';
373 $moline->fk_mrp_production = $line->id;
374 $moline->fk_stock_movement = $idstockmove;
375 $moline->fk_user_creat = $user->id;
376
377 $resultmoline = $moline->create($user);
378 if ($resultmoline <= 0) {
379 $error++;
380 setEventMessages($moline->error, $moline->errors, 'errors');
381 }
382
383 $pos++;
384 }
385 }
386
387 $i++;
388 }
389 }
390 }
391
392 if (!$error) {
393 $consumptioncomplete = true;
394 $productioncomplete = true;
395
396 if (GETPOSTINT('autoclose')) {
397 foreach ($object->lines as $line) {
398 if ($line->role == 'toconsume') {
399 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
400 $alreadyconsumed = 0;
401 foreach ($arrayoflines as $line2) {
402 $alreadyconsumed += $line2['qty'];
403 }
404
405 if ($alreadyconsumed < $line->qty) {
406 $consumptioncomplete = false;
407 }
408 }
409 if ($line->role == 'toproduce') {
410 $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
411 $alreadyproduced = 0;
412 foreach ($arrayoflines as $line2) {
413 $alreadyproduced += $line2['qty'];
414 }
415
416 if ($alreadyproduced < $line->qty) {
417 $productioncomplete = false;
418 }
419 }
420 }
421 } else {
422 $consumptioncomplete = false;
423 $productioncomplete = false;
424 }
425
426 // Update status of MO
427 dol_syslog("consumptioncomplete = ".json_encode($consumptioncomplete)." productioncomplete = ".json_encode($productioncomplete));
428 if ($consumptioncomplete && $productioncomplete) {
429 $result = $object->setStatut($object::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
430 } else {
431 $result = $object->setStatut($object::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
432 }
433 if ($result <= 0) {
434 $error++;
435 setEventMessages($object->error, $object->errors, 'errors');
436 }
437 }
438
439 if ($error) {
440 $action = str_replace('confirm_', '', $action);
441 $db->rollback();
442 } else {
443 $db->commit();
444
445 // Redirect to avoid to action done a second time if we make a back from browser
446 header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
447 exit;
448 }
449 }
450
451 // Action close produced
452 if ($action == 'confirm_produced' && $confirm == 'yes' && $permissiontoadd) {
453 $result = $object->setStatut($object::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
454 if ($result >= 0) {
455 // Define output language
456 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
457 $outputlangs = $langs;
458 $newlang = '';
459 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && GETPOST('lang_id', 'aZ09')) {
460 $newlang = GETPOST('lang_id', 'aZ09');
461 }
462 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) {
463 $newlang = $object->thirdparty->default_lang;
464 }
465 if (!empty($newlang)) {
466 $outputlangs = new Translate("", $conf);
467 $outputlangs->setDefaultLang($newlang);
468 }
469 $model = $object->model_pdf;
470 $ret = $object->fetch($id); // Reload to get new records
471
472 $object->generateDocument($model, $outputlangs, 0, 0, 0);
473 }
474 } else {
475 setEventMessages($object->error, $object->errors, 'errors');
476 }
477 }
478
479 if ($action == 'confirm_editline' && $permissiontoadd) {
480 $moline = new MoLine($db);
481 $res = $moline->fetch(GETPOSTINT('lineid'));
482 if ($result > 0) {
483 $extrafields->fetch_name_optionals_label($moline->element);
484 foreach ($extrafields->attributes[$moline->table_element]['label'] as $key => $label) {
485 $value = GETPOST('options_'.$key, 'alphanohtml');
486 $moline->array_options["options_".$key] = $value;
487 }
488 $moline->qty = GETPOSTINT('qty_lineProduce');
489 $res = $moline->update($user);
490 if ($res < 0) {
491 setEventMessages($moline->error, $moline->errors, 'errors');
492 header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
493 exit;
494 }
495 header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
496 exit;
497 }
498 }
499}
500
501
502
503/*
504 * View
505 */
506
507$form = new Form($db);
508$formproject = new FormProjets($db);
509$formproduct = new FormProduct($db);
510$tmpwarehouse = new Entrepot($db);
511$tmpbatch = new Productlot($db);
512$tmpstockmovement = new MouvementStock($db);
513
514$title = $langs->trans('Mo');
515$help_url = 'EN:Module_Manufacturing_Orders|FR:Module_Ordres_de_Fabrication|DE:Modul_Fertigungsauftrag';
516$morejs = array('/mrp/js/lib_dispatch.js.php');
517llxHeader('', $title, $help_url, '', 0, 0, $morejs, '', '', 'mod-mrp page-card_production');
518
519$newToken = newToken();
520
521// Part to show record
522if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) {
523 $res = $object->fetch_thirdparty();
524 $res = $object->fetch_optionals();
525
526 if (getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') && $object->fk_warehouse > 0) {
527 $tmpwarehouse->fetch($object->fk_warehouse);
528 $fk_default_warehouse = $object->fk_warehouse;
529 }
530
531 $head = moPrepareHead($object);
532
533 print dol_get_fiche_head($head, 'production', $langs->trans("ManufacturingOrder"), -1, $object->picto);
534
535 $formconfirm = '';
536
537 // Confirmation to delete
538 if ($action == 'delete') {
539 $formquestion = array(
540 array(
541 'label' => $langs->trans('MoCancelConsumedAndProducedLines'),
542 'name' => 'alsoCancelConsumedAndProducedLines',
543 'type' => 'checkbox',
544 'value' => 0
545 ),
546 );
547 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteMo'), $langs->trans('ConfirmDeleteMo'), 'confirm_delete', $formquestion, 0, 1);
548 }
549 // Confirmation to delete line
550 if ($action == 'deleteline') {
551 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid.'&fk_movement='.$fk_movement, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
552 }
553 // Clone confirmation
554 if ($action == 'clone') {
555 // Create an array for form
556 $formquestion = array();
557 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMo', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
558 }
559
560 // Confirmation of validation
561 if ($action == 'validate') {
562 // We check that object has a temporary ref
563 $ref = substr($object->ref, 1, 4);
564 if ($ref == 'PROV') {
565 $object->fetch_product();
566 $numref = $object->getNextNumRef($object->product);
567 } else {
568 $numref = $object->ref;
569 }
570
571 $text = $langs->trans('ConfirmValidateMo', $numref);
572 /*if (isModEnabled('notification'))
573 {
574 require_once DOL_DOCUMENT_ROOT . '/core/class/notify.class.php';
575 $notify = new Notify($db);
576 $text .= '<br>';
577 $text .= $notify->confirmMessage('BOM_VALIDATE', $object->socid, $object);
578 }*/
579
580 $formquestion = array();
581 if (isModEnabled('mrp')) {
582 $langs->load("mrp");
583 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
584 $formproduct = new FormProduct($db);
585 $forcecombo = 0;
586 if ($conf->browser->name == 'ie') {
587 $forcecombo = 1; // There is a bug in IE10 that make combo inside popup crazy
588 }
589 $formquestion = array(
590 // 'text' => $langs->trans("ConfirmClone"),
591 // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => 1),
592 // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' => 1),
593 );
594 }
595
596 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Validate'), $text, 'confirm_validate', $formquestion, 0, 1, 220);
597 }
598
599 // Confirmation to cancel
600 if ($action == 'cancel') {
601 $formquestion = array(
602 array(
603 'label' => $langs->trans('MoCancelConsumedAndProducedLines'),
604 'name' => 'alsoCancelConsumedAndProducedLines',
605 'type' => 'checkbox',
606 'value' => !getDolGlobalString('MO_ALSO_CANCEL_CONSUMED_AND_PRODUCED_LINES_BY_DEFAULT') ? 0 : 1
607 ),
608 );
609 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"] . '?id=' . $object->id, $langs->trans('CancelMo'), $langs->trans('ConfirmCancelMo'), 'confirm_cancel', $formquestion, 0, 1);
610 }
611
612 // Call Hook formConfirm
613 $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
614 $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
615 if (empty($reshook)) {
616 $formconfirm .= $hookmanager->resPrint;
617 } elseif ($reshook > 0) {
618 $formconfirm = $hookmanager->resPrint;
619 }
620
621 // Print form confirm
622 print $formconfirm;
623
624
625 // MO file
626 // ------------------------------------------------------------
627 $linkback = '<a href="'.DOL_URL_ROOT.'/mrp/mo_list.php?restore_lastsearch_values=1'.(!empty($socid) ? '&socid='.$socid : '').'">'.$langs->trans("BackToList").'</a>';
628
629 $morehtmlref = '<div class="refidno">';
630
631 /*
632 // Ref bis
633 $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', 0, 1);
634 $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', null, null, '', 1);
635 */
636
637 // Thirdparty
638 if (is_object($object->thirdparty)) {
639 $morehtmlref .= $object->thirdparty->getNomUrl(1, 'customer');
640 if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $object->thirdparty->id > 0) {
641 $morehtmlref .= ' (<a href="'.DOL_URL_ROOT.'/commande/list.php?socid='.$object->thirdparty->id.'&search_societe='.urlencode($object->thirdparty->name).'">'.$langs->trans("OtherOrders").'</a>)';
642 }
643 }
644
645 // Project
646 if (isModEnabled('project')) {
647 $langs->load("projects");
648 if (is_object($object->thirdparty)) {
649 $morehtmlref .= '<br>';
650 }
651 if ($permissiontoadd) {
652 $morehtmlref .= img_picto($langs->trans("Project"), 'project', 'class="pictofixedwidth"');
653 if ($action != 'classify') {
654 $morehtmlref .= '<a class="editfielda" href="'.$_SERVER['PHP_SELF'].'?action=classify&token='.newToken().'&id='.$object->id.'">'.img_edit($langs->transnoentitiesnoconv('SetProject')).'</a> ';
655 }
656 $morehtmlref .= $form->form_project($_SERVER['PHP_SELF'].'?id='.$object->id, $object->socid, $object->fk_project, ($action == 'classify' ? 'projectid' : 'none'), 0, 0, 0, 1, '', 'maxwidth300');
657 } else {
658 if (!empty($object->fk_project)) {
659 $proj = new Project($db);
660 $proj->fetch($object->fk_project);
661 $morehtmlref .= $proj->getNomUrl(1);
662 if ($proj->title) {
663 $morehtmlref .= '<span class="opacitymedium"> - '.dol_escape_htmltag($proj->title).'</span>';
664 }
665 }
666 }
667 }
668 $morehtmlref .= '</div>';
669
670
671 dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
672
673
674 print '<div class="fichecenter">';
675 print '<div class="fichehalfleft">';
676 print '<div class="underbanner clearboth"></div>';
677 print '<table class="border centpercent tableforfield">'."\n";
678
679 // Common attributes
680 $keyforbreak = 'fk_warehouse';
681 unset($object->fields['fk_project']);
682 unset($object->fields['fk_soc']);
683 include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
684
685 // Other attributes
686 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
687
688 print '</table>';
689 print '</div>';
690 print '</div>';
691
692 print '<div class="clearboth"></div>';
693
694 print dol_get_fiche_end();
695
696
697 if (!in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
698 print '<div class="tabsAction">';
699
700 $parameters = array();
701 // Note that $action and $object may be modified by hook
702 $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action);
703 if (empty($reshook)) {
704 // Validate
705 if ($object->status == $object::STATUS_DRAFT) {
706 if ($permissiontoadd) {
707 if (empty($object->table_element_line) || (is_array($object->lines) && count($object->lines) > 0)) {
708 print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate&token='.$newToken.'">'.$langs->trans("Validate").'</a>';
709 } else {
710 $langs->load("errors");
711 print '<a class="butActionRefused" href="" title="'.$langs->trans("ErrorAddAtLeastOneLineFirst").'">'.$langs->trans("Validate").'</a>';
712 }
713 }
714 }
715
716 // Consume or produce
717 if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
718 if ($permissiontoproduce) {
719 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeorproduce&token='.$newToken.'">'.$langs->trans('ConsumeOrProduce').'</a>';
720 } else {
721 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeOrProduce').'</a>';
722 }
723 } elseif ($object->status == Mo::STATUS_DRAFT) {
724 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeOrProduce').'</a>';
725 }
726
727 // ConsumeAndProduceAll
728 if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
729 if ($permissiontoproduce) {
730 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeandproduceall&token='.$newToken.'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
731 } else {
732 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
733 }
734 } elseif ($object->status == Mo::STATUS_DRAFT) {
735 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
736 }
737
738 // Cancel - Reopen
739 if ($permissiontoadd) {
740 if ($object->status == $object::STATUS_VALIDATED || $object->status == $object::STATUS_INPROGRESS) {
741 $arrayproduced = $object->fetchLinesLinked('produced', 0);
742 $nbProduced = 0;
743 foreach ($arrayproduced as $lineproduced) {
744 $nbProduced += $lineproduced['qty'];
745 }
746 if ($nbProduced > 0) { // If production has started, we can close it
747 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_produced&confirm=yes&token='.$newToken.'">'.$langs->trans("Close").'</a>'."\n";
748 } else {
749 print '<a class="butActionRefused" href="#" title="'.$langs->trans("GoOnTabProductionToProduceFirst", $langs->transnoentitiesnoconv("Production")).'">'.$langs->trans("Close").'</a>'."\n";
750 }
751
752 print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=cancel&token='.$newToken.'">'.$langs->trans("Cancel").'</a>'."\n";
753 }
754
755 if ($object->status == $object::STATUS_CANCELED) {
756 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&confirm=yes&token='.$newToken.'">'.$langs->trans("ReOpen").'</a>'."\n";
757 }
758
759 if ($object->status == $object::STATUS_PRODUCED) {
760 if ($permissiontoproduce) {
761 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&token='.$newToken.'">'.$langs->trans('ReOpen').'</a>';
762 } else {
763 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ReOpen').'</a>';
764 }
765 }
766 }
767 }
768
769 print '</div>';
770 }
771
772 if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline', 'addproduceline', 'editline'))) {
773 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
774 print '<input type="hidden" name="token" value="'.newToken().'">';
775 print '<input type="hidden" name="action" value="confirm_'.$action.'">';
776 print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
777 print '<input type="hidden" name="id" value="'.$id.'">';
778 // Note: closing form is add end of page
779
780 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
781 $defaultstockmovementlabel = GETPOST('inventorylabel', 'alphanohtml') ? GETPOST('inventorylabel', 'alphanohtml') : $langs->trans("ProductionForRef", $object->ref);
782 $defaultstockmovementcode = GETPOST('inventorycode', 'alphanohtml') ? GETPOST('inventorycode', 'alphanohtml') : dol_print_date(dol_now(), 'dayhourlog');
783
784 print '<div class="center'.(in_array($action, array('consumeorproduce', 'consumeandproduceall')) ? ' formconsumeproduce' : '').'">';
785 print '<div class="opacitymedium hideonsmartphone paddingbottom">'.$langs->trans("ConfirmProductionDesc", $langs->transnoentitiesnoconv("Confirm")).'<br></div>';
786 print '<span class="fieldrequired">'.$langs->trans("InventoryCode").':</span> <input type="text" class="minwidth150 maxwidth200" name="inventorycode" value="'.$defaultstockmovementcode.'"> &nbsp; ';
787 print '<span class="clearbothonsmartphone"></span>';
788 print $langs->trans("MovementLabel").': <input type="text" class="minwidth300" name="inventorylabel" value="'.$defaultstockmovementlabel.'"><br><br>';
789 print '<input type="checkbox" id="autoclose" name="autoclose" value="1"'.(GETPOSTISSET('inventorylabel') ? (GETPOST('autoclose') ? ' checked="checked"' : '') : ' checked="checked"').'> <label for="autoclose">'.$langs->trans("AutoCloseMO").'</label><br>';
790 print '<input type="submit" class="button" value="'.$langs->trans("Confirm").'" name="confirm">';
791 print ' &nbsp; ';
792 print '<input class="button button-cancel" type="submit" value="'.$langs->trans("Cancel").'" name="cancel">';
793 print '<br><br>';
794 print '</div>';
795
796 print '<br>';
797 }
798 }
799
800
801 /*
802 * Lines
803 */
804 $collapse = 1;
805
806 if (!empty($object->table_element_line)) {
807 // Show object lines
808 $object->fetchLines();
809
810 $bomcost = 0;
811 if ($object->fk_bom > 0) {
812 $bom = new BOM($db);
813 $res = $bom->fetch($object->fk_bom);
814 if ($res > 0) {
815 $bom->calculateCosts();
816 $bomcost = $bom->unit_cost;
817 }
818 }
819
820 // Lines to consume
821
822 print '<!-- Lines to consume -->'."\n";
823 print '<div class="fichecenter">';
824 print '<div class="fichehalfleft">';
825 print '<div class="clearboth"></div>';
826
827 $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addconsumeline&token='.newToken();
828 $permissiontoaddaconsumeline = ($object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED) ? 1 : -2;
829 $parameters = array('morecss' => 'reposition');
830 $helpText = '';
831 if ($permissiontoaddaconsumeline == -2) {
832 $helpText = $langs->trans('MOIsClosed');
833 }
834
835 $newcardbutton = '';
836 if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
837 $newcardbutton = dolGetButtonTitle($langs->trans('AddNewConsumeLines'), $helpText, 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaconsumeline, $parameters);
838 }
839
840 print load_fiche_titre($langs->trans('Consumption'), $newcardbutton, '', 0, '', '', '');
841
842 print '<div class="div-table-responsive-no-min">';
843 print '<table class="noborder noshadow centpercent nobottom">';
844
845 print '<!-- Line of title for products to consume -->'."\n";
846 print '<tr class="liste_titre">';
847 // Product
848 print '<td>'.$langs->trans("Product").'</td>';
849 // Qty
850 print '<td class="right">'.$langs->trans("Qty").'</td>';
851 // Unit
852 print '<td></td>';
853 // Cost price
854 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
855 print '<td class="right">'.$langs->trans("UnitCost").'</td>';
856 }
857 // Qty already consumed
858 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyConsumedShort"), $langs->trans("QtyAlreadyConsumed")).'</td>';
859 // Warehouse
860 print '<td>';
861 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
862 print $langs->trans("Warehouse");
863 if (isModEnabled('workstation')) {
864 print ' '.$langs->trans("or").' '.$langs->trans("Workstation");
865 }
866 // Select warehouse to force it everywhere
867 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
868 $listwarehouses = $tmpwarehouse->list_array(1);
869 if (count($listwarehouses) > 1) {
870 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, $langs->trans("ForceTo"), 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
871 } elseif (count($listwarehouses) == 1) {
872 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
873 }
874 }
875 }
876 print '</td>';
877
878 if (isModEnabled('stock')) {
879 // Available
880 print '<td align="right">';
881 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
882 print $langs->trans("Stock");
883 }
884 print '</td>';
885 }
886 // Lot - serial
887 if (isModEnabled('productbatch')) {
888 print '<td>';
889 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
890 print $langs->trans("Batch");
891 }
892 print '</td>';
893 }
894
895 // Split
896 print '<td></td>';
897
898 // SplitAll
899 print '<td></td>';
900
901 // Edit Line
902 if ($object->status == Mo::STATUS_DRAFT) {
903 print '<td></td>';
904 }
905
906 // Action
907 if ($permissiontodelete) {
908 print '<td></td>';
909 }
910
911 print '</tr>';
912
913 if ($action == 'addconsumeline') {
914 print '<!-- Add line to consume -->'."\n";
915 print '<tr class="liste_titre">';
916 // Product
917 print '<td>';
918 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth150');
919 print '</td>';
920 // Qty
921 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width40 right"></td>';
922 // Unit
923 print '<td></td>';
924 // Cost price
925 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
926 print '<td></td>';
927 }
928
929 $colspan="3";
930 if (isModEnabled('stock')) {
931 $colspan++;;
932 }
933 if (isModEnabled('productbatch')) {
934 $colspan++;
935 }
936 // Qty already consumed + Warehouse
937 print '<td colspan="'.$colspan.'">';
938 print '<input type="submit" class="button buttongen button-add" name="addconsumelinebutton" value="'.$langs->trans("Add").'">';
939 print '<input type="submit" class="button buttongen button-cancel" name="canceladdconsumelinebutton" value="'.$langs->trans("Cancel").'">';
940 print '</td>';
941 // Split All
942 print '<td></td>';
943 // Edit Line
944 if ($object->status == Mo::STATUS_DRAFT) {
945 print '<td></td>';
946 }
947 // Action
948 if ($permissiontodelete) {
949 print '<td></td>';
950 }
951 print '</tr>';
952
953 // Extrafields Line
954 if (is_object($objectline)) {
955 $extrafields->fetch_name_optionals_label($object->table_element_line);
956 $temps = $objectline->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
957 if (!empty($temps)) {
958 print '<tr class="liste_titre"><td style="padding-top: 20px" colspan="9" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
959 print $temps;
960 print '</td></tr>';
961 }
962 }
963 }
964
965 // Lines to consume
966
967 $bomcostupdated = 0; // We will recalculate the unitary cost to produce a product using the real "products to consume into MO"
968
969 if (!empty($object->lines)) {
970 $nblinetoconsume = 0;
971 foreach ($object->lines as $line) {
972 if ($line->role == 'toconsume') {
973 $nblinetoconsume++;
974 }
975 }
976
977 $nblinetoconsumecursor = 0;
978 foreach ($object->lines as $line) {
979 if ($line->role == 'toconsume') {
980 $nblinetoconsumecursor++;
981
982 $tmpproduct = new Product($db);
983 $tmpproduct->fetch($line->fk_product);
984 $linecost = price2num($tmpproduct->pmp, 'MT');
985
986 if ($object->qty > 0) {
987 // add free consume line cost to $bomcostupdated
988 $costprice = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
989 if (empty($costprice)) {
990 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
991 $productFournisseur = new ProductFournisseur($db);
992 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
993 $costprice = $productFournisseur->fourn_unitprice;
994 } else {
995 $costprice = 0;
996 }
997 }
998
999 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1000
1001 if ($useunit && $line->fk_unit > 0) {
1002 $reg = [];
1003 $qtyhourservice = 0;
1004 if (preg_match('/^(\d+)([a-z]+)$/', $tmpproduct->duration, $reg)) {
1005 $qtyhourservice = convertDurationtoHour((float) $reg[1], (string) $reg[2]);
1006 }
1007 $qtyhourforline = 0;
1008 if ($line->fk_unit) {
1009 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1010 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1011 }
1012
1013 if ($qtyhourservice && $qtyhourforline) {
1014 $linecost = price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1015 $bomcostupdated += price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1016 } else {
1017 $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1018 $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1019 }
1020 } else {
1021 $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1022 $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1023 }
1024 }
1025
1026 $bomcostupdated = price2num($bomcostupdated, 'MU');
1027 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1028 $alreadyconsumed = 0;
1029 foreach ($arrayoflines as $line2) {
1030 $alreadyconsumed += $line2['qty'];
1031 }
1032
1033 if ($action == 'editline' && $lineid == $line->id) {
1034 $linecost = price2num($tmpproduct->pmp, 'MT');
1035
1036 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1037 $alreadyconsumed = 0;
1038 if (is_array($arrayoflines) && !empty($arrayoflines)) {
1039 foreach ($arrayoflines as $line2) {
1040 $alreadyconsumed += $line2['qty'];
1041 }
1042 }
1043 $suffix = '_' . $line->id;
1044 print '<!-- Line to dispatch ' . $suffix . ' (line edited) -->' . "\n";
1045 // hidden fields for js function
1046 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1047 // Duration - Time spent
1048 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1049 print '<tr>';
1050 print '<input name="lineid" type="hidden" value="' . $line->id . '">';
1051
1052 // Product
1053 print '<td>' . $tmpproduct->getNomUrl(1);
1054 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</span>';
1055 print '</td>';
1056
1057 // Qty
1058 print '<td class="right nowraponall">';
1059 print '<input class="width40 right" name="qty_lineProduce" value="'. $line->qty.'">';
1060 print '</td>';
1061
1062 // Unit
1063 print '<td class="right nowraponall">';
1064 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1065 if ($useunit) {
1066 print measuringUnitString($line->fk_unit, '', '', 2);
1067 }
1068 print '</td>';
1069
1070 // Cost price
1071 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1072 print '<td></td>';
1073 }
1074
1075 // Qty consumed
1076 print '<td class="right">';
1077 print ' ' . price2num($alreadyconsumed, 'MS');
1078 print '</td>';
1079
1080 // Entrepot
1081 print '<td>';
1082 print '</td>';
1083
1084 // Stock
1085 if (isModEnabled('stock')) {
1086 print '<td class="nowraponall right">';
1087 if ($tmpproduct->isStockManaged()) {
1088 if ($tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1089 print img_warning($langs->trans('StockTooLow')).' ';
1090 }
1091 print '<span class="left">'. $tmpproduct->stock_reel .' </span>';
1092 }
1093 print '</td>';
1094 }
1095
1096 // Lot - serial
1097 if (isModEnabled('productbatch')) {
1098 print '<td></td>';
1099 }
1100 // Split + SplitAll + Edit line + Delete
1101 print '<td colspan="'.(3 + ($object->status == Mo::STATUS_DRAFT ? 1 : 0) + ($permissiontodelete ? 1 : 0)).'">';
1102 print '<input type="submit" class="button buttongen button-add small nominwidth" name="save" value="' . $langs->trans("Save") . '">';
1103 print '<input type="submit" class="button buttongen button-cancel small nominwidth" name="cancel" value="' . $langs->trans("Cancel") . '">';
1104 print '</td>';
1105
1106 print '</tr>';
1107
1108 // Extrafields Line
1109 if (!empty($extrafields)) {
1110 $line->fetch_optionals();
1111 $temps = $line->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
1112 if (!empty($temps)) {
1113 $colspan = 10;
1114 print '<tr><td colspan="'.$colspan.'"><div style="padding-top: 20px" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
1115 print $temps;
1116 print '</div></td></tr>';
1117 }
1118 }
1119 } else {
1120 $suffix = '_' . $line->id;
1121 print '<!-- Line to dispatch ' . $suffix . ' -->' . "\n";
1122 // hidden fields for js function
1123 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1124 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1125
1126 print '<tr data-line-id="' . $line->id . '">';
1127
1128 // Product
1129 print '<td>' . $tmpproduct->getNomUrl(1);
1130 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</div>';
1131 print '</td>';
1132
1133 // Qty
1134 print '<td class="right nowraponall">';
1135 $help = '';
1136 if ($line->qty_frozen) {
1137 $help = ($help ? '<br>' : '') . '<strong>' . $langs->trans("QuantityFrozen") . '</strong>: ' . yn(1) . ' (' . $langs->trans("QuantityConsumedInvariable") . ')';
1138 print $form->textwithpicto('', $help, -1, 'lock') . ' ';
1139 }
1140 if ($line->disable_stock_change) {
1141 $help = ($help ? '<br>' : '') . '<strong>' . $langs->trans("DisableStockChange") . '</strong>: ' . yn(1) . ' (' . (($tmpproduct->type == Product::TYPE_SERVICE && !getDolGlobalString('STOCK_SUPPORTS_SERVICES')) ? $langs->trans("NoStockChangeOnServices") : $langs->trans("DisableStockChangeHelp")) . ')';
1142 print $form->textwithpicto('', $help, -1, 'help') . ' ';
1143 }
1144 print price2num($line->qty, 'MS');
1145 print '</td>';
1146
1147 // Unit
1148 print '<td class="right nowraponall">';
1149 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1150 if ($useunit) {
1151 print measuringUnitString($line->fk_unit, '', '', 2);
1152 }
1153 print '</td>';
1154
1155 // Cost price
1156 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1157 print '<td class="right nowraponall">';
1158 print price($linecost);
1159 print '</td>';
1160 }
1161
1162 // Already consumed
1163 print '<td class="right">';
1164 if ($alreadyconsumed) {
1165 print '<script>';
1166 print 'jQuery(document).ready(function() {
1167 jQuery("#expandtoproduce' . $line->id . '").click(function() {
1168 console.log("Expand mrp_production line ' . $line->id . '");
1169 jQuery(".expanddetail' . $line->id . '").toggle();';
1170 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1171 print 'if (jQuery("#tablelines").hasClass("nobottom")) { jQuery("#tablelines").removeClass("nobottom"); } else { jQuery("#tablelines").addClass("nobottom"); }';
1172 }
1173 print '
1174 });
1175 });';
1176 print '</script>';
1177 if (empty($conf->use_javascript_ajax)) {
1178 print '<a href="' . $_SERVER["PHP_SELF"] . '?collapse=' . $collapse . ',' . $line->id . '">';
1179 }
1180 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce' . $line->id . '"');
1181 if (empty($conf->use_javascript_ajax)) {
1182 print '</a>';
1183 }
1184 } else {
1185 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1186 print '<script>jQuery("#tablelines").removeClass("nobottom");</script>';
1187 }
1188 }
1189 print ' ' . price2num($alreadyconsumed, 'MS');
1190 print '</td>';
1191
1192 // Warehouse and/or workstation
1193 print '<td>';
1194 if (getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') && $tmpwarehouse->id > 0) {
1195 print img_picto('', $tmpwarehouse->picto) . " " . $tmpwarehouse->label;
1196 }
1197 if (isModEnabled('workstation') && $line->fk_default_workstation > 0) {
1198 $tmpworkstation = new Workstation($db);
1199 $tmpworkstation->fetch($line->fk_default_workstation);
1200 print $tmpworkstation->getNomUrl(1);
1201 }
1202 print '</td>';
1203
1204 // Stock
1205 if (isModEnabled('stock')) {
1206 print '<td class="nowraponall right">';
1207 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $tmpproduct->type != Product::TYPE_SERVICE) {
1208 if (!$line->disable_stock_change && $tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1209 print img_warning($langs->trans('StockTooLow')) . ' ';
1210 }
1211 if (!getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') || empty($tmpwarehouse->id)) {
1212 print price2num($tmpproduct->stock_reel, 'MS'); // Available
1213 } else {
1214 // Print only the stock in the selected warehouse
1215 $tmpproduct->load_stock();
1216 $wh_stock = $tmpproduct->stock_warehouse[$tmpwarehouse->id];
1217 if (!empty($wh_stock)) {
1218 print price2num($wh_stock->real, 'MS');
1219 } else {
1220 print "0";
1221 }
1222 }
1223 }
1224 print '</td>';
1225 }
1226
1227 // Lot
1228 if (isModEnabled('productbatch')) {
1229 print '<td></td>';
1230 }
1231
1232 // Split
1233 print '<td></td>';
1234
1235 // Split All
1236 print '<td></td>';
1237
1238 // Action Edit line
1239 if ($object->status == Mo::STATUS_DRAFT) {
1240 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1241 print '<td class="center">';
1242 print '<a class="reposition editfielda" href="' . $href . '">';
1243 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1244 print '</a>';
1245 print '</td>';
1246 }
1247
1248 // Action delete line
1249 if ($permissiontodelete) {
1250 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=deleteline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1251 print '<td class="center">';
1252 print '<a class="reposition" href="' . $href . '">';
1253 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1254 print '</a>';
1255 print '</td>';
1256 }
1257
1258 print '</tr>';
1259
1260 // Extrafields Line
1261 if (!empty($extrafields)) {
1262 $line->fetch_optionals();
1263 $temps = $line->showOptionals($extrafields, 'view', array(), '', '', 1, 'line');
1264 if (!empty($temps)) {
1265 $colspan = 10;
1266 print '<tr><td colspan="'.$colspan.'"><div id="extrafield_lines_area_'.$line->id.'" name="extrafield_lines_area_'.$line->id.'">';
1267 print $temps;
1268 print '</div></td></tr>';
1269 }
1270 }
1271 }
1272
1273 // Show detailed of already consumed with js code to collapse
1274 foreach ($arrayoflines as $line2) {
1275 print '<tr class="expanddetail'.$line->id.' hideobject opacitylow">';
1276
1277 // Date
1278 print '<td>';
1279 $tmpstockmovement->id = $line2['fk_stock_movement'];
1280 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1281 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1282 print '</td>';
1283
1284 // Qty
1285 print '<td></td>';
1286
1287 // Unit
1288 print '<td></td>';
1289
1290 // Cost price
1291 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1292 print '<td></td>';
1293 }
1294
1295 //Already consumed
1296 print '<td class="right">'.$line2['qty'].'</td>';
1297
1298 // Warehouse
1299 print '<td class="tdoverflowmax150">';
1300 if ($line2['fk_warehouse'] > 0) {
1301 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1302 if ($result > 0) {
1303 print $tmpwarehouse->getNomUrl(1);
1304 }
1305 }
1306 print '</td>';
1307
1308 // Stock
1309 if (isModEnabled('stock')) {
1310 print '<td></td>';
1311 }
1312
1313 // Lot Batch
1314 if (isModEnabled('productbatch')) {
1315 print '<td>';
1316 if ($line2['batch'] != '') {
1317 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1318 print $tmpbatch->getNomUrl(1);
1319 }
1320 print '</td>';
1321 }
1322
1323 // Split
1324 print '<td></td>';
1325
1326 // Split All
1327 print '<td></td>';
1328
1329 // Action Edit line
1330 if ($object->status == Mo::STATUS_DRAFT) {
1331 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line2['rowid']);
1332 print '<td class="center">';
1333 print '<a class="reposition" href="' . $href . '">';
1334 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1335 print '</a>';
1336 print '</td>';
1337 }
1338
1339 // Action delete line
1340 if ($permissiontodelete) {
1341 $href = $_SERVER["PHP_SELF"].'?id='.((int) $object->id).'&action=deleteline&token='.newToken().'&lineid='.((int) $line2['rowid']).'&fk_movement='.((int) $line2['fk_stock_movement']);
1342 print '<td class="center">';
1343 print '<a class="reposition" href="'.$href.'">';
1344 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1345 print '</a>';
1346 print '</td>';
1347 }
1348
1349 print '</tr>';
1350 }
1351
1352 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1353 $i = 1;
1354 print '<!-- Enter line to consume -->'."\n";
1355 $maxQty = 1;
1356 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1357 // Ref
1358 print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
1359 $preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
1360 if ($action == 'consumeorproduce' && !getDolGlobalString('MRP_AUTO_SET_REMAINING_QUANTITIES_TO_BE_CONSUMED') && !GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
1361 $preselected = 0;
1362 }
1363
1364 $disable = '';
1365 if (getDolGlobalString('MRP_NEVER_CONSUME_MORE_THAN_EXPECTED') && ($line->qty - $alreadyconsumed) <= 0) {
1366 $disable = 'disabled';
1367 }
1368
1369 // input hidden with fk_product of line
1370 print '<input type="hidden" name="product-'.$line->id.'-'.$i.'" value="'.$line->fk_product.'">';
1371
1372 // Qty
1373 print '<td class="right"><input type="text" class="width50 right" id="qtytoconsume-' . $line->id . '-' . $i . '" name="qty-' . $line->id . '-' . $i . '" value="' . $preselected . '" ' . $disable . '></td>';
1374
1375 // Unit
1376 print '<td></td>';
1377
1378 // Cost
1379 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1380 print '<td></td>';
1381 }
1382
1383 // Already consumed
1384 print '<td></td>';
1385
1386 // Warehouse
1387 print '<td>';
1388 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1389 if (empty($line->disable_stock_change)) {
1390 $preselected = (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i) ? GETPOST('idwarehouse-'.$line->id.'-'.$i) : ($tmpproduct->fk_default_warehouse > 0 ? $tmpproduct->fk_default_warehouse : 'ifone'));
1391 print $formproduct->selectWarehouses($preselected, 'idwarehouse-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1392 } else {
1393 print '<span class="opacitymedium">'.$langs->trans("DisableStockChange").'</span>';
1394 }
1395 } else {
1396 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1397 }
1398 print '</td>';
1399
1400 // Stock
1401 if (isModEnabled('stock')) {
1402 print '<td></td>';
1403 }
1404
1405 // Lot / Batch
1406 if (isModEnabled('productbatch')) {
1407 print '<td class="nowraponall">';
1408 if ($tmpproduct->status_batch) {
1409 $preselected = (GETPOSTISSET('batch-'.$line->id.'-'.$i) ? GETPOST('batch-'.$line->id.'-'.$i) : '');
1410 print '<input type="text" class="width75" name="batch-'.$line->id.'-'.$i.'" value="'.$preselected.'" list="batch-'.$line->id.'-'.$i.'">';
1411 print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, 0, array());
1412 }
1413 print '</td>';
1414 }
1415
1416 // Split
1417 $type = 'batch';
1418 print '<td align="right" class="split">';
1419 print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
1420 print '</td>';
1421
1422 // Split All
1423 print '<td align="right" class="splitall">';
1424 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1425 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"');
1426 }
1427 print '</td>';
1428
1429 // Edit Line
1430 if ($object->status == Mo::STATUS_DRAFT) {
1431 print '<td></td>';
1432 }
1433
1434 // Action delete line
1435 if ($permissiontodelete) {
1436 print '<td></td>';
1437 }
1438
1439 print '</tr>';
1440 }
1441 }
1442 }
1443 }
1444
1445 print '</table>';
1446 print '</div>';
1447
1448 // default warehouse processing
1449 print '<script type="text/javascript">
1450 $(document).ready(function () {
1451 $("select[name=fk_default_warehouse]").change(function() {
1452 var fk_default_warehouse = $("option:selected", this).val();
1453 $("select[name^=idwarehouse-]").val(fk_default_warehouse).change();
1454 });
1455 });
1456 </script>';
1457
1458 if (in_array($action, array('consumeorproduce', 'consumeandproduceall')) &&
1459 getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE')) {
1460 print '<script>$(document).ready(function () {
1461 $("#fk_default_warehouse").change();
1462 });</script>';
1463 }
1464
1465
1466 // Lines to produce
1467
1468 print '</div>';
1469 print '<div class="fichehalfright">';
1470 print '<div class="clearboth"></div>';
1471
1472 $nblinetoproduce = 0;
1473 foreach ($object->lines as $line) {
1474 if ($line->role == 'toproduce') {
1475 $nblinetoproduce++;
1476 }
1477 }
1478
1479 $newcardbutton = '';
1480 $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addproduceline&token='.newToken();
1481 $permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
1482 $parameters = array('morecss' => 'reposition');
1483 if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
1484 if ($nblinetoproduce == 0 || $object->mrptype == 1) {
1485 $newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', (int) $permissiontoaddaproductline, $parameters);
1486 }
1487 }
1488
1489 print load_fiche_titre($langs->trans('Production'), $newcardbutton, '', 0, '', '');
1490
1491 print '<div class="div-table-responsive-no-min">';
1492 print '<table id="tablelinestoproduce" class="noborder noshadow nobottom centpercent">';
1493
1494 print '<tr class="liste_titre">';
1495 // Product
1496 print '<td>'.$langs->trans("Product").'</td>';
1497 // Qty
1498 print '<td class="right">'.$langs->trans("Qty").'</td>';
1500 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1501 print '<td class="right">'.$langs->trans("Unit").'</td>';
1502 }
1503 // Cost price
1504 if ($permissiontoupdatecost) {
1505 if (empty($bomcostupdated)) {
1506 print '<td class="right">'.$form->textwithpicto($langs->trans("UnitCost"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1507 } else {
1508 print '<td class="right">'.$form->textwithpicto($langs->trans("ManufacturingPrice"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1509 }
1510 }
1511 // Already produced
1512 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyProducedShort"), $langs->trans("QtyAlreadyProduced")).'</td>';
1513 // Warehouse
1514 print '<td>';
1515 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1516 print $langs->trans("Warehouse");
1517 }
1518 print '</td>';
1519
1520 // Lot
1521 if (isModEnabled('productbatch')) {
1522 print '<td>';
1523 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1524 print $langs->trans("Batch");
1525 }
1526 print '</td>';
1527
1528 // Split
1529 print '<td></td>';
1530
1531 // Split All
1532 print '<td></td>';
1533 }
1534
1535 // Action delete
1536 if ($permissiontodelete) {
1537 print '<td></td>';
1538 }
1539
1540 print '</tr>';
1541
1542 if ($action == 'addproduceline') {
1543 print '<!-- Add line to produce -->'."\n";
1544 print '<tr class="liste_titre">';
1545
1546 // Product
1547 print '<td>';
1548 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
1549 print '</td>';
1550 // Qty
1551 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
1552 //Unit
1553 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1554 print '<td></td>';
1555 }
1556 // Cost price
1557 if ($permissiontoupdatecost) {
1558 print '<td></td>';
1559 }
1560 // Action (cost price + already produced)
1561 print '<td colspan="2">';
1562 print '<input type="submit" class="button buttongen button-add" name="addproducelinebutton" value="'.$langs->trans("Add").'">';
1563 print '<input type="submit" class="button buttongen button-cancel" name="canceladdproducelinebutton" value="'.$langs->trans("Cancel").'">';
1564 print '</td>';
1565 // Lot - serial
1566 if (isModEnabled('productbatch')) {
1567 print '<td></td>';
1568
1569 // Split
1570 print '<td></td>';
1571
1572 // Split All
1573 print '<td></td>';
1574 }
1575 // Action delete
1576 if ($permissiontodelete) {
1577 print '<td></td>';
1578 }
1579 print '</tr>';
1580 }
1581
1582 if (!empty($object->lines)) {
1583 $nblinetoproduce = 0;
1584 foreach ($object->lines as $line) {
1585 if ($line->role == 'toproduce') {
1586 $nblinetoproduce++;
1587 }
1588 }
1589
1590 $nblinetoproducecursor = 0;
1591 foreach ($object->lines as $line) {
1592 if ($line->role == 'toproduce') {
1593 $i = 1;
1594
1595 $nblinetoproducecursor++;
1596
1597 $tmpproduct = new Product($db);
1598 $tmpproduct->fetch($line->fk_product);
1599
1600 $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
1601 $alreadyproduced = 0;
1602 foreach ($arrayoflines as $line2) {
1603 $alreadyproduced += $line2['qty'];
1604 }
1605
1606 $suffix = '_'.$line->id;
1607 print '<!-- Line to dispatch '.$suffix.' (toproduce) -->'."\n";
1608 // hidden fields for js function
1609 print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
1610 print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyproduced.'">';
1611
1612 print '<tr>';
1613 // Product
1614 print '<td>'.$tmpproduct->getNomUrl(1);
1615 print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
1616 print '</td>';
1617 // Qty
1618 print '<td class="right">'.$line->qty.'</td>';
1619 // Unit
1620 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1621 print '<td class="right">'.measuringUnitString($line->fk_unit, '', '', 1).'</td>';
1622 }
1623 // Cost price
1624 if ($permissiontoupdatecost) {
1625 // Defined $manufacturingcost
1626 $manufacturingcost = 0;
1627 $manufacturingcostsrc = '';
1628 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1629 $manufacturingcost = $bomcostupdated;
1630 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1631 if (empty($manufacturingcost)) {
1632 $manufacturingcost = $bomcost;
1633 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1634 }
1635 if (empty($manufacturingcost)) {
1636 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1637 $manufacturingcostsrc = $langs->trans("CostPrice");
1638 }
1639 if (empty($manufacturingcost)) {
1640 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1641 $manufacturingcostsrc = $langs->trans("PMPValue");
1642 }
1643 }
1644
1645 print '<td class="right nowraponall" title="'.dol_escape_htmltag($manufacturingcostsrc).'">';
1646 if ($manufacturingcost) {
1647 print price($manufacturingcost);
1648 }
1649 print '</td>';
1650 }
1651 // Already produced
1652 print '<td class="right nowraponall">';
1653 if ($alreadyproduced) {
1654 print '<script>';
1655 print 'jQuery(document).ready(function() {
1656 jQuery("#expandtoproduce'.$line->id.'").click(function() {
1657 console.log("Expand mrp_production line '.$line->id.'");
1658 jQuery(".expanddetailtoproduce'.$line->id.'").toggle();';
1659 if ($nblinetoproduce == $nblinetoproducecursor) {
1660 print 'if (jQuery("#tablelinestoproduce").hasClass("nobottom")) { jQuery("#tablelinestoproduce").removeClass("nobottom"); } else { jQuery("#tablelinestoproduce").addClass("nobottom"); }';
1661 }
1662 print '
1663 });
1664 });';
1665 print '</script>';
1666 if (empty($conf->use_javascript_ajax)) {
1667 print '<a href="'.$_SERVER["PHP_SELF"].'?collapse='.$collapse.','.$line->id.'">';
1668 }
1669 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce'.$line->id.'"');
1670 if (empty($conf->use_javascript_ajax)) {
1671 print '</a>';
1672 }
1673 }
1674 print ' '.$alreadyproduced;
1675 print '</td>';
1676 // Warehouse
1677 print '<td>';
1678 print '</td>';
1679 // Lot
1680 if (isModEnabled('productbatch')) {
1681 print '<td></td>';
1682
1683 // Split
1684 print '<td></td>';
1685
1686 // Split All
1687 print '<td></td>';
1688 }
1689 // Delete
1690 if ($permissiontodelete) {
1691 if ($line->origin_type == 'free') {
1692 $href = $_SERVER["PHP_SELF"];
1693 $href .= '?id='.$object->id;
1694 $href .= '&action=deleteline';
1695 $href .= '&lineid='.$line->id;
1696 print '<td class="center">';
1697 print '<a class="reposition" href="'.$href.'">';
1698 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), "delete");
1699 print '</a>';
1700 print '</td>';
1701 } else {
1702 print '<td></td>';
1703 }
1704 }
1705 print '</tr>';
1706
1707 // Show detailed of already consumed with js code to collapse
1708 foreach ($arrayoflines as $line2) {
1709 print '<tr class="expanddetailtoproduce'.$line->id.' hideobject opacitylow">';
1710 // Product
1711 print '<td>';
1712 $tmpstockmovement->id = $line2['fk_stock_movement'];
1713 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1714 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1715 print '</td>';
1716 // Qty
1717 print '<td></td>';
1718 // Unit
1719 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1720 print '<td></td>';
1721 }
1722 // Cost price
1723 if ($permissiontoupdatecost) {
1724 print '<td></td>';
1725 }
1726 // Already produced
1727 print '<td class="right">'.$line2['qty'].'</td>';
1728 // Warehouse
1729 print '<td class="tdoverflowmax150">';
1730 if ($line2['fk_warehouse'] > 0) {
1731 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1732 if ($result > 0) {
1733 print $tmpwarehouse->getNomUrl(1);
1734 }
1735 }
1736 print '</td>';
1737 // Lot
1738 if (isModEnabled('productbatch')) {
1739 print '<td>';
1740 if ($line2['batch'] != '') {
1741 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1742 print $tmpbatch->getNomUrl(1);
1743 }
1744 print '</td>';
1745
1746 // Split
1747 print '<td></td>';
1748
1749 // Split All
1750 print '<td></td>';
1751 }
1752 // Action delete
1753 if ($permissiontodelete) {
1754 print '<td></td>';
1755 }
1756 print '</tr>';
1757 }
1758
1759 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1760 print '<!-- Enter line to produce -->'."\n";
1761 $maxQty = 1;
1762 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1763 // Product
1764 print '<td><span class="opacitymedium">'.$langs->trans("ToProduce").'</span></td>';
1765 $preselected = (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i) ? GETPOST('qtytoproduce-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyproduced));
1766 if ($action == 'consumeorproduce' && !GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
1767 $preselected = 0;
1768 }
1769 // Qty
1770 print '<td class="right"><input type="text" class="width50 right" id="qtytoproduce-'.$line->id.'-'.$i.'" name="qtytoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1771 //Unit
1772 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1773 print '<td class="right"></td>';
1774 }
1775 // Cost
1776 if ($permissiontoupdatecost) {
1777 // Defined $manufacturingcost
1778 $manufacturingcost = 0;
1779 $manufacturingcostsrc = '';
1780 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1781 $manufacturingcost = $bomcostupdated;
1782 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1783 if (empty($manufacturingcost)) {
1784 $manufacturingcost = $bomcost;
1785 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1786 }
1787 if (empty($manufacturingcost)) {
1788 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1789 $manufacturingcostsrc = $langs->trans("CostPrice");
1790 }
1791 if (empty($manufacturingcost)) {
1792 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1793 $manufacturingcostsrc = $langs->trans("PMPValue");
1794 }
1795 }
1796
1797 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1798 $preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : ($manufacturingcost ? price($manufacturingcost) : ''));
1799 print '<td class="right"><input type="text" class="width75 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1800 } else {
1801 print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.($manufacturingcost ? $manufacturingcost : '').'"></td>';
1802 }
1803 }
1804 // Already produced
1805 print '<td></td>';
1806 // Warehouse
1807 print '<td>';
1808 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1809 $preselected = (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i) ? GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) : ($object->fk_warehouse > 0 ? $object->fk_warehouse : 'ifone'));
1810 print $formproduct->selectWarehouses($preselected, 'idwarehousetoproduce-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1811 } else {
1812 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1813 }
1814 print '</td>';
1815 // Lot
1816 if (isModEnabled('productbatch')) {
1817 print '<td>';
1818 if ($tmpproduct->status_batch) {
1819 $preselected = (GETPOSTISSET('batchtoproduce-'.$line->id.'-'.$i) ? GETPOST('batchtoproduce-'.$line->id.'-'.$i) : '');
1820 print '<input type="text" class="width75" name="batchtoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'">';
1821 }
1822 print '</td>';
1823 // Batch number in same column than the stock movement picto
1824 if ($tmpproduct->status_batch) {
1825 $type = 'batch';
1826 print '<td align="right" class="split">';
1827 print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissing\')"');
1828 print '</td>';
1829
1830 print '<td align="right" class="splitall">';
1831 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1832 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" onClick="addDispatchLine('.$line->id.', \'batch\', \'alltoproduce\')"');
1833 } //
1834 print '</td>';
1835 } else {
1836 print '<td></td>';
1837
1838 print '<td></td>';
1839 }
1840 }
1841
1842 // Action delete
1843 print '<td></td>';
1844
1845 print '</tr>';
1846 }
1847 }
1848 }
1849 }
1850
1851 print '</table>';
1852 print '</div>';
1853
1854 print '</div>';
1855 print '</div>';
1856 }
1857
1858 if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline'))) {
1859 print "</form>\n";
1860 } ?>
1861
1862 <script type="text/javascript" language="javascript">
1863
1864 $(document).ready(function() {
1865 //Consumption : When a warehouse is selected, only the lot/serial numbers that are available in it are offered
1866 updateselectbatchbywarehouse();
1867 //Consumption : When a lot/serial number is selected and it is only available in one warehouse, the warehouse is automatically selected
1868 updateselectwarehousebybatch();
1869 });
1870
1871 function updateselectbatchbywarehouse() {
1872 $(document).on('change', "select[name*='idwarehouse']", function () {
1873 console.log("We change warehouse so we update the list of possible batch number");
1874
1875 var selectwarehouse = $(this);
1876
1877 var selectbatch_name = selectwarehouse.attr('name').replace('idwarehouse', 'batch');
1878 var selectbatch = $("datalist[id*='" + selectbatch_name + "']");
1879 var selectedbatch = selectbatch.val();
1880
1881 var product_element_name = selectwarehouse.attr('name').replace('idwarehouse', 'product');
1882
1883 $.ajax({
1884 type: "POST",
1885 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1886 data: {
1887 action: "updateselectbatchbywarehouse",
1888 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1889 warehouse_id: $(this).val(),
1890 token: '<?php echo currentToken(); ?>',
1891 product_id: $("input[name='" + product_element_name + "']").val()
1892 }
1893 }).done(function (data) {
1894
1895 selectbatch.empty();
1896
1897 if (typeof data == "object") {
1898 console.log("data is already type object, no need to parse it");
1899 } else {
1900 console.log("data is type "+(typeof data));
1901 data = JSON.parse(data);
1902 }
1903
1904 selectbatch.append($('<option>', {
1905 value: '',
1906 }));
1907
1908 $.each(data, function (key, value) {
1909
1910 if(selectwarehouse.val() == -1) {
1911 var label = key + " (<?php echo $langs->trans('Stock total') ?> : " + value + ")";
1912 } else {
1913 var label = key + " (<?php echo $langs->trans('Stock') ?> : " + value + ")";
1914 }
1915
1916 if(key === selectedbatch) {
1917 var option ='<option value="'+key+'" selected>'+ label +'</option>';
1918 } else {
1919 var option ='<option value="'+key+'">'+ label +'</option>';
1920 }
1921
1922 selectbatch.append(option);
1923 });
1924 });
1925 });
1926 }
1927
1928 function updateselectwarehousebybatch() {
1929 $(document).on('change', 'input[name*=batch]', function(){
1930 console.log("We change batch so we update the list of possible warehouses");
1931
1932 var selectbatch = $(this);
1933
1934 var selectwarehouse_name = selectbatch.attr('name').replace('batch', 'idwarehouse');
1935 var selectwarehouse = $("select[name*='" + selectwarehouse_name + "']");
1936 var selectedwarehouse = selectwarehouse.val();
1937
1938 if(selectedwarehouse != -1){
1939 return;
1940 }
1941
1942 var product_element_name = selectbatch.attr('name').replace('batch', 'product');
1943
1944 $.ajax({
1945 type: "POST",
1946 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1947 data: {
1948 action: "updateselectwarehousebybatch",
1949 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1950 batch: $(this).val(),
1951 token: '<?php echo currentToken(); ?>',
1952 product_id: $("input[name='" + product_element_name + "']").val()
1953 }
1954 }).done(function (data) {
1955
1956 if (typeof data == "object") {
1957 console.log("data is already type object, no need to parse it");
1958 } else {
1959 console.log("data is type "+(typeof data));
1960 data = JSON.parse(data);
1961 }
1962
1963 if(data != 0){
1964 selectwarehouse.val(data).change();
1965 }
1966 });
1967 });
1968 }
1969
1970 </script>
1971
1972 <?php
1973}
1974
1975// End of page
1976llxFooter();
1977$db->close();
$id
Definition account.php:39
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:58
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:70
$object ref
Definition info.php:79
Class for BOM.
Definition bom.class.php:45
Class to manage warehouses.
Class to manage standard extra fields.
Class to manage generation of HTML components Only common components must be here.
Class with static methods for building HTML components related to products Only components common to ...
Class to manage building of HTML components.
Class for Mo.
Definition mo.class.php:34
Class MoLine.
Class to manage stock movements.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
const TYPE_SERVICE
Service.
Class with list of lots and properties.
Class to manage projects.
Class to manage translations.
Class for Workstation.
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:334
llxFooter()
Footer empty.
Definition document.php:107
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
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)
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolGetButtonTitle($label, $helpText='', $iconClass='fa fa-file', $url='', $id='', $status=1, $params=array())
Function dolGetButtonTitle : this kind of buttons are used in title in list.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
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'.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
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.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
moPrepareHead($object)
Prepare array of tabs for Mo.
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:137
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.