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