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