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'), '', '', '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 = 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 = 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 '<tr class="liste_titre">';
846 // Product
847 print '<td>'.$langs->trans("Product").'</td>';
848 // Qty
849 print '<td class="right">'.$langs->trans("Qty").'</td>';
850 // Unit
851 print '<td></td>';
852 // Cost price
853 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
854 print '<td class="right">'.$langs->trans("UnitCost").'</td>';
855 }
856 // Qty already consumed
857 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyConsumedShort"), $langs->trans("QtyAlreadyConsumed")).'</td>';
858 // Warehouse
859 print '<td>';
860 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
861 print $langs->trans("Warehouse");
862 if (isModEnabled('workstation')) {
863 print ' '.$langs->trans("or").' '.$langs->trans("Workstation");
864 }
865 // Select warehouse to force it everywhere
866 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
867 $listwarehouses = $tmpwarehouse->list_array(1);
868 if (count($listwarehouses) > 1) {
869 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, $langs->trans("ForceTo"), 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
870 } elseif (count($listwarehouses) == 1) {
871 print '<br>'.$form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth200', 1);
872 }
873 }
874 }
875 print '</td>';
876
877 if (isModEnabled('stock')) {
878 // Available
879 print '<td align="right">';
880 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
881 print $langs->trans("Stock");
882 }
883 print '</td>';
884 }
885 // Lot - serial
886 if (isModEnabled('productbatch')) {
887 print '<td>';
888 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
889 print $langs->trans("Batch");
890 }
891 print '</td>';
892 }
893 // Action
894 if ($permissiontodelete) {
895 print '<td></td>';
896 }
897
898 // Split
899 print '<td></td>';
900
901 // SplitAll
902 print '<td></td>';
903
904 // Edit Line
905 if ($object->status == Mo::STATUS_DRAFT) {
906 print '<td></td>';
907 }
908
909 print '</tr>';
910
911 if ($action == 'addconsumeline') {
912 print '<!-- Add line to consume -->'."\n";
913 print '<tr class="liste_titre">';
914 // Product
915 print '<td>';
916 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
917 print '</td>';
918 // Qty
919 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
920 // Unit
921 print '<td></td>';
922 // Cost price
923 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
924 print '<td></td>';
925 }
926 // Qty already consumed + Warehouse
927 print '<td colspan="2">';
928 print '<input type="submit" class="button buttongen button-add" name="addconsumelinebutton" value="'.$langs->trans("Add").'">';
929 print '<input type="submit" class="button buttongen button-cancel" name="canceladdconsumelinebutton" value="'.$langs->trans("Cancel").'">';
930 print '</td>';
931 if (isModEnabled('stock')) {
932 print '<td></td>';
933 }
934 // Lot - serial
935 if (isModEnabled('productbatch')) {
936 print '<td></td>';
937 }
938 // Action
939 if ($permissiontodelete) {
940 print '<td></td>';
941 }
942 // Split
943 print '<td></td>';
944 // SplitAll
945 print '<td></td>';
946 // Edit Line
947 if ($object->status == Mo::STATUS_DRAFT) {
948 print '<td></td>';
949 }
950 print '</tr>';
951
952 // Extrafields Line
953 if (is_object($objectline)) {
954 $extrafields->fetch_name_optionals_label($object->table_element_line);
955 $temps = $objectline->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
956 if (!empty($temps)) {
957 print '<tr class="liste_titre"><td style="padding-top: 20px" colspan="9" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
958 print $temps;
959 print '</td></tr>';
960 }
961 }
962 }
963
964 // Lines to consume
965
966 $bomcostupdated = 0; // We will recalculate the unitary cost to produce a product using the real "products to consume into MO"
967
968 if (!empty($object->lines)) {
969 $nblinetoconsume = 0;
970 foreach ($object->lines as $line) {
971 if ($line->role == 'toconsume') {
972 $nblinetoconsume++;
973 }
974 }
975
976 $nblinetoconsumecursor = 0;
977 foreach ($object->lines as $line) {
978 if ($line->role == 'toconsume') {
979 $nblinetoconsumecursor++;
980
981 $tmpproduct = new Product($db);
982 $tmpproduct->fetch($line->fk_product);
983 $linecost = price2num($tmpproduct->pmp, 'MT');
984
985 if ($object->qty > 0) {
986 // add free consume line cost to $bomcostupdated
987 $costprice = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
988 if (empty($costprice)) {
989 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
990 $productFournisseur = new ProductFournisseur($db);
991 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
992 $costprice = $productFournisseur->fourn_unitprice;
993 } else {
994 $costprice = 0;
995 }
996 }
997
998 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
999
1000 if ($useunit && $line->fk_unit > 0) {
1001 $reg = [];
1002 $qtyhourservice = 0;
1003 if (preg_match('/^(\d+)([a-z]+)$/', $tmpproduct->duration, $reg)) {
1004 $qtyhourservice = convertDurationtoHour($reg[1], (string) $reg[2]);
1005 }
1006 $qtyhourforline = 0;
1007 if ($line->fk_unit) {
1008 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1009 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1010 }
1011
1012 if ($qtyhourservice && $qtyhourforline) {
1013 $linecost = price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1014 $bomcostupdated += price2num(($qtyhourforline / $qtyhourservice * $costprice) / $object->qty, 'MU'); // same but with full accuracy
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 } else {
1020 $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
1021 $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
1022 }
1023 }
1024
1025 $bomcostupdated = price2num($bomcostupdated, 'MU');
1026 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1027 $alreadyconsumed = 0;
1028 foreach ($arrayoflines as $line2) {
1029 $alreadyconsumed += $line2['qty'];
1030 }
1031
1032 if ($action == 'editline' && $lineid == $line->id) {
1033 $linecost = price2num($tmpproduct->pmp, 'MT');
1034
1035 $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
1036 $alreadyconsumed = 0;
1037 if (is_array($arrayoflines) && !empty($arrayoflines)) {
1038 foreach ($arrayoflines as $line2) {
1039 $alreadyconsumed += $line2['qty'];
1040 }
1041 }
1042 $suffix = '_' . $line->id;
1043 print '<!-- Line to dispatch ' . $suffix . ' (line edited) -->' . "\n";
1044 // hidden fields for js function
1045 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1046 // Duration - Time spent
1047 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1048 print '<tr>';
1049 print '<input name="lineid" type="hidden" value="' . $line->id . '">';
1050
1051 // Product
1052 print '<td>' . $tmpproduct->getNomUrl(1);
1053 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</span>';
1054 print '</td>';
1055
1056 // Qty
1057 print '<td class="right nowraponall">';
1058 print '<input class="width40 right" name="qty_lineProduce" value="'. $line->qty.'">';
1059 print '</td>';
1060
1061 // Unit
1062 print '<td class="right nowraponall">';
1063 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1064 if ($useunit) {
1065 print measuringUnitString($line->fk_unit, '', '', 2);
1066 }
1067 print '</td>';
1068
1069 // Qty consumed
1070 print '<td class="right">';
1071 print ' ' . price2num($alreadyconsumed, 'MS');
1072 print '</td>';
1073
1074 // Entrepot
1075 print '<td>';
1076 print '</td>';
1077
1078 // Stock
1079 print '<td class="nowraponall right">';
1080 if ($tmpproduct->isStockManaged()) {
1081 if ($tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1082 print img_warning($langs->trans('StockTooLow')).' ';
1083 }
1084 print '<span class="left">'. $tmpproduct->stock_reel .' </span>';
1085 }
1086 print '</td>';
1087
1088 // Batch
1089 /*
1090 print '<td class="right">';
1091 print '</td>';
1092 */
1093
1094 // Action delete line
1095 print '<td colspan="'.($permissiontodelete ? 4 : 3).'">';
1096 print '<input type="submit" class="button buttongen button-add small nominwidth" name="save" value="' . $langs->trans("Save") . '">';
1097 print '<input type="submit" class="button buttongen button-cancel small nominwidth" name="cancel" value="' . $langs->trans("Cancel") . '">';
1098 print '</td>';
1099
1100 print '</tr>';
1101
1102 // Extrafields Line
1103 if (!empty($extrafields)) {
1104 $line->fetch_optionals();
1105 $temps = $line->showOptionals($extrafields, 'edit', array(), '', '', 1, 'line');
1106 if (!empty($temps)) {
1107 print '<td colspan="10"><div style="padding-top: 20px" id="extrafield_lines_area_edit" name="extrafield_lines_area_edit">';
1108 print $temps;
1109 print '</div></td>';
1110 }
1111 }
1112 } else {
1113 $suffix = '_' . $line->id;
1114 print '<!-- Line to dispatch ' . $suffix . ' -->' . "\n";
1115 // hidden fields for js function
1116 print '<input id="qty_ordered' . $suffix . '" type="hidden" value="' . $line->qty . '">';
1117 print '<input id="qty_dispatched' . $suffix . '" type="hidden" value="' . $alreadyconsumed . '">';
1118
1119 print '<tr data-line-id="' . $line->id . '">';
1120
1121 // Product
1122 print '<td>' . $tmpproduct->getNomUrl(1);
1123 print '<br><div class="opacitymedium small tdoverflowmax150" title="' . dol_escape_htmltag($tmpproduct->label) . '">' . $tmpproduct->label . '</div>';
1124 print '</td>';
1125
1126 // Qty
1127 print '<td class="right nowraponall">';
1128 $help = '';
1129 if ($line->qty_frozen) {
1130 $help = ($help ? '<br>' : '') . '<strong>' . $langs->trans("QuantityFrozen") . '</strong>: ' . yn(1) . ' (' . $langs->trans("QuantityConsumedInvariable") . ')';
1131 print $form->textwithpicto('', $help, -1, 'lock') . ' ';
1132 }
1133 if ($line->disable_stock_change) {
1134 $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")) . ')';
1135 print $form->textwithpicto('', $help, -1, 'help') . ' ';
1136 }
1137 print price2num($line->qty, 'MS');
1138 print '</td>';
1139
1140 // Unit
1141 print '<td class="right nowraponall">';
1142 $useunit = (($tmpproduct->type == Product::TYPE_PRODUCT && getDolGlobalInt('PRODUCT_USE_UNITS')) || (($tmpproduct->type == Product::TYPE_SERVICE) && ($line->fk_unit)));
1143 if ($useunit) {
1144 print measuringUnitString($line->fk_unit, '', '', 2);
1145 }
1146 print '</td>';
1147
1148 // Cost price
1149 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1150 print '<td class="right nowraponall">';
1151 print price($linecost);
1152 print '</td>';
1153 }
1154
1155 // Already consumed
1156 print '<td class="right">';
1157 if ($alreadyconsumed) {
1158 print '<script>';
1159 print 'jQuery(document).ready(function() {
1160 jQuery("#expandtoproduce' . $line->id . '").click(function() {
1161 console.log("Expand mrp_production line ' . $line->id . '");
1162 jQuery(".expanddetail' . $line->id . '").toggle();';
1163 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1164 print 'if (jQuery("#tablelines").hasClass("nobottom")) { jQuery("#tablelines").removeClass("nobottom"); } else { jQuery("#tablelines").addClass("nobottom"); }';
1165 }
1166 print '
1167 });
1168 });';
1169 print '</script>';
1170 if (empty($conf->use_javascript_ajax)) {
1171 print '<a href="' . $_SERVER["PHP_SELF"] . '?collapse=' . $collapse . ',' . $line->id . '">';
1172 }
1173 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce' . $line->id . '"');
1174 if (empty($conf->use_javascript_ajax)) {
1175 print '</a>';
1176 }
1177 } else {
1178 if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
1179 print '<script>jQuery("#tablelines").removeClass("nobottom");</script>';
1180 }
1181 }
1182 print ' ' . price2num($alreadyconsumed, 'MS');
1183 print '</td>';
1184 // Warehouse and/or workstation
1185 print '<td>';
1186 if (getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') && $tmpwarehouse->id > 0) {
1187 print img_picto('', $tmpwarehouse->picto) . " " . $tmpwarehouse->label;
1188 }
1189 if (isModEnabled('workstation') && $line->fk_default_workstation > 0) {
1190 $tmpworkstation = new Workstation($db);
1191 $tmpworkstation->fetch($line->fk_default_workstation);
1192 print $tmpworkstation->getNomUrl(1);
1193 }
1194 print '</td>';
1195 // Stock
1196 if (isModEnabled('stock')) {
1197 print '<td class="nowraponall right">';
1198 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $tmpproduct->type != Product::TYPE_SERVICE) {
1199 if (!$line->disable_stock_change && $tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
1200 print img_warning($langs->trans('StockTooLow')) . ' ';
1201 }
1202 if (!getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE') || empty($tmpwarehouse->id)) {
1203 print price2num($tmpproduct->stock_reel, 'MS'); // Available
1204 } else {
1205 // Print only the stock in the selected warehouse
1206 $tmpproduct->load_stock();
1207 $wh_stock = $tmpproduct->stock_warehouse[$tmpwarehouse->id];
1208 if (!empty($wh_stock)) {
1209 print price2num($wh_stock->real, 'MS');
1210 } else {
1211 print "0";
1212 }
1213 }
1214 }
1215 print '</td>';
1216 }
1217 // Lot
1218 if (isModEnabled('productbatch')) {
1219 print '<td></td>';
1220 }
1221
1222 // Split
1223 print '<td></td>';
1224
1225 // Split All
1226 print '<td></td>';
1227
1228 // Action Edit line
1229 if ($object->status == Mo::STATUS_DRAFT) {
1230 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1231 print '<td class="center">';
1232 print '<a class="reposition editfielda" href="' . $href . '">';
1233 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1234 print '</a>';
1235 print '</td>';
1236 }
1237
1238 // Action delete line
1239 if ($permissiontodelete) {
1240 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=deleteline&token=' . newToken() . '&lineid=' . ((int) $line->id);
1241 print '<td class="center">';
1242 print '<a class="reposition" href="' . $href . '">';
1243 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1244 print '</a>';
1245 print '</td>';
1246 }
1247
1248 print '</tr>';
1249 // Extrafields Line
1250 if (!empty($extrafields)) {
1251 $line->fetch_optionals();
1252 $temps = $line->showOptionals($extrafields, 'view', array(), '', '', 1, 'line');
1253 if (!empty($temps)) {
1254 print '<td colspan="10"><div id="extrafield_lines_area_'.$line->id.'" name="extrafield_lines_area_'.$line->id.'">';
1255 print $temps;
1256 print '</div></td>';
1257 }
1258 }
1259 }
1260 // Show detailed of already consumed with js code to collapse
1261 foreach ($arrayoflines as $line2) {
1262 print '<tr class="expanddetail'.$line->id.' hideobject opacitylow">';
1263
1264 // Date
1265 print '<td>';
1266 $tmpstockmovement->id = $line2['fk_stock_movement'];
1267 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1268 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1269 print '</td>';
1270
1271 // Already consumed
1272 print '<td></td>';
1273
1274 // Qty
1275 print '<td class="right">'.$line2['qty'].'</td>';
1276
1277 // Cost price
1278 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1279 print '<td></td>';
1280 }
1281
1282 // Warehouse
1283 print '<td class="tdoverflowmax150">';
1284 if ($line2['fk_warehouse'] > 0) {
1285 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1286 if ($result > 0) {
1287 print $tmpwarehouse->getNomUrl(1);
1288 }
1289 }
1290 print '</td>';
1291
1292 // Stock
1293 if (isModEnabled('stock')) {
1294 print '<td></td>';
1295 }
1296
1297 // Lot Batch
1298 if (isModEnabled('productbatch')) {
1299 print '<td>';
1300 if ($line2['batch'] != '') {
1301 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1302 print $tmpbatch->getNomUrl(1);
1303 }
1304 print '</td>';
1305 }
1306
1307 // Split
1308 print '<td></td>';
1309
1310 // Split All
1311 print '<td></td>';
1312
1313 // Action Edit line
1314 if ($object->status == Mo::STATUS_DRAFT) {
1315 $href = $_SERVER["PHP_SELF"] . '?id=' . ((int) $object->id) . '&action=editline&token=' . newToken() . '&lineid=' . ((int) $line2['rowid']);
1316 print '<td class="center">';
1317 print '<a class="reposition" href="' . $href . '">';
1318 print img_picto($langs->trans('TooltipEditAndRevertStockMovement'), 'edit');
1319 print '</a>';
1320 print '</td>';
1321 }
1322
1323 // Action delete line
1324 if ($permissiontodelete) {
1325 $href = $_SERVER["PHP_SELF"].'?id='.((int) $object->id).'&action=deleteline&token='.newToken().'&lineid='.((int) $line2['rowid']).'&fk_movement='.((int) $line2['fk_stock_movement']);
1326 print '<td class="center">';
1327 print '<a class="reposition" href="'.$href.'">';
1328 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1329 print '</a>';
1330 print '</td>';
1331 }
1332
1333 print '</tr>';
1334 }
1335
1336 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1337 $i = 1;
1338 print '<!-- Enter line to consume -->'."\n";
1339 $maxQty = 1;
1340 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1341 // Ref
1342 print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
1343 $preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
1344 if ($action == 'consumeorproduce' && !GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
1345 $preselected = 0;
1346 }
1347
1348 $disable = '';
1349 if (getDolGlobalString('MRP_NEVER_CONSUME_MORE_THAN_EXPECTED') && ($line->qty - $alreadyconsumed) <= 0) {
1350 $disable = 'disabled';
1351 }
1352
1353 // input hidden with fk_product of line
1354 print '<input type="hidden" name="product-'.$line->id.'-'.$i.'" value="'.$line->fk_product.'">';
1355
1356 // Qty
1357 print '<td class="right"><input type="text" class="width50 right" id="qtytoconsume-' . $line->id . '-' . $i . '" name="qty-' . $line->id . '-' . $i . '" value="' . $preselected . '" ' . $disable . '></td>';
1358
1359 // Unit
1360 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1361 print '<td></td>';
1362 }
1363
1364 // Cost
1365 if ($permissiontoupdatecost && getDolGlobalString('MRP_SHOW_COST_FOR_CONSUMPTION')) {
1366 print '<td></td>';
1367 }
1368
1369 // Already consumed
1370 print '<td></td>';
1371
1372 // Warehouse
1373 print '<td>';
1374 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1375 if (empty($line->disable_stock_change)) {
1376 $preselected = (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i) ? GETPOST('idwarehouse-'.$line->id.'-'.$i) : ($tmpproduct->fk_default_warehouse > 0 ? $tmpproduct->fk_default_warehouse : 'ifone'));
1377 print $formproduct->selectWarehouses($preselected, 'idwarehouse-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1378 } else {
1379 print '<span class="opacitymedium">'.$langs->trans("DisableStockChange").'</span>';
1380 }
1381 } else {
1382 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1383 }
1384 print '</td>';
1385
1386 // Stock
1387 if (isModEnabled('stock')) {
1388 print '<td></td>';
1389 }
1390
1391 // Lot / Batch
1392 if (isModEnabled('productbatch')) {
1393 print '<td class="nowraponall">';
1394 if ($tmpproduct->status_batch) {
1395 $preselected = (GETPOSTISSET('batch-'.$line->id.'-'.$i) ? GETPOST('batch-'.$line->id.'-'.$i) : '');
1396 print '<input type="text" class="width75" name="batch-'.$line->id.'-'.$i.'" value="'.$preselected.'" list="batch-'.$line->id.'-'.$i.'">';
1397 print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, '', '');
1398 }
1399 print '</td>';
1400 }
1401
1402 // Split
1403 $type = 'batch';
1404 print '<td align="right" class="split">';
1405 print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
1406 print '</td>';
1407
1408 // Split All
1409 print '<td align="right" class="splitall">';
1410 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1411 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"');
1412 }
1413 print '</td>';
1414
1415 // Edit Line
1416 if ($object->status == Mo::STATUS_DRAFT) {
1417 print '<td></td>';
1418 }
1419
1420 // Action delete line
1421 if ($permissiontodelete) {
1422 print '<td></td>';
1423 }
1424
1425 print '</tr>';
1426 }
1427 }
1428 }
1429 }
1430
1431 print '</table>';
1432 print '</div>';
1433
1434 // default warehouse processing
1435 print '<script type="text/javascript">
1436 $(document).ready(function () {
1437 $("select[name=fk_default_warehouse]").change(function() {
1438 var fk_default_warehouse = $("option:selected", this).val();
1439 $("select[name^=idwarehouse-]").val(fk_default_warehouse).change();
1440 });
1441 });
1442 </script>';
1443
1444 if (in_array($action, array('consumeorproduce', 'consumeandproduceall')) &&
1445 getDolGlobalString('STOCK_CONSUMPTION_FROM_MANUFACTURING_WAREHOUSE')) {
1446 print '<script>$(document).ready(function () {
1447 $("#fk_default_warehouse").change();
1448 });</script>';
1449 }
1450
1451
1452 // Lines to produce
1453
1454 print '</div>';
1455 print '<div class="fichehalfright">';
1456 print '<div class="clearboth"></div>';
1457
1458 $nblinetoproduce = 0;
1459 foreach ($object->lines as $line) {
1460 if ($line->role == 'toproduce') {
1461 $nblinetoproduce++;
1462 }
1463 }
1464
1465 $newcardbutton = '';
1466 $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addproduceline&token='.newToken();
1467 $permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
1468 $parameters = array('morecss' => 'reposition');
1469 if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
1470 if ($nblinetoproduce == 0 || $object->mrptype == 1) {
1471 $newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaproductline, $parameters);
1472 }
1473 }
1474
1475 print load_fiche_titre($langs->trans('Production'), $newcardbutton, '', 0, '', '');
1476
1477 print '<div class="div-table-responsive-no-min">';
1478 print '<table id="tablelinestoproduce" class="noborder noshadow nobottom centpercent">';
1479
1480 print '<tr class="liste_titre">';
1481 // Product
1482 print '<td>'.$langs->trans("Product").'</td>';
1483 // Qty
1484 print '<td class="right">'.$langs->trans("Qty").'</td>';
1486 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1487 print '<td class="right">'.$langs->trans("Unit").'</td>';
1488 }
1489 // Cost price
1490 if ($permissiontoupdatecost) {
1491 if (empty($bomcostupdated)) {
1492 print '<td class="right">'.$form->textwithpicto($langs->trans("UnitCost"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1493 } else {
1494 print '<td class="right">'.$form->textwithpicto($langs->trans("ManufacturingPrice"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1495 }
1496 }
1497 // Already produced
1498 print '<td class="right">'.$form->textwithpicto($langs->trans("QtyAlreadyProducedShort"), $langs->trans("QtyAlreadyProduced")).'</td>';
1499 // Warehouse
1500 print '<td>';
1501 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1502 print $langs->trans("Warehouse");
1503 }
1504 print '</td>';
1505
1506 // Lot
1507 if (isModEnabled('productbatch')) {
1508 print '<td>';
1509 if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1510 print $langs->trans("Batch");
1511 }
1512 print '</td>';
1513
1514 // Split
1515 print '<td></td>';
1516
1517 // Split All
1518 print '<td></td>';
1519 }
1520
1521 // Action delete
1522 if ($permissiontodelete) {
1523 print '<td></td>';
1524 }
1525
1526 print '</tr>';
1527
1528 if ($action == 'addproduceline') {
1529 print '<!-- Add line to produce -->'."\n";
1530 print '<tr class="liste_titre">';
1531
1532 // Product
1533 print '<td>';
1534 print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
1535 print '</td>';
1536 // Qty
1537 print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
1538 //Unit
1539 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1540 print '<td></td>';
1541 }
1542 // Cost price
1543 if ($permissiontoupdatecost) {
1544 print '<td></td>';
1545 }
1546 // Action (cost price + already produced)
1547 print '<td colspan="2">';
1548 print '<input type="submit" class="button buttongen button-add" name="addproducelinebutton" value="'.$langs->trans("Add").'">';
1549 print '<input type="submit" class="button buttongen button-cancel" name="canceladdproducelinebutton" value="'.$langs->trans("Cancel").'">';
1550 print '</td>';
1551 // Lot - serial
1552 if (isModEnabled('productbatch')) {
1553 print '<td></td>';
1554
1555 // Split
1556 print '<td></td>';
1557
1558 // Split All
1559 print '<td></td>';
1560 }
1561 // Action delete
1562 if ($permissiontodelete) {
1563 print '<td></td>';
1564 }
1565 print '</tr>';
1566 }
1567
1568 if (!empty($object->lines)) {
1569 $nblinetoproduce = 0;
1570 foreach ($object->lines as $line) {
1571 if ($line->role == 'toproduce') {
1572 $nblinetoproduce++;
1573 }
1574 }
1575
1576 $nblinetoproducecursor = 0;
1577 foreach ($object->lines as $line) {
1578 if ($line->role == 'toproduce') {
1579 $i = 1;
1580
1581 $nblinetoproducecursor++;
1582
1583 $tmpproduct = new Product($db);
1584 $tmpproduct->fetch($line->fk_product);
1585
1586 $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
1587 $alreadyproduced = 0;
1588 foreach ($arrayoflines as $line2) {
1589 $alreadyproduced += $line2['qty'];
1590 }
1591
1592 $suffix = '_'.$line->id;
1593 print '<!-- Line to dispatch '.$suffix.' (toproduce) -->'."\n";
1594 // hidden fields for js function
1595 print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
1596 print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyproduced.'">';
1597
1598 print '<tr>';
1599 // Product
1600 print '<td>'.$tmpproduct->getNomUrl(1);
1601 print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
1602 print '</td>';
1603 // Qty
1604 print '<td class="right">'.$line->qty.'</td>';
1605 // Unit
1606 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1607 print '<td class="right">'.measuringUnitString($line->fk_unit, '', '', 1).'</td>';
1608 }
1609 // Cost price
1610 if ($permissiontoupdatecost) {
1611 // Defined $manufacturingcost
1612 $manufacturingcost = 0;
1613 $manufacturingcostsrc = '';
1614 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1615 $manufacturingcost = $bomcostupdated;
1616 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1617 if (empty($manufacturingcost)) {
1618 $manufacturingcost = $bomcost;
1619 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1620 }
1621 if (empty($manufacturingcost)) {
1622 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1623 $manufacturingcostsrc = $langs->trans("CostPrice");
1624 }
1625 if (empty($manufacturingcost)) {
1626 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1627 $manufacturingcostsrc = $langs->trans("PMPValue");
1628 }
1629 }
1630
1631 print '<td class="right nowraponall" title="'.dol_escape_htmltag($manufacturingcostsrc).'">';
1632 if ($manufacturingcost) {
1633 print price($manufacturingcost);
1634 }
1635 print '</td>';
1636 }
1637 // Already produced
1638 print '<td class="right nowraponall">';
1639 if ($alreadyproduced) {
1640 print '<script>';
1641 print 'jQuery(document).ready(function() {
1642 jQuery("#expandtoproduce'.$line->id.'").click(function() {
1643 console.log("Expand mrp_production line '.$line->id.'");
1644 jQuery(".expanddetailtoproduce'.$line->id.'").toggle();';
1645 if ($nblinetoproduce == $nblinetoproducecursor) {
1646 print 'if (jQuery("#tablelinestoproduce").hasClass("nobottom")) { jQuery("#tablelinestoproduce").removeClass("nobottom"); } else { jQuery("#tablelinestoproduce").addClass("nobottom"); }';
1647 }
1648 print '
1649 });
1650 });';
1651 print '</script>';
1652 if (empty($conf->use_javascript_ajax)) {
1653 print '<a href="'.$_SERVER["PHP_SELF"].'?collapse='.$collapse.','.$line->id.'">';
1654 }
1655 print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce'.$line->id.'"');
1656 if (empty($conf->use_javascript_ajax)) {
1657 print '</a>';
1658 }
1659 }
1660 print ' '.$alreadyproduced;
1661 print '</td>';
1662 // Warehouse
1663 print '<td>';
1664 print '</td>';
1665 // Lot
1666 if (isModEnabled('productbatch')) {
1667 print '<td></td>';
1668
1669 // Split
1670 print '<td></td>';
1671
1672 // Split All
1673 print '<td></td>';
1674 }
1675 // Delete
1676 if ($permissiontodelete) {
1677 if ($line->origin_type == 'free') {
1678 $href = $_SERVER["PHP_SELF"];
1679 $href .= '?id='.$object->id;
1680 $href .= '&action=deleteline';
1681 $href .= '&lineid='.$line->id;
1682 print '<td class="center">';
1683 print '<a class="reposition" href="'.$href.'">';
1684 print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), "delete");
1685 print '</a>';
1686 print '</td>';
1687 } else {
1688 print '<td></td>';
1689 }
1690 }
1691 print '</tr>';
1692
1693 // Show detailed of already consumed with js code to collapse
1694 foreach ($arrayoflines as $line2) {
1695 print '<tr class="expanddetailtoproduce'.$line->id.' hideobject opacitylow">';
1696 // Product
1697 print '<td>';
1698 $tmpstockmovement->id = $line2['fk_stock_movement'];
1699 print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1700 print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1701 print '</td>';
1702 // Qty
1703 print '<td></td>';
1704 // Unit
1705 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1706 print '<td></td>';
1707 }
1708 // Cost price
1709 if ($permissiontoupdatecost) {
1710 print '<td></td>';
1711 }
1712 // Already produced
1713 print '<td class="right">'.$line2['qty'].'</td>';
1714 // Warehouse
1715 print '<td class="tdoverflowmax150">';
1716 if ($line2['fk_warehouse'] > 0) {
1717 $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1718 if ($result > 0) {
1719 print $tmpwarehouse->getNomUrl(1);
1720 }
1721 }
1722 print '</td>';
1723 // Lot
1724 if (isModEnabled('productbatch')) {
1725 print '<td>';
1726 if ($line2['batch'] != '') {
1727 $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1728 print $tmpbatch->getNomUrl(1);
1729 }
1730 print '</td>';
1731
1732 // Split
1733 print '<td></td>';
1734
1735 // Split All
1736 print '<td></td>';
1737 }
1738 // Action delete
1739 if ($permissiontodelete) {
1740 print '<td></td>';
1741 }
1742 print '</tr>';
1743 }
1744
1745 if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1746 print '<!-- Enter line to produce -->'."\n";
1747 $maxQty = 1;
1748 print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1749 // Product
1750 print '<td><span class="opacitymedium">'.$langs->trans("ToProduce").'</span></td>';
1751 $preselected = (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i) ? GETPOST('qtytoproduce-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyproduced));
1752 if ($action == 'consumeorproduce' && !GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
1753 $preselected = 0;
1754 }
1755 // Qty
1756 print '<td class="right"><input type="text" class="width50 right" id="qtytoproduce-'.$line->id.'-'.$i.'" name="qtytoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1757 //Unit
1758 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
1759 print '<td class="right"></td>';
1760 }
1761 // Cost
1762 if ($permissiontoupdatecost) {
1763 // Defined $manufacturingcost
1764 $manufacturingcost = 0;
1765 $manufacturingcostsrc = '';
1766 if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1767 $manufacturingcost = $bomcostupdated;
1768 $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1769 if (empty($manufacturingcost)) {
1770 $manufacturingcost = $bomcost;
1771 $manufacturingcostsrc = $langs->trans("ValueFromBom");
1772 }
1773 if (empty($manufacturingcost)) {
1774 $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1775 $manufacturingcostsrc = $langs->trans("CostPrice");
1776 }
1777 if (empty($manufacturingcost)) {
1778 $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1779 $manufacturingcostsrc = $langs->trans("PMPValue");
1780 }
1781 }
1782
1783 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1784 $preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : ($manufacturingcost ? price($manufacturingcost) : ''));
1785 print '<td class="right"><input type="text" class="width75 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1786 } else {
1787 print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.($manufacturingcost ? $manufacturingcost : '').'"></td>';
1788 }
1789 }
1790 // Already produced
1791 print '<td></td>';
1792 // Warehouse
1793 print '<td>';
1794 if ($tmpproduct->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1795 $preselected = (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i) ? GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) : ($object->fk_warehouse > 0 ? $object->fk_warehouse : 'ifone'));
1796 print $formproduct->selectWarehouses($preselected, 'idwarehousetoproduce-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1797 } else {
1798 print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1799 }
1800 print '</td>';
1801 // Lot
1802 if (isModEnabled('productbatch')) {
1803 print '<td>';
1804 if ($tmpproduct->status_batch) {
1805 $preselected = (GETPOSTISSET('batchtoproduce-'.$line->id.'-'.$i) ? GETPOST('batchtoproduce-'.$line->id.'-'.$i) : '');
1806 print '<input type="text" class="width75" name="batchtoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'">';
1807 }
1808 print '</td>';
1809 // Batch number in same column than the stock movement picto
1810 if ($tmpproduct->status_batch) {
1811 $type = 'batch';
1812 print '<td align="right" class="split">';
1813 print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissing\')"');
1814 print '</td>';
1815
1816 print '<td align="right" class="splitall">';
1817 if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) {
1818 print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" onClick="addDispatchLine('.$line->id.', \'batch\', \'alltoproduce\')"');
1819 } //
1820 print '</td>';
1821 } else {
1822 print '<td></td>';
1823
1824 print '<td></td>';
1825 }
1826 }
1827
1828 // Action delete
1829 print '<td></td>';
1830
1831 print '</tr>';
1832 }
1833 }
1834 }
1835 }
1836
1837 print '</table>';
1838 print '</div>';
1839
1840 print '</div>';
1841 print '</div>';
1842 }
1843
1844 if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline'))) {
1845 print "</form>\n";
1846 } ?>
1847
1848 <script type="text/javascript" language="javascript">
1849
1850 $(document).ready(function() {
1851 //Consumption : When a warehouse is selected, only the lot/serial numbers that are available in it are offered
1852 updateselectbatchbywarehouse();
1853 //Consumption : When a lot/serial number is selected and it is only available in one warehouse, the warehouse is automatically selected
1854 updateselectwarehousebybatch();
1855 });
1856
1857 function updateselectbatchbywarehouse() {
1858 $(document).on('change', "select[name*='idwarehouse']", function () {
1859 console.log("We change warehouse so we update the list of possible batch number");
1860
1861 var selectwarehouse = $(this);
1862
1863 var selectbatch_name = selectwarehouse.attr('name').replace('idwarehouse', 'batch');
1864 var selectbatch = $("datalist[id*='" + selectbatch_name + "']");
1865 var selectedbatch = selectbatch.val();
1866
1867 var product_element_name = selectwarehouse.attr('name').replace('idwarehouse', 'product');
1868
1869 $.ajax({
1870 type: "POST",
1871 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1872 data: {
1873 action: "updateselectbatchbywarehouse",
1874 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1875 warehouse_id: $(this).val(),
1876 token: '<?php echo currentToken(); ?>',
1877 product_id: $("input[name='" + product_element_name + "']").val()
1878 }
1879 }).done(function (data) {
1880
1881 selectbatch.empty();
1882
1883 if (typeof data == "object") {
1884 console.log("data is already type object, no need to parse it");
1885 } else {
1886 console.log("data is type "+(typeof data));
1887 data = JSON.parse(data);
1888 }
1889
1890 selectbatch.append($('<option>', {
1891 value: '',
1892 }));
1893
1894 $.each(data, function (key, value) {
1895
1896 if(selectwarehouse.val() == -1) {
1897 var label = " (<?php echo $langs->trans('Stock total') ?> : " + value + ")";
1898 } else {
1899 var label = " (<?php echo $langs->trans('Stock') ?> : " + value + ")";
1900 }
1901
1902 if(key === selectedbatch) {
1903 var option ='<option value="'+key+'" selected>'+ label +'</option>';
1904 } else {
1905 var option ='<option value="'+key+'">'+ label +'</option>';
1906 }
1907
1908 selectbatch.append(option);
1909 });
1910 });
1911 });
1912 }
1913
1914 function updateselectwarehousebybatch() {
1915 $(document).on('change', 'input[name*=batch]', function(){
1916 console.log("We change batch so we update the list of possible warehouses");
1917
1918 var selectbatch = $(this);
1919
1920 var selectwarehouse_name = selectbatch.attr('name').replace('batch', 'idwarehouse');
1921 var selectwarehouse = $("select[name*='" + selectwarehouse_name + "']");
1922 var selectedwarehouse = selectwarehouse.val();
1923
1924 if(selectedwarehouse != -1){
1925 return;
1926 }
1927
1928 var product_element_name = selectbatch.attr('name').replace('batch', 'product');
1929
1930 $.ajax({
1931 type: "POST",
1932 url: "<?php echo DOL_URL_ROOT . '/mrp/ajax/interface.php'; ?>",
1933 data: {
1934 action: "updateselectwarehousebybatch",
1935 permissiontoproduce: <?php echo $permissiontoproduce ?>,
1936 batch: $(this).val(),
1937 token: '<?php echo currentToken(); ?>',
1938 product_id: $("input[name='" + product_element_name + "']").val()
1939 }
1940 }).done(function (data) {
1941
1942 if (typeof data == "object") {
1943 console.log("data is already type object, no need to parse it");
1944 } else {
1945 console.log("data is type "+(typeof data));
1946 data = JSON.parse(data);
1947 }
1948
1949 if(data != 0){
1950 selectwarehouse.val(data).change();
1951 }
1952 });
1953 });
1954 }
1955
1956 </script>
1957
1958 <?php
1959}
1960
1961// End of page
1962llxFooter();
1963$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($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:36
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:333
llxFooter()
Footer empty.
Definition document.php:107
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).
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
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:139
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.