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