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