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