Annotation of RT/Invoicing/lib/RTI/State.pm, Revision 1.7
1.1 andrew 1: package RTI::State;
2: use strict;
3: use warnings;
4:
1.3 andrew 5: use 5.010;
1.1 andrew 6:
7: use DateTime;
1.3 andrew 8: use Carp;
1.1 andrew 9:
1.5 andrew 10: use YAML::XS qw/ LoadFile DumpFile /;
11: $YAML::XS::QuoteNumericStrings = 0;
1.3 andrew 12: use RTI::Util qw/ ymd_to_DateTime /;
1.1 andrew 13:
14: my $file = '';
15:
16: sub new {
17: my $class;
18: ( $class, $file ) = @_;
19:
20: my $self = { lastinvoice => 0, };
21: if ( -e $file ) {
22: $self = LoadFile($file) or die "Unable to load state: $!";
23:
24: $self->{lastinvoice} ||= 0;
25: while ( my ( $id, $invoice ) = each %{ $self->{invoice} } ) {
26: $self->{lastinvoice} = $id if $self->{lastinvoice} < $id;
1.3 andrew 27:
1.1 andrew 28: $invoice->{id} = $id;
1.3 andrew 29: $invoice->{$_} = ymd_to_DateTime( $invoice->{$_} )
30: for qw/ invdate start end /;
1.1 andrew 31: }
1.6 andrew 32: foreach my $custid (keys %{ $self->{payment} || {} }) {
33: foreach my $payment (@{ $self->{payment}->{$custid} || [] }) {
34: $payment->{date} = ymd_to_DateTime( $payment->{date} )
35: if $payment->{date};
36: }
37: }
1.1 andrew 38: }
39:
40: bless $self, $class;
41:
42: die "Need to pass filename to new: $!" unless $file;
43:
44: return $self;
45: }
46:
47: sub next_invoice_id {
48: my ($self) = @_;
49: return $self->{lastinvoice} + 1;
50: }
51:
52: sub add_invoice {
53: my ( $self, $invoice ) = @_;
54:
55: my $id = $invoice->{id} || $self->next_invoice_id;
56:
57: croak "Can't add duplicate invoice $id\n"
58: if exists $self->{invoice}->{$id};
59:
60: $invoice->{id} ||= $id;
1.3 andrew 61: $invoice->{invdate} ||= DateTime->now( time_zone => 'local' ),
1.1 andrew 62:
63: $self->{lastinvoice} = $id if $self->{lastinvoice} < $id;
64:
1.3 andrew 65: $self->{invoice}->{$id} = $invoice;
1.1 andrew 66: delete $self->{_tables};
67:
68: return $self->{lastinvoice};
69: }
70:
1.3 andrew 71: sub get_invoice {
1.1 andrew 72: my ( $self, $id ) = @_;
73: return $self->{invoice}->{$id};
74: }
75:
76: sub last_invoice {
77: my ( $self, $custid ) = @_;
78:
79: if ( !$self->{_table}->{last_invoice} ) {
80: my $invoices = $self->{invoice};
81: foreach my $id ( sort { $a <=> $b } keys %{$invoices} ) {
82: my $inv = $invoices->{$id};
83: next unless $inv->{custid};
84: $self->{_table}->{last_invoice}->{ $inv->{custid} } = $inv;
85: }
86: }
87:
88: return $self->{_table}->{last_invoice}->{$custid};
89: }
90:
91: sub txn_is_invoiced {
92: my ( $self, $txn ) = @_;
1.3 andrew 93:
1.1 andrew 94: if ( !$self->{_table}->{txn} ) {
95: my $invoices = $self->{invoice};
96: foreach my $id ( sort { $a <=> $b } keys %{$invoices} ) {
97: my $inv = $invoices->{$id};
98: foreach my $t ( @{ $inv->{transactions} } ) {
99: $self->{_table}->{txn}->{$t} = 1;
100: }
101: }
102: }
103: return $self->{_table}->{txn}->{$txn};
104: }
105:
106: sub unpaid_invoices {
1.3 andrew 107: my ( $self, $custid ) = @_;
1.1 andrew 108:
109: $self->_match_payments;
1.2 andrew 110: return defined $custid
111: ? $self->{_table}->{unpaid}->{$custid}
112: : $self->{_table}->{unpaid};
1.1 andrew 113: }
114:
1.7 ! andrew 115: sub credits {
! 116: my ( $self, $custid ) = @_;
! 117:
! 118: $self->_match_payments;
! 119: return defined $custid
! 120: ? $self->{_table}->{credit}->{$custid}
! 121: : $self->{_table}->{credit};
! 122: }
! 123:
1.1 andrew 124: sub save {
125: my ($self) = @_;
126:
127: delete $self->{_table};
128: delete $self->{lastinvoice};
129: foreach my $invoice ( values %{ $self->{invoice} } ) {
1.3 andrew 130: delete $invoice->{$_} for qw/
131: id
132: from
133: to
134: info
135: logo
136: projects
137: expenses
1.4 andrew 138: discount
139: hours
140: organization
1.3 andrew 141: /;
142:
143: foreach my $k ( keys %{$invoice} ) {
144: my $v = $invoice->{$k};
145:
146: if ( defined $v && length $v ) {
147: if ( ref $v eq 'DateTime' ) {
148: $invoice->{$k} = $v->ymd;
149: }
150: }
151: else {
152: delete $invoice->{$k};
153: }
1.6 andrew 154: }
155: }
156: foreach my $custid (keys %{ $self->{payment} || {} }) {
157: foreach my $payment (@{ $self->{payment}->{$custid} || [] }) {
158: $payment->{date} = $payment->{date}->ymd
159: if ref $payment->{date} eq 'DateTime';
1.3 andrew 160: }
1.1 andrew 161: }
162: DumpFile( $file, {%$self} ) or die "Unable to save state: $!";
163: }
164:
165: sub _match_payments {
166: my ($self) = @_;
167:
168: return if $self->{_table}{credit} && $self->{_table}{unpaid};
169:
170: my $invoices = $self->{invoice};
1.3 andrew 171: my %owes = map { $_ => $invoices->{$_}->{total} } keys %{$invoices};
1.1 andrew 172:
173: my %credit;
174:
175: foreach my $custid ( keys %{ $self->{payment} } ) {
176: $credit{$custid} = 0;
177:
178: my $payments = $self->{payment}->{$custid};
179: foreach my $p ( @{$payments} ) {
180: my $paid = $p->{paid};
181: $p->{invoices} ||= [];
182:
183: foreach my $id ( @{ $p->{invoices} } ) {
184: $owes{$id} ||= 0;
185:
186: if ( $owes{$id} == $paid ) {
187: $owes{$id} = 0;
188: $paid = 0;
189: }
190: elsif ( $owes{$id} > $paid ) {
191: $owes{$id} -= $paid;
192: $paid = 0;
193: }
194: elsif ( $owes{$id} < $paid ) {
195: $paid -= $owes{$id};
196: $owes{$id} = 0;
197: }
198: }
199:
200: $credit{$custid} += $paid;
201: }
202:
203: delete $credit{$custid} unless $credit{$custid};
204: }
205:
206: foreach my $id ( sort { $b <=> $a } keys %owes ) {
1.3 andrew 207: my $i = $invoices->{$id};
1.1 andrew 208: my $custid = $i->{custid} or next;
209:
210: my $owes = sprintf "%0.2f", $owes{$id} || 0;
211: my $paid = sprintf "%0.2f", $credit{$custid} || 0;
212:
213: if ( $owes == $paid ) {
214: $owes = 0;
215: $paid = 0;
216: }
217: elsif ( $owes > $paid ) {
218: $owes -= $paid;
219: $paid = 0;
220: }
221: elsif ( $owes < $paid ) {
222: $paid -= $owes;
223: $owes = 0;
224: }
225:
226: $self->{_table}{unpaid}{$custid}{$id} = $owes if $owes;
227:
228: if ($paid) {
229: $credit{$custid} = $paid;
230: }
1.3 andrew 231: elsif ( exists $credit{$custid} ) {
1.1 andrew 232: delete $credit{$custid};
233: }
234: }
235:
236: $self->{_table}{credit} = \%credit;
237: }
238:
239: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>