4 /* HELPER functions FOR FEES {{{ */
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);
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;
15 for ($year = $start_info['year']; $year <= $end_info['year']; $year++) {
16 if (!isset($ret[$year])) $ret[$year] = array();
18 $month = (($year == $start_info['year']) ? $start_info['mon'] : 1);
19 $month <= (($year == $end_info['year'] ) ? $end_info['mon'] : 12);
22 if (isset($ret[$year][$month])) continue;
23 $ret[$year][$month] = array(
25 'member_type' => NULL,
27 'payment_interval' => NULL
34 function _fees_apply_event_information(&$info, $events) {
35 if (!empty($events)) {
37 foreach ($events as $event) {
39 $timestamp = db_date2unixtime($event['event_date']);
40 if ($timestamp < FOUNDING_DATE) $timestamp = FOUNDING_DATE;
41 $date_info = getdate($timestamp);
43 if (!isset($info[$date_info['year']])) continue;
44 if (!isset($info[$date_info['year']][$date_info['mon']])) continue;
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'];
56 $payment_interval = NULL;
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'];
63 if (!isset($info[$year][$month]['member_type'])) $info[$year][$month]['member_type'] = $member_type;
64 else $member_type = $info[$year][$month]['member_type'];
66 if (!isset($info[$year][$month]['is_member'])) $info[$year][$month]['is_member'] = $is_member;
67 else $is_member = $info[$year][$month]['is_member'];
69 if (!isset($info[$year][$month]['payment_interval'])) $info[$year][$month]['payment_interval'] = $payment_interval;
70 else $payment_interval = $info[$year][$month]['payment_interval'];
76 function fees_get_list_for_member($member_id, $end_date) {
78 static $cache = array();
80 $end_date = mktime(0, 0, 0, date('m', $end_date) + 1, 0, date('Y', $end_date)); // last day of given month
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;
94 $ret = _fees_build_month_array(FOUNDING_DATE, $end_date);
95 if (empty($ret)) return;
97 _fees_apply_event_information($ret, db_get_events_for_member($member_id));
99 $cache[$member_id][$end_date] = $ret;
103 function fees_sum_for_member($member_id, $end_date) {
104 $membership_info = fees_get_list_for_member($member_id, $end_date);
108 foreach ($membership_info as $year => $months) {
109 foreach ($months as $month => $info) {
110 if ($info['is_member']) $total = bcadd($total, $info['fee']);
116 function fees_for_member_at_date($member_id, $end_date) {
117 $membership_info = fees_get_list_for_member($member_id, $end_date);
119 $this_year = array_pop($membership_info);
120 $this_month = array_pop($this_year);
121 if ($this_month['is_member']) return $this_month['fee'];
125 function fees_info_for_member($member_id, $end_date) {
126 $membership_info = fees_get_list_for_member($member_id, $end_date);
128 $this_year = array_pop($membership_info);
129 return array_pop($this_year);
132 function fees_sum_by_month($end_date) {
133 $members = db_get_members();
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']);
147 function fees_get_list_for_month($year, $month) {
148 $members = db_get_members();
150 foreach ($members as $member) {
151 $membership_info = fees_get_list_for_member($member['id'], mktime(0, 0, 0, $month, 1, $year));
153 if (empty($membership_info)) continue;
154 $member['fee'] = $membership_info[$year][$month]['fee'];
155 $member['is_member'] = $membership_info[$year][$month]['is_member'];
161 function fee_next_directdebit_for_member($member_id, $max_date = NULL) {
163 $member = db_get_member_with_id($member_id);
164 if (!$member['directdebit']) return NULL;
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);
175 $start_date = mktime(0, 0, 0, $month, $day, $year);
176 if (isset($max_date) && $start_date > $max_date) return NULL;
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);
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);
197 'date' => $start_date,
198 'value' => bcsub($sum_new_fees, $sum_new_paid),
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;
208 $ret['info'] = dtaus_string(sprintf('CCCFFM %d, %s', $member['number'], format_month($start_date)));
211 $end_date = mktime(0, 0, 0, $month + $months - 1, 1, $year);
212 $sum_fee_end = fees_sum_for_member($member_id, $end_date);
213 $ret['value'] = bcadd($ret['value'], bcsub($sum_fee_end, $sum_fees));
214 $ret['info'] = dtaus_string(sprintf('CCCFFM %d, %s-%s', $member['number'], format_month($start_date), format_month($end_date)));
219 if ($month == 13) { $month = 1; $year++; }
227 function action_fees() {/*{{{*/
229 if (isset($_REQUEST['member_id'])) {
230 render_fees_for_member($_REQUEST['member_id']);
233 if (isset($_REQUEST['year']) && isset($_REQUEST['month'])) {
234 render_accrued_fees_for_month($_REQUEST['year'], $_REQUEST['month']);
238 render_fees_by_member();
239 render_accrued_fees_by_month();
240 render_next_direct_debit();
241 render_future_fees();
245 function render_fees_by_member() {/*{{{*/
246 $members = db_get_members();
248 <h2>Mitgliedsbeiträge nach Mitglied</h2>
251 <th>Mitgliedsnummer</th>
253 <th style="text-align: right;">Angefallene Beiträge</th>
254 <th style="text-align: right;">Aktueller Beitrag</th>
255 <th style="text-align: right;">Offener Beitrag</th>
257 <?php if (empty($members)) $members = array(); ?>
258 <?php foreach ($members as $member) : ?>
260 $current_fee = fees_for_member_at_date($member['id'], time());
262 $sum_fees = fees_sum_for_member($member['id'], time());
263 $sum_old_fees = fees_sum_for_member($member['id'], DIRECTDEBIT_DATE - 86400);
264 $sum_old_paid = finance_get_paid_fees_for_member($member['id'], true);
265 $sum_new_paid = finance_get_paid_fees_for_member($member['id']);
266 $sum_new_fees = bcsub($sum_fees, $sum_old_fees);
267 $open_fees = bcadd(bcsub($sum_old_fees, $sum_old_paid), max(bcsub($sum_new_fees, $sum_new_paid), 0));
270 <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
271 <td><?=html_escape($member['nickname'])?></td>
272 <td style="text-align: right;"><?=format_money($sum_fees)?></td>
273 <td style="text-align: right;"><?=isset($current_fee) ? format_money($current_fee) : '-' ?></td>
274 <td style="text-align: right;"><?=$open_fees > 0 ? format_money($open_fees) : '-' ?></td>
281 function render_future_fees() {/*{{{*/
282 $total_paid = finance_get_total_paid_fees();
283 $this_year = date('Y');
284 $this_month = date('m');
285 $fees = fees_sum_by_month(mktime(0, 0, 0, date('m') + 6, date('d'), date('Y')+1));
287 foreach ($fees as $year => $months) {
288 foreach ($months as $month => $fee) {
289 $total = bcadd($total, $fee);
290 $fees[$year][$month] = array('total' => $total, 'fee' => $fee);
293 $fees = array_reverse($fees, true);
295 <h2>Beitragsprognose nach Monat</h2>
299 <th style="text-align: right;">Mitgliedsbeiträge</th>
300 <th style="text-align: right;">kummuliert</th>
301 <th style="text-align: right;">eingenommen</th>
302 <th style="text-align: right;"><strong>offen</strong></th>
304 <?php foreach ($fees as $year => $months) : ?>
305 <?php $months = array_reverse($months, true); ?>
306 <?php foreach ($months as $month => $data) : ?>
307 <tr<?php if ($year == $this_year && $month == $this_month) : ?> class="current"<?php endif?>>
308 <td><a href="<?=html_escape(link_to('fees', array('year' => $year, 'month'=> $month)))?>"><?=html_escape(format_month($year, $month))?></a></td>
309 <td style="text-align: right;"><?=html_escape(format_money($data['fee']))?></td>
310 <td style="text-align: right;"><?=html_escape(format_money($data['total']))?></td>
311 <td style="text-align: right;">
312 <?php if ($year == $this_year && $month == $this_month) : ?>
313 <?=html_escape(format_money($total_paid))?>
316 <td style="text-align: right;">
317 <?php if ($year > $this_year || ($year >= $this_year && $month >= $this_month)) : ?>
318 <?=html_escape(format_money(bcsub($data['total'], $total_paid)))?>
328 function render_accrued_fees_by_month() {/*{{{*/
329 $fees = fees_sum_by_month(time());
330 $fees = array_reverse($fees, true);
332 <h2>Angefallene Mitgliedsbeiträge nach Monat</h2>
336 <th style="text-align: right;">Mitgliedsbeitrag</th>
338 <?php foreach ($fees as $year => $months) : ?>
339 <?php $months = array_reverse($months, true); ?>
340 <?php foreach ($months as $month => $fee) : ?>
342 <td><a href="<?=html_escape(link_to('fees', array('year' => $year, 'month'=> $month)))?>"><?=html_escape(format_month($year, $month))?></a></td>
343 <td style="text-align: right;"><?=html_escape(format_money($fee))?></td>
351 function render_accrued_fees_for_month($year, $month) {/*{{{*/
352 $fees = fees_get_list_for_month($year, $month);
354 <h2>Angefallene Mitgliedsbeiträge für <?=format_month($year, $month)?></h2>
357 <th>Mitgliedsnummer</th>
359 <th style="text-align: right;">Mitgliedsbeitrag</th>
361 <?php foreach ($fees as $info) : ?>
363 <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $info['id'])))?>"><?=html_escape($info['number'])?></a></td>
364 <td><?=html_escape($info['nickname'])?></td>
365 <td style="text-align: right;"><?=html_escape($info['is_member'] ? format_money($info['fee']) : '-')?></td>
369 <p><a href="<?=html_escape(link_to('fees'))?>">Alle angefallenen Mitgliedsbeiträge</a></p>
373 function render_next_direct_debit() {/*{{{*/
374 $members = db_get_members();
376 <h2>Nächste Abbuchungen nach Mitglied</h2>
379 <th>Mitgliedsnummer</th>
381 <th style="text-align: right;">Verwendungszweck</th>
382 <th style="text-align: right;">Betrag</th>
384 <?php if (empty($members)) $members = array(); ?>
385 <?php foreach ($members as $member) : ?>
386 <?php $next_debit = fee_next_directdebit_for_member($member['id']); ?>
388 <td><a href="<?=html_escape(link_to('fees', array('member_id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
389 <td><?=html_escape($member['nickname'])?></td>
390 <?php if (empty($next_debit)) : ?>
392 <td style="text-align: right;">-</td>
394 <td><?=html_escape($next_debit['info'])?></td>
395 <td style="text-align: right;"><?=format_money($next_debit['value'])?></td>
403 function render_fees_for_member($member_id) {/*{{{*/
404 global $MEMBER_TYPES, $EARNING_TYPES, $EXPENSE_TYPES;
406 $member = db_get_member_with_id($member_id);
407 if (!isset($member)) redirect(link_to('fees'));
409 $membership_info = fees_get_list_for_member($member_id, time());
410 $membership_info = array_reverse($membership_info, true);
412 $paid_fees = finance_list_paid_fees_for_member($member_id, time(), true);
414 $sum_new_paid = finance_get_paid_fees_for_member($member_id);
415 $sum_old_paid = finance_get_paid_fees_for_member($member_id, true);
416 $sum_old_fees = fees_sum_for_member($member_id, DIRECTDEBIT_DATE - 86400);
417 $sum_fees = fees_sum_for_member($member_id, time());
418 $sum_new_fees = bcsub($sum_fees, $sum_old_fees);
423 if (bccomp($sum_new_fees, $sum_new_paid) == 1) $new_open = 1;
424 if (bccomp($sum_old_fees, $sum_old_paid) == 1) $old_open = 1;
426 if ($new_open && $old_open) {
427 $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.',
428 format_money(bcadd(bcsub($sum_old_fees, $sum_old_paid), bcsub($sum_new_fees, $sum_new_paid))),
429 format_money(bcsub($sum_old_fees, $sum_old_paid)),
430 format_date(DIRECTDEBIT_DATE),
431 format_money(bcsub($sum_new_fees, $sum_new_paid))
435 $state = sprintf('Es sind noch %1$s Mitgliedsbeitrag offen.', format_money(bcsub($sum_new_fees, $sum_new_paid)));
438 $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)));
441 $next_debit = fee_next_directdebit_for_member($member_id);
444 <h2>Mitgliedsbeiträge von <?=html_escape(!empty($member['nickname']) ? $member['nickname'] : sprintf('Mitglied Nr. %d', $member['number']))?></h2>
445 <h3>Mitgliedsdetails</h3>
448 <th>Mitgliedsnummer</th>
453 <td><a href="<?=html_escape(link_to('view_member', array('id'=> $member['id'])))?>"><?=html_escape($member['number'])?></a></td>
454 <td><?=html_escape($member['nickname'])?></strong></p></td>
456 <?php if (empty($state)) : ?>
457 Kein Beitragsrückstand
459 <?=wordwrap(html_escape($state), 70, '<br/>')?>
464 <div style="float: left">
465 <h3>Angefallene Mitgliedsbeiträge</h3>
469 <th>Mitgliedsart</th>
470 <th style="text-align: right;">Mitgliedsbeitrag</th>
472 <?php foreach ($membership_info as $year => $months) : ?>
473 <?php $months = array_reverse($months, true); ?>
474 <?php foreach ($months as $month => $info) : ?>
476 <td><?=html_escape(format_month($year, $month))?></td>
477 <td><?=html_escape($info['is_member'] ? $MEMBER_TYPES[$info['member_type']] : 'Kein Mitglied')?></td>
478 <td style="text-align: right;"><?=html_escape($info['is_member'] ? format_money($info['fee']) : '-')?></td>
483 <p><a href="<?=html_escape(link_to('fees'))?>">Alle angefallenen Mitgliedsbeiträge</a></p>
485 <div style="float: left; margin-left: 1em;">
486 <h3>Nächste Abbuchung</h3>
489 <th>Verwendungszweck</th>
490 <th style="text-align: right;">Betrag</th>
492 <?php if (empty($next_debit)) : ?>
494 <td style="text-align: right;">-</td>
496 <td><?=html_escape($next_debit['info'])?></td>
497 <td style="text-align: right;"><?=format_money($next_debit['value'])?></td>
500 <h3>Bezahlte Mitgliedsbeiträge</h3>
504 <th style="text-align: right;">Typ</th>
505 <th style="text-align: right;">Betrag</th>
507 <?php foreach ($paid_fees as $payment) : ?>
509 <td><?=html_escape(format_date(db_date2unixtime($payment['date'])))?></td>
510 <td><?=($payment['value'] < 0) ? $EXPENSE_TYPES[$payment['type']]: $EARNING_TYPES[$payment['type']]?></td>
511 <td style="text-align: right;"><?=format_money($payment['value'])?></td>
516 <br style="clear: left;"/>