Annotation of RT/Invoicing/lib/RTI/State.pm, Revision 1.6
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:
115: sub save {
116: my ($self) = @_;
117:
118: delete $self->{_table};
119: delete $self->{lastinvoice};
120: foreach my $invoice ( values %{ $self->{invoice} } ) {
1.3 andrew 121: delete $invoice->{$_} for qw/
122: id
123: from
124: to
125: info
126: logo
127: projects
128: expenses
1.4 andrew 129: discount
130: hours
131: organization
1.3 andrew 132: /;
133:
134: foreach my $k ( keys %{$invoice} ) {
135: my $v = $invoice->{$k};
136:
137: if ( defined $v && length $v ) {
138: if ( ref $v eq 'DateTime' ) {
139: $invoice->{$k} = $v->ymd;
140: }
141: }
142: else {
143: delete $invoice->{$k};
144: }
1.6 ! andrew 145: }
! 146: }
! 147: foreach my $custid (keys %{ $self->{payment} || {} }) {
! 148: foreach my $payment (@{ $self->{payment}->{$custid} || [] }) {
! 149: $payment->{date} = $payment->{date}->ymd
! 150: if ref $payment->{date} eq 'DateTime';
1.3 andrew 151: }
1.1 andrew 152: }
153: DumpFile( $file, {%$self} ) or die "Unable to save state: $!";
154: }
155:
156: sub _match_payments {
157: my ($self) = @_;
158:
159: return if $self->{_table}{credit} && $self->{_table}{unpaid};
160:
161: my $invoices = $self->{invoice};
1.3 andrew 162: my %owes = map { $_ => $invoices->{$_}->{total} } keys %{$invoices};
1.1 andrew 163:
164: my %credit;
165:
166: foreach my $custid ( keys %{ $self->{payment} } ) {
167: $credit{$custid} = 0;
168:
169: my $payments = $self->{payment}->{$custid};
170: foreach my $p ( @{$payments} ) {
171: my $paid = $p->{paid};
172: $p->{invoices} ||= [];
173:
174: foreach my $id ( @{ $p->{invoices} } ) {
175: $owes{$id} ||= 0;
176:
177: if ( $owes{$id} == $paid ) {
178: $owes{$id} = 0;
179: $paid = 0;
180: }
181: elsif ( $owes{$id} > $paid ) {
182: $owes{$id} -= $paid;
183: $paid = 0;
184: }
185: elsif ( $owes{$id} < $paid ) {
186: $paid -= $owes{$id};
187: $owes{$id} = 0;
188: }
189: }
190:
191: $credit{$custid} += $paid;
192: }
193:
194: delete $credit{$custid} unless $credit{$custid};
195: }
196:
197: foreach my $id ( sort { $b <=> $a } keys %owes ) {
1.3 andrew 198: my $i = $invoices->{$id};
1.1 andrew 199: my $custid = $i->{custid} or next;
200:
201: my $owes = sprintf "%0.2f", $owes{$id} || 0;
202: my $paid = sprintf "%0.2f", $credit{$custid} || 0;
203:
204: if ( $owes == $paid ) {
205: $owes = 0;
206: $paid = 0;
207: }
208: elsif ( $owes > $paid ) {
209: $owes -= $paid;
210: $paid = 0;
211: }
212: elsif ( $owes < $paid ) {
213: $paid -= $owes;
214: $owes = 0;
215: }
216:
217: $self->{_table}{unpaid}{$custid}{$id} = $owes if $owes;
218:
219: if ($paid) {
220: $credit{$custid} = $paid;
221: }
1.3 andrew 222: elsif ( exists $credit{$custid} ) {
1.1 andrew 223: delete $credit{$custid};
224: }
225: }
226:
227: $self->{_table}{credit} = \%credit;
228: }
229:
230: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>