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