caedes has asked for the wisdom of the Perl Monks concerning the following question:
So for instance, let's say the group have to pay for $5 of things per day. If we have 20 members on that day then the total due by each person for that day is 25 cents. Now add to the mix the fact that the funds needed can change from month to month.
So this is what my algorithm does. It uses an array ref of date ranges and funding rates to take account for month to month funding changes.
This means that the group needs $100 a month for 2002 and $200 a month for 2003.my $rates = [ ['01/01/2002','01/01/2003',100], ['01/01/2003','01/01/2004',200] ];
We then calculate each members total debt by iterating over the different funding periods. We figure out what his total hours used in the first period are. We then figure out the total hours used by other people by iterating over all the other people and finding the intersection of their date-ranges and his date-ranges. We then use these two numbers to calculate his share of the groups debt. Then we go to the next funding period and do the same thing.
If this process seems too complex then just wait untill you see the actual code! It started out pretty clean and easy for me to understand when it didn't require the funding to change from month to month, but now it has reached a point where I can no longer effectively debug it. To tell you the truth I'm almost more interested in someone pointing out a better algorythm or a pre-existing program rather than finding the bug in this code.
The main bug right now is that the debt actually gets smaller as time passes, not larger as it should! I've tested this by overloading time(), however that code isn't included in the module right now. To make simpler I'm mainly interested in what is returned by the get_data() routine. Many of the other routines only have to do with the presentation of that data.
So, without further adue here it is:
package Zephir::Dues; use strict; use Zephir; ## vars for testing## my $setup = 161.29; ############### my $rate = [ ['01/01/1990', '09/01/2003', 105], ['08/01/2003', '10/01/2003', 190], ['10/01/2003', '01/01/2007', 140], ]; sub notify { if(not Zephir::authorize('Administrator')){ return Zephir::boxit("Not Authorized","Error"); } my @users = split(',', $Z::r{usernames}); for(@users){ my %hash = Zephir::userdata($_); $hash{username} = $_; $_ = \%hash; } my $out = Zephir::tmpl( template => 'Dues_notify', users => \@users, ); return Zephir::boxit($out,"Results"); } sub notify_email { if(not Zephir::authorize('Administrator')){ return Zephir::boxit("Not Authorized","Error"); } my @users = split(',',$Z::r{emails}); my $email = Zephir::tmpl(template => 'Dues_email'); my ($subject,$body) = $email =~ /^(.*?)[\n\r\f](.*)$/s; for(@users){ Zephir::email( to=>$_, from=>'myemail@alsdkfj.com', body=>$body, subject=>$subject, ); } print "Location: http://$Z::var{HOSTNAME}$Z::var{Z_URL}?lib=Dues&a +ction=admin\n\n"; return; } sub admin { if(not Zephir::authorize('Administrator')){ return Zephir::boxit("Not Authorized","Error"); } add_membership() if(defined $Z::r{usernames}); add_payment() if(defined $Z::r{payment}); my $info = $Z::var{DB}->selectcol_arrayref('SELECT name FROM dues' +); my %temp; @temp{@$info} = (); delete $temp{''}; @$info = keys %temp; my ($total_payments, $total_debt, $balance, @users); $Z::r{show} ||= 'all'; $Z::r{show_other} = 'all'; $Z::r{show_other} = 'current' if($Z::r{show} eq 'all'); for(@$info){ my $temp = get_data($_); next unless defined $temp; $total_payments += $temp->{total_payments}; $total_debt += $temp->{total_debt}; $balance += $temp->{balance}; next if($Z::r{show} eq 'current' and not defined $temp->{curre +nt}); push @users, ($temp); } @users = sort { $a->{balance} <=> $b->{balance} } @users; my $hash_ref = load_edit_jid() if(defined $Z::r{jid}); $hash_ref = load_edit_pid() if(defined $Z::r{pid}); $hash_ref->{tjoin} = dates($hash_ref->{tjoin}, 'tjoin'); $hash_ref->{tquit} = dates($hash_ref->{tquit}, 'tquit'); my $select_names = Zephir::select_box( name => 'name', 'values' => [keys %temp], selected => $hash_ref->{name} || '', ); my $out = Zephir::tmpl( template => 'Dues_admin', rows => \@users, total_payments => cash($total_payments), total_debt => cash($total_debt), balance => cash($balance), select_names => $select_names, %Z::r, %$hash_ref, ); return $out; return Zephir::template( title => 'WHA dues administration', body_loop => $out, ); } sub dates { my ($date, $name) = @_; my ($mm,$dd,$yy) = split('/', $date || ''); my @m_labels = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','S +ep','Oct','Nov','Dec'); my $month = Zephir::select_box( name => "month_$name", 'values' => [(1..12)], labels => \@m_labels, selected => $mm||'', ); my $day = Zephir::select_box( name => "day_$name", 'values' => [(1..31)], selected => $dd||'', ); my $year = Zephir::select_box( name => "year_$name", 'values' => [(1995..2010)], selected => $yy||'', ); return "$month$day$year"; } sub load_edit_jid { return $Z::var{DB}->selectrow_hashref( 'SELECT * FROM dues WHERE id=?', undef, $Z::r{jid}); } sub load_edit_pid { return $Z::var{DB}->selectrow_hashref( 'SELECT * FROM dues WHERE id=?', undef, $Z::r{pid}); } sub add_membership { if(not Zephir::authorize('Administrator')){ return Zephir::boxit("Not Authorized","Error"); } $Z::r{tjoin} = "$Z::r{month_tjoin}/$Z::r{day_tjoin}/$Z::r{year_tjo +in}"; $Z::r{tquit} = "$Z::r{month_tquit}/$Z::r{day_tquit}/$Z::r{year_tqu +it}"; if(defined $Z::r{delete}){ $Z::var{DB}->do('DELETE FROM dues WHERE id=?', undef, $Z::r{id +}); } elsif($Z::r{id} ne ''){ $Z::var{DB}->do('UPDATE dues SET usernames=?,tjoin=?,tquit=?,s +hares=? WHERE id=? and name=?' ,undef, $Z::r{usernames}, $Z::r{tjoin}, $Z::r{tquit}, $Z:: +r{shares}, $Z::r{id}, $Z::r{name} ) if($Z::r{usernames} ne '' and $Z::r{name} ne '' and $Z:: +r{shares} ne ''); } else{ $Z::var{DB}->do('INSERT INTO dues (usernames,name,tjoin,tquit, +shares) VALUES(?,?,?,?,?)' ,undef, $Z::r{usernames}, $Z::r{name}, $Z::r{tjoin}, $Z::r +{tquit}, $Z::r{shares}, ) if($Z::r{usernames} ne '' and $Z::r{name} ne '' and $Z:: +r{shares} ne ''); } } sub add_payment { if(not Zephir::authorize('Administrator')){ return Zephir::boxit("Not Authorized","Error"); } $Z::r{tjoin} = "$Z::r{month_tjoin}/$Z::r{day_tjoin}/$Z::r{year_tjo +in}"; if(defined $Z::r{delete}){ $Z::var{DB}->do('DELETE FROM dues WHERE id=?', undef, $Z::r{id +}); } elsif($Z::r{id} ne ''){ $Z::var{DB}->do('UPDATE dues SET tjoin=?,payment=? WHERE id=? +and name=?' ,undef, $Z::r{tjoin}, $Z::r{payment}, $Z::r{id}, $Z::r{nam +e} ) if($Z::r{tjoin} ne '' and $Z::r{payment} ne ''); } else{ $Z::var{DB}->do('INSERT INTO dues (name,tjoin,payment) VALUES( +?,?,?)' ,undef, $Z::r{name}, $Z::r{tjoin}, $Z::r{payment}, ) if($Z::r{name} ne '' and $Z::r{tjoin} ne '' and $Z::r{pa +yment} ne ''); } } sub get_data { my ($name,$tmpl) = @_; # usernames is a list like ',username,' or ',user1,user2,user3,' my $info = $Z::var{DB}->selectall_arrayref( 'SELECT usernames,tjoin,tquit,shares,id FROM dues WHERE name=? + AND length(usernames)' ,undef, $name); my $usernames = $info->[0]->[0]; #join is time in this context my $payments = $Z::var{DB}->selectall_arrayref( 'SELECT tjoin,payment,id FROM dues WHERE name=? AND payment>0' ,undef, $name); my $total_debt = 0; my (@values, $total_members, $current); for(@$info){ my (undef, $join, $quit, $shares, $id) = @$_; my ($m1,$d1,$y1) = split('/', $join); my ($m2,$d2,$y2) = split('/', $quit); my $t1 = Date_to_Time($m1,$d1,$y1); my $t2 = Date_to_Time($m2,$d2,$y2); my $ttime = $t2-$t1; if($ttime < 0){ $t2 = time(); $ttime = $t2 - $t1; $quit = $current = "current"; } for(@$rate){ my ($d1, $d2, $rate) = @$_; my $rt1 = Date_to_Time(split('/', $d1)); my $rt2 = Date_to_Time(split('/', $d2)); (my $other_share_hours, $total_members) = total_share_hour +s($t1, $t2); my $hours = intersect($rt1, $rt2, $t1, $t2) / 3600; my $debt = ($hours**2) / $other_share_hours * $shares * $r +ate * 12 / 365.24 / 24; $total_debt += $debt; } $_ = "$shares shares: $join-$quit"; push @values, $id; } $total_debt += $setup / ($total_members || 0.001); $info = Zephir::select_box( name=>'jid','values' => \@values, labe +ls => $info ); my $total_payments = 0; undef @values; for(@$payments){ my ($time, $payment, $id) = @$_; $total_payments += $payment; $payment = cash($payment); $_ = "\$$payment ($time)"; push @values, $id; } $payments = Zephir::select_box( name => 'pid', 'values' => \@value +s, labels => $payments ); my $balance = $total_payments - $total_debt; $name =~ s/ / /gs; my %out = ( usernames => $usernames, name => $name, info => $info, payments => $payments, total_payments => cash($total_payments), total_debt => cash($total_debt), balance => cash($balance), current => $current, ); return \%out; } sub cash { my $cash = shift; $cash = int($cash*100)/100; $cash = sprintf("%1.2f",$cash); return $cash; } sub gtime { my $time = shift; $time = Zephir::ztime($time); $time =~ s/^(.*?) .*$/$1/; return $time } sub main { my ($name); my $un = $Z::a{username} || ')&^#$)*#$^'; # usernames is a list like 'username' or 'user1,user2,user3' my $info = $Z::var{DB}->selectall_arrayref( 'SELECT usernames,name FROM dues WHERE LENGTH(usernames)' ); for(@$info){ if($_->[0] =~ /[,\W]*\Q$un\E[,\W]*/){ $name = $_->[1]; } } if(not defined $name){ return Zephir::boxit("You don't appear to be registered as a W +HA member under this username.","Error"); } $info = get_data($name,"Dues_user"); ($info->{rate}) = total_share_hours(time - 3600*24*30,time); $info->{rate} = $rate->[-1]->[2] * 24 * 30 / $info->{rate}; $info->{rate} = cash($info->{rate}); # use total_share_hours to find rate for last month my $out = Zephir::tmpl(%$info); return Zephir::template( title => 'WHA dues', body_loop => $out, ); } sub intersect { my ($a,$b,$c,$d) = @_; my ($n1,$n2) = ([$a,$b],[$c,$d]); if($a > $c){ my $t = $n1; $n1 = $n2; $n2 = $t; } return (($n1->[1]-$n2->[0])*($n1->[1]>$n2->[0])) - (($n1->[1]-$n2- +>[1])*($n1->[1]>$n2->[1])); } my $tsh; sub total_share_hours { my ($join, $quit) = @_; if(not defined $tsh){ $tsh = $Z::var{DB}->selectall_arrayref( 'SELECT tjoin,tquit,shares,name FROM dues'); for(@$tsh){ my ($j,$q,$s,$n) = @$_; next unless $s; my ($m1,$d1,$y1) = split('/', $j); my ($m2,$d2,$y2) = split('/', $q); $j = Date_to_Time($m1,$d1,$y1); $q = Date_to_Time($m2,$d2,$y2); $q = time if($q < $j); $_ = [$j,$q,$s,$n]; } } my $total = 0; my (%members,$tm); for(@$tsh){ my ($j,$q,$s,$n) = @$_; next unless $s; $tm++ if(not defined $members{$n}); $members{$n} = 1; my $hours = intersect($join,$quit,$j,$q); $total += $s * $hours / 3600; } return ($total, $tm); } sub Date_to_Time { my ($MM, $DD, $YYYY) = @_; my $maxtime = time + 3600*24*365; --$MM; my ($mm,$dd,$yy); my $guess = ($YYYY - 1969 - 1)*3600*24*365*($YYYY > 1969); while($guess < $maxtime){ $guess += 3600*24; ($mm,$dd,$yy) = (localtime($guess))[4,3,5]; $guess += ($MM - $mm - 1)*30*24*3600*($MM > $mm); next unless $mm == $MM; next unless $dd == $DD; $yy += 1900; next unless $yy == $YYYY; last; } return $guess; } 1;
-caedes
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Membership Dues Application bugs
by Roger (Parson) on Dec 08, 2003 at 07:35 UTC | |
by caedes (Pilgrim) on Dec 08, 2003 at 15:50 UTC | |
|
Re: Membership Dues Application bugs
by ysth (Canon) on Dec 08, 2003 at 01:19 UTC |