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