Total Complexity | 194 |
Total Lines | 1278 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like MouvementStock often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MouvementStock, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
42 | class MouvementStock extends CommonObject |
||
43 | { |
||
44 | /** |
||
45 | * @var string Id to identify managed objects |
||
46 | */ |
||
47 | public $element = 'stockmouvement'; |
||
48 | |||
49 | /** |
||
50 | * @var string Name of table without prefix where object is stored |
||
51 | */ |
||
52 | public $table_element = 'stock_mouvement'; |
||
53 | |||
54 | |||
55 | /** |
||
56 | * @var int ID product |
||
57 | */ |
||
58 | public $product_id; |
||
59 | |||
60 | /** |
||
61 | * @var int ID warehouse |
||
62 | * @deprecated |
||
63 | * @see $warehouse_id |
||
64 | */ |
||
65 | public $entrepot_id; |
||
66 | |||
67 | /** |
||
68 | * @var int ID warehouse |
||
69 | */ |
||
70 | public $warehouse_id; |
||
71 | |||
72 | /** |
||
73 | * @var float Quantity |
||
74 | */ |
||
75 | public $qty; |
||
76 | |||
77 | /** |
||
78 | * @var int Type of movement |
||
79 | * 0=input (stock increase by a manual/direct stock transfer, correction or inventory), |
||
80 | * 1=output (stock decrease after by a manual/direct stock transfer, correction or inventory), |
||
81 | * 2=output (stock decrease after a business event like sale, shipment or manufacturing, ...), |
||
82 | * 3=input (stock increase after a business event like purchase, reception or manufacturing, ...) |
||
83 | * Note that qty should be > 0 with 0 or 3, < 0 with 1 or 2. |
||
84 | */ |
||
85 | public $type; |
||
86 | |||
87 | public $datem = ''; |
||
88 | public $price; |
||
89 | |||
90 | /** |
||
91 | * @var int ID user author |
||
92 | */ |
||
93 | public $fk_user_author; |
||
94 | |||
95 | /** |
||
96 | * @var string stock movements label |
||
97 | */ |
||
98 | public $label; |
||
99 | |||
100 | /** |
||
101 | * @var int ID |
||
102 | * @deprecated |
||
103 | * @see $origin_id |
||
104 | */ |
||
105 | public $fk_origin; |
||
106 | |||
107 | /** |
||
108 | * @var int Origin id |
||
109 | */ |
||
110 | public $origin_id; |
||
111 | |||
112 | /** |
||
113 | * @var string origintype |
||
114 | * @deprecated |
||
115 | * see $origin_type |
||
116 | */ |
||
117 | public $origintype; |
||
118 | |||
119 | /** |
||
120 | * @var string Origin type ('project', ...) |
||
121 | */ |
||
122 | public $origin_type; |
||
123 | public $line_id_oject_src; |
||
124 | public $line_id_oject_origin; |
||
125 | |||
126 | |||
127 | public $inventorycode; |
||
128 | public $batch; |
||
129 | |||
130 | public $line_id_object_src; |
||
131 | public $line_id_object_origin; |
||
132 | |||
133 | public $eatby; |
||
134 | public $sellby; |
||
135 | |||
136 | |||
137 | |||
138 | public $fields = array( |
||
139 | 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10, 'showoncombobox' => 1), |
||
140 | 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 15), |
||
141 | 'datem' => array('type' => 'datetime', 'label' => 'Datem', 'enabled' => 1, 'visible' => -1, 'position' => 20), |
||
142 | 'fk_product' => array('type' => 'integer:Product:product/class/product.class.php:1', 'label' => 'Product', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 25), |
||
143 | 'fk_entrepot' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Warehouse', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 30), |
||
144 | 'value' => array('type' => 'double', 'label' => 'Value', 'enabled' => 1, 'visible' => -1, 'position' => 35), |
||
145 | 'price' => array('type' => 'double(24,8)', 'label' => 'Price', 'enabled' => 1, 'visible' => -1, 'position' => 40), |
||
146 | 'type_mouvement' => array('type' => 'smallint(6)', 'label' => 'Type mouvement', 'enabled' => 1, 'visible' => -1, 'position' => 45), |
||
147 | 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Fk user author', 'enabled' => 1, 'visible' => -1, 'position' => 50), |
||
148 | 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => -1, 'position' => 55), |
||
149 | 'fk_origin' => array('type' => 'integer', 'label' => 'Fk origin', 'enabled' => 1, 'visible' => -1, 'position' => 60), |
||
150 | 'origintype' => array('type' => 'varchar(32)', 'label' => 'Origintype', 'enabled' => 1, 'visible' => -1, 'position' => 65), |
||
151 | 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 70), |
||
152 | 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => '$conf->project->enabled', 'visible' => -1, 'notnull' => 1, 'position' => 75), |
||
153 | 'inventorycode' => array('type' => 'varchar(128)', 'label' => 'InventoryCode', 'enabled' => 1, 'visible' => -1, 'position' => 80), |
||
154 | 'batch' => array('type' => 'varchar(30)', 'label' => 'Batch', 'enabled' => 1, 'visible' => -1, 'position' => 85), |
||
155 | 'eatby' => array('type' => 'date', 'label' => 'Eatby', 'enabled' => 1, 'visible' => -1, 'position' => 90), |
||
156 | 'sellby' => array('type' => 'date', 'label' => 'Sellby', 'enabled' => 1, 'visible' => -1, 'position' => 95), |
||
157 | 'fk_project' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Fk project', 'enabled' => 1, 'visible' => -1, 'position' => 100), |
||
158 | ); |
||
159 | |||
160 | |||
161 | |||
162 | /** |
||
163 | * Constructor |
||
164 | * |
||
165 | * @param DoliDB $db Database handler |
||
166 | */ |
||
167 | public function __construct($db) |
||
168 | { |
||
169 | $this->db = $db; |
||
170 | } |
||
171 | |||
172 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore |
||
173 | /** |
||
174 | * Add a movement of stock (in one direction only). |
||
175 | * This is the lowest level method to record a stock change. There is no control if warehouse is open or not. |
||
176 | * $this->origin_type and $this->origin_id can be also be set to save the source object of movement. |
||
177 | * |
||
178 | * @param User $user User object |
||
179 | * @param int $fk_product Id of product |
||
180 | * @param int $entrepot_id Id of warehouse |
||
181 | * @param float $qty Qty of movement (can be <0 or >0 depending on parameter type) |
||
182 | * @param int $type Direction of movement: |
||
183 | * 0=input (stock increase by a stock transfer), 1=output (stock decrease by a stock transfer), |
||
184 | * 2=output (stock decrease), 3=input (stock increase) |
||
185 | * Note that qty should be > 0 with 0 or 3, < 0 with 1 or 2. |
||
186 | * @param int $price Unit price HT of product, used to calculate average weighted price (AWP or PMP in french). If 0, average weighted price is not changed. |
||
187 | * @param string $label Label of stock movement |
||
188 | * @param string $inventorycode Inventory code |
||
189 | * @param integer|string $datem Force date of movement |
||
190 | * @param integer|string $eatby eat-by date. Will be used if lot does not exists yet and will be created. |
||
191 | * @param integer|string $sellby sell-by date. Will be used if lot does not exists yet and will be created. |
||
192 | * @param string $batch batch number |
||
193 | * @param boolean $skip_batch If set to true, stock movement is done without impacting batch record |
||
194 | * @param int $id_product_batch Id product_batch (when skip_batch is false and we already know which record of product_batch to use) |
||
195 | * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (useful only if product is a subproduct) |
||
196 | * @param int $donotcleanemptylines Do not clean lines in stock table with qty=0 (because we want to have this done by the caller) |
||
197 | * @param boolean $force_update_batch Allows to add batch stock movement even if $product doesn't use batch anymore |
||
198 | * @return int|string Return integer <0 if KO, 0 if fk_product is null or product id does not exists, >0 if OK, or printabl result of hook |
||
199 | */ |
||
200 | public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0, $disablestockchangeforsubproduct = 0, $donotcleanemptylines = 0, $force_update_batch = false) |
||
201 | { |
||
202 | // phpcs:enable |
||
203 | global $conf, $langs; |
||
204 | |||
205 | |||
206 | $error = 0; |
||
207 | dol_syslog(get_class($this) . "::_create start userid=$user->id, fk_product=$fk_product, warehouse_id=$entrepot_id, qty=$qty, type=$type, price=$price, label=$label, inventorycode=$inventorycode, datem=" . $datem . ", eatby=" . $eatby . ", sellby=" . $sellby . ", batch=" . $batch . ", skip_batch=" . json_encode($skip_batch)); |
||
208 | |||
209 | // Call hook at beginning |
||
210 | global $action, $hookmanager; |
||
211 | $hookmanager->initHooks(array('mouvementstock')); |
||
212 | |||
213 | if (is_object($hookmanager)) { |
||
214 | $parameters = array( |
||
215 | 'currentcontext' => 'mouvementstock', |
||
216 | 'user' => &$user, |
||
217 | 'fk_product' => &$fk_product, |
||
218 | 'entrepot_id' => &$entrepot_id, |
||
219 | 'qty' => &$qty, |
||
220 | 'type' => &$type, |
||
221 | 'price' => &$price, |
||
222 | 'label' => &$label, |
||
223 | 'inventorycode' => &$inventorycode, |
||
224 | 'datem' => &$datem, |
||
225 | 'eatby' => &$eatby, |
||
226 | 'sellby' => &$sellby, |
||
227 | 'batch' => &$batch, |
||
228 | 'skip_batch' => &$skip_batch, |
||
229 | 'id_product_batch' => &$id_product_batch |
||
230 | ); |
||
231 | $reshook = $hookmanager->executeHooks('stockMovementCreate', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks |
||
232 | |||
233 | if ($reshook < 0) { |
||
234 | if (!empty($hookmanager->resPrint)) { |
||
235 | dol_print_error(null, $hookmanager->resPrint); |
||
236 | } |
||
237 | return $reshook; |
||
238 | } elseif ($reshook > 0) { |
||
239 | return $hookmanager->resPrint; |
||
240 | } |
||
241 | } |
||
242 | // end hook at beginning |
||
243 | |||
244 | // Clean parameters |
||
245 | $price = price2num($price, 'MU'); // Clean value for the casse we receive a float zero value, to have it a real zero value. |
||
246 | if (empty($price)) { |
||
247 | $price = 0; |
||
248 | } |
||
249 | $now = (!empty($datem) ? $datem : dol_now()); |
||
250 | |||
251 | // Check parameters |
||
252 | if (!($fk_product > 0)) { |
||
253 | return 0; |
||
254 | } |
||
255 | if (!($entrepot_id > 0)) { |
||
256 | return 0; |
||
257 | } |
||
258 | |||
259 | if (is_numeric($eatby) && $eatby < 0) { |
||
260 | dol_syslog(get_class($this) . "::_create start ErrorBadValueForParameterEatBy eatby = " . $eatby); |
||
261 | $this->errors[] = 'ErrorBadValueForParameterEatBy'; |
||
262 | return -1; |
||
263 | } |
||
264 | if (is_numeric($sellby) && $sellby < 0) { |
||
265 | dol_syslog(get_class($this) . "::_create start ErrorBadValueForParameterSellBy sellby = " . $sellby); |
||
266 | $this->errors[] = 'ErrorBadValueForParameterSellBy'; |
||
267 | return -1; |
||
268 | } |
||
269 | |||
270 | // Set properties of movement |
||
271 | $this->product_id = $fk_product; |
||
272 | $this->entrepot_id = $entrepot_id; // deprecated |
||
273 | $this->warehouse_id = $entrepot_id; |
||
274 | $this->qty = $qty; |
||
275 | $this->type = $type; |
||
276 | $this->price = price2num($price); |
||
277 | $this->label = $label; |
||
278 | $this->inventorycode = $inventorycode; |
||
279 | $this->datem = $now; |
||
280 | $this->batch = $batch; |
||
281 | |||
282 | $mvid = 0; |
||
283 | |||
284 | $product = new Product($this->db); |
||
285 | |||
286 | $result = $product->fetch($fk_product); |
||
287 | if ($result < 0) { |
||
288 | $this->error = $product->error; |
||
289 | $this->errors = $product->errors; |
||
290 | dol_print_error(null, "Failed to fetch product"); |
||
291 | return -1; |
||
292 | } |
||
293 | if ($product->id <= 0) { // Can happen if database is corrupted (a product id exist in stock with product that has been removed) |
||
294 | return 0; |
||
295 | } |
||
296 | |||
297 | // Define if we must make the stock change (If product type is a service or if stock is used also for services) |
||
298 | // Only record into stock tables will be disabled by this (the rest like writing into lot table or movement of subproucts are done) |
||
299 | $movestock = 0; |
||
300 | if ($product->type != Product::TYPE_SERVICE || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) { |
||
301 | $movestock = 1; |
||
302 | } |
||
303 | |||
304 | $this->db->begin(); |
||
305 | |||
306 | // Set value $product->stock_reel and detail per warehouse into $product->stock_warehouse array |
||
307 | if ($movestock) { |
||
308 | $product->load_stock('novirtual'); |
||
309 | } |
||
310 | |||
311 | // Test if product require batch data. If yes, and there is not or values are not correct, we throw an error. |
||
312 | if (isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) { |
||
313 | if (empty($batch)) { |
||
314 | $langs->load("errors"); |
||
315 | $this->errors[] = $langs->transnoentitiesnoconv("ErrorTryToMakeMoveOnProductRequiringBatchData", $product->ref); |
||
316 | dol_syslog("Try to make a movement of a product with status_batch on without any batch data", LOG_ERR); |
||
317 | |||
318 | $this->db->rollback(); |
||
319 | return -2; |
||
320 | } |
||
321 | |||
322 | // Check table llx_product_lot from batchnumber for same product |
||
323 | // If found and eatby/sellby defined into table and provided and differs, return error |
||
324 | // If found and eatby/sellby defined into table and not provided, we take value from table |
||
325 | // If found and eatby/sellby not defined into table and provided, we update table |
||
326 | // If found and eatby/sellby not defined into table and not provided, we do nothing |
||
327 | // If not found, we add record |
||
328 | $sql = "SELECT pb.rowid, pb.batch, pb.eatby, pb.sellby FROM " . $this->db->prefix() . "product_lot as pb"; |
||
329 | $sql .= " WHERE pb.fk_product = " . ((int) $fk_product) . " AND pb.batch = '" . $this->db->escape($batch) . "'"; |
||
330 | |||
331 | dol_syslog(get_class($this) . "::_create scan serial for this product to check if eatby and sellby match", LOG_DEBUG); |
||
332 | |||
333 | $resql = $this->db->query($sql); |
||
334 | if ($resql) { |
||
335 | $num = $this->db->num_rows($resql); |
||
336 | $i = 0; |
||
337 | if ($num > 0) { |
||
338 | while ($i < $num) { |
||
339 | $obj = $this->db->fetch_object($resql); |
||
340 | if ($obj->eatby) { |
||
341 | if ($eatby) { |
||
342 | $tmparray = dol_getdate($eatby, true); |
||
343 | $eatbywithouthour = dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year']); |
||
344 | if ($this->db->jdate($obj->eatby) != $eatby && $this->db->jdate($obj->eatby) != $eatbywithouthour) { // We test date without hours and with hours for backward compatibility |
||
345 | // If found and eatby/sellby defined into table and provided and differs, return error |
||
346 | $langs->load("stocks"); |
||
347 | $this->errors[] = $langs->transnoentitiesnoconv("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby), 'dayhour'), dol_print_date($eatbywithouthour, 'dayhour')); |
||
348 | dol_syslog("ThisSerialAlreadyExistWithDifferentDate batch=" . $batch . ", eatby found into product_lot = " . $obj->eatby . " = " . dol_print_date($this->db->jdate($obj->eatby), 'dayhourrfc') . " so eatbywithouthour = " . $eatbywithouthour . " = " . dol_print_date($eatbywithouthour) . " - eatby provided = " . $eatby . " = " . dol_print_date($eatby, 'dayhourrfc'), LOG_ERR); |
||
349 | $this->db->rollback(); |
||
350 | return -3; |
||
351 | } |
||
352 | } else { |
||
353 | $eatby = $obj->eatby; // If found and eatby/sellby defined into table and not provided, we take value from table |
||
354 | } |
||
355 | } else { |
||
356 | if ($eatby) { // If found and eatby/sellby not defined into table and provided, we update table |
||
357 | $productlot = new Productlot($this->db); |
||
358 | $result = $productlot->fetch($obj->rowid); |
||
359 | $productlot->eatby = $eatby; |
||
360 | $result = $productlot->update($user); |
||
361 | if ($result <= 0) { |
||
362 | $this->error = $productlot->error; |
||
363 | $this->errors = $productlot->errors; |
||
364 | $this->db->rollback(); |
||
365 | return -5; |
||
366 | } |
||
367 | } |
||
368 | } |
||
369 | if ($obj->sellby) { |
||
370 | if ($sellby) { |
||
371 | $tmparray = dol_getdate($sellby, true); |
||
372 | $sellbywithouthour = dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year']); |
||
373 | if ($this->db->jdate($obj->sellby) != $sellby && $this->db->jdate($obj->sellby) != $sellbywithouthour) { // We test date without hours and with hours for backward compatibility |
||
374 | // If found and eatby/sellby defined into table and provided and differs, return error |
||
375 | $this->errors[] = $langs->transnoentitiesnoconv("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)); |
||
376 | dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)), LOG_ERR); |
||
377 | $this->db->rollback(); |
||
378 | return -3; |
||
379 | } |
||
380 | } else { |
||
381 | $sellby = $obj->sellby; // If found and eatby/sellby defined into table and not provided, we take value from table |
||
382 | } |
||
383 | } else { |
||
384 | if ($sellby) { // If found and eatby/sellby not defined into table and provided, we update table |
||
385 | $productlot = new Productlot($this->db); |
||
386 | $result = $productlot->fetch($obj->rowid); |
||
387 | $productlot->sellby = $sellby; |
||
388 | $result = $productlot->update($user); |
||
389 | if ($result <= 0) { |
||
390 | $this->error = $productlot->error; |
||
391 | $this->errors = $productlot->errors; |
||
392 | $this->db->rollback(); |
||
393 | return -5; |
||
394 | } |
||
395 | } |
||
396 | } |
||
397 | |||
398 | $i++; |
||
399 | } |
||
400 | } else { // If not found, we add record |
||
401 | $productlot = new Productlot($this->db); |
||
402 | $productlot->origin = !empty($this->origin_type) ? $this->origin_type : ''; |
||
|
|||
403 | $productlot->origin_id = !empty($this->origin_id) ? $this->origin_id : 0; |
||
404 | $productlot->entity = $conf->entity; |
||
405 | $productlot->fk_product = $fk_product; |
||
406 | $productlot->batch = $batch; |
||
407 | // If we are here = first time we manage this batch, so we used dates provided by users to create lot |
||
408 | $productlot->eatby = $eatby; |
||
409 | $productlot->sellby = $sellby; |
||
410 | $result = $productlot->create($user); |
||
411 | if ($result <= 0) { |
||
412 | $this->error = $productlot->error; |
||
413 | $this->errors = $productlot->errors; |
||
414 | $this->db->rollback(); |
||
415 | return -4; |
||
416 | } |
||
417 | } |
||
418 | } else { |
||
419 | dol_print_error($this->db); |
||
420 | $this->db->rollback(); |
||
421 | return -1; |
||
422 | } |
||
423 | } |
||
424 | |||
425 | // Check if stock is enough when qty is < 0 |
||
426 | // Note that qty should be > 0 with type 0 or 3, < 0 with type 1 or 2. |
||
427 | if ($movestock && $qty < 0 && !getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) { |
||
428 | if (isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) { |
||
429 | $foundforbatch = 0; |
||
430 | $qtyisnotenough = 0; |
||
431 | if (isset($product->stock_warehouse[$entrepot_id])) { |
||
432 | foreach ($product->stock_warehouse[$entrepot_id]->detail_batch as $batchcursor => $prodbatch) { |
||
433 | if ((string) $batch != (string) $batchcursor) { // Lot '59' must be different than lot '59c' |
||
434 | continue; |
||
435 | } |
||
436 | |||
437 | $foundforbatch = 1; |
||
438 | if ($prodbatch->qty < abs($qty)) { |
||
439 | $qtyisnotenough = $prodbatch->qty; |
||
440 | } |
||
441 | break; |
||
442 | } |
||
443 | } |
||
444 | if (!$foundforbatch || $qtyisnotenough) { |
||
445 | $langs->load("stocks"); |
||
446 | include_once DOL_DOCUMENT_ROOT . '/product/stock/class/entrepot.class.php'; |
||
447 | $tmpwarehouse = new Entrepot($this->db); |
||
448 | $tmpwarehouse->fetch($entrepot_id); |
||
449 | |||
450 | $this->error = $langs->trans('qtyToTranferLotIsNotEnough', $product->ref, $batch, $qtyisnotenough, $tmpwarehouse->ref); |
||
451 | $this->errors[] = $langs->trans('qtyToTranferLotIsNotEnough', $product->ref, $batch, $qtyisnotenough, $tmpwarehouse->ref); |
||
452 | $this->db->rollback(); |
||
453 | return -8; |
||
454 | } |
||
455 | } else { |
||
456 | if (isset($product->stock_warehouse[$entrepot_id]) && (empty($product->stock_warehouse[$entrepot_id]->real) || $product->stock_warehouse[$entrepot_id]->real < abs($qty))) { |
||
457 | $langs->load("stocks"); |
||
458 | $this->error = $langs->trans('qtyToTranferIsNotEnough') . ' : ' . $product->ref; |
||
459 | $this->errors[] = $langs->trans('qtyToTranferIsNotEnough') . ' : ' . $product->ref; |
||
460 | $this->db->rollback(); |
||
461 | return -8; |
||
462 | } |
||
463 | } |
||
464 | } |
||
465 | |||
466 | if ($movestock) { // Change stock for current product, change for subproduct is done after |
||
467 | // Set $origin_type, origin_id and fk_project |
||
468 | $fk_project = $this->fk_project; |
||
469 | if (!empty($this->origin_type)) { // This is set by caller for tracking reason |
||
470 | $origin_type = $this->origin_type; |
||
471 | $origin_id = $this->origin_id; |
||
472 | if (empty($fk_project) && $origin_type == 'project') { |
||
473 | $fk_project = $origin_id; |
||
474 | $origin_type = ''; |
||
475 | $origin_id = 0; |
||
476 | } |
||
477 | } else { |
||
478 | $fk_project = 0; |
||
479 | $origin_type = ''; |
||
480 | $origin_id = 0; |
||
481 | } |
||
482 | |||
483 | $sql = "INSERT INTO " . $this->db->prefix() . "stock_mouvement("; |
||
484 | $sql .= " datem, fk_product, batch, eatby, sellby,"; |
||
485 | $sql .= " fk_entrepot, value, type_mouvement, fk_user_author, label, inventorycode, price, fk_origin, origintype, fk_projet"; |
||
486 | $sql .= ")"; |
||
487 | $sql .= " VALUES ('" . $this->db->idate($this->datem) . "', " . ((int) $this->product_id) . ", "; |
||
488 | $sql .= " " . ($batch ? "'" . $this->db->escape($batch) . "'" : "null") . ", "; |
||
489 | $sql .= " " . ($eatby ? "'" . $this->db->idate($eatby) . "'" : "null") . ", "; |
||
490 | $sql .= " " . ($sellby ? "'" . $this->db->idate($sellby) . "'" : "null") . ", "; |
||
491 | $sql .= " " . ((int) $this->entrepot_id) . ", " . ((float) $this->qty) . ", " . ((int) $this->type) . ","; |
||
492 | $sql .= " " . ((int) $user->id) . ","; |
||
493 | $sql .= " '" . $this->db->escape($label) . "',"; |
||
494 | $sql .= " " . ($inventorycode ? "'" . $this->db->escape($inventorycode) . "'" : "null") . ","; |
||
495 | $sql .= " " . ((float) price2num($price)) . ","; |
||
496 | $sql .= " " . ((int) $origin_id) . ","; |
||
497 | $sql .= " '" . $this->db->escape($origin_type) . "',"; |
||
498 | $sql .= " " . ((int) $fk_project); |
||
499 | $sql .= ")"; |
||
500 | |||
501 | dol_syslog(get_class($this) . "::_create insert record into stock_mouvement", LOG_DEBUG); |
||
502 | $resql = $this->db->query($sql); |
||
503 | |||
504 | if ($resql) { |
||
505 | $mvid = $this->db->last_insert_id($this->db->prefix() . "stock_mouvement"); |
||
506 | $this->id = $mvid; |
||
507 | } else { |
||
508 | $this->error = $this->db->lasterror(); |
||
509 | $this->errors[] = $this->error; |
||
510 | $error = -1; |
||
511 | } |
||
512 | |||
513 | // Define current values for qty and pmp |
||
514 | $oldqty = $product->stock_reel; |
||
515 | $oldpmp = $product->pmp; |
||
516 | $oldqtywarehouse = 0; |
||
517 | |||
518 | // Test if there is already a record for couple (warehouse / product), so later we will make an update or create. |
||
519 | $alreadyarecord = 0; |
||
520 | if (!$error) { |
||
521 | $sql = "SELECT rowid, reel FROM " . $this->db->prefix() . "product_stock"; |
||
522 | $sql .= " WHERE fk_entrepot = " . ((int) $entrepot_id) . " AND fk_product = " . ((int) $fk_product); // This is a unique key |
||
523 | |||
524 | dol_syslog(get_class($this) . "::_create check if a record already exists in product_stock", LOG_DEBUG); |
||
525 | $resql = $this->db->query($sql); |
||
526 | if ($resql) { |
||
527 | $obj = $this->db->fetch_object($resql); |
||
528 | if ($obj) { |
||
529 | $alreadyarecord = 1; |
||
530 | $oldqtywarehouse = $obj->reel; |
||
531 | $fk_product_stock = $obj->rowid; |
||
532 | } |
||
533 | $this->db->free($resql); |
||
534 | } else { |
||
535 | $this->errors[] = $this->db->lasterror(); |
||
536 | $error = -2; |
||
537 | } |
||
538 | } |
||
539 | |||
540 | // Calculate new AWP (PMP) |
||
541 | $newpmp = 0; |
||
542 | if (!$error) { |
||
543 | if ($type == 0 || $type == 3) { |
||
544 | // After a stock increase |
||
545 | // Note: PMP is calculated on stock input only (type of movement = 0 or 3). If type == 0 or 3, qty should be > 0. |
||
546 | // Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input) |
||
547 | if ($price > 0 || (getDolGlobalString('STOCK_UPDATE_AWP_EVEN_WHEN_ENTRY_PRICE_IS_NULL') && $price == 0 && in_array($this->origin_type, array('order_supplier', 'invoice_supplier')))) { |
||
548 | $oldqtytouse = ($oldqty >= 0 ? $oldqty : 0); |
||
549 | // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined |
||
550 | if ($oldpmp > 0) { |
||
551 | $newpmp = price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU'); |
||
552 | } else { |
||
553 | $newpmp = $price; // For this product, PMP was not yet set. We set it to input price. |
||
554 | } |
||
555 | //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." "; |
||
556 | //print "qty=".$qty." newpmp=".$newpmp; |
||
557 | //exit; |
||
558 | } else { |
||
559 | $newpmp = $oldpmp; |
||
560 | } |
||
561 | } else { |
||
562 | // ($type == 1 || $type == 2) |
||
563 | // -> After a stock decrease, we don't change value of the AWP/PMP of a product. |
||
564 | // else |
||
565 | // Type of movement unknown |
||
566 | $newpmp = $oldpmp; |
||
567 | } |
||
568 | } |
||
569 | // Update stock quantity |
||
570 | if (!$error) { |
||
571 | if ($alreadyarecord > 0) { |
||
572 | $sql = "UPDATE " . $this->db->prefix() . "product_stock SET reel = " . ((float) $oldqtywarehouse + (float) $qty); |
||
573 | $sql .= " WHERE fk_entrepot = " . ((int) $entrepot_id) . " AND fk_product = " . ((int) $fk_product); |
||
574 | } else { |
||
575 | $sql = "INSERT INTO " . $this->db->prefix() . "product_stock"; |
||
576 | $sql .= " (reel, fk_entrepot, fk_product) VALUES "; |
||
577 | $sql .= " (" . ((float) $qty) . ", " . ((int) $entrepot_id) . ", " . ((int) $fk_product) . ")"; |
||
578 | } |
||
579 | |||
580 | dol_syslog(get_class($this) . "::_create update stock value", LOG_DEBUG); |
||
581 | $resql = $this->db->query($sql); |
||
582 | if (!$resql) { |
||
583 | $this->errors[] = $this->db->lasterror(); |
||
584 | $error = -3; |
||
585 | } elseif (empty($fk_product_stock)) { |
||
586 | $fk_product_stock = $this->db->last_insert_id($this->db->prefix() . "product_stock"); |
||
587 | } |
||
588 | } |
||
589 | |||
590 | // Update detail of stock for the lot. |
||
591 | if (!$error && isModEnabled('productbatch') && (($product->hasbatch() && !$skip_batch) || $force_update_batch)) { |
||
592 | if ($id_product_batch > 0) { |
||
593 | $result = $this->createBatch($id_product_batch, $qty); |
||
594 | if ($result == -2 && $fk_product_stock > 0) { // The entry for this product batch does not exists anymore, bu we already have a llx_product_stock, so we recreate the batch entry in product_batch |
||
595 | $param_batch = array('fk_product_stock' => $fk_product_stock, 'batchnumber' => $batch); |
||
596 | $result = $this->createBatch($param_batch, $qty); |
||
597 | } |
||
598 | } else { |
||
599 | $param_batch = array('fk_product_stock' => $fk_product_stock, 'batchnumber' => $batch); |
||
600 | $result = $this->createBatch($param_batch, $qty); |
||
601 | } |
||
602 | if ($result < 0) { |
||
603 | $error++; |
||
604 | } |
||
605 | } |
||
606 | |||
607 | // Update PMP and denormalized value of stock qty at product level |
||
608 | if (!$error) { |
||
609 | $newpmp = price2num($newpmp, 'MU'); |
||
610 | |||
611 | // $sql = "UPDATE ".$this->db->prefix()."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty; |
||
612 | // $sql.= " WHERE rowid = ".((int) $fk_product); |
||
613 | // Update pmp + denormalized fields because we change content of produt_stock. Warning: Do not use "SET p.stock", does not works with pgsql |
||
614 | $sql = "UPDATE " . $this->db->prefix() . "product as p SET pmp = " . ((float) $newpmp) . ","; |
||
615 | $sql .= " stock=(SELECT SUM(ps.reel) FROM " . $this->db->prefix() . "product_stock as ps WHERE ps.fk_product = p.rowid)"; |
||
616 | $sql .= " WHERE rowid = " . ((int) $fk_product); |
||
617 | |||
618 | dol_syslog(get_class($this) . "::_create update AWP", LOG_DEBUG); |
||
619 | $resql = $this->db->query($sql); |
||
620 | if (!$resql) { |
||
621 | $this->errors[] = $this->db->lasterror(); |
||
622 | $error = -4; |
||
623 | } |
||
624 | } |
||
625 | |||
626 | if (empty($donotcleanemptylines)) { |
||
627 | // If stock is now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine |
||
628 | // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. |
||
629 | $sql = "DELETE FROM " . $this->db->prefix() . "product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM " . $this->db->prefix() . "product_batch as pb)"; |
||
630 | $resql = $this->db->query($sql); |
||
631 | // We do not test error, it can fails if there is child in batch details |
||
632 | } |
||
633 | } |
||
634 | |||
635 | // Add movement for sub products (recursive call) |
||
636 | if (!$error && getDolGlobalString('PRODUIT_SOUSPRODUITS') && !getDolGlobalString('INDEPENDANT_SUBPRODUCT_STOCK') && empty($disablestockchangeforsubproduct)) { |
||
637 | $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label, $inventorycode, $datem); // we use 0 as price, because AWP must not change for subproduct |
||
638 | } |
||
639 | |||
640 | if ($movestock && !$error) { |
||
641 | // Call trigger |
||
642 | $result = $this->call_trigger('STOCK_MOVEMENT', $user); |
||
643 | if ($result < 0) { |
||
644 | $error++; |
||
645 | } |
||
646 | // End call triggers |
||
647 | // Check unicity for serial numbered equipment once all movement were done. |
||
648 | if (!$error && isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) { |
||
649 | if ($product->status_batch == 2 && $qty > 0) { // We check only if we increased qty |
||
650 | if ($this->getBatchCount($fk_product, $batch) > 1) { |
||
651 | $error++; |
||
652 | $this->errors[] = $langs->trans("TooManyQtyForSerialNumber", $product->ref, $batch); |
||
653 | } |
||
654 | } |
||
655 | } |
||
656 | } |
||
657 | |||
658 | if (!$error) { |
||
659 | $this->db->commit(); |
||
660 | return $mvid; |
||
661 | } else { |
||
662 | $this->db->rollback(); |
||
663 | dol_syslog(get_class($this) . "::_create error code=" . $error, LOG_ERR); |
||
664 | return -6; |
||
665 | } |
||
666 | } |
||
667 | |||
668 | |||
669 | |||
670 | /** |
||
671 | * Load object in memory from the database |
||
672 | * |
||
673 | * @param int $id Id object |
||
674 | * |
||
675 | * @return int Return integer <0 if KO, 0 if not found, >0 if OK |
||
676 | */ |
||
677 | public function fetch($id) |
||
678 | { |
||
679 | dol_syslog(__METHOD__, LOG_DEBUG); |
||
680 | |||
681 | $sql = "SELECT"; |
||
682 | $sql .= " t.rowid,"; |
||
683 | $sql .= " t.tms,"; |
||
684 | $sql .= " t.datem,"; |
||
685 | $sql .= " t.fk_product,"; |
||
686 | $sql .= " t.fk_entrepot,"; |
||
687 | $sql .= " t.value,"; |
||
688 | $sql .= " t.price,"; |
||
689 | $sql .= " t.type_mouvement,"; |
||
690 | $sql .= " t.fk_user_author,"; |
||
691 | $sql .= " t.label,"; |
||
692 | $sql .= " t.fk_origin as origin_id,"; |
||
693 | $sql .= " t.origintype as origin_type,"; |
||
694 | $sql .= " t.inventorycode,"; |
||
695 | $sql .= " t.batch,"; |
||
696 | $sql .= " t.eatby,"; |
||
697 | $sql .= " t.sellby,"; |
||
698 | $sql .= " t.fk_projet as fk_project"; |
||
699 | $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as t"; |
||
700 | $sql .= " WHERE t.rowid = " . ((int) $id); |
||
701 | |||
702 | $resql = $this->db->query($sql); |
||
703 | if ($resql) { |
||
704 | $numrows = $this->db->num_rows($resql); |
||
705 | if ($numrows) { |
||
706 | $obj = $this->db->fetch_object($resql); |
||
707 | |||
708 | $this->id = $obj->rowid; |
||
709 | |||
710 | $this->product_id = $obj->fk_product; |
||
711 | $this->warehouse_id = $obj->fk_entrepot; |
||
712 | $this->qty = $obj->value; |
||
713 | $this->type = $obj->type_mouvement; |
||
714 | |||
715 | $this->tms = $this->db->jdate($obj->tms); |
||
716 | $this->datem = $this->db->jdate($obj->datem); |
||
717 | $this->price = $obj->price; |
||
718 | $this->fk_user_author = $obj->fk_user_author; |
||
719 | $this->label = $obj->label; |
||
720 | $this->fk_origin = $obj->origin_id; // For backward compatibility |
||
721 | $this->origintype = $obj->origin_type; // For backward compatibility |
||
722 | $this->origin_id = $obj->origin_id; |
||
723 | $this->origin_type = $obj->origin_type; |
||
724 | $this->inventorycode = $obj->inventorycode; |
||
725 | $this->batch = $obj->batch; |
||
726 | $this->eatby = $this->db->jdate($obj->eatby); |
||
727 | $this->sellby = $this->db->jdate($obj->sellby); |
||
728 | $this->fk_project = $obj->fk_project; |
||
729 | } |
||
730 | |||
731 | // Retrieve all extrafield |
||
732 | $this->fetch_optionals(); |
||
733 | |||
734 | // $this->fetch_lines(); |
||
735 | |||
736 | $this->db->free($resql); |
||
737 | |||
738 | if ($numrows) { |
||
739 | return 1; |
||
740 | } else { |
||
741 | return 0; |
||
742 | } |
||
743 | } else { |
||
744 | $this->errors[] = 'Error ' . $this->db->lasterror(); |
||
745 | dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR); |
||
746 | |||
747 | return -1; |
||
748 | } |
||
749 | } |
||
750 | |||
751 | |||
752 | |||
753 | |||
754 | /** |
||
755 | * Create movement in database for all subproducts |
||
756 | * |
||
757 | * @param User $user Object user |
||
758 | * @param int $idProduct Id product |
||
759 | * @param int $entrepot_id Warehouse id |
||
760 | * @param float $qty Quantity |
||
761 | * @param int $type Type |
||
762 | * @param int $price Price |
||
763 | * @param string $label Label of movement |
||
764 | * @param string $inventorycode Inventory code |
||
765 | * @param integer|string $datem Force date of movement |
||
766 | * @return int Return integer <0 if KO, 0 if OK |
||
767 | */ |
||
768 | private function _createSubProduct($user, $idProduct, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '') |
||
769 | { |
||
770 | global $langs; |
||
771 | |||
772 | $error = 0; |
||
773 | $pids = array(); |
||
774 | $pqtys = array(); |
||
775 | |||
776 | $sql = "SELECT fk_product_pere, fk_product_fils, qty"; |
||
777 | $sql .= " FROM " . $this->db->prefix() . "product_association"; |
||
778 | $sql .= " WHERE fk_product_pere = " . ((int) $idProduct); |
||
779 | $sql .= " AND incdec = 1"; |
||
780 | |||
781 | dol_syslog(get_class($this) . "::_createSubProduct for parent product " . $idProduct, LOG_DEBUG); |
||
782 | $resql = $this->db->query($sql); |
||
783 | if ($resql) { |
||
784 | $i = 0; |
||
785 | while ($obj = $this->db->fetch_object($resql)) { |
||
786 | $pids[$i] = $obj->fk_product_fils; |
||
787 | $pqtys[$i] = $obj->qty; |
||
788 | $i++; |
||
789 | } |
||
790 | $this->db->free($resql); |
||
791 | } else { |
||
792 | $error = -2; |
||
793 | } |
||
794 | |||
795 | // Create movement for each subproduct |
||
796 | foreach ($pids as $key => $value) { |
||
797 | if (!$error) { |
||
798 | $tmpmove = dol_clone($this, 1); |
||
799 | |||
800 | $result = $tmpmove->_create($user, $pids[$key], $entrepot_id, ($qty * $pqtys[$key]), $type, 0, $label, $inventorycode, $datem); // This will also call _createSubProduct making this recursive |
||
801 | if ($result < 0) { |
||
802 | $this->error = $tmpmove->error; |
||
803 | $this->errors = array_merge($this->errors, $tmpmove->errors); |
||
804 | if ($result == -2) { |
||
805 | $this->errors[] = $langs->trans("ErrorNoteAlsoThatSubProductCantBeFollowedByLot"); |
||
806 | } |
||
807 | $error = $result; |
||
808 | } |
||
809 | unset($tmpmove); |
||
810 | } |
||
811 | } |
||
812 | |||
813 | return $error; |
||
814 | } |
||
815 | |||
816 | |||
817 | /** |
||
818 | * Decrease stock for product and subproducts |
||
819 | * |
||
820 | * @param User $user Object user |
||
821 | * @param int $fk_product Id product |
||
822 | * @param int $entrepot_id Warehouse id |
||
823 | * @param float $qty Quantity |
||
824 | * @param int $price Price |
||
825 | * @param string $label Label of stock movement |
||
826 | * @param int|string $datem Force date of movement |
||
827 | * @param int|string $eatby eat-by date |
||
828 | * @param int|string $sellby sell-by date |
||
829 | * @param string $batch batch number |
||
830 | * @param int $id_product_batch Id product_batch |
||
831 | * @param string $inventorycode Inventory code |
||
832 | * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) |
||
833 | * @return int Return integer <0 if KO, >0 if OK |
||
834 | */ |
||
835 | public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0) |
||
836 | { |
||
837 | global $conf; |
||
838 | |||
839 | $skip_batch = empty($conf->productbatch->enabled); |
||
840 | |||
841 | return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines); |
||
842 | } |
||
843 | |||
844 | /** |
||
845 | * Increase stock for product and subproducts |
||
846 | * |
||
847 | * @param User $user Object user |
||
848 | * @param int $fk_product Id product |
||
849 | * @param int $entrepot_id Warehouse id |
||
850 | * @param float $qty Quantity |
||
851 | * @param int $price Price |
||
852 | * @param string $label Label of stock movement |
||
853 | * @param integer|string $eatby eat-by date |
||
854 | * @param integer|string $sellby sell-by date |
||
855 | * @param string $batch batch number |
||
856 | * @param integer|string $datem Force date of movement |
||
857 | * @param int $id_product_batch Id product_batch |
||
858 | * @param string $inventorycode Inventory code |
||
859 | * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) |
||
860 | * @return int Return integer <0 if KO, >0 if OK |
||
861 | */ |
||
862 | public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0) |
||
863 | { |
||
864 | global $conf; |
||
865 | |||
866 | $skip_batch = empty($conf->productbatch->enabled); |
||
867 | |||
868 | return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines); |
||
869 | } |
||
870 | |||
871 | /** |
||
872 | * Count number of product in stock before a specific date |
||
873 | * |
||
874 | * @param int $productidselected Id of product to count |
||
875 | * @param integer $datebefore Date limit |
||
876 | * @return int Number |
||
877 | */ |
||
878 | public function calculateBalanceForProductBefore($productidselected, $datebefore) |
||
879 | { |
||
880 | $nb = 0; |
||
881 | |||
882 | $sql = "SELECT SUM(value) as nb from " . $this->db->prefix() . "stock_mouvement"; |
||
883 | $sql .= " WHERE fk_product = " . ((int) $productidselected); |
||
884 | $sql .= " AND datem < '" . $this->db->idate($datebefore) . "'"; |
||
885 | |||
886 | dol_syslog(get_class($this) . __METHOD__, LOG_DEBUG); |
||
887 | $resql = $this->db->query($sql); |
||
888 | if ($resql) { |
||
889 | $obj = $this->db->fetch_object($resql); |
||
890 | if ($obj) { |
||
891 | $nb = $obj->nb; |
||
892 | } |
||
893 | return (empty($nb) ? 0 : $nb); |
||
894 | } else { |
||
895 | dol_print_error($this->db); |
||
896 | return -1; |
||
897 | } |
||
898 | } |
||
899 | |||
900 | /** |
||
901 | * Create or update batch record (update table llx_product_batch). No check is done here, done by parent. |
||
902 | * |
||
903 | * @param array|int $dluo Could be either |
||
904 | * - int if row id of product_batch table (for update) |
||
905 | * - or complete array('fk_product_stock'=>, 'batchnumber'=>) |
||
906 | * @param float $qty Quantity of product with batch number. May be a negative amount. |
||
907 | * @return int Return integer <0 if KO, -2 if we try to update a product_batchid that does not exist, else return productbatch id |
||
908 | */ |
||
909 | private function createBatch($dluo, $qty) |
||
910 | { |
||
911 | global $user, $langs; |
||
912 | |||
913 | $langs->load('productbatch'); |
||
914 | |||
915 | $pdluo = new Productbatch($this->db); |
||
916 | |||
917 | $result = 0; |
||
918 | |||
919 | // Try to find an existing record with same batch number or id |
||
920 | if (is_numeric($dluo)) { |
||
921 | $result = $pdluo->fetch($dluo); |
||
922 | if (empty($pdluo->id)) { |
||
923 | // We didn't find the line. May be it was deleted before by a previous move in same transaction. |
||
924 | $this->error = $langs->trans('CantMoveNonExistantSerial'); |
||
925 | $this->errors[] = $this->error; |
||
926 | $result = -2; |
||
927 | } |
||
928 | } elseif (is_array($dluo)) { |
||
929 | if (isset($dluo['fk_product_stock'])) { |
||
930 | $vfk_product_stock = $dluo['fk_product_stock']; |
||
931 | $vbatchnumber = $dluo['batchnumber']; |
||
932 | |||
933 | $result = $pdluo->find($vfk_product_stock, '', '', $vbatchnumber); // Search on batch number only (eatby and sellby are deprecated here) |
||
934 | } else { |
||
935 | dol_syslog(get_class($this) . "::createBatch array param dluo must contain at least key fk_product_stock", LOG_ERR); |
||
936 | $result = -1; |
||
937 | } |
||
938 | } else { |
||
939 | dol_syslog(get_class($this) . "::createBatch error invalid param dluo", LOG_ERR); |
||
940 | $result = -1; |
||
941 | } |
||
942 | |||
943 | if ($result >= 0) { |
||
944 | // No error |
||
945 | if ($pdluo->id > 0) { // product_batch record found |
||
946 | //print "Avant ".$pdluo->qty." Apres ".($pdluo->qty + $qty)."<br>"; |
||
947 | $pdluo->qty += $qty; |
||
948 | if ($pdluo->qty == 0) { |
||
949 | $result = $pdluo->delete($user, 1); |
||
950 | } else { |
||
951 | $result = $pdluo->update($user, 1); |
||
952 | } |
||
953 | } else { // product_batch record not found |
||
954 | $pdluo->fk_product_stock = $vfk_product_stock; |
||
955 | $pdluo->qty = $qty; |
||
956 | $pdluo->eatby = empty($dluo['eatby']) ? '' : $dluo['eatby']; // No more used. Now eatby date is store in table of lot, no more into prouct_batch table. |
||
957 | $pdluo->sellby = empty($dluo['sellby']) ? '' : $dluo['sellby']; // No more used. Now sellby date is store in table of lot, no more into prouct_batch table. |
||
958 | $pdluo->batch = $vbatchnumber; |
||
959 | |||
960 | $result = $pdluo->create($user, 1); |
||
961 | if ($result < 0) { |
||
962 | $this->error = $pdluo->error; |
||
963 | $this->errors = $pdluo->errors; |
||
964 | } |
||
965 | } |
||
966 | } |
||
967 | |||
968 | return $result; |
||
969 | } |
||
970 | |||
971 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
972 | /** |
||
973 | * Return Url link of origin object |
||
974 | * |
||
975 | * @param int $origin_id Id origin |
||
976 | * @param string $origin_type Type origin ('project', 'xxx@MODULENAME', etc) |
||
977 | * @return string |
||
978 | */ |
||
979 | public function get_origin($origin_id, $origin_type) |
||
980 | { |
||
981 | // phpcs:enable |
||
982 | $origin = ''; |
||
983 | |||
984 | switch ($origin_type) { |
||
985 | case 'commande': |
||
986 | $origin = new Commande($this->db); |
||
987 | break; |
||
988 | case 'shipping': |
||
989 | $origin = new Expedition($this->db); |
||
990 | break; |
||
991 | case 'facture': |
||
992 | $origin = new Facture($this->db); |
||
993 | break; |
||
994 | case 'order_supplier': |
||
995 | $origin = new CommandeFournisseur($this->db); |
||
996 | break; |
||
997 | case 'invoice_supplier': |
||
998 | $origin = new FactureFournisseur($this->db); |
||
999 | break; |
||
1000 | case 'project': |
||
1001 | $origin = new Project($this->db); |
||
1002 | break; |
||
1003 | case 'mo': |
||
1004 | $origin = new Mo($this->db); |
||
1005 | break; |
||
1006 | case 'user': |
||
1007 | $origin = new User($this->db); |
||
1008 | break; |
||
1009 | case 'reception': |
||
1010 | $origin = new Reception($this->db); |
||
1011 | break; |
||
1012 | case 'inventory': |
||
1013 | require_once constant('DOL_DOCUMENT_ROOT') . '/product/inventory/class/inventory.class.php'; |
||
1014 | $origin = new Inventory($this->db); |
||
1015 | break; |
||
1016 | default: |
||
1017 | if ($origin_type) { |
||
1018 | // Separate origin_type with "@" : left part is class name, right part is module name |
||
1019 | $origin_type_array = explode('@', $origin_type); |
||
1020 | $classname = $origin_type_array[0]; |
||
1021 | $modulename = empty($origin_type_array[1]) ? strtolower($classname) : $origin_type_array[1]; |
||
1022 | |||
1023 | $result = dol_include_once('/' . $modulename . '/class/' . $classname . '.class.php'); |
||
1024 | |||
1025 | if ($result) { |
||
1026 | $classname = ucfirst($classname); |
||
1027 | $origin = new $classname($this->db); |
||
1028 | } |
||
1029 | } |
||
1030 | break; |
||
1031 | } |
||
1032 | |||
1033 | if (empty($origin) || !is_object($origin)) { |
||
1034 | return ''; |
||
1035 | } |
||
1036 | |||
1037 | if ($origin->fetch($origin_id) > 0) { |
||
1038 | return $origin->getNomUrl(1); |
||
1039 | } |
||
1040 | |||
1041 | return ''; |
||
1042 | } |
||
1043 | |||
1044 | /** |
||
1045 | * Set attribute origin_type and fk_origin to object |
||
1046 | * |
||
1047 | * @param string $origin_element Type of element |
||
1048 | * @param int $origin_id Id of element |
||
1049 | * @param int $line_id_object_src Id line of element Source |
||
1050 | * @param int $line_id_object_origin Id line of element Origin |
||
1051 | * |
||
1052 | * @return void |
||
1053 | */ |
||
1054 | public function setOrigin($origin_element, $origin_id, $line_id_object_src = 0, $line_id_object_origin = 0) |
||
1055 | { |
||
1056 | $this->origin_type = $origin_element; |
||
1057 | $this->origin_id = $origin_id; |
||
1058 | $this->line_id_object_src = $line_id_object_src; |
||
1059 | $this->line_id_object_origin = $line_id_object_origin; |
||
1060 | // For backward compatibility |
||
1061 | $this->origintype = $origin_element; |
||
1062 | $this->fk_origin = $origin_id; |
||
1063 | } |
||
1064 | |||
1065 | |||
1066 | /** |
||
1067 | * Initialise an instance with random values. |
||
1068 | * Used to build previews or test instances. |
||
1069 | * id must be 0 if object instance is a specimen. |
||
1070 | * |
||
1071 | * @return int |
||
1072 | */ |
||
1073 | public function initAsSpecimen() |
||
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * Return html string with picto for type of movement |
||
1085 | * |
||
1086 | * @param int $withlabel With label |
||
1087 | * @return string String with URL |
||
1088 | */ |
||
1089 | public function getTypeMovement($withlabel = 0) |
||
1122 | } |
||
1123 | |||
1124 | /** |
||
1125 | * Return a link (with optionally the picto) |
||
1126 | * Use this->id,this->lastname, this->firstname |
||
1127 | * |
||
1128 | * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto) |
||
1129 | * @param string $option On what the link point to ('' = Tab of stock movement of warehouse, 'movements' = list of movements) |
||
1130 | * @param integer $notooltip 1=Disable tooltip |
||
1131 | * @param int $maxlen Max length of visible user name |
||
1132 | * @param string $morecss Add more css on link |
||
1133 | * @return string String with URL |
||
1134 | */ |
||
1135 | public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $maxlen = 24, $morecss = '') |
||
1175 | } |
||
1176 | |||
1177 | /** |
||
1178 | * Return label statut |
||
1179 | * |
||
1180 | * @param int $mode 0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto |
||
1181 | * @return string Label of status |
||
1182 | */ |
||
1183 | public function getLibStatut($mode = 0) |
||
1184 | { |
||
1185 | return $this->LibStatut($mode); |
||
1186 | } |
||
1187 | |||
1188 | // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps |
||
1189 | /** |
||
1190 | * Return the label of the status |
||
1191 | * |
||
1192 | * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto |
||
1193 | * @return string Label of status |
||
1194 | */ |
||
1195 | public function LibStatut($mode = 0) |
||
1196 | { |
||
1197 | // phpcs:enable |
||
1198 | global $langs; |
||
1199 | |||
1200 | if ($mode == 0 || $mode == 1) { |
||
1201 | return $langs->trans('StatusNotApplicable'); |
||
1202 | } elseif ($mode == 2) { |
||
1203 | return img_picto($langs->trans('StatusNotApplicable'), 'statut9') . ' ' . $langs->trans('StatusNotApplicable'); |
||
1204 | } elseif ($mode == 3) { |
||
1205 | return img_picto($langs->trans('StatusNotApplicable'), 'statut9'); |
||
1206 | } elseif ($mode == 4) { |
||
1207 | return img_picto($langs->trans('StatusNotApplicable'), 'statut9') . ' ' . $langs->trans('StatusNotApplicable'); |
||
1208 | } elseif ($mode == 5) { |
||
1209 | return $langs->trans('StatusNotApplicable') . ' ' . img_picto($langs->trans('StatusNotApplicable'), 'statut9'); |
||
1210 | } |
||
1211 | |||
1212 | return 'Bad value for mode'; |
||
1213 | } |
||
1214 | |||
1215 | /** |
||
1216 | * Create object on disk |
||
1217 | * |
||
1218 | * @param string $modele force le modele a utiliser ('' to not force) |
||
1219 | * @param Translate $outputlangs Object langs to use for output |
||
1220 | * @param int $hidedetails Hide details of lines |
||
1221 | * @param int $hidedesc Hide description |
||
1222 | * @param int $hideref Hide ref |
||
1223 | * @return int 0 if KO, 1 if OK |
||
1224 | */ |
||
1225 | public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) |
||
1245 | } |
||
1246 | |||
1247 | /** |
||
1248 | * Delete object in database |
||
1249 | * |
||
1250 | * @param User $user User that deletes |
||
1251 | * @param int $notrigger 0=launch triggers after, 1=disable triggers |
||
1252 | * @return int Return integer <0 if KO, >0 if OK |
||
1253 | */ |
||
1254 | public function delete(User $user, $notrigger = 0) |
||
1255 | { |
||
1256 | return $this->deleteCommon($user, $notrigger); |
||
1257 | //return $this->deleteCommon($user, $notrigger, 1); |
||
1258 | } |
||
1259 | |||
1260 | /** |
||
1261 | * Retrieve number of equipment for a product lot/serial |
||
1262 | * |
||
1263 | * @param int $fk_product Product id |
||
1264 | * @param string $batch batch number |
||
1265 | * @return int Return integer <0 if KO, number of equipment found if OK |
||
1266 | */ |
||
1267 | private function getBatchCount($fk_product, $batch) |
||
1291 | } |
||
1292 | |||
1293 | /** |
||
1294 | * reverse movement for object by updating infos |
||
1295 | * @return int 1 if OK,-1 if KO |
||
1296 | */ |
||
1297 | public function reverseMouvement() |
||
1298 | { |
||
1299 | $formattedDate = "REVERTMV" . dol_print_date($this->datem, '%Y%m%d%His'); |
||
1300 | if ($this->label == 'Annulation movement ID' . $this->id) { |
||
1320 | } |
||
1321 | } |
||
1322 | } |
||
1323 |
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.