dolibarr 20.0.5
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) 2025 Noé Cendrier <noe.cendrier@altairis.fr>
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 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'), '', '', '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 = GETPOSTFLOAT('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 = GETPOST('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 = GETPOST('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 = GETPOSTFLOAT('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$help_url = 'EN:Module_Manufacturing_Orders|FR:Module_Ordres_de_Fabrication|DE:Modul_Fertigungsauftrag';
515$morejs = array('/mrp/js/lib_dispatch.js.php');
516llxHeader('', $langs->trans('Mo'), $help_url, '', 0, 0, $morejs);
517
518$newToken = newToken();
519
520// Part to show record
521if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) {
522 $res = $object->fetch_thirdparty();
523 $res = $object->fetch_optionals();
524
525 if (getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') && $object->fk_warehouse > 0) {
526 $tmpwarehouse->fetch($object->fk_warehouse);
527 $fk_default_warehouse = $object->fk_warehouse;
528 }
529
530 $head = moPrepareHead($object);
531
532 print dol_get_fiche_head($head, 'production', $langs->trans("ManufacturingOrder"), -1, $object->picto);
533
534 $formconfirm = '';
535
536 // Confirmation to delete
537 if ($action == 'delete') {
538 $formquestion = array(
539 array(
540 'label' => $langs->trans('MoCancelConsumedAndProducedLines'),
541 'name' => 'alsoCancelConsumedAndProducedLines',
542 'type' => 'checkbox',
543 'value' => 0
544 ),
545 );
546 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteMo'), $langs->trans('ConfirmDeleteMo'), 'confirm_delete', $formquestion, 0, 1);
547 }
548 // Confirmation to delete line
549 if ($action == 'deleteline') {
550 $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);
551 }
552 // Clone confirmation
553 if ($action == 'clone') {
554 // Create an array for form
555 $formquestion = array();
556 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMo', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
557 }
558
559 // Confirmation of validation
560 if ($action == 'validate') {
561 // We check that object has a temporary ref
562 $ref = substr($object->ref, 1, 4);
563 if ($ref == 'PROV') {
564 $object->fetch_product();
565 $numref = $object->getNextNumRef($object->product);
566 } else {
567 $numref = $object->ref;
568 }
569
570 $text = $langs->trans('ConfirmValidateMo', $numref);
571 /*if (isModEnabled('notification'))
572 {
573 require_once DOL_DOCUMENT_ROOT . '/core/class/notify.class.php';
574 $notify = new Notify($db);
575 $text .= '<br>';
576 $text .= $notify->confirmMessage('BOM_VALIDATE', $object->socid, $object);
577 }*/
578
579 $formquestion = array();
580 if (isModEnabled('mrp')) {
581 $langs->load("mrp");
582 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
583 $formproduct = new FormProduct($db);
584 $forcecombo = 0;
585 if ($conf->browser->name == 'ie') {
586 $forcecombo = 1; // There is a bug in IE10 that make combo inside popup crazy
587 }
588 $formquestion = array(
589 // 'text' => $langs->trans("ConfirmClone"),
590 // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => 1),
591 // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' => 1),
592 );
593 }
594
595 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Validate'), $text, 'confirm_validate', $formquestion, 0, 1, 220);
596 }
597
598 // Confirmation to cancel
599 if ($action == 'cancel') {
600 $formquestion = array(
601 array(
602 'label' => $langs->trans('MoCancelConsumedAndProducedLines'),
603 'name' => 'alsoCancelConsumedAndProducedLines',
604 'type' => 'checkbox',
605 'value' => !getDolGlobalString('MO_ALSO_CANCEL_CONSUMED_AND_PRODUCED_LINES_BY_DEFAULT') ? 0 : 1
606 ),
607 );
608 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"] . '?id=' . $object->id, $langs->trans('CancelMo'), $langs->trans('ConfirmCancelMo'), 'confirm_cancel', $formquestion, 0, 1);
609 }
610
611 // Call Hook formConfirm
612 $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
613 $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
614 if (empty($reshook)) {
615 $formconfirm .= $hookmanager->resPrint;
616 } elseif ($reshook > 0) {
617 $formconfirm = $hookmanager->resPrint;
618 }
619
620 // Print form confirm
621 print $formconfirm;
622
623
624 // MO file
625 // ------------------------------------------------------------
626 $linkback = '<a href="'.DOL_URL_ROOT.'/mrp/mo_list.php?restore_lastsearch_values=1'.(!empty($socid) ? '&socid='.$socid : '').'">'.$langs->trans("BackToList").'</a>';
627
628 $morehtmlref = '<div class="refidno">';
629
630 /*
631 // Ref bis
632 $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', 0, 1);
633 $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', null, null, '', 1);
634 */
635
636 // Thirdparty
637 if (is_object($object->thirdparty)) {
638 $morehtmlref .= $object->thirdparty->getNomUrl(1, 'customer');
639 if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $object->thirdparty->id > 0) {
640 $morehtmlref .= ' (<a href="'.DOL_URL_ROOT.'/commande/list.php?socid='.$object->thirdparty->id.'&search_societe='.urlencode($object->thirdparty->name).'">'.$langs->trans("OtherOrders").'</a>)';
641 }
642 }
643
644 // Project
645 if (isModEnabled('project')) {
646 $langs->load("projects");
647 if (is_object($object->thirdparty)) {
648 $morehtmlref .= '<br>';
649 }
650 if ($permissiontoadd) {
651 $morehtmlref .= img_picto($langs->trans("Project"), 'project', 'class="pictofixedwidth"');
652 if ($action != 'classify') {
653 $morehtmlref .= '<a class="editfielda" href="'.$_SERVER['PHP_SELF'].'?action=classify&token='.newToken().'&id='.$object->id.'">'.img_edit($langs->transnoentitiesnoconv('SetProject')).'</a> ';
654 }
655 $morehtmlref .= $form->form_project($_SERVER['PHP_SELF'].'?id='.$object->id, $object->socid, $object->fk_project, ($action == 'classify' ? 'projectid' : 'none'), 0, 0, 0, 1, '', 'maxwidth300');
656 } else {
657 if (!empty($object->fk_project)) {
658 $proj = new Project($db);
659 $proj->fetch($object->fk_project);
660 $morehtmlref .= $proj->getNomUrl(1);
661 if ($proj->title) {
662 $morehtmlref .= '<span class="opacitymedium"> - '.dol_escape_htmltag($proj->title).'</span>';
663 }
664 }
665 }
666 }
667 $morehtmlref .= '</div>';
668
669
670 dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
671
672
673 print '<div class="fichecenter">';
674 print '<div class="fichehalfleft">';
675 print '<div class="underbanner clearboth"></div>';
676 print '<table class="border centpercent tableforfield">'."\n";
677
678 // Common attributes
679 $keyforbreak = 'fk_warehouse';
680 unset($object->fields['fk_project']);
681 unset($object->fields['fk_soc']);
682 include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
683
684 // Other attributes
685 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
686
687 print '</table>';
688 print '</div>';
689 print '</div>';
690
691 print '<div class="clearboth"></div>';
692
693 print dol_get_fiche_end();
694
695
696 if (!in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
697 print '<div class="tabsAction">';
698
699 $parameters = array();
700 // Note that $action and $object may be modified by hook
701 $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action);
702 if (empty($reshook)) {
703 // Validate
704 if ($object->status == $object::STATUS_DRAFT) {
705 if ($permissiontoadd) {
706 if (empty($object->table_element_line) || (is_array($object->lines) && count($object->lines) > 0)) {
707 print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate&token='.$newToken.'">'.$langs->trans("Validate").'</a>';
708 } else {
709 $langs->load("errors");
710 print '<a class="butActionRefused" href="" title="'.$langs->trans("ErrorAddAtLeastOneLineFirst").'">'.$langs->trans("Validate").'</a>';
711 }
712 }
713 }
714
715 // Consume or produce
716 if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
717 if ($permissiontoproduce) {
718 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeorproduce&token='.$newToken.'">'.$langs->trans('ConsumeOrProduce').'</a>';
719 } else {
720 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeOrProduce').'</a>';
721 }
722 } elseif ($object->status == Mo::STATUS_DRAFT) {
723 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeOrProduce').'</a>';
724 }
725
726 // ConsumeAndProduceAll
727 if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
728 if ($permissiontoproduce) {
729 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeandproduceall&token='.$newToken.'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
730 } else {
731 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
732 }
733 } elseif ($object->status == Mo::STATUS_DRAFT) {
734 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
735 }
736
737 // Cancel - Reopen
738 if ($permissiontoadd) {
739 if ($object->status == $object::STATUS_VALIDATED || $object->status == $object::STATUS_INPROGRESS) {
740 $arrayproduced = $object->fetchLinesLinked('produced', 0);
741 $nbProduced = 0;
742 foreach ($arrayproduced as $lineproduced) {
743 $nbProduced += $lineproduced['qty'];
744 }
745 if ($nbProduced > 0) { // If production has started, we can close it
746 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_produced&confirm=yes&token='.$newToken.'">'.$langs->trans("Close").'</a>'."\n";
747 } else {
748 print '<a class="butActionRefused" href="#" title="'.$langs->trans("GoOnTabProductionToProduceFirst", $langs->transnoentitiesnoconv("Production")).'">'.$langs->trans("Close").'</a>'."\n";
749 }
750
751 print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=cancel&token='.$newToken.'">'.$langs->trans("Cancel").'</a>'."\n";
752 }
753
754 if ($object->status == $object::STATUS_CANCELED) {
755 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&confirm=yes&token='.$newToken.'">'.$langs->trans("ReOpen").'</a>'."\n";
756 }
757
758 if ($object->status == $object::STATUS_PRODUCED) {
759 if ($permissiontoproduce) {
760 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&token='.$newToken.'">'.$langs->trans('ReOpen').'</a>';
761 } else {
762 print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ReOpen').'</a>';
763 }
764 }
765 }
766 }
767
768 print '</div>';
769 }
770
771 if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline', 'addproduceline', 'editline'))) {
772 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
773 print '<input type="hidden" name="token" value="'.newToken().'">';
774 print '<input type="hidden" name="action" value="confirm_'.$action.'">';
775 print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
776 print '<input type="hidden" name="id" value="'.$id.'">';
777 // Note: closing form is add end of page
778
779 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
780 $defaultstockmovementlabel = GETPOST('inventorylabel', 'alphanohtml') ? GETPOST('inventorylabel', 'alphanohtml') : $langs->trans("ProductionForRef", $object->ref);
781 $defaultstockmovementcode = GETPOST('inventorycode', 'alphanohtml') ? GETPOST('inventorycode', 'alphanohtml') : dol_print_date(dol_now(), 'dayhourlog');
782
783 print '<div class="center'.(in_array($action, array('consumeorproduce', 'consumeandproduceall')) ? ' formconsumeproduce' : '').'">';
784 print '<div class="opacitymedium hideonsmartphone paddingbottom">'.$langs->trans("ConfirmProductionDesc", $langs->transnoentitiesnoconv("Confirm")).'<br></div>';
785 print '<span class="fieldrequired">'.$langs->trans("InventoryCode").':</span> <input type="text" class="minwidth150 maxwidth200" name="inventorycode" value="'.$defaultstockmovementcode.'"> &nbsp; ';
786 print '<span class="clearbothonsmartphone"></span>';
787 print $langs->trans("MovementLabel").': <input type="text" class="minwidth300" name="inventorylabel" value="'.$defaultstockmovementlabel.'"><br><br>';
788 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>';
789 print '<input type="submit" class="button" value="'.$langs->trans("Confirm").'" name="confirm">';
790 print ' &nbsp; ';
791 print '<input class="button button-cancel" type="submit" value="'.$langs->trans("Cancel").'" name="cancel">';
792 print '<br><br>';
793 print '</div>';
794
795 print '<br>';
796 }
797 }
798
799
800 /*
801 * Lines
802 */
803 $collapse = 1;
804
805 if (!empty($object->table_element_line)) {
806 // Show object lines
807 $object->fetchLines();
808
809 $bomcost = 0;
810 if ($object->fk_bom > 0) {
811 $bom = new BOM($db);
812 $res = $bom->fetch($object->fk_bom);
813 if ($res > 0) {
814 $bom->calculateCosts();
815 $bomcost = $bom->unit_cost;
816 }
817 }
818
819 // Lines to consume
820
821 print '<div class="fichecenter">';
822 print '<div class="fichehalfleft">';
823 print '<div class="clearboth"></div>';
824
825 $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addconsumeline&token='.newToken();
826 $permissiontoaddaconsumeline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
827 $parameters = array('morecss' => 'reposition');
828
829 $newcardbutton = '';
830 if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
831 $newcardbutton = dolGetButtonTitle($langs->trans('AddNewConsumeLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaconsumeline, $parameters);
832 }
833
834 print load_fiche_titre($langs->trans('Consumption'), $newcardbutton, '', 0, '', '', '');
835
836 print '<div class="div-table-responsive-no-min">';
837 print '<table class="noborder noshadow centpercent nobottom">';
838
839 print '<!-- Line of title for products to consume -->'."\n";
840 print '<tr class="liste_titre">';
841 // Product
842 print '<td>'.$langs->trans("Product").'</td>';
843 // Qty
844 print '<td class="right">'.$langs->trans("Qty").'</td>';
845 // Unit
846 print '<td></td>';
847 // Cost price
848 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
849 print '<td class="right">'.$langs->trans("UnitCost").'</td>';
850 }
851 // Qty already consumed
852 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyConsumedShort"), $langs->trans("QtyAlreadyConsumed")).'</td>';
853 // Warehouse
854 print '<td>';
855 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
856 print $langs->trans("Warehouse");
857 if (isModEnabled('workstation')) {
858 print ' '.$langs->trans("or").' '.$langs->trans("Workstation");
859 }
860 // Select warehouse to force it everywhere
861 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
862 $listwarehouses = $tmpwarehouse->list_array(1);
863 if (count($listwarehouses) > 1) {
864 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, $langs->trans("ForceTo"), 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
865 } elseif (count($listwarehouses) == 1) {
866 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
867 }
868 }
869 }
870 print '</td>';
871
872 if (isModEnabled('stock')) {
873 // Available
874 print '<td align="right">';
875 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
876 print $langs->trans("Stock");
877 }
878 print '</td>';
879 }
880 // Lot - serial
881 if (isModEnabled('productbatch')) {
882 print '<td>';
883 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
884 print $langs->trans("Batch");
885 }
886 print '</td>';
887 }
888
889 // Split
890 print '<td></td>';
891
892 // SplitAll
893 print '<td></td>';
894
895 // Edit Line
896 if ($object->status == Mo::STATUS_DRAFT) {
897 print '<td></td>';
898 }
899
900 // Action
901 if ($permissiontodelete) {
902 print '<td></td>';
903 }
904
905 print '</tr>';
906
907 if ($action == 'addconsumeline') {
908 print '<!-- Add line to consume -->'."\n";
909 print '<tr class="liste_titre">';
910 // Product
911 print '<td>';
912 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
913 print '</td>';
914 // Qty
915 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
916 // Unit
917 print '<td></td>';
918 // Cost price
919 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
920 print '<td></td>';
921 }
922 // Qty already consumed + Warehouse
923 print '<td colspan="2">';
924 print '<input type="submit" class="button buttongen button-add" name="addconsumelinebutton" value="'.$langs->trans("Add").'">';
925 print '<input type="submit" class="button buttongen button-cancel" name="canceladdconsumelinebutton" value="'.$langs->trans("Cancel").'">';
926 print '</td>';
927 if (isModEnabled('stock')) {
928 print '<td></td>';
929 }
930 // Lot - serial
931 if (isModEnabled('productbatch')) {
932 print '<td></td>';
933 }
934 // Split
935 print '<td></td>';
936 // SplitAll
937 print '<td></td>';
938 // Edit Line
939 if ($object->status == Mo::STATUS_DRAFT) {
940 print '<td></td>';
941 }
942 // Action
943 if ($permissiontodelete) {
944 print '<td></td>';
945 }
946 print '</tr>';
947
948 // Extrafields Line
949 if (is_object($objectline)) {
950 $extrafields->fetch_name_optionals_label($object->table_element_line);
951 $temps = $objectline->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
952 if (!empty($temps)) {
953 print '<tr class="liste_titre"><td style="padding-top: 20px" colspan="9" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
954 print $temps;
955 print '</td></tr>';
956 }
957 }
958 }
959
960 // Lines to consume
961
962 $bomcostupdated = 0; // We will recalculate the unitary cost to produce a product using the real "products to consume into MO"
963
964 if (!empty($object->lines)) {
965 $nblinetoconsume = 0;
966 foreach ($object->lines as $line) {
967 if ($line->role == 'toconsume') {
968 $nblinetoconsume++;
969 }
970 }
971
972 $nblinetoconsumecursor = 0;
973 foreach ($object->lines as $line) {
974 if ($line->role == 'toconsume') {
975 $nblinetoconsumecursor++;
976
977 $tmpproduct = new Product($db);
978 $tmpproduct->fetch($line->fk_product);
979 $linecost = price2num($tmpproduct->pmp, 'MT');
980
981 if ($object->qty > 0) {
982 // add free consume line cost to $bomcostupdated
983 $costprice = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
984 if (empty($costprice)) {
985 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
986 $productFournisseur = new ProductFournisseur($db);
987 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product, $line->qty) > 0) {
988 $costprice = $productFournisseur->fourn_unitprice;
989 } else {
990 $costprice = 0;
991 }
992 }
993
994 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
995
996 if ($useunit && $line->fk_unit > 0) {
997 $reg = [];
998 $qtyhourservice = 0;
999 if (preg_match('/^(\d+)([a-z]+)$/', $tmpproduct->duration, $reg)) {
1000 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1001 }
1002 $qtyhourforline = 0;
1003 if ($line->fk_unit) {
1004 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1005 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1006 }
1007
1008 if ($qtyhourservice && $qtyhourforline) {
1009 $linecost = price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1010 $bomcostupdated += price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1011 } else {
1012 $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1013 $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1014 }
1015 } else {
1016 $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1017 $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1018 }
1019 }
1020
1021 $bomcostupdated = price2num($bomcostupdated, 'MU');
1022 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1023 $alreadyconsumed = 0;
1024 foreach ($arrayoflines as $line2) {
1025 $alreadyconsumed += $line2['qty'];
1026 }
1027
1028 if ($action == 'editline' && $lineid == $line->id) {
1029 $linecost = price2num($tmpproduct->pmp, 'MT');
1030
1031 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1032 $alreadyconsumed = 0;
1033 if (is_array($arrayoflines) && !empty($arrayoflines)) {
1034 foreach ($arrayoflines as $line2) {
1035 $alreadyconsumed += $line2['qty'];
1036 }
1037 }
1038 $suffix = '_' . $line->id;
1039 print '<!-- Line to dispatch ' . $suffix . ' (line edited) -->' . "\n";
1040 // hidden fields for js function
1041 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1042 // Duration - Time spent
1043 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1044 print '<tr>';
1045 print '<input name="lineid" type="hidden" value="' . $line->id . '">';
1046
1047 // Product
1048 print '<td>' . $tmpproduct->getNomUrl(1);
1049 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</span>';
1050 print '</td>';
1051
1052 // Qty
1053 print '<td class="right nowraponall">';
1054 print '<input class="width40 right" name="qty_lineProduce" value="'. $line->qty.'">';
1055 print '</td>';
1056
1057 // Unit
1058 print '<td class="right nowraponall">';
1059 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1060 if ($useunit) {
1061 print measuringUnitString($line->fk_unit, '', '', 2);
1062 }
1063 print '</td>';
1064
1065 // Cost price
1066 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1067 print '<td></td>';
1068 }
1069
1070 // Qty consumed
1071 print '<td class="right">';
1072 print ' ' . price2num($alreadyconsumed, 'MS');
1073 print '</td>';
1074
1075 // Entrepot
1076 print '<td>';
1077 print '</td>';
1078
1079 // Stock
1080 if (isModEnabled('stock')) {
1081 print '<td class="nowraponall right">';
1082 if ($tmpproduct->isStockManaged()) {
1083 if ($tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1084 print img_warning($langs->trans('StockTooLow')).' ';
1085 }
1086 print '<span class="left">'. $tmpproduct->stock_reel .' </span>';
1087 }
1088 print '</td>';
1089 }
1090
1091 // Lot - serial
1092 if (isModEnabled('productbatch')) {
1093 print '<td></td>';
1094 }
1095 // Split + SplitAll + Edit line + Delete
1096 print '<td colspan="'.(3 + ($object->status == Mo::STATUS_DRAFT ? 1 : 0) + ($permissiontodelete ? 1 : 0)).'">';
1097 print '<input type="submit" class="button buttongen button-add small nominwidth" name="save" value="' . $langs->trans("Save") . '">';
1098 print '<input type="submit" class="button buttongen button-cancel small nominwidth" name="cancel" value="' . $langs->trans("Cancel") . '">';
1099 print '</td>';
1100
1101 print '</tr>';
1102
1103 // Extrafields Line
1104 if (!empty($extrafields)) {
1105 $line->fetch_optionals();
1106 $temps = $line->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
1107 if (!empty($temps)) {
1108 $colspan = 10;
1109 print '<tr><td colspan="'.$colspan.'"><div style="padding-top: 20px" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
1110 print $temps;
1111 print '</div></td></tr>';
1112 }
1113 }
1114 } else {
1115 $suffix = '_' . $line->id;
1116 print '<!-- Line to dispatch ' . $suffix . ' -->' . "\n";
1117 // hidden fields for js function
1118 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1119 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1120
1121 print '<tr data-line-id="' . $line->id . '">';
1122
1123 // Product
1124 print '<td>' . $tmpproduct->getNomUrl(1);
1125 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</div>';
1126 print '</td>';
1127
1128 // Qty
1129 print '<td class="right nowraponall">';
1130 $help = '';
1131 if ($line->qty_frozen) {
1132 $help = ($help ? '<br>' : '') . '<strong>' . $langs->trans("QuantityFrozen") . '</strong>: ' . yn(1) . ' (' . $langs->trans("QuantityConsumedInvariable") . ')';
1133 print $form->textwithpicto('', $help, -1, 'lock') . ' ';
1134 }
1135 if ($line->disable_stock_change) {
1136 $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")) . ')';
1137 print $form->textwithpicto('', $help, -1, 'help') . ' ';
1138 }
1139 print price2num($line->qty, 'MS');
1140 print '</td>';
1141
1142 // Unit
1143 print '<td class="right nowraponall">';
1144 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1145 if ($useunit) {
1146 print measuringUnitString($line->fk_unit, '', '', 2);
1147 }
1148 print '</td>';
1149
1150 // Cost price
1151 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1152 print '<td class="right nowraponall">';
1153 print price($linecost);
1154 print '</td>';
1155 }
1156
1157 // Already consumed
1158 print '<td class="right">';
1159 if ($alreadyconsumed) {
1160 print '<script>';
1161 print 'jQuery(document).ready(function() {
1162 jQuery("#expandtoproduce' . $line->id . '").click(function() {
1163 console.log("Expand mrp_production line ' . $line->id . '");
1164 jQuery(".expanddetail' . $line->id . '").toggle();';
1165 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1166 print 'if (jQuery("#tablelines").hasClass("nobottom")) { jQuery("#tablelines").removeClass("nobottom"); } else { jQuery("#tablelines").addClass("nobottom"); }';
1167 }
1168 print '
1169 });
1170 });';
1171 print '</script>';
1172 if (empty($conf->use_javascript_ajax)) {
1173 print '<a href="' . $_SERVER["PHP_SELF"] . '?collapse=' . $collapse . ',' . $line->id . '">';
1174 }
1175 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce' . $line->id . '"');
1176 if (empty($conf->use_javascript_ajax)) {
1177 print '</a>';
1178 }
1179 } else {
1180 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1181 print '<script>jQuery("#tablelines").removeClass("nobottom");</script>';
1182 }
1183 }
1184 print ' ' . price2num($alreadyconsumed, 'MS');
1185 print '</td>';
1186
1187 // Warehouse and/or workstation
1188 print '<td>';
1189 if (getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') && $tmpwarehouse->id > 0) {
1190 print img_picto('', $tmpwarehouse->picto) . " " . $tmpwarehouse->label;
1191 }
1192 if (isModEnabled('workstation') && $line->fk_default_workstation > 0) {
1193 $tmpworkstation = new Workstation($db);
1194 $tmpworkstation->fetch($line->fk_default_workstation);
1195 print $tmpworkstation->getNomUrl(1);
1196 }
1197 print '</td>';
1198
1199 // Stock
1200 if (isModEnabled('stock')) {
1201 print '<td class="nowraponall right">';
1202 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $tmpproduct->type != Product::TYPE_SERVICE) {
1203 if (!$line->disable_stock_change && $tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1204 print img_warning($langs->trans('StockTooLow')) . ' ';
1205 }
1206 if (!getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') || empty($tmpwarehouse->id)) {
1207 print price2num($tmpproduct->stock_reel, 'MS'); // Available
1208 } else {
1209 // Print only the stock in the selected warehouse
1210 $tmpproduct->load_stock();
1211 $wh_stock = $tmpproduct->stock_warehouse[$tmpwarehouse->id];
1212 if (!empty($wh_stock)) {
1213 print price2num($wh_stock->real, 'MS');
1214 } else {
1215 print "0";
1216 }
1217 }
1218 }
1219 print '</td>';
1220 }
1221
1222 // Lot
1223 if (isModEnabled('productbatch')) {
1224 print '<td></td>';
1225 }
1226
1227 // Split
1228 print '<td></td>';
1229
1230 // Split All
1231 print '<td></td>';
1232
1233 // Action Edit line
1234 if ($object->status == Mo::STATUS_DRAFT) {
1235 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1236 print '<td class="center">';
1237 print '<a class="reposition editfielda" href="' . $href . '">';
1238 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1239 print '</a>';
1240 print '</td>';
1241 }
1242
1243 // Action delete line, if no consumption has occurred for this product
1244 if ($permissiontodelete && empty($arrayoflines)) {
1245 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=deleteline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1246 print '<td class="center">';
1247 print '<a class="reposition" href="' . $href . '">';
1248 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1249 print '</a>';
1250 print '</td>';
1251 }
1252
1253 print '</tr>';
1254
1255 // Extrafields Line
1256 if (!empty($extrafields)) {
1257 $line->fetch_optionals();
1258 $temps = $line->showOptionals($extrafields, 'view', array(), '', '', 1, 'line');
1259 if (!empty($temps)) {
1260 $colspan = 10;
1261 print '<tr><td colspan="'.$colspan.'"><div id="extrafield_lines_area_'.$line->id.'" name="extrafield_lines_area_'.$line->id.'">';
1262 print $temps;
1263 print '</div></td></tr>';
1264 }
1265 }
1266 }
1267
1268 // Show detailed of already consumed with js code to collapse
1269 foreach ($arrayoflines as $line2) {
1270 print '<tr class="expanddetail'.$line->id.' hideobject opacitylow">';
1271
1272 // Date
1273 print '<td>';
1274 $tmpstockmovement->id = $line2['fk_stock_movement'];
1275 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1276 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1277 print '</td>';
1278
1279 // Qty
1280 print '<td></td>';
1281
1282 // Unit
1283 print '<td></td>';
1284
1285 // Cost price
1286 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1287 print '<td></td>';
1288 }
1289
1290 //Already consumed
1291 print '<td class="right">'.$line2['qty'].'</td>';
1292
1293 // Warehouse
1294 print '<td class="tdoverflowmax150">';
1295 if ($line2['fk_warehouse'] > 0) {
1296 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1297 if ($result > 0) {
1298 print $tmpwarehouse->getNomUrl(1);
1299 }
1300 }
1301 print '</td>';
1302
1303 // Stock
1304 if (isModEnabled('stock')) {
1305 print '<td></td>';
1306 }
1307
1308 // Lot Batch
1309 if (isModEnabled('productbatch')) {
1310 print '<td>';
1311 if ($line2['batch'] != '') {
1312 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1313 print $tmpbatch->getNomUrl(1);
1314 }
1315 print '</td>';
1316 }
1317
1318 // Split
1319 print '<td></td>';
1320
1321 // Split All
1322 print '<td></td>';
1323
1324 // Action Edit line
1325 if ($object->status == Mo::STATUS_DRAFT) {
1326 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line2['rowid']);
1327 print '<td class="center">';
1328 print '<a class="reposition" href="' . $href . '">';
1329 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1330 print '</a>';
1331 print '</td>';
1332 }
1333
1334 // Action delete line
1335 if ($permissiontodelete) {
1336 $href = $_SERVER["PHP_SELF"].'?id='.((int) $object->id).'&action=deleteline&token='.newToken().'&lineid='.((int) $line2['rowid']).'&fk_movement='.((int) $line2['fk_stock_movement']);
1337 print '<td class="center">';
1338 print '<a class="reposition" href="'.$href.'">';
1339 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1340 print '</a>';
1341 print '</td>';
1342 }
1343
1344 print '</tr>';
1345 }
1346
1347 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1348 $i = 1;
1349 print '<!-- Enter line to consume -->'."\n";
1350 $maxQty = 1;
1351 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1352 // Ref
1353 print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
1354 $preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
1355 if ($action == 'consumeorproduce' && !GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
1356 $preselected = 0;
1357 }
1358
1359 $disable = '';
1360 if (getDolGlobalString('MRP_NEVER_CONSUME_MORE_THAN_EXPECTED') && ($line->qty - $alreadyconsumed) <= 0) {
1361 $disable = 'disabled';
1362 }
1363
1364 // input hidden with fk_product of line
1365 print '<input type="hidden" name="product-'.$line->id.'-'.$i.'" value="'.$line->fk_product.'">';
1366
1367 // Qty
1368 print '<td class="right"><input type="text" class="width50 right" id="qtytoconsume-' . $line->id . '-' . $i . '" name="qty-' . $line->id . '-' . $i . '" value="' . $preselected . '" ' . $disable . '></td>';
1369
1370 // Unit
1371 print '<td></td>';
1372
1373 // Cost
1374 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1375 print '<td></td>';
1376 }
1377
1378 // Already consumed
1379 print '<td></td>';
1380
1381 // Warehouse
1382 print '<td>';
1383 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1384 if (empty($line->disable_stock_change)) {
1385 $preselected = (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i) ? GETPOST('idwarehouse-'.$line->id.'-'.$i) : ($tmpproduct->fk_default_warehouse > 0 ? $tmpproduct->fk_default_warehouse : 'ifone'));
1386 print $formproduct->selectWarehouses($preselected, 'idwarehouse-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1387 } else {
1388 print '<span class="opacitymedium">'.$langs->trans("DisableStockChange").'</span>';
1389 }
1390 } else {
1391 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1392 }
1393 print '</td>';
1394
1395 // Stock
1396 if (isModEnabled('stock')) {
1397 print '<td></td>';
1398 }
1399
1400 // Lot / Batch
1401 if (isModEnabled('productbatch')) {
1402 print '<td class="nowraponall">';
1403 if ($tmpproduct->status_batch) {
1404 $preselected = (GETPOSTISSET('batch-'.$line->id.'-'.$i) ? GETPOST('batch-'.$line->id.'-'.$i) : '');
1405 print '<input type="text" class="width75" name="batch-'.$line->id.'-'.$i.'" value="'.$preselected.'" list="batch-'.$line->id.'-'.$i.'">';
1406 print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, '', '');
1407 }
1408 print '</td>';
1409 }
1410
1411 // Split
1412 $type = 'batch';
1413 print '<td align="right" class="split">';
1414 print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
1415 print '</td>';
1416
1417 // Split All
1418 print '<td align="right" class="splitall">';
1419 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1420 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"');
1421 }
1422 print '</td>';
1423
1424 // Edit Line
1425 if ($object->status == Mo::STATUS_DRAFT) {
1426 print '<td></td>';
1427 }
1428
1429 // Action delete line
1430 if ($permissiontodelete) {
1431 print '<td></td>';
1432 }
1433
1434 print '</tr>';
1435 }
1436 }
1437 }
1438 }
1439
1440 print '</table>';
1441 print '</div>';
1442
1443 // default warehouse processing
1444 print '<script type="text/javascript">
1445 $(document).ready(function () {
1446 $("select[name=fk_default_warehouse]").change(function() {
1447 var fk_default_warehouse = $("option:selected", this).val();
1448 $("select[name^=idwarehouse-]").val(fk_default_warehouse).change();
1449 });
1450 });
1451 </script>';
1452
1453 if (in_array($action, array('consumeorproduce', 'consumeandproduceall')) &&
1454 getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE')) {
1455 print '<script>$(document).ready(function () {
1456 $("#fk_default_warehouse").change();
1457 });</script>';
1458 }
1459
1460
1461 // Lines to produce
1462
1463 print '</div>';
1464 print '<div class="fichehalfright">';
1465 print '<div class="clearboth"></div>';
1466
1467 $nblinetoproduce = 0;
1468 foreach ($object->lines as $line) {
1469 if ($line->role == 'toproduce') {
1470 $nblinetoproduce++;
1471 }
1472 }
1473
1474 $newcardbutton = '';
1475 $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addproduceline&token='.newToken();
1476 $permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
1477 $parameters = array('morecss' => 'reposition');
1478 if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
1479 if ($nblinetoproduce == 0 || $object->mrptype == 1) {
1480 $newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaproductline, $parameters);
1481 }
1482 }
1483
1484 print load_fiche_titre($langs->trans('Production'), $newcardbutton, '', 0, '', '');
1485
1486 print '<div class="div-table-responsive-no-min">';
1487 print '<table id="tablelinestoproduce" class="noborder noshadow nobottom centpercent">';
1488
1489 print '<tr class="liste_titre">';
1490 // Product
1491 print '<td>'.$langs->trans("Product").'</td>';
1492 // Qty
1493 print '<td class="right">'.$langs->trans("Qty").'</td>';
1495 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1496 print '<td class="right">'.$langs->trans("Unit").'</td>';
1497 }
1498 // Cost price
1499 if ($permissiontoupdatecost) {
1500 if (empty($bomcostupdated)) {
1501 print '<td class="right">'.$form->textwithpicto($langs->trans("UnitCost"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1502 } else {
1503 print '<td class="right">'.$form->textwithpicto($langs->trans("ManufacturingPrice"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1504 }
1505 }
1506 // Already produced
1507 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyProducedShort"), $langs->trans("QtyAlreadyProduced")).'</td>';
1508 // Warehouse
1509 print '<td>';
1510 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1511 print $langs->trans("Warehouse");
1512 }
1513 print '</td>';
1514
1515 // Lot
1516 if (isModEnabled('productbatch')) {
1517 print '<td>';
1518 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1519 print $langs->trans("Batch");
1520 }
1521 print '</td>';
1522
1523 // Split
1524 print '<td></td>';
1525
1526 // Split All
1527 print '<td></td>';
1528 }
1529
1530 // Action delete
1531 if ($permissiontodelete) {
1532 print '<td></td>';
1533 }
1534
1535 print '</tr>';
1536
1537 if ($action == 'addproduceline') {
1538 print '<!-- Add line to produce -->'."\n";
1539 print '<tr class="liste_titre">';
1540
1541 // Product
1542 print '<td>';
1543 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
1544 print '</td>';
1545 // Qty
1546 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
1547 //Unit
1548 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1549 print '<td></td>';
1550 }
1551 // Cost price
1552 if ($permissiontoupdatecost) {
1553 print '<td></td>';
1554 }
1555 // Action (cost price + already produced)
1556 print '<td colspan="2">';
1557 print '<input type="submit" class="button buttongen button-add" name="addproducelinebutton" value="'.$langs->trans("Add").'">';
1558 print '<input type="submit" class="button buttongen button-cancel" name="canceladdproducelinebutton" value="'.$langs->trans("Cancel").'">';
1559 print '</td>';
1560 // Lot - serial
1561 if (isModEnabled('productbatch')) {
1562 print '<td></td>';
1563
1564 // Split
1565 print '<td></td>';
1566
1567 // Split All
1568 print '<td></td>';
1569 }
1570 // Action delete
1571 if ($permissiontodelete) {
1572 print '<td></td>';
1573 }
1574 print '</tr>';
1575 }
1576
1577 if (!empty($object->lines)) {
1578 $nblinetoproduce = 0;
1579 foreach ($object->lines as $line) {
1580 if ($line->role == 'toproduce') {
1581 $nblinetoproduce++;
1582 }
1583 }
1584
1585 $nblinetoproducecursor = 0;
1586 foreach ($object->lines as $line) {
1587 if ($line->role == 'toproduce') {
1588 $i = 1;
1589
1590 $nblinetoproducecursor++;
1591
1592 $tmpproduct = new Product($db);
1593 $tmpproduct->fetch($line->fk_product);
1594
1595 $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
1596 $alreadyproduced = 0;
1597 foreach ($arrayoflines as $line2) {
1598 $alreadyproduced += $line2['qty'];
1599 }
1600
1601 $suffix = '_'.$line->id;
1602 print '<!-- Line to dispatch '.$suffix.' (toproduce) -->'."\n";
1603 // hidden fields for js function
1604 print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
1605 print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyproduced.'">';
1606
1607 print '<tr>';
1608 // Product
1609 print '<td>'.$tmpproduct->getNomUrl(1);
1610 print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
1611 print '</td>';
1612 // Qty
1613 print '<td class="right">'.$line->qty.'</td>';
1614 // Unit
1615 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1616 print '<td class="right">'.measuringUnitString($line->fk_unit, '', '', 1).'</td>';
1617 }
1618 // Cost price
1619 if ($permissiontoupdatecost) {
1620 // Defined $manufacturingcost
1621 $manufacturingcost = 0;
1622 $manufacturingcostsrc = '';
1623 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1624 $manufacturingcost = $bomcostupdated;
1625 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1626 if (empty($manufacturingcost)) {
1627 $manufacturingcost = $bomcost;
1628 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1629 }
1630 if (empty($manufacturingcost)) {
1631 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1632 $manufacturingcostsrc = $langs->trans("CostPrice");
1633 }
1634 if (empty($manufacturingcost)) {
1635 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1636 $manufacturingcostsrc = $langs->trans("PMPValue");
1637 }
1638 }
1639
1640 print '<td class="right nowraponall" title="'.dol_escape_htmltag($manufacturingcostsrc).'">';
1641 if ($manufacturingcost) {
1642 print price($manufacturingcost);
1643 }
1644 print '</td>';
1645 }
1646 // Already produced
1647 print '<td class="right nowraponall">';
1648 if ($alreadyproduced) {
1649 print '<script>';
1650 print 'jQuery(document).ready(function() {
1651 jQuery("#expandtoproduce'.$line->id.'").click(function() {
1652 console.log("Expand mrp_production line '.$line->id.'");
1653 jQuery(".expanddetailtoproduce'.$line->id.'").toggle();';
1654 if ($nblinetoproduce == $nblinetoproducecursor) {
1655 print 'if (jQuery("#tablelinestoproduce").hasClass("nobottom")) { jQuery("#tablelinestoproduce").removeClass("nobottom"); } else { jQuery("#tablelinestoproduce").addClass("nobottom"); }';
1656 }
1657 print '
1658 });
1659 });';
1660 print '</script>';
1661 if (empty($conf->use_javascript_ajax)) {
1662 print '<a href="'.$_SERVER["PHP_SELF"].'?collapse='.$collapse.','.$line->id.'">';
1663 }
1664 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce'.$line->id.'"');
1665 if (empty($conf->use_javascript_ajax)) {
1666 print '</a>';
1667 }
1668 }
1669 print ' '.$alreadyproduced;
1670 print '</td>';
1671 // Warehouse
1672 print '<td>';
1673 print '</td>';
1674 // Lot
1675 if (isModEnabled('productbatch')) {
1676 print '<td></td>';
1677
1678 // Split
1679 print '<td></td>';
1680
1681 // Split All
1682 print '<td></td>';
1683 }
1684 // Delete
1685 if ($permissiontodelete) {
1686 if ($line->origin_type == 'free') {
1687 $href = $_SERVER["PHP_SELF"];
1688 $href .= '?id='.$object->id;
1689 $href .= '&action=deleteline';
1690 $href .= '&token='.newToken();
1691 $href .= '&lineid='.$line->id;
1692 print '<td class="center">';
1693 print '<a class="reposition" href="'.$href.'">';
1694 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), "delete");
1695 print '</a>';
1696 print '</td>';
1697 } else {
1698 print '<td></td>';
1699 }
1700 }
1701 print '</tr>';
1702
1703 // Show detailed of already consumed with js code to collapse
1704 foreach ($arrayoflines as $line2) {
1705 print '<tr class="expanddetailtoproduce'.$line->id.' hideobject opacitylow">';
1706 // Product
1707 print '<td>';
1708 $tmpstockmovement->id = $line2['fk_stock_movement'];
1709 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1710 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1711 print '</td>';
1712 // Qty
1713 print '<td></td>';
1714 // Unit
1715 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1716 print '<td></td>';
1717 }
1718 // Cost price
1719 if ($permissiontoupdatecost) {
1720 print '<td></td>';
1721 }
1722 // Already produced
1723 print '<td class="right">'.$line2['qty'].'</td>';
1724 // Warehouse
1725 print '<td class="tdoverflowmax150">';
1726 if ($line2['fk_warehouse'] > 0) {
1727 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1728 if ($result > 0) {
1729 print $tmpwarehouse->getNomUrl(1);
1730 }
1731 }
1732 print '</td>';
1733 // Lot
1734 if (isModEnabled('productbatch')) {
1735 print '<td>';
1736 if ($line2['batch'] != '') {
1737 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1738 print $tmpbatch->getNomUrl(1);
1739 }
1740 print '</td>';
1741
1742 // Split
1743 print '<td></td>';
1744
1745 // Split All
1746 print '<td></td>';
1747 }
1748 // Action delete
1749 if ($permissiontodelete) {
1750 print '<td></td>';
1751 }
1752 print '</tr>';
1753 }
1754
1755 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1756 print '<!-- Enter line to produce -->'."\n";
1757 $maxQty = 1;
1758 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1759 // Product
1760 print '<td><span class="opacitymedium">'.$langs->trans("ToProduce").'</span></td>';
1761 $preselected = (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i) ? GETPOST('qtytoproduce-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyproduced));
1762 if ($action == 'consumeorproduce' && !GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
1763 $preselected = 0;
1764 }
1765 // Qty
1766 print '<td class="right"><input type="text" class="width50 right" id="qtytoproduce-'.$line->id.'-'.$i.'" name="qtytoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1767 //Unit
1768 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1769 print '<td class="right"></td>';
1770 }
1771 // Cost
1772 if ($permissiontoupdatecost) {
1773 // Defined $manufacturingcost
1774 $manufacturingcost = 0;
1775 $manufacturingcostsrc = '';
1776 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1777 $manufacturingcost = $bomcostupdated;
1778 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1779 if (empty($manufacturingcost)) {
1780 $manufacturingcost = $bomcost;
1781 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1782 }
1783 if (empty($manufacturingcost)) {
1784 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1785 $manufacturingcostsrc = $langs->trans("CostPrice");
1786 }
1787 if (empty($manufacturingcost)) {
1788 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1789 $manufacturingcostsrc = $langs->trans("PMPValue");
1790 }
1791 }
1792
1793 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1794 $preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : ($manufacturingcost ? price($manufacturingcost) : ''));
1795 print '<td class="right"><input type="text" class="width75 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1796 } else {
1797 print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.($manufacturingcost ? $manufacturingcost : '').'"></td>';
1798 }
1799 }
1800 // Already produced
1801 print '<td></td>';
1802 // Warehouse
1803 print '<td>';
1804 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1805 $preselected = (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i) ? GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) : ($object->fk_warehouse > 0 ? $object->fk_warehouse : 'ifone'));
1806 print $formproduct->selectWarehouses($preselected, 'idwarehousetoproduce-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1807 } else {
1808 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1809 }
1810 print '</td>';
1811 // Lot
1812 if (isModEnabled('productbatch')) {
1813 print '<td>';
1814 if ($tmpproduct->status_batch) {
1815 $preselected = (GETPOSTISSET('batchtoproduce-'.$line->id.'-'.$i) ? GETPOST('batchtoproduce-'.$line->id.'-'.$i) : '');
1816 print '<input type="text" class="width75" name="batchtoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'">';
1817 }
1818 print '</td>';
1819 // Batch number in same column than the stock movement picto
1820 if ($tmpproduct->status_batch) {
1821 $type = 'batch';
1822 print '<td align="right" class="split">';
1823 print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissing\')"');
1824 print '</td>';
1825
1826 print '<td align="right" class="splitall">';
1827 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1828 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" onClick="addDispatchLine('.$line->id.', \'batch\', \'alltoproduce\')"');
1829 } //
1830 print '</td>';
1831 } else {
1832 print '<td></td>';
1833
1834 print '<td></td>';
1835 }
1836 }
1837
1838 // Action delete
1839 print '<td></td>';
1840
1841 print '</tr>';
1842 }
1843 }
1844 }
1845 }
1846
1847 print '</table>';
1848 print '</div>';
1849
1850 print '</div>';
1851 print '</div>';
1852 }
1853
1854 if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline'))) {
1855 print "</form>\n";
1856 } ?>
1857
1858 <script type="text/javascript" language="javascript">
1859
1860 $(document).ready(function() {
1861 //Consumption : When a warehouse is selected, only the lot/serial numbers that are available in it are offered
1862 updateselectbatchbywarehouse();
1863 //Consumption : When a lot/serial number is selected and it is only available in one warehouse, the warehouse is automatically selected
1864 updateselectwarehousebybatch();
1865 });
1866
1867 function updateselectbatchbywarehouse() {
1868 $(document).on('change', "select[name*='idwarehouse']", function () {
1869 console.log("We change warehouse so we update the list of possible batch number");
1870
1871 var selectwarehouse = $(this);
1872
1873 var selectbatch_name = selectwarehouse.attr('name').replace('idwarehouse', 'batch');
1874 var selectbatch = $("datalist[id*='" + selectbatch_name + "']");
1875 var selectedbatch = selectbatch.val();
1876
1877 var product_element_name = selectwarehouse.attr('name').replace('idwarehouse', 'product');
1878
1879 $.ajax({
1880 type: "POST",
1881 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1882 data: {
1883 action: "updateselectbatchbywarehouse",
1884 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1885 warehouse_id: $(this).val(),
1886 token: '<?php echo currentToken(); ?>',
1887 product_id: $("input[name='" + product_element_name + "']").val()
1888 }
1889 }).done(function (data) {
1890
1891 selectbatch.empty();
1892
1893 if (typeof data == "object") {
1894 console.log("data is already type object, no need to parse it");
1895 } else {
1896 console.log("data is type "+(typeof data));
1897 data = JSON.parse(data);
1898 }
1899
1900 selectbatch.append($('<option>', {
1901 value: '',
1902 }));
1903
1904 $.each(data, function (key, value) {
1905
1906 if(selectwarehouse.val() == -1) {
1907 var label = key + " (<?php echo $langs->trans('Stock total') ?> : " + value + ")";
1908 } else {
1909 var label = key + " (<?php echo $langs->trans('Stock') ?> : " + value + ")";
1910 }
1911
1912 if(key === selectedbatch) {
1913 var option ='<option value="'+key+'" selected>'+ label +'</option>';
1914 } else {
1915 var option ='<option value="'+key+'">'+ label +'</option>';
1916 }
1917
1918 selectbatch.append(option);
1919 });
1920 });
1921 });
1922 }
1923
1924 function updateselectwarehousebybatch() {
1925 $(document).on('change', 'input[name*=batch]', function(){
1926 console.log("We change batch so we update the list of possible warehouses");
1927
1928 var selectbatch = $(this);
1929
1930 var selectwarehouse_name = selectbatch.attr('name').replace('batch', 'idwarehouse');
1931 var selectwarehouse = $("select[name*='" + selectwarehouse_name + "']");
1932 var selectedwarehouse = selectwarehouse.val();
1933
1934 if(selectedwarehouse != -1){
1935 return;
1936 }
1937
1938 var product_element_name = selectbatch.attr('name').replace('batch', 'product');
1939
1940 $.ajax({
1941 type: "POST",
1942 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1943 data: {
1944 action: "updateselectwarehousebybatch",
1945 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1946 batch: $(this).val(),
1947 token: '<?php echo currentToken(); ?>',
1948 product_id: $("input[name='" + product_element_name + "']").val()
1949 }
1950 }).done(function (data) {
1951
1952 if (typeof data == "object") {
1953 console.log("data is already type object, no need to parse it");
1954 } else {
1955 console.log("data is type "+(typeof data));
1956 data = JSON.parse(data);
1957 }
1958
1959 if(data != 0){
1960 selectwarehouse.val(data).change();
1961 }
1962 });
1963 });
1964 }
1965
1966 </script>
1967
1968 <?php
1969}
1970
1971// End of page
1972llxFooter();
1973$db->close();
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()
Empty header.
Definition wrapper.php:55
llxFooter()
Empty footer.
Definition wrapper.php:69
$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:37
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
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
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.
yn($yesno, $case=1, $color=0)
Return yes or no in current language.
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'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
GETPOSTFLOAT($paramname, $rounding='')
Return the value of a $_GET or $_POST supervariable, converted into float.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0)
Set event messages in dol_events session object.
getDolGlobalString($key, $default='')
Return 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.