PHP 8.2 Compatibility Improvement, better implementation.
[fa-stable.git] / includes / ui / allocation_cart.inc
1 <?php
2 /**********************************************************************
3     Copyright (C) FrontAccounting, LLC.
4         Released under the terms of the GNU General Public License, GPL, 
5         as published by the Free Software Foundation, either version 3 
6         of the License, or (at your option) any later version.
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
10     See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
12 /*
13         Class for supplier/customer payment/credit allocations edition
14         and related helpers.
15 */
16 //-----------------------------------------------------------------------------------
17
18 class allocation
19 {
20
21         var $trans_no; 
22         var $type;
23         var $person_id = '';
24         var $person_name = '';
25         var $person_type;       // PT_SUPPLIER/PT_CUSTOMER
26         var $person_curr;
27         var $date_;
28         var $amount = 0; /*Total amount of the transaction in FX */
29         var $bank_amount = 0;
30         var $currency;
31
32         var $allocs; /*array of transactions allocated to */
33
34         function __construct($type, $trans_no, $person_id = null, $person_type_id=null)
35         {
36                 $this->allocs = array();
37                 
38                 $this->trans_no = $trans_no;
39                 $this->type = $type;
40                 if ($person_id)
41                         $this->set_person($person_id, $person_type_id);
42
43                 $this->read($type, $trans_no, $person_id, $person_type_id); // read payment or credit
44         }
45
46         function set_person($person_id, $person_type)
47         {
48                 $this->person_id = $person_id;
49                 $this->person_type = $person_type;
50                 $this->person_curr = $person_type == PT_SUPPLIER ?
51                         get_supplier_currency($person_id) : get_customer_currency($person_id);
52                 return $this->person_curr;
53         }
54
55         function add_item($type, $type_no, $date_, $due_date, $amount, $amount_allocated, 
56                 $current_allocated, $ref)
57         {
58                 if (floatcmp($amount, 0))
59                 {
60                         $this->allocs[count($this->allocs)] = new allocation_item($type, $type_no, 
61                                 $date_, $due_date, $amount, $amount_allocated, $current_allocated, $ref);
62                         return true;
63                 } 
64                 else 
65                 {
66                         return false;
67                 }
68         }
69         
70         function update_item($index, $type, $type_no, $date_, $due_date, 
71                 $amount, $amount_allocated, $current_allocated, $ref)
72         {
73                 if (floatcmp($amount, 0))
74                 {
75                         $this->allocs[$index] = new allocation_item($type, $type_no, 
76                                 $date_, $due_date, $amount, $amount_allocated, $current_allocated, $ref);
77                         return true;
78                 } 
79                 else 
80                 {
81                         return false;
82                 }
83         }
84         
85         function add_or_update_item($type, $type_no, $date_, $due_date, 
86                 $amount, $amount_allocated, $current_allocated, $ref)
87         {
88                 for ($i = 0; $i < count($this->allocs); $i++) 
89                 {
90                         $item = $this->allocs[$i];
91                         if (($item->type == $type) && ($item->type_no == $type_no)) 
92                         {
93                                 return $this->update_item($i, $type, $type_no, $date_, $due_date, 
94                                         $amount, $amount_allocated, $current_allocated, $ref);
95                         }
96                 }
97         return $this->add_item($type, $type_no, $date_, $due_date, 
98                 $amount, $amount_allocated, $current_allocated, $ref);
99         }
100
101         //
102         //      Read payment or credit current/available allocations to cart.
103         //
104         // FIXME - read all transactions below twice seems to be suboptimal
105         //
106         function read($type = null, $trans_no = 0, $person_id=null, $person_type_id=null)
107         {
108                 if ($type !== null) {   // otherwise re-read
109                         $type = $this->type;
110                         $trans_no = $this->trans_no;
111
112                         if (isset($person_type_id))
113                         {
114                                 $this->person_type = $person_type_id;
115                                 $this->person_id = $person_id;
116                         } else { // guess person_type_id
117                                 if (in_array($type, array(ST_BANKPAYMENT, ST_BANKDEPOSIT)))
118                                 {
119                                         $bank_trans = db_fetch(get_bank_trans($type, $trans_no));
120                                         $this->person_type = $bank_trans['person_type_id'];
121                                 } else
122                                         $this->person_type = in_array($type, array(ST_SUPPCREDIT, ST_SUPPAYMENT)) ? PT_SUPPLIER : PT_CUSTOMER;
123                         }
124
125                         if ($trans_no) {
126                                 $trans = $this->person_type == PT_SUPPLIER ? get_supp_trans($trans_no, $type, $person_id)
127                                         : get_customer_trans($trans_no, $type, $person_id);
128
129                                 $this->person_id = $trans[$this->person_type == PT_SUPPLIER ? 'supplier_id':'debtor_no'];
130                                 $this->person_name = $trans[$this->person_type == PT_SUPPLIER ? "supplier_name":"DebtorName"];
131                                 $this->date_ = sql2date($trans["tran_date"]);
132                                 $this->person_curr = $trans['curr_code'];
133                                 $this->currency = isset($trans['bank_curr_code']) ? $trans['bank_curr_code'] : $trans['curr_code'];
134                                 $this->bank_amount = isset($trans["bank_amount"]) ? $trans["bank_amount"] : $trans["Total"]; // not set for journal entry
135                                 $this->amount = $trans["Total"];
136                         }
137                 }
138                 /* Now populate the array of possible (and previous actual) allocations 
139                 for this customer/supplier. First get the transactions that have 
140                 outstanding balances ie Total-alloc >0 */
141
142                 $this->allocs = array();
143                 if ($this->person_id)
144                 {
145                         if ($this->person_type==PT_SUPPLIER)
146                                 $trans_items = get_allocatable_to_supp_transactions($this->person_id);
147                         else
148                                 $trans_items = get_allocatable_to_cust_transactions($this->person_id);
149                         while ($myrow = db_fetch($trans_items))
150                         {
151                                 $this->add_item($myrow["type"], $myrow["trans_no"],
152                                         sql2date($myrow["tran_date"]),
153                                         sql2date($myrow["due_date"]),
154                                         $myrow["Total"], // trans total
155                                         $myrow["alloc"], // trans total allocated
156                                         0,
157                                         $myrow["reference"]); // this allocation
158                         }
159                 }
160                 if ($this->trans_no == 0) return; // this is new payment
161
162                 /* Now get trans that might have previously been allocated to by this trans
163                 NB existing entries where still some of the trans outstanding entered from
164                 above logic will be overwritten with the prev alloc detail below */
165
166                 if ($this->person_type==PT_SUPPLIER)
167                         $trans_items = get_allocatable_to_supp_transactions($this->person_id, 
168                                 $this->trans_no, $this->type);
169                 else
170                         $trans_items = get_allocatable_to_cust_transactions($this->person_id, 
171                                 $this->trans_no, $this->type);
172
173                 $amount = $this->amount;
174                 while ($myrow = db_fetch($trans_items))
175                 {
176                         $amount -= $myrow["Total"];
177                         $this->add_or_update_item ($myrow["type"], $myrow["trans_no"],
178                                 sql2date($myrow["tran_date"]),
179                                 sql2date($myrow["due_date"]),
180                                 $myrow["Total"],
181                                 $myrow["alloc"] - $myrow["amt"], $myrow["amt"], $myrow["reference"]);
182                 }
183
184                 /* Finally, if there any unallocated money remaining, assign the unallocated portion to
185                 the earliest transactions. This makes the javascript All/None keys consistent
186                 with the transaction amount. */
187
188                 if ($amount > 0) {
189                         foreach ($this->allocs as $alloc_item) {
190                                 $allocatable = $alloc_item->amount - $alloc_item->amount_allocated;
191                                 if ($allocatable > 0) {
192                                         if ($amount >= $allocatable) {
193                                                 $alloc_item->current_allocated = $allocatable;
194                                                 $amount -= $allocatable;
195                                         } else {
196                                                 $alloc_item->current_allocated += $amount;
197                                                 break;
198                                         }
199                                 }
200                         }
201                 }
202         }
203         //
204         //      Update allocations in database.
205         //
206         function write()
207         {
208                 global  $no_exchange_variations;
209
210                 begin_transaction();
211
212                 if ($this->person_type == PT_SUPPLIER)
213                         clear_supp_alloctions($this->type, $this->trans_no, $this->person_id, $this->date_);
214                 else
215                         clear_cust_alloctions($this->type, $this->trans_no, $this->person_id, $this->date_);
216
217                 // now add the new allocations
218                 $total_allocated = 0;
219                 $dec = user_price_dec();
220                 foreach ($this->allocs as $alloc_item)
221                 {
222                         if ($alloc_item->current_allocated > 0)
223                         {
224                                 $amount = round($alloc_item->current_allocated, $dec);
225
226                                 if ($this->person_type == PT_SUPPLIER) {
227                                         add_supp_allocation($amount,
228                                                 $this->type, $this->trans_no,
229                                         $alloc_item->type, $alloc_item->type_no, $this->person_id, $this->date_);
230
231                                         update_supp_trans_allocation($alloc_item->type, $alloc_item->type_no, $this->person_id);
232                                 } else {
233                                         add_cust_allocation($amount,
234                                                 $this->type, $this->trans_no,
235                                         $alloc_item->type, $alloc_item->type_no, $this->person_id, $this->date_);
236
237                                         update_debtor_trans_allocation($alloc_item->type, $alloc_item->type_no, $this->person_id);
238                                 }
239                                 // Exchange Variations Joe Hunt 2008-09-20 ////////////////////
240                                 if ($alloc_item->type != ST_SALESORDER && !@$no_exchange_variations
241                                         && $alloc_item->type != ST_PURCHORDER && $alloc_item->type != ST_JOURNAL && $this->type != ST_JOURNAL)
242                                         exchange_variation($this->type, $this->trans_no,
243                                                 $alloc_item->type, $alloc_item->type_no, $this->date_,
244                                                 $amount, $this->person_type);
245
246                                 //////////////////////////////////////////////////////////////
247                                 $total_allocated += $alloc_item->current_allocated;
248                         }
249
250                 }  /*end of the loop through the array of allocations made */
251                 if ($this->person_type == PT_SUPPLIER)
252                         update_supp_trans_allocation($this->type, $this->trans_no, $this->person_id);
253                 else
254                         update_debtor_trans_allocation($this->type,     $this->trans_no, $this->person_id);
255
256                 commit_transaction();
257
258         }
259
260
261
262 //-----------------------------------------------------------------------------------
263
264 class allocation_item 
265 {
266
267         var $type;
268         var $type_no;
269         
270         var $date_;
271         var $due_date;
272         
273         var $amount_allocated;
274         var $amount;
275         var $ref;
276         
277         var $current_allocated;
278         
279         function __construct($type, $type_no, $date_, $due_date, $amount, 
280                 $amount_allocated, $current_allocated, $ref)
281         {
282
283                 $this->type = $type;
284                 $this->type_no = $type_no;
285
286                 $this->ref = $ref;
287
288                 $this->date_ = $date_;
289                 $this->due_date = $due_date;
290                 
291                 $this->amount = $amount;
292                 $this->amount_allocated = $amount_allocated;
293                 $this->current_allocated = $current_allocated;
294         }
295 }
296
297 //--------------------------------------------------------------------------------
298
299 function show_allocatable($show_totals) {
300
301         global $systypes_array;
302         
303     $k = $total_allocated = 0;
304
305         $cart = $_SESSION['alloc'];
306         $supp_ref = in_array($cart->type, array(ST_SUPPCREDIT, ST_SUPPAYMENT, ST_BANKPAYMENT));
307
308         if (count($cart->allocs)) 
309         {
310                 display_heading(sprintf(_("Allocated amounts in %s:"), $cart->person_curr));
311                 start_table(TABLESTYLE, "width='60%'");
312                 $th = array(_("Transaction Type"), _("#"), $supp_ref ? _("Supplier Ref"): _("Ref"), _("Date"), _("Due Date"), _("Amount"),
313                         _("Other Allocations"), _("Left to Allocate"), _("This Allocation"),'','');
314
315                 table_header($th);
316
317                 foreach ($cart->allocs as $id => $alloc_item)
318                 {
319                     if (floatcmp(abs($alloc_item->amount), $alloc_item->amount_allocated))
320                     {
321                                 alt_table_row_color($k);
322                         label_cell($systypes_array[$alloc_item->type]);
323                                 label_cell(get_trans_view_str($alloc_item->type, $alloc_item->type_no), "nowrap align='right'");
324                                 label_cell($alloc_item->ref);
325                         label_cell($alloc_item->date_, "align=right");
326                         label_cell($alloc_item->due_date, "align=right");
327                         amount_cell(abs($alloc_item->amount));
328                                 amount_cell($alloc_item->amount_allocated);
329
330                         $_POST['amount' . $id] = price_format($alloc_item->current_allocated);
331
332                         $un_allocated = round((abs($alloc_item->amount) - $alloc_item->amount_allocated), 6);
333                         amount_cell($un_allocated, false,'', 'maxval'.$id);
334                         amount_cells(null, "amount" . $id);//, input_num('amount' . $id));
335                                 label_cell("<a href='javascript:void(0)' name=Alloc$id onclick='allocate_all(this.name.substr(5));return true;'>"
336                                          . _("All") . "</a>");
337                                 label_cell("<a href='javascript:void(0)' name=DeAll$id onclick='allocate_none(this.name.substr(5));return true;'>"
338                                          . _("None") . "</a>".hidden("un_allocated" . $id, 
339                                          price_format($un_allocated), false));
340                                 end_row();
341
342                         $total_allocated += input_num('amount' . $id);
343                         }
344                 }
345                 if ($show_totals) {
346                 label_row(_("Total Allocated"), price_format($total_allocated),
347                         "colspan=8 align=right", "align=right id='total_allocated'", 3);
348
349                         $amount = abs($cart->amount);
350
351                         if (floatcmp($amount, $total_allocated) < 0)
352                 {
353                         $font1 = "<font color=red>";
354                         $font2 = "</font>";
355             }
356                 else
357                         $font1 = $font2 = "";
358                         $left_to_allocate = price_format($amount - $total_allocated);
359                 label_row(_("Left to Allocate"), $font1 . $left_to_allocate . $font2, 
360                                 "colspan=8 align=right", "nowrap align=right id='left_to_allocate'",
361                                  3);
362                 }
363                 end_table(1);
364         }
365         hidden('TotalNumberOfAllocs', count($cart->allocs));
366 }
367 //--------------------------------------------------------------------------------
368
369 function check_allocations()
370 {
371         global $SysPrefs;
372
373         $total_allocated = 0;
374
375         for ($counter = 0; $counter < get_post("TotalNumberOfAllocs"); $counter++)
376         {
377                 if (!isset($_POST['amount'.$counter])) continue;
378                 if (!check_num('amount' . $counter, 0))
379                 {
380                         display_error(_("The entry for one or more amounts is invalid or negative."));
381                         set_focus('amount'.$counter);
382                         return false;
383                  }
384
385                   /* Now check to see that the AllocAmt is no greater than the
386                  amount left to be allocated against the transaction under review;
387                  skip check if no allocation is set to avoid deadlock on mistakenly overallocated transactions*/
388                  $allocated = input_num('amount' . $counter);
389                  if ($allocated && ($allocated > input_num('un_allocated' . $counter)))
390                  {
391                         display_error(_("At least one transaction is overallocated."));
392                         set_focus('amount'.$counter);
393                         return false;
394                  }
395
396                 if ($_SESSION['alloc']->person_type == PT_CUSTOMER) {
397                         if ($_SESSION['alloc']->allocs[$counter]->type == ST_SALESORDER)
398                                 $trans = get_sales_order_header($_SESSION['alloc']->allocs[$counter]->type_no, $_SESSION['alloc']->allocs[$counter]->type);
399                         else
400                                 $trans = get_customer_trans($_SESSION['alloc']->allocs[$counter]->type_no, $_SESSION['alloc']->allocs[$counter]->type,$_SESSION['alloc']->person_id);
401
402                         if ($trans['debtor_no'] != $_SESSION['alloc']->person_id) {
403                                 display_error(_("Allocated transaction allocated is not related to company selected."));
404                                 set_focus('amount'.$counter);
405                                 return false;
406                         }
407                 } elseif ($_SESSION['alloc']->person_type == PT_SUPPLIER) {
408                         if ($_SESSION['alloc']->allocs[$counter]->type == ST_PURCHORDER)
409                 $trans = get_po($_SESSION['alloc']->allocs[$counter]->type_no);
410             else
411                 $trans = get_supp_trans($_SESSION['alloc']->allocs[$counter]->type_no, $_SESSION['alloc']->allocs[$counter]->type,  $_SESSION['alloc']->person_id);                     
412                         if ($trans['supplier_id'] != $_SESSION['alloc']->person_id) {
413                                 display_error(_("Allocated transaction allocated is not related to company selected."));
414                                 set_focus('amount'.$counter);
415                                 return false;
416                         }
417                 }
418
419                 $_SESSION['alloc']->allocs[$counter]->current_allocated = input_num('amount' . $counter);
420
421                 $total_allocated += input_num('amount' . $counter);
422         }
423
424         $amount = abs($_SESSION['alloc']->amount);
425
426         if ($total_allocated - ($amount + input_num('discount'))  > $SysPrefs->allocation_settled_allowance())
427         {
428                 display_error(_("These allocations cannot be processed because the amount allocated is more than the total amount left to allocate."));
429                 return false;
430         }
431
432         return true;
433 }
434
435 //----------------------------------------------------------------------------------------
436 //
437 //      Returns sales or purchase invoice allocations to be reallocated after invoice edition.
438 //
439 function get_inv_allocations($trans_no, $trans_type, $person_id)
440 {
441         $allocs = array();
442         if ($trans_type == ST_SUPPINVOICE || $trans_type == ST_SUPPCREDIT)
443                 $result = get_allocatable_from_supp_transactions($person_id, $trans_no, $trans_type);
444         else
445                 $result = get_allocatable_from_cust_transactions($person_id, $trans_no, $trans_type);
446
447         while($dat = db_fetch($result))
448         {
449                 $allocs[] = array('type'=> $dat['type'], 'trans_no'=> $dat['trans_no'], 'amount'=>$dat['alloc']);
450         }
451         return $allocs;
452 }