Fixed typos…
[memberdb.git] / include / fees.php
1 <?php
2
3
4 /* HELPER functions FOR FEES {{{ */
5
6 // build an empty structure for holding monthly information
7 function _fees_build_month_array($start_date, $end_date, $preset = array()) {
8     $start_info = getdate($start_date);
9     $end_info   = getdate($end_date);
10
11     if ($start_info['year'] > $end_info['year']) return $preset;
12     if ($start_info['year'] == $end_info['year'] && $start_info['mon'] > $end_info['mon']) return $preset;
13
14     $ret = $preset;
15     for ($year = $start_info['year']; $year <= $end_info['year']; $year++) {
16         if (!isset($ret[$year])) $ret[$year] = array();
17         for (
18             $month  = (($year == $start_info['year']) ? $start_info['mon'] :  1);
19             $month <= (($year == $end_info['year']  ) ? $end_info['mon']   : 12);
20             $month++
21         ) {
22             if (isset($ret[$year][$month])) continue;
23             $ret[$year][$month] = array(
24                 'is_member'         => NULL,
25                 'member_type'       => NULL,
26                 'fee'               => NULL,
27                 'payment_interval'  => NULL
28             );
29         }
30     }
31     return $ret;
32 }
33
34 function _fees_apply_event_information(&$info, $events) {
35     if (!empty($events)) {
36
37         foreach ($events as $event) {
38
39             $timestamp = db_date2unixtime($event['event_date']);
40             if ($timestamp < FOUNDING_DATE) $timestamp = FOUNDING_DATE;
41             $date_info = getdate($timestamp);
42
43             if (!isset($info[$date_info['year']])) continue;
44             if (!isset($info[$date_info['year']][$date_info['mon']])) continue;
45
46             if ($event['fee']              !== NULL)      $info[$date_info['year']][$date_info['mon']]['fee']              = $event['fee'];
47             if ($event['member_type']      !== NULL)      $info[$date_info['year']][$date_info['mon']]['member_type']      = $event['member_type'];
48             if ($event['event_type']       !== 'changed') $info[$date_info['year']][$date_info['mon']]['is_member']        = ($event['event_type'] == 'joined' ? 1 : 0);
49             if ($event['payment_interval'] !== NULL)      $info[$date_info['year']][$date_info['mon']]['payment_interval'] = $event['payment_interval'];
50         }
51     }
52     
53     $fee              = 0;
54     $member_type      = NULL;
55     $is_member        = false;
56     $payment_interval = NULL;
57
58     foreach (array_keys($info) as $year) {
59         foreach (array_keys($info[$year]) as $month) {
60             if (!isset($info[$year][$month]['fee'])) $info[$year][$month]['fee'] = $fee;
61             else $fee = $info[$year][$month]['fee'];
62
63             if (!isset($info[$year][$month]['member_type'])) $info[$year][$month]['member_type'] = $member_type;
64             else $member_type = $info[$year][$month]['member_type'];
65
66             if (!isset($info[$year][$month]['is_member'])) $info[$year][$month]['is_member'] = $is_member;
67             else $is_member = $info[$year][$month]['is_member'];
68
69             if (!isset($info[$year][$month]['payment_interval'])) $info[$year][$month]['payment_interval'] = $payment_interval;
70             else $payment_interval = $info[$year][$month]['payment_interval'];
71         }
72     }
73     return;
74 }
75
76 function fees_get_list_for_member($member_id, $end_date) {
77
78     static $cache = array();
79
80     $end_date = mktime(0, 0, 0, date('m', $end_date) + 1, 0, date('Y', $end_date)); // last day of given month
81
82     if (isset($cache[$member_id][$end_date])) return $cache[$member_id][$end_date];
83     if (isset($cache[$member_id])) {
84         foreach (array_reverse(array_keys($cache[$member_id])) as $cache_date) {
85             if ($cache_date <= $end_date) {
86                 $ret = _fees_build_month_array($cache_date /* XXX einen Monat später wäre an dieser Stelle richtiger*/, $end_date, $cache[$member_id][$cache_date]);
87                 _fees_apply_event_information($ret, db_get_events_for_member($member_id, $cache_date, $end_date));
88                 $cache[$member_id][$end_date] = $ret;
89                 return $ret;
90             }
91         }
92     }
93
94     $ret = _fees_build_month_array(FOUNDING_DATE, $end_date);
95     if (empty($ret)) return;
96
97     _fees_apply_event_information($ret, db_get_events_for_member($member_id));
98
99     $cache[$member_id][$end_date] = $ret;
100     return $ret;
101 }
102
103 function fees_sum_for_member($member_id, $end_date) {
104     $membership_info = fees_get_list_for_member($member_id, $end_date);
105     
106     $total = '0';
107
108     foreach ($membership_info as $year => $months) {
109         foreach ($months as $month => $info) {
110             if ($info['is_member']) $total = bcadd($total, $info['fee']);
111         }
112     }
113     return $total;
114 }
115
116 function fees_for_member_at_date($member_id, $end_date) {
117     $membership_info = fees_get_list_for_member($member_id, $end_date);
118     
119     $this_year = array_pop($membership_info);
120     $this_month = array_pop($this_year);
121     if ($this_month['is_member']) return $this_month['fee'];
122     return NULL;
123 }
124
125 function fees_info_for_member($member_id, $end_date) {
126     $membership_info = fees_get_list_for_member($member_id, $end_date);
127     
128     $this_year = array_pop($membership_info);
129     return array_pop($this_year);
130 }
131
132 function fees_sum_by_month($end_date) {
133     $members = db_get_members();
134     $fees = array();
135     if (empty($members)) return array();
136     foreach ($members as $member) {
137         $membership_info = fees_get_list_for_member($member['id'], $end_date);
138         foreach ($membership_info as $year => $months) {
139             foreach ($months as $month => $info) {
140                 if (!isset($fees[$year][$month])) $fees[$year][$month] = '0';
141                 if ($info['is_member']) $fees[$year][$month] = bcadd($fees[$year][$month], $info['fee']);
142             }
143         }
144     }
145     return $fees;
146 }
147 function fees_get_list_for_month($year, $month) {
148     $members = db_get_members();
149     $fees = array();
150     foreach ($members as $member) {
151         $membership_info = fees_get_list_for_member($member['id'], mktime(0, 0, 0, $month, 1, $year));
152
153         if (empty($membership_info)) continue;
154         $member['fee']       = $membership_info[$year][$month]['fee'];
155         $member['is_member'] = $membership_info[$year][$month]['is_member'];
156         $fees[] = $member;
157     }
158     return $fees;
159 }
160
161 function fee_next_directdebit_for_member($member_id, $max_date = NULL) {
162
163     $member = db_get_member_with_id($member_id);
164     if (!$member['directdebit']) return NULL;
165
166     $sum_old_fees   = fees_sum_for_member($member_id, DIRECTDEBIT_DATE - 86400);
167     $sum_new_paid   = finance_get_paid_fees_for_member($member_id);
168     $year   = date('Y', DIRECTDEBIT_DATE);
169     $month  = date('n', DIRECTDEBIT_DATE);
170     $day    = DIRECTDEBIT_DAY_OF_MONTH;
171    
172     $alldirectdebits = array();
173
174     while (true) {
175         $start_date = mktime(0, 0, 0, $month, $day, $year);
176         if (isset($max_date) && $start_date > $max_date) return $alldirectdebits;
177
178         // check if fee is zero at the moment and skip to next event
179         // quit searching if theres no event in future
180         $current_fee = fees_for_member_at_date($member_id, $start_date);
181         if (empty($current_fee)) {
182             $events = db_get_events_for_member($member_id, $start_date + 86400);
183             if (empty($events)) return NULL;
184             $start_date = db_date2unixtime($events[0]['event_date']);
185             $day   = date('j', $start_date);
186             $month = date('n', $start_date);
187             $year  = date('Y', $start_date);
188             continue;
189         }
190
191         $sum_fees       = fees_sum_for_member($member_id, $start_date);
192         $sum_new_fees   = bcsub($sum_fees, $sum_old_fees);
193         if (bccomp($sum_new_fees, $sum_new_paid) == 1) {
194             $info = fees_get_list_for_member($member_id, $start_date);
195             $months = 1;
196             $ret = array(
197                 'date'  => $start_date,
198                 'value' => bcsub($sum_new_fees, $sum_new_paid),
199                 'info'  => '',
200             );
201             switch ($info[$year][$month]['payment_interval']) {
202                 case 'monthly'   : $months = 1;  break;
203                 case 'quarterly' : $months = 3;  break;
204                 case 'halfyearly': $months = 6;  break;
205                 case 'yearly'    : $months = 12; break;
206             }
207             if ($months == 1) {
208                 $ret['info'] = dtaus_string(sprintf('CCCFFM %d, %s', $member['number'], format_month($start_date))); 
209             } else {
210                 $end_date = mktime(0, 0, 0, $month + $months - 1, DIRECTDEBIT_DAY_OF_MONTH, $year);
211                 $sum_fee_end = fees_sum_for_member($member_id, $end_date);
212                 $ret['value'] = bcadd($ret['value'], bcsub($sum_fee_end, $sum_fees));
213                 $ret['info'] = dtaus_string(sprintf('CCCFFM %d, %s-%s', $member['number'], format_month($start_date), format_month($end_date)));
214             }
215             $sumprepareddebits=0;
216             foreach ($alldirectdebits as $debit) { $sumprepareddebits += $debit['value']; }
217             $ret['value'] = bcsub($ret['value'], $sumprepareddebits);
218             $alldirectdebits[] = $ret;
219         }
220         $day = DIRECTDEBIT_DAY_OF_MONTH;
221         $month++;
222         if ($month == 13) { $month = 1; $year++; }
223     }
224     
225     
226 }
227 /* }}} */
228
229
230 function action_fees() {/*{{{*/
231
232     if (isset($_REQUEST['member_id'])) {
233         render_fees_for_member($_REQUEST['member_id']);
234         return;
235     }
236     if (isset($_REQUEST['year']) && isset($_REQUEST['month'])) {
237         render_accrued_fees_for_month($_REQUEST['year'], $_REQUEST['month']);
238         return;
239     }
240
241     render_fees_by_member();
242     render_accrued_fees_by_month();
243     render_next_direct_debit();
244     render_future_fees();
245
246 }/*}}}*/
247
248 function render_fees_by_member() {/*{{{*/
249     $members = db_get_members();
250 ?>
251 <h2>Mitgliedsbeitr&auml;ge nach Mitglied</h2>
252 <table>
253     <tr>
254         <th>Mitgliedsnummer</th>
255         <th>Nickname</th>
256         <th style="text-align: right;">Angefallene Beitr&auml;ge</th>
257         <th style="text-align: right;">Aktueller Beitrag</th>
258         <th style="text-align: right;">Offener Beitrag</th>
259     </tr>
260 <?php if (empty($members)) $members = array(); ?>
261 <?php foreach ($members as $member) : ?>
262 <?php
263     $current_fee  = fees_for_member_at_date($member['id'], time());
264
265     $sum_fees     = fees_sum_for_member($member['id'], time());
266     $sum_old_fees = fees_sum_for_member($member['id'], DIRECTDEBIT_DATE - 86400);
267     $sum_old_paid = finance_get_paid_fees_for_member($member['id'], true);
268     $sum_new_paid = finance_get_paid_fees_for_member($member['id']);
269     $sum_new_fees = bcsub($sum_fees, $sum_old_fees);
270     $open_fees    = bcadd(bcsub($sum_old_fees, $sum_old_paid), max(bcsub($sum_new_fees, $sum_new_paid), 0));
271 ?>
272     <tr>
273         <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
274         <td><?=html_escape($member['nickname'])?></td>
275         <td style="text-align: right;"><?=format_money($sum_fees)?></td>
276         <td style="text-align: right;"><?=isset($current_fee) ? format_money($current_fee) : '-' ?></td>
277         <td style="text-align: right;"><?=$open_fees > 0 ? format_money($open_fees) : '-' ?></td>
278     </tr>
279 <?php endforeach ?>
280 </table>
281 <?php
282 }/*}}}*/
283
284 function render_future_fees() {/*{{{*/
285     $total_paid = finance_get_total_paid_fees();
286     $this_year = date('Y');
287     $this_month = date('m');
288     $fees = fees_sum_by_month(mktime(0, 0, 0, date('m') + 6,   date('d'),   date('Y')+1));
289     $total = 0;
290     foreach ($fees as $year => $months) {
291         foreach ($months as $month => $fee) {
292             $total = bcadd($total, $fee);
293             $fees[$year][$month] = array('total' => $total, 'fee' => $fee);
294         }
295     }
296     $fees = array_reverse($fees, true);
297 ?>
298 <h2>Beitragsprognose nach Monat</h2>
299 <table>
300     <tr>
301         <th>Monat</th>
302         <th style="text-align: right;">Mitgliedsbeitr&auml;ge</th>
303         <th style="text-align: right;">kummuliert</th>
304         <th style="text-align: right;">eingenommen</th>
305         <th style="text-align: right;"><strong>offen</strong></th>
306     </tr>
307 <?php foreach ($fees as $year => $months) : ?>
308     <?php $months = array_reverse($months, true); ?>
309     <?php foreach ($months as $month => $data) : ?>
310     <tr<?php if ($year == $this_year && $month == $this_month) : ?> class="current"<?php endif?>>
311         <td><a href="<?=html_escape(link_to('fees', array('year' => $year, 'month'=> $month)))?>"><?=html_escape(format_month($year, $month))?></a></td>
312         <td style="text-align: right;"><?=html_escape(format_money($data['fee']))?></td>
313         <td style="text-align: right;"><?=html_escape(format_money($data['total']))?></td>
314         <td style="text-align: right;">
315         <?php if ($year == $this_year && $month == $this_month) : ?>
316             <?=html_escape(format_money($total_paid))?>
317         <?php endif ?>
318         </td>
319         <td style="text-align: right;">
320         <?php if ($year > $this_year || ($year >= $this_year && $month >= $this_month)) : ?>
321             <?=html_escape(format_money(bcsub($data['total'], $total_paid)))?>
322         <?php endif ?>
323         </td>
324     </tr>
325     <?php endforeach ?>
326 <?php endforeach ?>
327 </table>
328 <?php
329 }/*}}}*/
330
331 function render_accrued_fees_by_month() {/*{{{*/
332     $fees = fees_sum_by_month(time());
333     $fees = array_reverse($fees, true);
334 ?>
335 <h2>Angefallene Mitgliedsbeitr&auml;ge nach Monat</h2>
336 <table>
337     <tr>
338         <th>Monat</th>
339         <th style="text-align: right;">Mitgliedsbeitrag</th>
340     </tr>
341 <?php foreach ($fees as $year => $months) : ?>
342     <?php $months = array_reverse($months, true); ?>
343     <?php foreach ($months as $month => $fee) : ?>
344     <tr>
345         <td><a href="<?=html_escape(link_to('fees', array('year' => $year, 'month'=> $month)))?>"><?=html_escape(format_month($year, $month))?></a></td>
346         <td style="text-align: right;"><?=html_escape(format_money($fee))?></td>
347     </tr>
348     <?php endforeach ?>
349 <?php endforeach ?>
350 </table>
351 <?php
352 }/*}}}*/
353
354 function render_accrued_fees_for_month($year, $month) {/*{{{*/
355     $fees = fees_get_list_for_month($year, $month);
356 ?>
357 <h2>Angefallene Mitgliedsbeitr&auml;ge f&uuml;r <?=format_month($year, $month)?></h2>
358 <table>
359     <tr>
360         <th>Mitgliedsnummer</th>
361         <th>Nickname</th>
362         <th style="text-align: right;">Mitgliedsbeitrag</th>
363     </tr>
364 <?php foreach ($fees as $info) : ?>
365     <tr>
366         <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $info['id'])))?>"><?=html_escape($info['number'])?></a></td>
367         <td><?=html_escape($info['nickname'])?></td>
368         <td style="text-align: right;"><?=html_escape($info['is_member'] ? format_money($info['fee']) : '-')?></td>
369     </tr>
370 <?php endforeach ?>
371 </table>
372 <p><a href="<?=html_escape(link_to('fees'))?>">Alle angefallenen Mitgliedsbeitr&auml;ge</a></p>
373 <?php
374 }/*}}}*/
375
376 function render_next_direct_debit() {/*{{{*/
377     $members = db_get_members();
378 ?>
379 <h2>Nächste Abbuchungen nach Mitglied</h2>
380 <table>
381     <tr>
382         <th>Mitgliedsnummer</th>
383         <th>Nickname</th>
384         <th style="text-align: right;">Verwendungszweck</th>
385         <th style="text-align: right;">Betrag</th>
386     </tr>
387 <?php if (empty($members)) $members = array(); ?>
388 <?php foreach ($members as $member) : ?>
389     <?php $next_debit = fee_next_directdebit_for_member($member['id']); ?>
390     <?php
391         if (count($next_debit) > 1) {
392             $sum=0;
393             foreach ($d as $next_debit) {
394                 $sum += $d['value'];
395             }
396             $next_debit['info'] = $sum;
397             $next_debit['info'] = '[mehrere]';
398         }
399     ?>
400     <tr>
401         <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
402         <td><?=html_escape($member['nickname'])?></td>
403 <?php if (empty($next_debit)) : ?>
404         <td>-</td>
405         <td style="text-align: right;">-</td>
406 <?php else : ?>
407         <td><?=html_escape($next_debit['info'])?></td>
408         <td style="text-align: right;"><?=format_money($next_debit['value'])?></td>
409 <?php endif ?>
410     </tr>
411 <?php endforeach ?>
412 </table>
413 <?php
414 }
415
416 function render_fees_for_member($member_id) {/*{{{*/
417     global $MEMBER_TYPES, $EARNING_TYPES, $EXPENSE_TYPES;
418
419     $member      = db_get_member_with_id($member_id);
420     if (!isset($member)) redirect(link_to('fees'));
421
422     $membership_info = fees_get_list_for_member($member_id, time());
423     $membership_info = array_reverse($membership_info, true);
424
425     $paid_fees = finance_list_paid_fees_for_member($member_id, time(), true);
426
427     $sum_new_paid = finance_get_paid_fees_for_member($member_id);
428     $sum_old_paid = finance_get_paid_fees_for_member($member_id, true);
429     $sum_old_fees = fees_sum_for_member($member_id, DIRECTDEBIT_DATE - 86400);
430     $sum_fees     = fees_sum_for_member($member_id, time());
431     $sum_new_fees = bcsub($sum_fees, $sum_old_fees);
432
433     $state = '';
434     $new_open = 0;
435     $old_open = 0;
436     if (bccomp($sum_new_fees, $sum_new_paid) == 1) $new_open = 1;
437     if (bccomp($sum_old_fees, $sum_old_paid) == 1) $old_open = 1;
438
439     if ($new_open && $old_open) {
440         $state = sprintf('Es sind noch %1$s Mitgliedsbeitrag offen, davon %2$s für die Zeit vor dem %3$s und %4$s für danach.',
441             format_money(bcadd(bcsub($sum_old_fees, $sum_old_paid), bcsub($sum_new_fees, $sum_new_paid))),
442             format_money(bcsub($sum_old_fees, $sum_old_paid)),
443             format_date(DIRECTDEBIT_DATE),
444             format_money(bcsub($sum_new_fees, $sum_new_paid))
445         );
446     }
447     elseif($new_open) {
448         $state = sprintf('Es sind noch %1$s Mitgliedsbeitrag offen.', format_money(bcsub($sum_new_fees, $sum_new_paid)));
449     }
450     elseif($old_open) {
451         $state = sprintf('Für die Zeit vor dem %1$s sind noch %2$s Mitgliedsbeitrag offen.', format_date(DIRECTDEBIT_DATE), format_money(bcsub($sum_old_fees, $sum_old_paid)));
452     }
453
454     $next_debit = fee_next_directdebit_for_member($member_id);
455
456 ?>
457 <h2>Mitgliedsbeitr&auml;ge von <?=html_escape(!empty($member['nickname']) ? $member['nickname'] : sprintf('Mitglied Nr. %d', $member['number']))?></h2>
458 <h3>Mitgliedsdetails</h3>
459 <table>
460     <tr>
461         <th>Mitgliedsnummer</th>
462         <th>Nickname</th>
463         <th>Status</th>
464     </tr>
465     <tr>
466         <td><a href="<?=html_escape(link_to('view_member', array('id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
467         <td><?=html_escape($member['nickname'])?></strong></p></td>
468         <td>
469         <?php if (empty($state)) : ?>
470             Kein Beitragsrückstand
471         <?php else : ?>
472             <?=wordwrap(html_escape($state), 70, '<br/>')?>
473         <?php endif ?>
474 </td>
475     </tr>
476 </table>
477 <div style="float: left">
478 <h3>Angefallene Mitgliedsbeitr&auml;ge</h3>
479 <table>
480     <tr>
481         <th>Monat</th>
482         <th>Mitgliedsart</th>
483         <th style="text-align: right;">Mitgliedsbeitrag</th>
484     </tr>
485 <?php foreach ($membership_info as $year => $months) : ?>
486     <?php $months = array_reverse($months, true); ?>
487     <?php foreach ($months as $month => $info) : ?>
488     <tr>
489         <td><?=html_escape(format_month($year, $month))?></td>
490         <td><?=html_escape($info['is_member'] ? $MEMBER_TYPES[$info['member_type']] : 'Kein Mitglied')?></td>
491         <td style="text-align: right;"><?=html_escape($info['is_member'] ? format_money($info['fee']) : '-')?></td>
492     </tr>
493     <?php endforeach ?>
494 <?php endforeach ?>
495 </table>
496 <p><a href="<?=html_escape(link_to('fees'))?>">Alle angefallenen Mitgliedsbeitr&auml;ge</a></p>
497 </div>
498 <div style="float: left; margin-left: 1em;">
499 <h3>Nächste Abbuchung</h3>
500 <table>
501     <tr>
502         <th>Verwendungszweck</th>
503         <th style="text-align: right;">Betrag</th>
504     </tr>
505 <?php if (empty($next_debit)) : ?>
506         <td>-</td>
507         <td style="text-align: right;">-</td>
508 <?php else : ?>
509 <?php
510     if (count($next_debit) > 1) {
511         $sum=0;
512         foreach ($d as $next_debit) {
513             $sum += $d['value'];
514         }
515         $next_debit['info'] = $sum;
516         $next_debit['info'] = '[mehrere]';
517     }
518 ?>
519         <td><?=html_escape($next_debit['info'])?></td>
520         <td style="text-align: right;"><?=format_money($next_debit['value'])?></td>
521 <?php endif ?> 
522 </table>
523 <h3>Bezahlte Mitgliedsbeitr&auml;ge</h3>
524 <table>
525     <tr>
526         <th>Monat</th>
527         <th style="text-align: right;">Typ</th>
528         <th style="text-align: right;">Betrag</th>
529     </tr>
530 <?php foreach ($paid_fees as $payment) : ?>
531     <tr>
532         <td><?=html_escape(format_date(db_date2unixtime($payment['date'])))?></td>
533         <td><?=($payment['value'] < 0) ? $EXPENSE_TYPES[$payment['type']]: $EARNING_TYPES[$payment['type']]?></td>
534         <td style="text-align: right;"><?=format_money($payment['value'])?></td>
535     </tr>
536 <?php endforeach ?>
537 </table>
538 </div>
539 <br style="clear: left;"/>
540 <?php
541 }/*}}}*/
542
543