mmi_workflow.class.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. <?php
  2. require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
  3. require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
  4. //require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
  5. dol_include_once('custom/mmicommon/class/mmi_generic.class.php');
  6. // @todo check if module
  7. dol_include_once('/custom/mmipayments/class/mmi_payments.class.php');
  8. class mmi_workflow extends mmi_generic_1_0
  9. {
  10. const MOD_NAME = 'mmiworkflow';
  11. public static function order_1clic_invoice($user, $order, $validate=true)
  12. {
  13. if (! $user->rights->facture->creer)
  14. return;
  15. //var_dump($object);
  16. global $conf, $langs, $db, $soc;
  17. $invoice = new Facture($db);
  18. $invoice_gen = true;
  19. // Vérif si pas déjà une facture !
  20. $order->fetchObjectLinked();
  21. //var_dump($order->linkedObjectsIds); die();
  22. if(!empty($order->linkedObjectsIds['facture'])) {
  23. foreach($order->linkedObjectsIds['facture'] as $id) {
  24. $invoice->fetch($id);
  25. break;
  26. }
  27. // @todo check if already
  28. //$invoice_gen = false;
  29. }
  30. else {
  31. $invoice->createFromOrder($order, $user);
  32. // Validation
  33. if ($validate)
  34. $invoice->validate($user);
  35. // Assign payments
  36. // @todo c'est degue je dois tester l'activation du module !!
  37. if (class_exists('mmi_payments')) {
  38. mmi_payments::invoice_autoassign_payments($invoice);
  39. }
  40. // Retrieve everything again
  41. $invoice->fetch($invoice->id);
  42. }
  43. // Fix bug
  44. static::invoice_1ctfix($user, $invoice);
  45. static::order_1ctfix($user, $order);
  46. // Parfois ça ne classe pas correctement si on a un écart de centime par exemple
  47. if (!$order->billed)
  48. $order->classifyBilled($user); // On est sur du 1clic !
  49. // Génération PDF
  50. if ($invoice_gen) {
  51. $docmodel = $invoice->model_pdf;
  52. $hidedetails = 0;
  53. $hidedesc = 0;
  54. $hideref = 0;
  55. $moreparams = null;
  56. $invoice->generateDocument($docmodel, $langs, $hidedetails, $hidedesc, $hideref, $moreparams);
  57. }
  58. // Send invoice by email if Option true, not already sent and invoice validated correctly (closes & paid) !
  59. // STOP if not auto send
  60. if (empty($conf->global->MMI_ORDER_1CLIC_INVOICE_EMAIL_AUTO))
  61. return;
  62. // Customer
  63. $thirdparty = $invoice->thirdparty;
  64. // if specified not to send auto (thirdparty option)
  65. // OR specified not to send auto to pro (module option + thirdparty check pro field)
  66. if (
  67. !empty($thirdparty->array_options['options_invoice_noautosend'])
  68. || (!empty($conf->global->MMI_ORDER_1CLIC_INVOICE_EMAIL_AUTO_NOPRO) && !empty($thirdparty->array_options['options_pro']))
  69. )
  70. return;
  71. // Check already sent
  72. $sql = 'SELECT 1
  73. FROM '.MAIN_DB_PREFIX.'actioncomm a
  74. WHERE a.code="AC_BILL_SENTBYMAIL" AND a.elementtype="invoice" AND a.fk_element='.$invoice->id.'
  75. LIMIT 1';
  76. //echo $sql;
  77. $q = $db->query($sql);
  78. //var_dump($q); //die();
  79. $invoice_sent = $q->num_rows>0;
  80. //var_dump($invoice_sent);
  81. if ($invoice_sent)
  82. return;
  83. // Check closed (sent), and payed if not professionnal
  84. $validated = (empty($thirdparty->array_options['options_pro']) && $invoice->statut == Facture::STATUS_CLOSED)
  85. || (!empty($thirdparty->array_options['options_pro']) && in_array($invoice->statut, [Facture::STATUS_VALIDATED, Facture::STATUS_CLOSED]));
  86. //var_dump($validated, $invoice); die();
  87. if (!$validated)
  88. return;
  89. static::invoice_email($user, $invoice);
  90. return true;
  91. }
  92. public static function order_1ctfix($user, $object, $autovalidate=true)
  93. {
  94. global $conf, $langs, $db, $soc, $hookmanager;
  95. $sql = 'SELECT SUM(ip.amount) paid
  96. FROM '.MAIN_DB_PREFIX.'paiement_object ip
  97. WHERE ip.fk_object='.$object->id;
  98. $q2 = $db->query($sql);
  99. if (!$q2 || !($row = $q2->fetch_object()))
  100. return;
  101. //var_dump($row->paid);
  102. $difflimit = !empty($conf->global->MMI_1CT_DIFFLIMIT) ?$conf->global->MMI_1CT_DIFFLIMIT :0.03;
  103. $diff = $object->total_ttc-$row->paid;
  104. $diffround = round($diff, 2);
  105. $diffabs = abs($diffround);
  106. //var_dump($diffabs); die();
  107. // Marge de tolérance
  108. if ($diffabs == 0 || $diffabs > $difflimit)
  109. return;
  110. $statut = $object->statut;
  111. if ($statut>0)
  112. $object->setDraft($user);
  113. foreach($object->lines as $line) if (($line->subprice > 0 || $line->subprice < 0) && $line->qty > 0) {
  114. // On ajuste le subprice pour tomber pile poil sans avoir à faire de modif
  115. $subprice = $line->subprice - ($diff>=1 ?$diff-0.5 :($diff<=-1 ?$diff-0.5 :$diff))/$line->qty/(1+$line->tva_tx/100);
  116. //var_dump($line->subprice, $subprice);
  117. $r = $object->updateline($line->id, $line->desc, $subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->date_start, $line->date_end, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, 0, $line->fk_unit, $subprice, 0, $line->ref_ext);
  118. // On vérifie normalement on est bon du premier coup mais on sait jamais, donc on boucle sur les autre produits si jamais
  119. $diff = $object->total_ttc-$row->paid;
  120. if (round($diff, 2)==0)
  121. break;
  122. }
  123. // On valide quel que soit le statut initial
  124. if ($statut>=1 || $autovalidate) {
  125. $object->valid($user);
  126. if ($statut==Commande::STATUS_SHIPMENTONPROCESS) {
  127. $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande SET fk_statut='.Commande::STATUS_SHIPMENTONPROCESS.'
  128. WHERE rowid='.$object->id;
  129. $q2 = $db->query($sql);
  130. if (!$q2) {
  131. // @todo error
  132. }
  133. }
  134. elseif($statut==Commande::STATUS_CLOSED) {
  135. $object->cloture($user);
  136. }
  137. }
  138. // @todo vérif si vraiment utile...
  139. $object->fetch($object->id);
  140. return true;
  141. }
  142. public static function order_vat_tx_fix($user, $object)
  143. {
  144. global $conf, $langs, $db, $soc, $hookmanager;
  145. // @todo...
  146. }
  147. public static function invoice_1ctfix($user, $object, $autovalidate=true)
  148. {
  149. global $conf, $langs, $db, $soc, $hookmanager;
  150. // Si pas de paiement, rien à corriger...
  151. $sql = 'SELECT SUM(ip.amount) paid
  152. FROM '.MAIN_DB_PREFIX.'paiement_facture ip
  153. WHERE ip.fk_facture='.$object->id;
  154. $q2 = $db->query($sql);
  155. if (!$q2 || !($row = $q2->fetch_object()))
  156. return;
  157. //var_dump($row->paid, $order->total_ttc);
  158. $difflimit = !empty($conf->global->MMI_1CT_DIFFLIMIT) ?$conf->global->MMI_1CT_DIFFLIMIT :0.03;
  159. $diff = $object->total_ttc-$row->paid;
  160. $diffround = round($diff, 2);
  161. $diffabs = abs($diffround);
  162. //var_dump($diffabs);
  163. // Marge de tolérance
  164. if ($diffabs == 0 || $diffabs > $difflimit)
  165. return;
  166. $statut = $object->statut;
  167. if ($statut>0)
  168. $object->setDraft($user);
  169. // $line->subprice f*****g string should be a float
  170. foreach($object->lines as $line) if (($line->subprice > 0 || $line->subprice < 0) && $line->qty > 0) {
  171. //var_dump($line); die();
  172. // On ajuste le subprice pour tomber pile poil sans avoir à faire de modif
  173. $subprice = $line->subprice - ($diff>=1 ?$diff-0.5 :($diff<=-1 ?$diff-0.5 :$diff))/$line->qty/(1+$line->tva_tx/100);
  174. //var_dump($line->subprice, $subprice);
  175. $r = $object->updateline($line->id, $line->desc, $subprice, $line->qty, $line->remise_percent, $line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, $line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, 0, 100, $line->fk_unit, $subprice, 0, $line->ref_ext);
  176. // On vérifie normalement on est bon du premier coup mais on sait jamais, donc on boucle sur les autre produits si jamais
  177. $diff = $object->total_ttc-$row->paid;
  178. if (round($diff, 2)==0)
  179. break;
  180. }
  181. // On valide quel que soit le statut initial
  182. if ($statut>=1 || $autovalidate) {
  183. $object->validate($user);
  184. $diff = $object->total_ttc-$row->paid;
  185. // @todo voir pourquoi ça ne passe pas parfois, peut-être nécessite de refaire un fetch
  186. if (round($diff, 2)==0 && $object->statut<=1)
  187. $object->setPaid($user);
  188. }
  189. // @todo vérif si vraiment utile...
  190. $object->fetch($object->id);
  191. return true;
  192. }
  193. public static function invoice_vat_tx_fix($user, $object)
  194. {
  195. global $conf, $langs, $db, $soc, $hookmanager;
  196. // @todo...
  197. }
  198. public static function invoice_email($user, $invoice)
  199. {
  200. global $conf, $langs, $db, $soc, $hookmanager;
  201. $massaction = 'confirm_presend';
  202. $_POST['oneemailperrecipient'] = 'on';
  203. $_POST['addmaindocfile'] = 'on';
  204. $_POST['sendmail'] = 'on';
  205. $objectclass = 'Facture';
  206. $thirdparty = $invoice->thirdparty;
  207. // @todo fetch associated order
  208. $order = NULL;
  209. //var_dump($thirdparty); die();
  210. //var_dump($thirdparty); die();
  211. //var_dump($thirdparty->nom); die();
  212. $_POST['receiver'] = $thirdparty->nom.' <'.$thirdparty->email.'>';
  213. //var_dump($_POST['receiver']); die();
  214. $_POST['fromtype'] = 'company'; // @todo modifier ! mettre le responsable du client
  215. $_POST['subject'] = 'Votre facture '.$conf->global->MAIN_INFO_SOCIETE_NOM;
  216. $_POST['message'] = 'Bonjour '.$thirdparty->nom.",\r\n\r\n"
  217. .'Veuillez trouver ci-joint la facture de votre dernier achat chez '.$conf->global->MAIN_INFO_SOCIETE_NOM.','."\r\n"
  218. .'en espérant que vous serez satisfait de nos produits'."\r\n\r\n"
  219. .($order ?'Réf Commande: '.$order->ref."\r\n\r\n" :'')
  220. .'A bientôt !'."\r\n\r\n"
  221. .'L\'équipe '.$conf->global->MAIN_INFO_SOCIETE_NOM."\r\n";
  222. $toselect = [$invoice->id];
  223. $uploaddir = DOL_DOCUMENT_ROOT.'/../documents/facture';
  224. require DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
  225. // Update "emailsent" field
  226. $sql = 'SELECT *
  227. FROM '.MAIN_DB_PREFIX.'facture_extrafields
  228. WHERE fk_object='.$invoice->id;
  229. $q = $db->query($sql);
  230. if ($q && ($row = $q->fetch_object())) {
  231. if (!$row->emailsent) {
  232. $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_extrafields
  233. SET emailsent=1
  234. WHERE fk_object='.$invoice->id;
  235. $db->query($sql);
  236. }
  237. }
  238. else {
  239. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facture_extrafields
  240. (fk_object, emailsent)
  241. VALUES
  242. ('.$invoice->id.', 1)';
  243. $db->query($sql);
  244. }
  245. //var_dump($sql);
  246. }
  247. // Création expédition
  248. public static function order_1clic_shipping($user, $order, $validate=true, $lots=[])
  249. {
  250. //var_dump($order); die();
  251. //var_dump($user); die();
  252. global $conf, $db, $langs;
  253. dol_syslog($langs->trans("mmi_workflow::order_1clic_shipping() ".$order->id.' : '.json_encode($lots)), LOG_NOTICE);
  254. if (! $user->rights->expedition->creer)
  255. return;
  256. // vérif statut
  257. if ((int)$order->status !== Commande::STATUS_VALIDATED)
  258. return;
  259. $order->fetchObjectLinked();
  260. //var_dump($order->linkedObjectsIds); die();
  261. if(!empty($order->linkedObjectsIds['shipping']))
  262. return;
  263. $origin = 'commande';
  264. $origin_id = $order->id;
  265. $objectsrc = $order;
  266. $date_delivery = date('Y-m-d');
  267. $mode_pdf = '';
  268. // Paramétrage du picking des lots
  269. // 0 => par DDM par ancienneté (puis même dépôt)
  270. // 1 => Même dépôt en premier puis DDM
  271. // (prévoir le paramétrage d'un ordre des dépôts)
  272. $batch_conf = 0;
  273. // Paramétrage du picking
  274. // 0 => Recherche dans tous les dépôts
  275. // 1 => Recherche dans une liste de dépôts (et sous-dépôts)
  276. // 2 => Recherche dans un dépôt (et sous-dépôts)
  277. $warehouse_conf = 0;
  278. // Default
  279. $warehouse_ids = 1;
  280. $error = 0;
  281. $db->begin();
  282. $object = new Expedition($db);
  283. $extrafields = new ExtraFields($db);
  284. $staticwarehouse = new Entrepot($db);
  285. if ($warehouse_ids > 0) {
  286. $staticwarehouse->fetch($warehouse_ids);
  287. }
  288. $object->origin = $origin;
  289. $object->origin_id = $origin_id;
  290. $object->fk_project = $objectsrc->fk_project;
  291. // $object->weight = 0;
  292. // $object->sizeH = 0;
  293. // $object->sizeW = 0;
  294. // $object->sizeS = 0;
  295. // $object->size_units = 0;
  296. // $object->weight_units = 0;
  297. $object->socid = $objectsrc->socid;
  298. $object->ref_customer = $objectsrc->ref_client;
  299. $object->model_pdf = $mode_pdf;
  300. $object->date_delivery = $date_delivery; // Date delivery planed
  301. $object->shipping_method_id = $objectsrc->shipping_method_id;
  302. $object->tracking_number = '';
  303. $object->note_private = $objectsrc->note_private;
  304. $object->note_public = $objectsrc->note_public;
  305. $object->fk_incoterms = $objectsrc->fk_incoterms;
  306. $object->location_incoterms = $objectsrc->location_incoterms;
  307. $batch_line = array();
  308. $stockLine = array();
  309. $array_options = array();
  310. $num = count($objectsrc->lines);
  311. $totalqty = 0;
  312. // Analyse des quantités concernant les lots demandés
  313. $lots2 = [];
  314. if (!empty($lots)) {
  315. foreach($lots as &$lot) {
  316. $sql = 'SELECT pl.fk_product, p.ref, p.label, pl.batch, pl.eatby, pl.sellby, SUM(IF(pb.qty>0,pb.qty,0)) qty
  317. FROM '.MAIN_DB_PREFIX.'product_lot pl
  318. LEFT JOIN '.MAIN_DB_PREFIX.'product p ON p.rowid=pl.fk_product
  319. LEFT JOIN '.MAIN_DB_PREFIX.'product_stock ps ON ps.fk_product=pl.fk_product
  320. LEFT JOIN '.MAIN_DB_PREFIX.'product_batch pb ON pb.batch=pl.batch AND pb.fk_product_stock=ps.rowid
  321. WHERE pl.rowid = '.$lot['fk_product_lot'];
  322. $q = $db->query($sql);
  323. if($row=$q->fetch_assoc()) {
  324. $lot = array_merge($lot, $row);
  325. if (empty($lots2[$lot['fk_product']][$lot['batch']]))
  326. $lots2[$lot['fk_product']][$lot['batch']] = $lot;
  327. else
  328. $lots2[$lot['fk_product']][$lot['batch']]['qte'] += $lot['qte'];
  329. // Analyse erreur stock a priori
  330. if ($lots2[$lot['fk_product']][$lot['batch']]['qte'] > $lots2[$lot['fk_product']][$lot['batch']]['qty']) {
  331. setEventMessages($langs->trans("ErrorProductStockUnavailable", $lot['ref'].' '.$lot['label']), null, 'errors');
  332. dol_syslog($langs->trans("ErrorProductStockUnavailable", $lot['ref'].' '.$lot['label']), LOG_ERR);
  333. $error++;
  334. break;
  335. }
  336. }
  337. }
  338. }
  339. //var_dump($lots2);
  340. $sql = '';
  341. // Parcours produits commande
  342. if (!$error) for ($i = 0; $i < $num; $i++) {
  343. $line = $objectsrc->lines[$i];
  344. //var_dump($line->qty);
  345. //var_dump($conf->productbatch->enabled, $line);
  346. if (! $line->fk_product)
  347. continue;
  348. $product = new Product($db);
  349. $product->fetch($line->fk_product);
  350. if (! $product->id)
  351. continue;
  352. // Kit alimentaire => ne pas expédier ça bug
  353. //if (!empty($product->array_options['options_compose']))
  354. // continue;
  355. // Product shippable (not service, etc.)
  356. if ($product->type != 0)
  357. continue;
  358. $product->load_stock('warehouseopen');
  359. //var_dump($product->label);
  360. // Extrafields
  361. $array_options[$i] = $extrafields->getOptionalsFromPost($object->table_element_line, $i);
  362. // cbien de ce produit est dans l'expé
  363. // (permet de savoir lorsqu'on s'arrête)
  364. $subtotalqty = 0;
  365. // Parcours entrepots
  366. // @todo préférer $warehouse_id & enfants
  367. if (!empty($conf->productbatch->enabled) && $line->product_tobatch) { // If product need a batch number
  368. // A construire : $batch_line[$i] qui sera utilisé via adlinebatch($batch_line[$i])
  369. // c.f. expedition/card.php
  370. $batch_line[$i] = [];
  371. $productlots = [];
  372. // Détail des batchs ajoutés
  373. $sub_qty = [];
  374. if ($batch_conf==0) {
  375. // Premier parcours, tri par lot par DDM
  376. foreach($product->stock_warehouse as $stock) {
  377. foreach($stock->detail_batch as $batch=>$productbatch) {
  378. if (!isset($productlots[$batch]))
  379. $productlots[$batch] = $productbatch->sellby ?date('Y-m-d', $productbatch->sellby) :'';
  380. }
  381. }
  382. asort($productlots);
  383. //var_dump($productlots);
  384. if (isset($lots2[$product->id])) {
  385. foreach($lots2[$product->id] as $batch=>&$batch_detail) {
  386. // Plus rien à prendre
  387. if ($batch_detail['qte']<=0)
  388. continue;
  389. // Plus rien en stock
  390. if ($batch_detail['qty']<=0) {
  391. setEventMessages($langs->trans("ErrorProductStockUnavailable", $product->label), null, 'errors');
  392. dol_syslog($langs->trans("ErrorProductStockUnavailable", $product->label), LOG_ERR);
  393. $error++;
  394. break;
  395. }
  396. // Parcours des dépôts
  397. foreach($product->stock_warehouse as $stock) {
  398. //var_dump($stock);
  399. // Recherche du batch/lot si présent dans le dépôt
  400. //var_dump(array_keys($stock->detail_batch));
  401. if(isset($stock->detail_batch[$batch])) {
  402. $productbatch = $stock->detail_batch[$batch];
  403. //var_dump($productbatch);
  404. // Au mieux la qté restant à expédier, au pire ce qui reste dans le batch
  405. $batchqty = min($productbatch->qty, $line->qty-$subtotalqty, $batch_detail['qte'], $batch_detail['qty']);
  406. $batch_detail['qte'] -= $batchqty;
  407. $batch_detail['qty'] -= $batchqty;
  408. $subtotalqty += $batchqty;
  409. $sub_qty[] = [
  410. 'q' => $batchqty, // the qty we want to move for this stock record
  411. 'id_batch' => $productbatch->id, // the id into llx_product_batch of stock record to move
  412. ];
  413. }
  414. // Assez => on stoppe
  415. if ($subtotalqty >= $line->qty)
  416. break;
  417. }
  418. // Assez => on stoppe
  419. if ($subtotalqty >= $line->qty)
  420. break;
  421. }
  422. }
  423. else {
  424. // Parcours des lot par ordre de DDM
  425. foreach($productlots as $batch=>$batch_ddm) {
  426. // Parcours des dépôts
  427. foreach($product->stock_warehouse as $stock) {
  428. //var_dump($stock);
  429. // Recherche du batch/lot si présent dans le dépôt
  430. //var_dump(array_keys($stock->detail_batch));
  431. if(isset($stock->detail_batch[$batch])) {
  432. $productbatch = $stock->detail_batch[$batch];
  433. //var_dump($productbatch);
  434. // Au mieux la qté restant à expédier, au pire ce qui reste dans le batch
  435. $batchqty = min($productbatch->qty, $line->qty-$subtotalqty);
  436. $subtotalqty += $batchqty;
  437. $sub_qty[] = [
  438. 'q' => $batchqty, // the qty we want to move for this stock record
  439. 'id_batch' => $productbatch->id, // the id into llx_product_batch of stock record to move
  440. ];
  441. }
  442. // Assez => on stoppe
  443. if ($subtotalqty >= $line->qty)
  444. break;
  445. }
  446. // Assez => on stoppe
  447. if ($subtotalqty >= $line->qty)
  448. break;
  449. }
  450. }
  451. }
  452. $batch_line[$i]['detail'] = $sub_qty; // array of details
  453. $batch_line[$i]['qty'] = $subtotalqty;
  454. $batch_line[$i]['ix_l'] = $line->id;
  455. //var_dump($batch_line[$i]);
  456. } else { // @todo finir propduits
  457. foreach($product->stock_warehouse as $warehouse_id=>$stock) {
  458. //var_dump($stock);
  459. $stockqty = min($stock->real, $line->qty-$subtotalqty);
  460. $stockLine[$i][] = [
  461. 'qty' => $stockqty,
  462. 'warehouse_id' => $warehouse_id,
  463. 'ix_l' => $line->id,
  464. ];
  465. $subtotalqty += $stockqty;
  466. if ($subtotalqty >= $line->qty)
  467. break;
  468. }
  469. }
  470. //var_dump($subtotalqty);
  471. // Pas assez de produit -> on stoppe
  472. if ($subtotalqty < $line->qty) {
  473. //var_dump($product->label);
  474. setEventMessages($langs->trans("ErrorProductStockUnavailable", $product->label), null, 'errors');
  475. dol_syslog($langs->trans("ErrorProductStockUnavailable", $product->label), LOG_ERR);
  476. $error++;
  477. break;
  478. }
  479. $totalqty += $subtotalqty;
  480. }
  481. //var_dump($batch_line[2]);
  482. // Ajout lignes
  483. if (!$error) {
  484. if ($totalqty > 0) { // There is at least one thing to ship
  485. //var_dump($_POST);exit;
  486. for ($i = 0; $i < $num; $i++) {
  487. // Vu la construction c'est l'un ou l'autre
  488. if (isset($batch_line[$i])) {
  489. // batch mode
  490. $ret = $object->addline_batch($batch_line[$i], $array_options[$i]);
  491. if ($ret < 0) {
  492. setEventMessages($object->error, $object->errors, 'errors');
  493. $error++;
  494. }
  495. }
  496. elseif (isset($stockLine[$i])) {
  497. //shipment from multiple stock locations
  498. $nbstockline = count($stockLine[$i]);
  499. for ($j = 0; $j < $nbstockline; $j++) {
  500. $ret = $object->addline($stockLine[$i][$j]['warehouse_id'], $stockLine[$i][$j]['ix_l'], $stockLine[$i][$j]['qty'], $array_options[$i]);
  501. if ($ret < 0) {
  502. setEventMessages($object->error, $object->errors, 'errors');
  503. $error++;
  504. }
  505. }
  506. }
  507. }
  508. // Fill array 'array_options' with data from add form
  509. $ret = $extrafields->setOptionalsFromPost(null, $object);
  510. if ($ret < 0) {
  511. $error++;
  512. }
  513. if (!$error) {
  514. $ret = $object->create($user); // This create shipment (like Odoo picking) and lines of shipments. Stock movement will be done when validating shipment.
  515. if ($ret <= 0) {
  516. setEventMessages($object->error, $object->errors, 'errors');
  517. $error++;
  518. }
  519. }
  520. } else {
  521. setEventMessages($langs->trans("ErrorEmptyShipping"), null, 'errors');
  522. $error++;
  523. }
  524. }
  525. // Validation
  526. if (!$error) {
  527. if ($validate) {
  528. $result = $object->valid($user);
  529. if ($result<0) {
  530. setEventMessages($object->error, $object->errors, 'errors');
  531. dol_syslog(get_class().' ::' .$object->error.implode(',',$object->errors), LOG_ERR);
  532. $error++;
  533. }
  534. // Auto close shipping
  535. if (
  536. !empty($conf->global->MMI_ORDER_1CLIC_INVOICE_SHIPPING_AUTOCLOSE)
  537. || (!empty($conf->global->MMIPAYMENTS_CAISSE_USER) && $conf->global->MMIPAYMENTS_CAISSE_USER==$user->id)
  538. || (!empty($conf->global->MMIPAYMENTS_CAISSE_COMPANY) && $conf->global->MMIPAYMENTS_CAISSE_COMPANY==$order->thirdparty->id)
  539. ) {
  540. $result = $object->setClosed();
  541. if ($result<0) {
  542. setEventMessages($object->error, $object->errors, 'errors');
  543. dol_syslog(get_class().' ::' .$object->error.implode(',',$object->errors), LOG_ERR);
  544. $error++;
  545. }
  546. }
  547. }
  548. }
  549. // Génération PDF
  550. if (!$error) {
  551. // Retrieve everything
  552. $object->fetch($object->id);
  553. // @todo : Niet! il faut mettre espadon mais d'abord vérifier qu'il est bien à jour
  554. $docmodel = 'rouget';
  555. $object->generateDocument($docmodel, $langs);
  556. }
  557. // OK ou rollback
  558. if (!$error) {
  559. $db->commit();
  560. return $object;
  561. //var_dump($expe);
  562. } else {
  563. $db->rollback();
  564. }
  565. }
  566. public static function invoice_draftify($user, Facture $invoice)
  567. {
  568. global $db;
  569. return $db->query("UPDATE ".MAIN_DB_PREFIX."facture i
  570. LEFT JOIN ".MAIN_DB_PREFIX."accounting_bookkeeping j ON j.doc_type='customer_invoice' AND j.fk_doc=i.rowid
  571. SET i.fk_statut=0
  572. WHERE i.rowid=".$invoice->id." AND j.rowid IS NULL");
  573. }
  574. public static function order_1clic_invoice_shipping($user, Commande $order)
  575. {
  576. global $conf;
  577. if ($user->rights->expedition->creer) {
  578. if (! ($expe = static::order_1clic_shipping($user, $order, true))) {
  579. return;
  580. }
  581. }
  582. // Création Facture
  583. // @todo check if invoice not already done !
  584. if (!empty($conf->global->MMI_ORDER_1CLIC_INVOICE) && empty($conf->global->MMI_ORDER_1CLIC_INVOICE_DELAY) && $user->rights->facture->creer) {
  585. if (! ($invoice = static::order_1clic_invoice($user, $order, true))) {
  586. return;
  587. }
  588. }
  589. return true;
  590. }
  591. /**
  592. * Calcul des expéditions commande client
  593. */
  594. public static function commande_expedition($id)
  595. {
  596. global $db;
  597. $sql = 'SELECT cl.rowid, cl.qty, SUM(ed.qty) qty_expe
  598. FROM '.MAIN_DB_PREFIX.'commandedet cl
  599. LEFT JOIN '.MAIN_DB_PREFIX.'expeditiondet ed ON ed.fk_origin_line=cl.rowid
  600. LEFT JOIN '.MAIN_DB_PREFIX.'product p ON p.rowid=cl.fk_product
  601. LEFT JOIN '.MAIN_DB_PREFIX.'product_extrafields p2 ON p2.fk_object=cl.fk_product
  602. WHERE cl.fk_commande='.$id.' AND cl.qty > 0 AND cl.product_type=0 AND (p2.rowid IS NULL OR p2.compose IS NULL OR p2.compose=0)
  603. GROUP BY cl.rowid
  604. HAVING qty_expe IS NULL OR cl.qty != qty_expe';
  605. //var_dump($sql); //die();
  606. $q = $db->query($sql);
  607. $expe_ok = ($q->num_rows == 0 ?1 :0);
  608. //var_dump($expe_ok); //die();
  609. $sql = 'SELECT rowid, expe_ok
  610. FROM '.MAIN_DB_PREFIX.'commande_extrafields
  611. WHERE fk_object='.$id;
  612. $q = $db->query($sql);
  613. if (list($rowid, $expe_ok_old)=$q->fetch_row()) {
  614. if ($expe_ok_old == $expe_ok)
  615. return;
  616. $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_extrafields
  617. SET expe_ok='.$expe_ok.'
  618. WHERE rowid='.$rowid;
  619. }
  620. else
  621. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'commande_extrafields
  622. (fk_object, expe_ok)
  623. VALUES
  624. ('.$id.', '.$expe_ok.')';
  625. //var_dump($sql);
  626. $q = $db->query($sql);
  627. }
  628. /**
  629. * Calcul des réceptions commande fournisseur
  630. */
  631. public static function commande_four_reception($id)
  632. {
  633. global $user, $db;
  634. $sql = 'SELECT cl.rowid, cl.qty, SUM(cd.qty) qty_recpt
  635. FROM '.MAIN_DB_PREFIX.'commande_fournisseurdet cl
  636. LEFT JOIN '.MAIN_DB_PREFIX.'commande_fournisseur_dispatch cd ON cd.fk_commande=cl.fk_commande AND cd.fk_commandefourndet=cl.rowid
  637. LEFT JOIN '.MAIN_DB_PREFIX.'reception r ON r.rowid=cd.fk_reception
  638. WHERE cl.fk_commande='.$id.'
  639. GROUP BY cl.rowid
  640. HAVING qty_recpt IS NULL OR cl.qty != qty_recpt';
  641. //var_dump($sql); //die();
  642. $q = $db->query($sql);
  643. $recpt_ok = ($q->num_rows == 0 ?1 :0);
  644. //var_dump($recpt_ok); //die();
  645. $sql = 'SELECT rowid, recpt_ok
  646. FROM '.MAIN_DB_PREFIX.'commande_fournisseur_extrafields
  647. WHERE fk_object='.$id;
  648. //var_dump($sql); //die();
  649. $q = $db->query($sql);
  650. //var_dump($q);
  651. $update = false;
  652. if (list($rowid, $recpt_ok_old)=$q->fetch_row()) {
  653. //var_dump($rowid, $recpt_ok_old, $recpt_ok);
  654. if ($recpt_ok_old != $recpt_ok) {
  655. $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur_extrafields
  656. SET recpt_ok='.$recpt_ok.'
  657. WHERE rowid='.$rowid;
  658. $update = true;
  659. }
  660. }
  661. else {
  662. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'commande_fournisseur_extrafields
  663. (fk_object, recpt_ok)
  664. VALUES
  665. ('.$id.', '.$recpt_ok.')';
  666. $update = true;
  667. }
  668. //var_dump($sql);
  669. if ($update)
  670. $q = $db->query($sql);
  671. if ($recpt_ok) {
  672. require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
  673. $cmd = new CommandeFournisseur($db);
  674. $cmd->fetch($id);
  675. if ($cmd->statut==CommandeFournisseur::STATUS_RECEIVED_PARTIALLY) {
  676. $cmd->statut = CommandeFournisseur::STATUS_RECEIVED_COMPLETELY;
  677. $cmd->update($user);
  678. }
  679. }
  680. }
  681. }
  682. mmi_workflow::__init();