dolibarr  x.y.z
mo_production.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2019-2020 Laurent Destailleur <eldy@users.sourceforge.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16  */
17 
24 // Load Dolibarr environment
25 require '../main.inc.php';
26 
27 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
28 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
29 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
30 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
31 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
32 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
33 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
34 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
35 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
36 dol_include_once('/mrp/class/mo.class.php');
37 dol_include_once('/bom/class/bom.class.php');
38 dol_include_once('/mrp/lib/mrp_mo.lib.php');
39 
40 // Load translation files required by the page
41 $langs->loadLangs(array("mrp", "stocks", "other", "product", "productbatch"));
42 
43 // Get parameters
44 $id = GETPOST('id', 'int');
45 $ref = GETPOST('ref', 'alpha');
46 $action = GETPOST('action', 'aZ09');
47 $confirm = GETPOST('confirm', 'alpha');
48 $cancel = GETPOST('cancel', 'aZ09');
49 $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'mocard'; // To manage different context of search
50 $backtopage = GETPOST('backtopage', 'alpha');
51 $lineid = GETPOST('lineid', 'int');
52 $fk_movement = GETPOST('fk_movement', 'int');
53 $fk_default_warehouse = GETPOST('fk_default_warehouse', 'int');
54 
55 $collapse = GETPOST('collapse', 'aZ09comma');
56 
57 // Initialize technical objects
58 $object = new Mo($db);
59 $extrafields = new ExtraFields($db);
60 $diroutputmassaction = $conf->mrp->dir_output.'/temp/massgeneration/'.$user->id;
61 $hookmanager->initHooks(array('mocard', 'globalcard')); // Note that conf->hooks_modules contains array
62 
63 // Fetch optionals attributes and labels
64 $extrafields->fetch_name_optionals_label($object->table_element);
65 
66 $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
67 
68 // Initialize array of search criterias
69 $search_all = GETPOST("search_all", 'alpha');
70 $search = array();
71 foreach ($object->fields as $key => $val) {
72  if (GETPOST('search_'.$key, 'alpha')) {
73  $search[$key] = GETPOST('search_'.$key, 'alpha');
74  }
75 }
76 
77 if (empty($action) && empty($id) && empty($ref)) {
78  $action = 'view';
79 }
80 
81 // Load object
82 include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once.
83 
84 // Security check - Protection if external user
85 //if ($user->socid > 0) accessforbidden();
86 //if ($user->socid > 0) $socid = $user->socid;
87 $isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0);
88 $result = restrictedArea($user, 'mrp', $object->id, 'mrp_mo', '', 'fk_soc', 'rowid', $isdraft);
89 
90 $permissionnote = $user->rights->mrp->write; // Used by the include of actions_setnotes.inc.php
91 $permissiondellink = $user->rights->mrp->write; // Used by the include of actions_dellink.inc.php
92 $permissiontoadd = $user->rights->mrp->write; // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php
93 $permissiontodelete = $user->rights->mrp->delete || ($permissiontoadd && isset($object->status) && $object->status == $object::STATUS_DRAFT);
94 $upload_dir = $conf->mrp->multidir_output[isset($object->entity) ? $object->entity : 1];
95 
96 $permissiontoproduce = $permissiontoadd;
97 $permissiontoupdatecost = $user->rights->bom->read; // User who can define cost must have knowledge of pricing
98 
99 
100 /*
101  * Actions
102  */
103 
104 $parameters = array();
105 $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
106 if ($reshook < 0) {
107  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
108 }
109 
110 if (empty($reshook)) {
111  $error = 0;
112 
113  $backurlforlist = dol_buildpath('/mrp/mo_list.php', 1);
114 
115  if (empty($backtopage) || ($cancel && empty($id))) {
116  //var_dump($backurlforlist);exit;
117  if (empty($id) && (($action != 'add' && $action != 'create') || $cancel)) {
118  $backtopage = $backurlforlist;
119  } else {
120  $backtopage = DOL_URL_ROOT.'/mrp/mo_production.php?id='.($id > 0 ? $id : '__ID__');
121  }
122  }
123  $triggermodname = 'MO_MODIFY'; // Name of trigger action code to execute when we modify record
124 
125  // Actions cancel, add, update, delete or clone
126  include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php';
127 
128  // Actions when linking object each other
129  include DOL_DOCUMENT_ROOT.'/core/actions_dellink.inc.php';
130 
131  // Actions when printing a doc from card
132  include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
133 
134  // Actions to send emails
135  $triggersendname = 'MO_SENTBYMAIL';
136  $autocopy = 'MAIN_MAIL_AUTOCOPY_MO_TO';
137  $trackid = 'mo'.$object->id;
138  include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';
139 
140  // Action to move up and down lines of object
141  //include DOL_DOCUMENT_ROOT.'/core/actions_lineupdown.inc.php'; // Must be include, not include_once
142 
143  if ($action == 'set_thirdparty' && $permissiontoadd) {
144  $object->setValueFrom('fk_soc', GETPOST('fk_soc', 'int'), '', '', 'date', '', $user, $triggermodname);
145  }
146  if ($action == 'classin' && $permissiontoadd) {
147  $object->setProject(GETPOST('projectid', 'int'));
148  }
149 
150  if ($action == 'confirm_reopen' && $permissiontoadd) {
151  $result = $object->setStatut($object::STATUS_INPROGRESS, 0, '', 'MRP_REOPEN');
152  }
153 
154  if (($action == 'confirm_addconsumeline' && GETPOST('addconsumelinebutton') && $permissiontoadd)
155  || ($action == 'confirm_addproduceline' && GETPOST('addproducelinebutton') && $permissiontoadd)) {
156  $moline = new MoLine($db);
157 
158  // Line to produce
159  $moline->fk_mo = $object->id;
160  $moline->qty = GETPOST('qtytoadd', 'int');
161  $moline->fk_product = GETPOST('productidtoadd', 'int');
162  if (GETPOST('addconsumelinebutton')) {
163  $moline->role = 'toconsume';
164  } else {
165  $moline->role = 'toproduce';
166  }
167  $moline->origin_type = 'free'; // free consume line
168  $moline->position = 0;
169 
170  $resultline = $moline->create($user, false); // Never use triggers here
171  if ($resultline <= 0) {
172  $error++;
173  setEventMessages($moline->error, $molines->errors, 'errors');
174  }
175 
176  $action = '';
177  // Redirect to refresh the tab information
178  header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
179  }
180 
181  if (in_array($action, array('confirm_consumeorproduce', 'confirm_consumeandproduceall')) && $permissiontoproduce) {
182  $stockmove = new MouvementStock($db);
183 
184  $labelmovement = GETPOST('inventorylabel', 'alphanohtml');
185  $codemovement = GETPOST('inventorycode', 'alphanohtml');
186 
187  $db->begin();
188  $pos = 0;
189  // Process line to consume
190  foreach ($object->lines as $line) {
191  if ($line->role == 'toconsume') {
192  $tmpproduct = new Product($db);
193  $tmpproduct->fetch($line->fk_product);
194 
195  $i = 1;
196  while (GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
197  $qtytoprocess = price2num(GETPOST('qty-'.$line->id.'-'.$i));
198 
199  if ($qtytoprocess != 0) {
200  // Check warehouse is set if we should have to
201  if (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i)) { // If there is a warehouse to set
202  if (!(GETPOST('idwarehouse-'.$line->id.'-'.$i) > 0)) { // If there is no warehouse set.
203  $langs->load("errors");
204  setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), $tmpproduct->ref), null, 'errors');
205  $error++;
206  }
207  if ($tmpproduct->status_batch && (!GETPOST('batch-'.$line->id.'-'.$i))) {
208  $langs->load("errors");
209  setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), $tmpproduct->ref), null, 'errors');
210  $error++;
211  }
212  }
213 
214  $idstockmove = 0;
215  if (!$error && GETPOST('idwarehouse-'.$line->id.'-'.$i) > 0) {
216  // Record stock movement
217  $id_product_batch = 0;
218  $stockmove->setOrigin($object->element, $object->id);
219 
220  if ($qtytoprocess >= 0) {
221  $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);
222  } else {
223  $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);
224  }
225  if ($idstockmove < 0) {
226  $error++;
227  setEventMessages($stockmove->error, $stockmove->errors, 'errors');
228  }
229  }
230 
231  if (!$error) {
232  // Record consumption
233  $moline = new MoLine($db);
234  $moline->fk_mo = $object->id;
235  $moline->position = $pos;
236  $moline->fk_product = $line->fk_product;
237  $moline->fk_warehouse = GETPOST('idwarehouse-'.$line->id.'-'.$i);
238  $moline->qty = $qtytoprocess;
239  $moline->batch = GETPOST('batch-'.$line->id.'-'.$i);
240  $moline->role = 'consumed';
241  $moline->fk_mrp_production = $line->id;
242  $moline->fk_stock_movement = $idstockmove;
243  $moline->fk_user_creat = $user->id;
244 
245  $resultmoline = $moline->create($user);
246  if ($resultmoline <= 0) {
247  $error++;
248  setEventMessages($moline->error, $moline->errors, 'errors');
249  }
250 
251  $pos++;
252  }
253  }
254 
255  $i++;
256  }
257  }
258  }
259 
260  // Process line to produce
261  $pos = 0;
262 
263  foreach ($object->lines as $line) {
264  if ($line->role == 'toproduce') {
265  $tmpproduct = new Product($db);
266  $tmpproduct->fetch($line->fk_product);
267 
268  $i = 1;
269  while (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
270  $qtytoprocess = price2num(GETPOST('qtytoproduce-'.$line->id.'-'.$i));
271  $pricetoprocess = GETPOST('pricetoproduce-'.$line->id.'-'.$i) ? price2num(GETPOST('pricetoproduce-'.$line->id.'-'.$i)) : 0;
272 
273  if ($qtytoprocess != 0) {
274  // Check warehouse is set if we should have to
275  if (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i)) { // If there is a warehouse to set
276  if (!(GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) > 0)) { // If there is no warehouse set.
277  $langs->load("errors");
278  setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), $tmpproduct->ref), null, 'errors');
279  $error++;
280  }
281  if (isModEnabled('productbatch') && $tmpproduct->status_batch && (!GETPOST('batchtoproduce-'.$line->id.'-'.$i))) {
282  $langs->load("errors");
283  setEventMessages($langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), $tmpproduct->ref), null, 'errors');
284  $error++;
285  }
286  }
287 
288  $idstockmove = 0;
289  if (!$error && GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) > 0) {
290  // Record stock movement
291  $id_product_batch = 0;
292  $stockmove->origin_type = $object->element;
293  $stockmove->origin_id = $object->id;
294 
295  $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);
296  if ($idstockmove < 0) {
297  $error++;
298  setEventMessages($stockmove->error, $stockmove->errors, 'errors');
299  }
300  }
301 
302  if (!$error) {
303  // Record production
304  $moline = new MoLine($db);
305  $moline->fk_mo = $object->id;
306  $moline->position = $pos;
307  $moline->fk_product = $line->fk_product;
308  $moline->fk_warehouse = GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i);
309  $moline->qty = $qtytoprocess;
310  $moline->batch = GETPOST('batchtoproduce-'.$line->id.'-'.$i);
311  $moline->role = 'produced';
312  $moline->fk_mrp_production = $line->id;
313  $moline->fk_stock_movement = $idstockmove;
314  $moline->fk_user_creat = $user->id;
315 
316  $resultmoline = $moline->create($user);
317  if ($resultmoline <= 0) {
318  $error++;
319  setEventMessages($moline->error, $moline->errors, 'errors');
320  }
321 
322  $pos++;
323  }
324  }
325 
326  $i++;
327  }
328  }
329  }
330 
331  if (!$error) {
332  $consumptioncomplete = true;
333  $productioncomplete = true;
334 
335  if (GETPOST('autoclose', 'int')) {
336  foreach ($object->lines as $line) {
337  if ($line->role == 'toconsume') {
338  $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
339  $alreadyconsumed = 0;
340  foreach ($arrayoflines as $line2) {
341  $alreadyconsumed += $line2['qty'];
342  }
343 
344  if ($alreadyconsumed < $line->qty) {
345  $consumptioncomplete = false;
346  }
347  }
348  if ($line->role == 'toproduce') {
349  $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
350  $alreadyproduced = 0;
351  foreach ($arrayoflines as $line2) {
352  $alreadyproduced += $line2['qty'];
353  }
354 
355  if ($alreadyproduced < $line->qty) {
356  $productioncomplete = false;
357  }
358  }
359  }
360  } else {
361  $consumptioncomplete = false;
362  $productioncomplete = false;
363  }
364 
365  // Update status of MO
366  dol_syslog("consumptioncomplete = ".$consumptioncomplete." productioncomplete = ".$productioncomplete);
367  //var_dump("consumptioncomplete = ".$consumptioncomplete." productioncomplete = ".$productioncomplete);
368  if ($consumptioncomplete && $productioncomplete) {
369  $result = $object->setStatut($object::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
370  } else {
371  $result = $object->setStatut($object::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
372  }
373  if ($result <= 0) {
374  $error++;
375  setEventMessages($object->error, $object->errors, 'errors');
376  }
377  }
378 
379  if ($error) {
380  $action = str_replace('confirm_', '', $action);
381  $db->rollback();
382  } else {
383  $db->commit();
384 
385  // Redirect to avoid to action done a second time if we make a back from browser
386  header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id);
387  exit;
388  }
389  }
390 
391  // Action close produced
392  if ($action == 'confirm_produced' && $confirm == 'yes' && $permissiontoadd) {
393  $result = $object->setStatut($object::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
394  if ($result >= 0) {
395  // Define output language
396  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
397  $outputlangs = $langs;
398  $newlang = '';
399  if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && GETPOST('lang_id', 'aZ09')) {
400  $newlang = GETPOST('lang_id', 'aZ09');
401  }
402  if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) {
403  $newlang = $object->thirdparty->default_lang;
404  }
405  if (!empty($newlang)) {
406  $outputlangs = new Translate("", $conf);
407  $outputlangs->setDefaultLang($newlang);
408  }
409  $model = $object->model_pdf;
410  $ret = $object->fetch($id); // Reload to get new records
411 
412  $object->generateDocument($model, $outputlangs, 0, 0, 0);
413  }
414  } else {
415  setEventMessages($object->error, $object->errors, 'errors');
416  }
417  }
418 }
419 
420 
421 
422 /*
423  * View
424  */
425 
426 $form = new Form($db);
427 $formproject = new FormProjets($db);
428 $formproduct = new FormProduct($db);
429 $tmpwarehouse = new Entrepot($db);
430 $tmpbatch = new Productlot($db);
431 $tmpstockmovement = new MouvementStock($db);
432 
433 $help_url = 'EN:Module_Manufacturing_Orders|FR:Module_Ordres_de_Fabrication';
434 llxHeader('', $langs->trans('Mo'), $help_url, '', 0, 0, array('/mrp/js/lib_dispatch.js.php'));
435 
436 $newToken = newToken();
437 
438 // Part to show record
439 if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) {
440  $res = $object->fetch_thirdparty();
441  $res = $object->fetch_optionals();
442 
443  $head = moPrepareHead($object);
444 
445  print dol_get_fiche_head($head, 'production', $langs->trans("ManufacturingOrder"), -1, $object->picto);
446 
447  $formconfirm = '';
448 
449  // Confirmation to delete
450  if ($action == 'delete') {
451  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteMo'), $langs->trans('ConfirmDeleteMo'), 'confirm_delete', '', 0, 1);
452  }
453  // Confirmation to delete line
454  if ($action == 'deleteline') {
455  $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);
456  }
457  // Clone confirmation
458  if ($action == 'clone') {
459  // Create an array for form
460  $formquestion = array();
461  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMo', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
462  }
463 
464  // Confirmation of validation
465  if ($action == 'validate') {
466  // We check that object has a temporary ref
467  $ref = substr($object->ref, 1, 4);
468  if ($ref == 'PROV') {
469  $object->fetch_product();
470  $numref = $object->getNextNumRef($object->fk_product);
471  } else {
472  $numref = $object->ref;
473  }
474 
475  $text = $langs->trans('ConfirmValidateMo', $numref);
476  /*if (isModEnabled('notification'))
477  {
478  require_once DOL_DOCUMENT_ROOT . '/core/class/notify.class.php';
479  $notify = new Notify($db);
480  $text .= '<br>';
481  $text .= $notify->confirmMessage('BOM_VALIDATE', $object->socid, $object);
482  }*/
483 
484  $formquestion = array();
485  if (isModEnabled('mrp')) {
486  $langs->load("mrp");
487  require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
488  $formproduct = new FormProduct($db);
489  $forcecombo = 0;
490  if ($conf->browser->name == 'ie') {
491  $forcecombo = 1; // There is a bug in IE10 that make combo inside popup crazy
492  }
493  $formquestion = array(
494  // 'text' => $langs->trans("ConfirmClone"),
495  // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => 1),
496  // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' => 1),
497  );
498  }
499 
500  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Validate'), $text, 'confirm_validate', $formquestion, 0, 1, 220);
501  }
502 
503  // Call Hook formConfirm
504  $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
505  $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
506  if (empty($reshook)) {
507  $formconfirm .= $hookmanager->resPrint;
508  } elseif ($reshook > 0) {
509  $formconfirm = $hookmanager->resPrint;
510  }
511 
512  // Print form confirm
513  print $formconfirm;
514 
515 
516  // MO file
517  // ------------------------------------------------------------
518  $linkback = '<a href="'.DOL_URL_ROOT.'/mrp/mo_list.php?restore_lastsearch_values=1'.(!empty($socid) ? '&socid='.$socid : '').'">'.$langs->trans("BackToList").'</a>';
519 
520  $morehtmlref = '<div class="refidno">';
521  /*
522  // Ref bis
523  $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', 0, 1);
524  $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->mrp->creer, 'string', '', null, null, '', 1);*/
525  // Thirdparty
526  if (is_object($object->thirdparty)) {
527  $morehtmlref .= $object->thirdparty->getNomUrl(1, 'customer');
528  if (empty($conf->global->MAIN_DISABLE_OTHER_LINK) && $object->thirdparty->id > 0) {
529  $morehtmlref .= ' (<a href="'.DOL_URL_ROOT.'/commande/list.php?socid='.$object->thirdparty->id.'&search_societe='.urlencode($object->thirdparty->name).'">'.$langs->trans("OtherOrders").'</a>)';
530  }
531  }
532  // Project
533  if (isModEnabled('project')) {
534  $langs->load("projects");
535  if (is_object($object->thirdparty)) {
536  $morehtmlref .= '<br>';
537  }
538  if ($permissiontoadd) {
539  $morehtmlref .= img_picto($langs->trans("Project"), 'project', 'class="pictofixedwidth"');
540  if ($action != 'classify') {
541  $morehtmlref .= '<a class="editfielda" href="'.$_SERVER['PHP_SELF'].'?action=classify&token='.newToken().'&id='.$object->id.'">'.img_edit($langs->transnoentitiesnoconv('SetProject')).'</a> ';
542  }
543  $morehtmlref .= $form->form_project($_SERVER['PHP_SELF'].'?id='.$object->id, $object->socid, $object->fk_project, ($action == 'classify' ? 'projectid' : 'none'), 0, ($action == 'classify' ? 1 : 0), 0, 1, '');
544  } else {
545  if (!empty($object->fk_project)) {
546  $proj = new Project($db);
547  $proj->fetch($object->fk_project);
548  $morehtmlref .= $proj->getNomUrl(1);
549  if ($proj->title) {
550  $morehtmlref .= '<span class="opacitymedium"> - '.dol_escape_htmltag($proj->title).'</span>';
551  }
552  }
553  }
554  }
555  $morehtmlref .= '</div>';
556 
557 
558  dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
559 
560 
561  print '<div class="fichecenter">';
562  print '<div class="fichehalfleft">';
563  print '<div class="underbanner clearboth"></div>';
564  print '<table class="border centpercent tableforfield">'."\n";
565 
566  // Common attributes
567  $keyforbreak = 'fk_warehouse';
568  unset($object->fields['fk_project']);
569  unset($object->fields['fk_soc']);
570  include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
571 
572  // Other attributes
573  include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
574 
575  print '</table>';
576  print '</div>';
577  print '</div>';
578 
579  print '<div class="clearboth"></div>';
580 
581  print dol_get_fiche_end();
582 
583 
584  if (!in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
585  print '<div class="tabsAction">';
586 
587  $parameters = array();
588  // Note that $action and $object may be modified by hook
589  $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action);
590  if (empty($reshook)) {
591  // Validate
592  if ($object->status == $object::STATUS_DRAFT) {
593  if ($permissiontoadd) {
594  if (empty($object->table_element_line) || (is_array($object->lines) && count($object->lines) > 0)) {
595  print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate&token='.$newToken.'">'.$langs->trans("Validate").'</a>';
596  } else {
597  $langs->load("errors");
598  print '<a class="butActionRefused" href="" title="'.$langs->trans("ErrorAddAtLeastOneLineFirst").'">'.$langs->trans("Validate").'</a>';
599  }
600  }
601  }
602 
603  // Consume or produce
604  if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
605  if ($permissiontoproduce) {
606  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeorproduce&token='.$newToken.'">'.$langs->trans('ConsumeOrProduce').'</a>';
607  } else {
608  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeOrProduce').'</a>';
609  }
610  } elseif ($object->status == Mo::STATUS_DRAFT) {
611  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeOrProduce').'</a>';
612  }
613 
614  // ConsumeAndProduceAll
615  if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
616  if ($permissiontoproduce) {
617  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeandproduceall&token='.$newToken.'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
618  } else {
619  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
620  }
621  } elseif ($object->status == Mo::STATUS_DRAFT) {
622  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ValidateBefore").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
623  }
624 
625  // Cancel - Reopen
626  if ($permissiontoadd) {
627  if ($object->status == $object::STATUS_VALIDATED || $object->status == $object::STATUS_INPROGRESS) {
628  $arrayproduced = $object->fetchLinesLinked('produced', 0);
629  $nbProduced = 0;
630  foreach ($arrayproduced as $lineproduced) {
631  $nbProduced += $lineproduced['qty'];
632  }
633  if ($nbProduced > 0) { // If production has started, we can close it
634  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_produced&confirm=yes&token='.$newToken.'">'.$langs->trans("Close").'</a>'."\n";
635  } else {
636  print '<a class="butActionRefused" href="#" title="'.$langs->trans("GoOnTabProductionToProduceFirst", $langs->transnoentitiesnoconv("Production")).'">'.$langs->trans("Close").'</a>'."\n";
637  }
638 
639  print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_close&confirm=yes&token='.$newToken.'">'.$langs->trans("Cancel").'</a>'."\n";
640  }
641 
642  if ($object->status == $object::STATUS_CANCELED) {
643  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&confirm=yes&token='.$newToken.'">'.$langs->trans("Re-Open").'</a>'."\n";
644  }
645 
646  if ($object->status == $object::STATUS_PRODUCED) {
647  if ($permissiontoproduce) {
648  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&token='.$newToken.'">'.$langs->trans('ReOpen').'</a>';
649  } else {
650  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ReOpen').'</a>';
651  }
652  }
653  }
654  }
655 
656  print '</div>';
657  }
658 
659  if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline', 'addproduceline'))) {
660  print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
661  print '<input type="hidden" name="token" value="'.newToken().'">';
662  print '<input type="hidden" name="action" value="confirm_'.$action.'">';
663  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
664  print '<input type="hidden" name="id" value="'.$id.'">';
665  // Note: closing form is add end of page
666 
667  if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
668  $defaultstockmovementlabel = GETPOST('inventorylabel', 'alphanohtml') ? GETPOST('inventorylabel', 'alphanohtml') : $langs->trans("ProductionForRef", $object->ref);
669  $defaultstockmovementcode = GETPOST('inventorycode', 'alphanohtml') ? GETPOST('inventorycode', 'alphanohtml') : dol_print_date(dol_now(), 'dayhourlog');
670 
671  print '<div class="center'.(in_array($action, array('consumeorproduce', 'consumeandproduceall')) ? ' formconsumeproduce' : '').'">';
672  print '<div class="opacitymedium hideonsmartphone paddingbottom">'.$langs->trans("ConfirmProductionDesc", $langs->transnoentitiesnoconv("Confirm")).'<br></div>';
673  print '<span class="fieldrequired">'.$langs->trans("InventoryCode").':</span> <input type="text" class="minwidth200 maxwidth250" name="inventorycode" value="'.$defaultstockmovementcode.'"> &nbsp; ';
674  print '<span class="clearbothonsmartphone"></span>';
675  print $langs->trans("MovementLabel").': <input type="text" class="minwidth300" name="inventorylabel" value="'.$defaultstockmovementlabel.'"><br><br>';
676  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>';
677  print '<input type="submit" class="button" value="'.$langs->trans("Confirm").'" name="confirm">';
678  print ' &nbsp; ';
679  print '<input class="button button-cancel" type="submit" value="'.$langs->trans("Cancel").'" name="cancel">';
680  print '<br><br>';
681  print '</div>';
682 
683  print '<br>';
684  }
685  }
686 
687 
688  /*
689  * Lines
690  */
691  $collapse = 1;
692 
693  if (!empty($object->table_element_line)) {
694  // Show object lines
695  $object->fetchLines();
696 
697  $bomcost = 0;
698  if ($object->fk_bom > 0) {
699  $bom = new Bom($db);
700  $res = $bom->fetch($object->fk_bom);
701  if ($res > 0) {
702  $bom->calculateCosts();
703  $bomcost = $bom->unit_cost;
704  }
705  }
706 
707  // Lines to consume
708 
709  print '<div class="fichecenter">';
710  print '<div class="fichehalfleft">';
711  print '<div class="clearboth"></div>';
712 
713  $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addconsumeline&token='.newToken();
714  $permissiontoaddaconsumeline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
715  $parameters = array('morecss'=>'reposition');
716 
717  $newcardbutton = '';
718  if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
719  $newcardbutton = dolGetButtonTitle($langs->trans('AddNewConsumeLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaconsumeline, $parameters);
720  }
721 
722  print load_fiche_titre($langs->trans('Consumption'), $newcardbutton, '', 0, '', '', '');
723 
724  print '<div class="div-table-responsive-no-min">';
725  print '<table class="noborder noshadow centpercent nobottom">';
726 
727  print '<tr class="liste_titre">';
728  print '<td>'.$langs->trans("Product").'</td>';
729  // Qty
730  print '<td class="right">'.$langs->trans("Qty").'</td>';
731  // Cost price
732  if ($permissiontoupdatecost && !empty($conf->global->MRP_SHOW_COST_FOR_CONSUMPTION)) {
733  print '<td class="right">'.$langs->trans("UnitCost").'</td>';
734  }
735  // Qty already consumed
736  print '<td class="right">'.$langs->trans("QtyAlreadyConsumed").'</td>';
737  // Warehouse
738  print '<td>';
739  if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
740  print $langs->trans("Warehouse");
741 
742  // Select warehouse to force it everywhere
743  if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
744  $listwarehouses = $tmpwarehouse->list_array(1);
745  if (count($listwarehouses) > 1) {
746  print '<br><span class="opacitymedium">' . $langs->trans("ForceTo") . '</span> ' . $form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 1, 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth300', 1);
747  } elseif (count($listwarehouses) == 1) {
748  print '<br><span class="opacitymedium">' . $langs->trans("ForceTo") . '</span> ' . $form->selectarray('fk_default_warehouse', $listwarehouses, $fk_default_warehouse, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100 maxwidth300', 1);
749  }
750  }
751  }
752  print '</td>';
753  if (isModEnabled('stock')) {
754  // Available
755  print '<td align="right">';
756  if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
757  print $langs->trans("Stock");
758  }
759  print '</td>';
760  }
761  // Lot - serial
762  if (isModEnabled('productbatch')) {
763  print '<td>';
764  if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
765  print $langs->trans("Batch");
766  }
767  print '</td>';
768  }
769  // Action
770  if ($permissiontodelete) {
771  print '<td></td>';
772  }
773 
774  // Split
775  print '<td></td>';
776 
777  // SplitAll
778  print '<td></td>';
779 
780  print '</tr>';
781 
782  if ($action == 'addconsumeline') {
783  print '<!-- Add line to consume -->'."\n";
784  print '<tr class="liste_titre">';
785  print '<td>';
786  print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
787  print '</td>';
788  // Qty
789  print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
790  // Cost price
791  if ($permissiontoupdatecost && !empty($conf->global->MRP_SHOW_COST_FOR_CONSUMPTION)) {
792  print '<td></td>';
793  }
794  // Qty already consumed
795  print '<td colspan="2">';
796  // Warehouse
797  print '<input type="submit" class="button buttongen button-add" name="addconsumelinebutton" value="'.$langs->trans("Add").'">';
798  print '<input type="submit" class="button buttongen button-cancel" name="canceladdconsumelinebutton" value="'.$langs->trans("Cancel").'">';
799  print '</td>';
800  if (isModEnabled('stock')) {
801  print '<td></td>';
802  }
803  // Lot - serial
804  if (isModEnabled('productbatch')) {
805  print '<td></td>';
806  }
807  // Action
808  if ($permissiontodelete) {
809  print '<td></td>';
810  }
811  // Split
812  print '<td></td>';
813  // SplitAll
814  print '<td></td>';
815  print '</tr>';
816  }
817 
818  // Lines to consume
819 
820  $bomcostupdated = 0; // We will recalculate the unitary cost to produce a product using the real "products to consume into MO"
821 
822  if (!empty($object->lines)) {
823  $nblinetoconsume = 0;
824  foreach ($object->lines as $line) {
825  if ($line->role == 'toconsume') {
826  $nblinetoconsume++;
827  }
828  }
829 
830  $nblinetoconsumecursor = 0;
831  foreach ($object->lines as $line) {
832  if ($line->role == 'toconsume') {
833  $nblinetoconsumecursor++;
834 
835  $tmpproduct = new Product($db);
836  $tmpproduct->fetch($line->fk_product);
837  $linecost = price2num($tmpproduct->pmp, 'MT');
838 
839  if ($object->qty > 0) {
840  // add free consume line cost to $bomcostupdated
841  $costprice = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
842  if (empty($costprice)) {
843  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
844  $productFournisseur = new ProductFournisseur($db);
845  if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
846  $costprice = $productFournisseur->fourn_unitprice;
847  } else {
848  $costprice = 0;
849  }
850  }
851  $linecost = price2num(($line->qty * $costprice) / $object->qty, 'MT'); // price for line for all quantities
852  $bomcostupdated += price2num(($line->qty * $costprice) / $object->qty, 'MU'); // same but with full accuracy
853  }
854 
855  $bomcostupdated = price2num($bomcostupdated, 'MU');
856  $arrayoflines = $object->fetchLinesLinked('consumed', $line->id);
857  $alreadyconsumed = 0;
858  foreach ($arrayoflines as $line2) {
859  $alreadyconsumed += $line2['qty'];
860  }
861 
862  $suffix = '_'.$line->id;
863  print '<!-- Line to dispatch '.$suffix.' -->'."\n";
864  // hidden fields for js function
865  print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
866  print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyconsumed.'">';
867 
868  print '<tr data-line-id="'.$line->id.'">';
869  // Product
870  print '<td>'.$tmpproduct->getNomUrl(1);
871  print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
872  print '</td>';
873  // Qty
874  print '<td class="right nowraponall">';
875  $help = ''; $picto = 'help';
876  if ($line->qty_frozen) {
877  $help = ($help ? '<br>' : '').'<strong>'.$langs->trans("QuantityFrozen").'</strong>: '.yn(1).' ('.$langs->trans("QuantityConsumedInvariable").')';
878  print $form->textwithpicto('', $help, -1, 'lock').' ';
879  }
880  if ($line->disable_stock_change) {
881  $help = ($help ? '<br>' : '').'<strong>'.$langs->trans("DisableStockChange").'</strong>: '.yn(1).' ('.(($tmpproduct->type == Product::TYPE_SERVICE && empty($conf->global->STOCK_SUPPORTS_SERVICES)) ? $langs->trans("NoStockChangeOnServices") : $langs->trans("DisableStockChangeHelp")).')';
882  print $form->textwithpicto('', $help, -1, 'help').' ';
883  }
884  print price2num($line->qty, 'MS');
885  print '</td>';
886  // Cost price
887  if ($permissiontoupdatecost && !empty($conf->global->MRP_SHOW_COST_FOR_CONSUMPTION)) {
888  print '<td class="right nowraponall">';
889  print price($linecost);
890  print '</td>';
891  }
892  // Already consumed
893  print '<td class="right">';
894  if ($alreadyconsumed) {
895  print '<script>';
896  print 'jQuery(document).ready(function() {
897  jQuery("#expandtoproduce'.$line->id.'").click(function() {
898  console.log("Expand mrp_production line '.$line->id.'");
899  jQuery(".expanddetail'.$line->id.'").toggle();';
900  if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
901  print 'if (jQuery("#tablelines").hasClass("nobottom")) { jQuery("#tablelines").removeClass("nobottom"); } else { jQuery("#tablelines").addClass("nobottom"); }';
902  }
903  print '
904  });
905  });';
906  print '</script>';
907  if (empty($conf->use_javascript_ajax)) {
908  print '<a href="'.$_SERVER["PHP_SELF"].'?collapse='.$collapse.','.$line->id.'">';
909  }
910  print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce'.$line->id.'"');
911  if (empty($conf->use_javascript_ajax)) {
912  print '</a>';
913  }
914  } else {
915  if ($nblinetoconsume == $nblinetoconsumecursor) { // If it is the last line
916  print '<script>jQuery("#tablelines").removeClass("nobottom");</script>';
917  }
918  }
919  print ' '.price2num($alreadyconsumed, 'MS');
920  print '</td>';
921  // Warehouse
922  print '<td>';
923  print '</td>';
924  // Stock
925  if (isModEnabled('stock')) {
926  print '<td class="nowraponall right">';
927  if ($tmpproduct->stock_reel < ($line->qty - $alreadyconsumed)) {
928  print img_warning($langs->trans('StockTooLow')).' ';
929  }
930  print price2num($tmpproduct->stock_reel, 'MS'); // Available
931  print '</td>';
932  }
933  // Lot
934  if (isModEnabled('productbatch')) {
935  print '<td></td>';
936  }
937  // Action delete line
938  if ($permissiontodelete) {
939  $href = $_SERVER["PHP_SELF"].'?id='.((int) $object->id).'&action=deleteline&token='.newToken().'&lineid='.((int) $line->id);
940  print '<td class="center">';
941  print '<a class="reposition" href="'.$href.'">';
942  print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
943  print '</a>';
944  print '</td>';
945  }
946 
947  // Split
948  print '<td></td>';
949 
950  // Split All
951  print '<td></td>';
952 
953  print '</tr>';
954 
955  // Show detailed of already consumed with js code to collapse
956  foreach ($arrayoflines as $line2) {
957  print '<tr class="expanddetail'.$line->id.' hideobject opacitylow">';
958 
959  // Date
960  print '<td>';
961  $tmpstockmovement->id = $line2['fk_stock_movement'];
962  print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
963  print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
964  print '</td>';
965 
966  // Already consumed
967  print '<td></td>';
968 
969  // Qty
970  print '<td class="right">'.$line2['qty'].'</td>';
971 
972  // Cost price
973  if ($permissiontoupdatecost && !empty($conf->global->MRP_SHOW_COST_FOR_CONSUMPTION)) {
974  print '<td></td>';
975  }
976 
977  // Warehouse
978  print '<td class="tdoverflowmax150">';
979  if ($line2['fk_warehouse'] > 0) {
980  $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
981  if ($result > 0) {
982  print $tmpwarehouse->getNomUrl(1);
983  }
984  }
985  print '</td>';
986 
987  // Stock
988  if (isModEnabled('stock')) {
989  print '<td></td>';
990  }
991 
992  // Lot Batch
993  if (isModEnabled('productbatch')) {
994  print '<td>';
995  if ($line2['batch'] != '') {
996  $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
997  print $tmpbatch->getNomUrl(1);
998  }
999  print '</td>';
1000  }
1001 
1002  // Action delete line
1003  if ($permissiontodelete) {
1004  $href = $_SERVER["PHP_SELF"].'?id='.((int) $object->id).'&action=deleteline&token='.newToken().'&lineid='.((int) $line->id).'&fk_movement='.((int) $line2['fk_stock_movement']);
1005  print '<td class="center">';
1006  print '<a class="reposition" href="'.$href.'">';
1007  print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), 'delete');
1008  print '</a>';
1009  print '</td>';
1010  }
1011 
1012  print '</tr>';
1013  }
1014 
1015  if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1016  $i = 1;
1017  print '<!-- Enter line to consume -->'."\n";
1018  $maxQty = 1;
1019  print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1020  // Ref
1021  print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
1022  $preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
1023  if ($action == 'consumeorproduce' && !GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
1024  $preselected = 0;
1025  }
1026 
1027  $disable = '';
1028  if (!empty($conf->global->MRP_NEVER_CONSUME_MORE_THAN_EXPECTED) && ($line->qty - $alreadyconsumed) <= 0) {
1029  $disable = 'disabled';
1030  }
1031 
1032  // input hidden with fk_product of line
1033  print '<input type="hidden" name="product-'.$line->id.'-'.$i.'" value="'.$line->fk_product.'">';
1034 
1035  // Qty
1036  print '<td class="right"><input type="text" class="width50 right" id="qtytoconsume-'.$line->id.'-'.$i.'" name="qty-'.$line->id.'-'.$i.'" value="'.$preselected.'" '.$disable.'></td>';
1037 
1038  // Cost
1039  if ($permissiontoupdatecost && !empty($conf->global->MRP_SHOW_COST_FOR_CONSUMPTION)) {
1040  print '<td></td>';
1041  }
1042 
1043  // Already consumed
1044  print '<td></td>';
1045 
1046  // Warehouse
1047  print '<td>';
1048  if ($tmpproduct->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
1049  if (empty($line->disable_stock_change)) {
1050  $preselected = (GETPOSTISSET('idwarehouse-'.$line->id.'-'.$i) ? GETPOST('idwarehouse-'.$line->id.'-'.$i) : ($tmpproduct->fk_default_warehouse > 0 ? $tmpproduct->fk_default_warehouse : 'ifone'));
1051  print $formproduct->selectWarehouses($preselected, 'idwarehouse-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1052  } else {
1053  print '<span class="opacitymedium">'.$langs->trans("DisableStockChange").'</span>';
1054  }
1055  } else {
1056  print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1057  }
1058  print '</td>';
1059 
1060  // Stock
1061  if (isModEnabled('stock')) {
1062  print '<td></td>';
1063  }
1064 
1065  // Lot / Batch
1066  if (isModEnabled('productbatch')) {
1067  print '<td>';
1068  if ($tmpproduct->status_batch) {
1069  $preselected = (GETPOSTISSET('batch-'.$line->id.'-'.$i) ? GETPOST('batch-'.$line->id.'-'.$i) : '');
1070  print '<input type="text" class="width50" name="batch-'.$line->id.'-'.$i.'" value="'.$preselected.'" list="batch-'.$line->id.'-'.$i.'">';
1071  print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, '', '');
1072 
1073  $type = 'batch';
1074  print '<td align="right" class="split">';
1075  print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
1076  print '</td>';
1077  print '<td align="right" class="splitall">';
1078  if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"');
1079  print '</td>';
1080  }
1081  print '</td>';
1082  }
1083 
1084  // Action delete line
1085  if ($permissiontodelete) {
1086  print '<td></td>';
1087  }
1088 
1089  print '</tr>';
1090  }
1091  }
1092  }
1093  }
1094 
1095  print '</table>';
1096  print '</div>';
1097 
1098  // default warehouse processing
1099  print '<script type="text/javascript">
1100  $(document).ready(function () {
1101  $("select[name=fk_default_warehouse]").change(function() {
1102  var fk_default_warehouse = $("option:selected", this).val();
1103  $("select[name^=idwarehouse-]").val(fk_default_warehouse).change();
1104  });
1105  });
1106  </script>';
1107 
1108 
1109  // Lines to produce
1110 
1111  print '</div>';
1112  print '<div class="fichehalfright">';
1113  print '<div class="clearboth"></div>';
1114 
1115  $nblinetoproduce = 0;
1116  foreach ($object->lines as $line) {
1117  if ($line->role == 'toproduce') {
1118  $nblinetoproduce++;
1119  }
1120  }
1121 
1122  $newcardbutton = '';
1123  $url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addproduceline&token='.newToken();
1124  $permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
1125  $parameters = array('morecss'=>'reposition');
1126  if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
1127  if ($nblinetoproduce == 0 || $object->mrptype == 1) {
1128  $newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaproductline, $parameters);
1129  }
1130  }
1131 
1132  print load_fiche_titre($langs->trans('Production'), $newcardbutton, '', 0, '', '');
1133 
1134  print '<div class="div-table-responsive-no-min">';
1135  print '<table id="tablelinestoproduce" class="noborder noshadow nobottom centpercent">';
1136 
1137  print '<tr class="liste_titre">';
1138  print '<td>'.$langs->trans("Product").'</td>';
1139  print '<td class="right">'.$langs->trans("Qty").'</td>';
1140  if ($permissiontoupdatecost) {
1141  if (empty($bomcostupdated)) {
1142  print '<td class="right">'.$form->textwithpicto($langs->trans("UnitCost"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1143  } else {
1144  print '<td class="right">'.$form->textwithpicto($langs->trans("ManufacturingPrice"), $langs->trans("AmountUsedToUpdateWAP")).'</td>';
1145  }
1146  }
1147  print '<td class="right">'.$langs->trans("QtyAlreadyProduced").'</td>';
1148  print '<td>';
1149  if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1150  print $langs->trans("Warehouse");
1151  }
1152  print '</td>';
1153  if (isModEnabled('productbatch')) {
1154  print '<td>';
1155  if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1156  print $langs->trans("Batch");
1157 
1158  // Split
1159  print '<td></td>';
1160 
1161  // Split All
1162  print '<td></td>';
1163  }
1164  print '</td>';
1165  print '<td></td>';
1166  }
1167  print '</tr>';
1168 
1169  if ($action == 'addproduceline') {
1170  print '<!-- Add line to produce -->'."\n";
1171  print '<tr class="liste_titre">';
1172  print '<td>';
1173  print $form->select_produits('', 'productidtoadd', '', 0, 0, -1, 2, '', 1, array(), 0, '1', 0, 'maxwidth300');
1174  print '</td>';
1175  // Qty
1176  print '<td class="right"><input type="text" name="qtytoadd" value="1" class="width50 right"></td>';
1177  // Cost price
1178  print '<td></td>';
1179 
1180  // Qty already produced
1181  print '<td colspan="2">';
1182  // Warehouse
1183  print '<input type="submit" class="button buttongen button-add" name="addproducelinebutton" value="'.$langs->trans("Add").'">';
1184  print '<input type="submit" class="button buttongen button-cancel" name="canceladdproducelinebutton" value="'.$langs->trans("Cancel").'">';
1185  print '</td>';
1186  // Lot - serial
1187  if (isModEnabled('productbatch')) {
1188  print '<td></td>';
1189  }
1190  // Action
1191  if ($permissiontodelete) {
1192  print '<td></td>';
1193  }
1194  print '</tr>';
1195  }
1196 
1197  if (!empty($object->lines)) {
1198  $nblinetoproduce = 0;
1199  foreach ($object->lines as $line) {
1200  if ($line->role == 'toproduce') {
1201  $nblinetoproduce++;
1202  }
1203  }
1204 
1205  $nblinetoproducecursor = 0;
1206  foreach ($object->lines as $line) {
1207  if ($line->role == 'toproduce') {
1208  $i = 1;
1209 
1210  $nblinetoproducecursor++;
1211 
1212  $tmpproduct = new Product($db);
1213  $tmpproduct->fetch($line->fk_product);
1214 
1215  $arrayoflines = $object->fetchLinesLinked('produced', $line->id);
1216  $alreadyproduced = 0;
1217  foreach ($arrayoflines as $line2) {
1218  $alreadyproduced += $line2['qty'];
1219  }
1220 
1221  $suffix = '_'.$line->id;
1222  print '<!-- Line to dispatch '.$suffix.' -->'."\n";
1223  // hidden fields for js function
1224  print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
1225  print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyproduced.'">';
1226 
1227  print '<tr>';
1228  print '<td>'.$tmpproduct->getNomUrl(1);
1229  print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
1230  print '</td>';
1231  print '<td class="right">'.$line->qty.'</td>';
1232  if ($permissiontoupdatecost) {
1233  // Defined $manufacturingcost
1234  $manufacturingcost = 0;
1235  $manufacturingcostsrc = '';
1236  if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1237  $manufacturingcost = $bomcostupdated;
1238  $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1239  if (empty($manufacturingcost)) {
1240  $manufacturingcost = $bomcost;
1241  $manufacturingcostsrc = $langs->trans("ValueFromBom");
1242  }
1243  if (empty($manufacturingcost)) {
1244  $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1245  $manufacturingcostsrc = $langs->trans("CostPrice");
1246  }
1247  if (empty($manufacturingcost)) {
1248  $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1249  $manufacturingcostsrc = $langs->trans("PMPValue");
1250  }
1251  }
1252 
1253  print '<td class="right nowraponall" title="'.dol_escape_htmltag($manufacturingcostsrc).'">';
1254  if ($manufacturingcost) {
1255  print price($manufacturingcost);
1256  }
1257  print '</td>';
1258  }
1259  print '<td class="right nowraponall">';
1260  if ($alreadyproduced) {
1261  print '<script>';
1262  print 'jQuery(document).ready(function() {
1263  jQuery("#expandtoproduce'.$line->id.'").click(function() {
1264  console.log("Expand mrp_production line '.$line->id.'");
1265  jQuery(".expanddetailtoproduce'.$line->id.'").toggle();';
1266  if ($nblinetoproduce == $nblinetoproducecursor) {
1267  print 'if (jQuery("#tablelinestoproduce").hasClass("nobottom")) { jQuery("#tablelinestoproduce").removeClass("nobottom"); } else { jQuery("#tablelinestoproduce").addClass("nobottom"); }';
1268  }
1269  print '
1270  });
1271  });';
1272  print '</script>';
1273  if (empty($conf->use_javascript_ajax)) {
1274  print '<a href="'.$_SERVER["PHP_SELF"].'?collapse='.$collapse.','.$line->id.'">';
1275  }
1276  print img_picto($langs->trans("ShowDetails"), "chevron-down", 'id="expandtoproduce'.$line->id.'"');
1277  if (empty($conf->use_javascript_ajax)) {
1278  print '</a>';
1279  }
1280  }
1281  print ' '.$alreadyproduced;
1282  print '</td>';
1283  print '<td>'; // Warehouse
1284  print '</td>';
1285  if (isModEnabled('productbatch')) {
1286  print '<td></td>'; // Lot
1287  }
1288 
1289  if ($permissiontodelete && $line->origin_type == 'free') {
1290  $href = $_SERVER["PHP_SELF"];
1291  $href .= '?id='.$object->id;
1292  $href .= '&action=deleteline';
1293  $href .= '&lineid='.$line->id;
1294  print '<td class="center">';
1295  print '<a class="reposition" href="'.$href.'">';
1296  print img_picto($langs->trans('TooltipDeleteAndRevertStockMovement'), "delete");
1297  print '</a>';
1298  print '</td>';
1299  }
1300  print '</tr>';
1301 
1302  // Show detailed of already consumed with js code to collapse
1303  foreach ($arrayoflines as $line2) {
1304  print '<tr class="expanddetailtoproduce'.$line->id.' hideobject opacitylow">';
1305  print '<td>';
1306  $tmpstockmovement->id = $line2['fk_stock_movement'];
1307  print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$tmpstockmovement->id.'">'.img_picto($langs->trans("StockMovement"), 'movement', 'class="paddingright"').'</a>';
1308  print dol_print_date($line2['date'], 'dayhour', 'tzuserrel');
1309  print '</td>';
1310  // Qty
1311  print '<td></td>';
1312  // Cost price
1313  if ($permissiontoupdatecost) {
1314  print '<td></td>';
1315  }
1316  // Qty already produced
1317  print '<td class="right">'.$line2['qty'].'</td>';
1318  print '<td class="tdoverflowmax150">';
1319  if ($line2['fk_warehouse'] > 0) {
1320  $result = $tmpwarehouse->fetch($line2['fk_warehouse']);
1321  if ($result > 0) {
1322  print $tmpwarehouse->getNomUrl(1);
1323  }
1324  }
1325  print '</td>';
1326  if (isModEnabled('productbatch')) {
1327  print '<td>';
1328  if ($line2['batch'] != '') {
1329  $tmpbatch->fetch(0, $line2['fk_product'], $line2['batch']);
1330  print $tmpbatch->getNomUrl(1);
1331  }
1332  print '</td>';
1333  print '<td></td>';
1334 
1335  // Split
1336  print '<td></td>';
1337 
1338  // Split All
1339  print '<td></td>';
1340  }
1341  print '</tr>';
1342  }
1343 
1344  if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
1345  print '<!-- Enter line to produce -->'."\n";
1346  $maxQty = 1;
1347  print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
1348  print '<td><span class="opacitymedium">'.$langs->trans("ToProduce").'</span></td>';
1349  $preselected = (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i) ? GETPOST('qtytoproduce-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyproduced));
1350  if ($action == 'consumeorproduce' && !GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
1351  $preselected = 0;
1352  }
1353  print '<td class="right"><input type="text" class="width50 right" id="qtytoproduce-'.$line->id.'-'.$i.'" name="qtytoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1354  if ($permissiontoupdatecost) {
1355  // Defined $manufacturingcost
1356  $manufacturingcost = 0;
1357  $manufacturingcostsrc = '';
1358  if ($object->mrptype == 0) { // If MO is a "Manufacture" type (and not "Disassemble")
1359  $manufacturingcost = $bomcostupdated;
1360  $manufacturingcostsrc = $langs->trans("CalculatedFromProductsToConsume");
1361  if (empty($manufacturingcost)) {
1362  $manufacturingcost = $bomcost;
1363  $manufacturingcostsrc = $langs->trans("ValueFromBom");
1364  }
1365  if (empty($manufacturingcost)) {
1366  $manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
1367  $manufacturingcostsrc = $langs->trans("CostPrice");
1368  }
1369  if (empty($manufacturingcost)) {
1370  $manufacturingcost = price2num($tmpproduct->pmp, 'MU');
1371  $manufacturingcostsrc = $langs->trans("PMPValue");
1372  }
1373  }
1374 
1375  if ($tmpproduct->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
1376  $preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : ($manufacturingcost ? price($manufacturingcost) : ''));
1377  print '<td class="right"><input type="text" class="width75 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
1378  } else {
1379  print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.($manufacturingcost ? $manufacturingcost : '').'"></td>';
1380  }
1381  }
1382  print '<td></td>';
1383  print '<td>';
1384  if ($tmpproduct->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
1385  $preselected = (GETPOSTISSET('idwarehousetoproduce-'.$line->id.'-'.$i) ? GETPOST('idwarehousetoproduce-'.$line->id.'-'.$i) : ($object->fk_warehouse > 0 ? $object->fk_warehouse : 'ifone'));
1386  print $formproduct->selectWarehouses($preselected, 'idwarehousetoproduce-'.$line->id.'-'.$i, '', 1, 0, $line->fk_product, '', 1, 0, null, 'maxwidth200 csswarehouse_'.$line->id.'_'.$i);
1387  } else {
1388  print '<span class="opacitymedium">'.$langs->trans("NoStockChangeOnServices").'</span>';
1389  }
1390  print '</td>';
1391  if (isModEnabled('productbatch')) {
1392  print '<td>';
1393  if ($tmpproduct->status_batch) {
1394  $preselected = (GETPOSTISSET('batchtoproduce-'.$line->id.'-'.$i) ? GETPOST('batchtoproduce-'.$line->id.'-'.$i) : '');
1395  print '<input type="text" class="width50" name="batchtoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'">';
1396  }
1397  print '</td>';
1398  // Batch number in same column than the stock movement picto
1399  print '<td>';
1400  if ($tmpproduct->status_batch) {
1401  $type = 'batch';
1402  print '<td align="right" class="split">';
1403  print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissing\')"');
1404  print '</td>';
1405 
1406  print '<td align="right" class="splitall">';
1407  if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" onClick="addDispatchLine('.$line->id.', \'batch\', \'alltoproduce\')"'); //
1408  print '</td>';
1409  }
1410  print '</td>';
1411  }
1412  print '</tr>';
1413  }
1414  }
1415  }
1416  }
1417 
1418  print '</table>';
1419  print '</div>';
1420 
1421  print '</div>';
1422  print '</div>';
1423  }
1424 
1425  if (in_array($action, array('consumeorproduce', 'consumeandproduceall', 'addconsumeline'))) {
1426  print "</form>\n";
1427  }
1428 }
1429 
1430 // End of page
1431 llxFooter();
1432 $db->close();
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:78
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:1565
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.
if($cancel &&! $id) if($action=='add' &&! $cancel) if($action=='delete') if($id) $form
Actions.
Definition: card.php:143
dol_banner_tab($object, $paramid, $morehtml='', $shownav=1, $fieldid='rowid', $fieldref='ref', $morehtmlref='', $moreparam='', $nodbprefix=0, $morehtmlleft='', $morehtmlstatus='', $onlybanner=0, $morehtmlright='')
Show tab footer of a card.
load_fiche_titre($titre, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='')
Show tabs of a record.
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
yn($yesno, $case=1, $color=0)
Return yes or no in current language.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='')
Set event messages in dol_events session object.
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
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.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
isModEnabled($module)
Is Dolibarr module enabled.
img_edit($titlealt='default', $float=0, $other='')
Show logo editer/modifier fiche.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
$formconfirm
if ($action == 'delbookkeepingyear') {
moPrepareHead($object)
Prepare array of tabs for Mo.
Definition: mrp_mo.lib.php:30
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.