@@ -3,6 +3,7 @@ package LedgerSMB::Workflow::Action::Reconciliation;
33use v5.36;
44use warnings;
55no warnings " experimental::for_list" ; # # no critic -- accepted in 5.40
6+ no warnings " experimental::builtin" ; # # no critic -- accepted in 5.40
67
78use parent qw( LedgerSMB::Workflow::Action ) ;
89
@@ -31,12 +32,16 @@ Available values:
3132
3233=over 8
3334
35+ =item * add_pending_items
36+
3437=item * approve
3538
3639=item * delete
3740
3841=item * submit
3942
43+ =item * reconcile
44+
4045=item * reject
4146
4247=back
@@ -67,12 +72,18 @@ Used by the workflow engine to dispatch work to the action instance.
6772=cut
6873
6974sub execute ($self , $wf ) {
70- if ($self -> entrypoint eq ' approve' ) {
75+ if ($self -> entrypoint eq ' add_pending_items' ) {
76+ $self -> _add_pending_items( $wf );
77+ }
78+ elsif ($self -> entrypoint eq ' approve' ) {
7179 $self -> _approve( $wf );
7280 }
7381 elsif ($self -> entrypoint eq ' delete' ) {
7482 $self -> _delete( $wf );
7583 }
84+ elsif ($self -> entrypoint eq ' reconcile' ) {
85+ $self -> _reconcile( $wf );
86+ }
7687 elsif ($self -> entrypoint eq ' submit' ) {
7788 $self -> _submit( $wf );
7889 }
@@ -81,23 +92,246 @@ sub execute($self, $wf) {
8192 }
8293}
8394
95+ # ####################################
96+ #
97+ # aadd_pending_items
98+ #
99+ # ####################################
100+
101+ sub _add_pending_payments ($recon_fx , $pending ) {
102+ # group lines of a single payment
103+ my %payments = ( __NOPAYMENT__ => [] );
104+ for my $pl ($pending -> @*) {
105+ if (defined $pl -> {payment_id }) {
106+ $payments {$pl -> {payment_id }} //= [];
107+ push $payments {$pl -> {payment_id }}-> @*, $pl ;
108+ }
109+ else {
110+ push $payments {__NOPAYMENT__ }-> @*, $pl ;
111+ }
112+ }
113+
114+ # add payment lines awaiting reconciliation
115+ my @new_recon ;
116+ for my ($payment_id , $lines ) (%payments ) {
117+ next if $payment_id eq ' __NOPAYMENT__' ;
118+
119+ push @new_recon , {
120+ amount => (sum0
121+ map {
122+ $recon_fx ? $_ -> {amount_tc } : $_ -> {amount_bc }
123+ } $lines -> @*),
124+ post_date => $lines -> [0]-> {paymentdate },
125+ source => $lines -> [0]-> {source },
126+ links => $lines ,
127+ };
128+ }
129+
130+ return ($payments {__NOPAYMENT__ }, \@new_recon );
131+ }
132+
133+ sub _adjust_todo_lines ($pending , $book_todo ) {
134+ # add adjustment lines to existing payment lines
135+ my %existing_sources ;
136+ for my $line ($book_todo -> @*) {
137+ $existing_sources {$line -> {source }} //= [];
138+ push $existing_sources {$line -> {source }}-> @*, $line ;
139+ }
140+ for my ($index , $line ) (indexed $pending -> @*) {
141+ next unless exists $existing_sources {$line -> {source }};
142+ my $existing = $existing_sources {$line -> {source }};
143+ my @same_date = grep {
144+ $_ -> {post_date } eq $line -> {post_date }
145+ } $existing -> @*;
146+ next if scalar (@same_date ) != 1;
147+
148+ splice $pending -> @*, $index , 1;
149+ push $same_date [0]-> {links }-> @*, $line ;
150+ }
151+
152+ return ;
153+ }
154+
155+ sub _add_remaining_lines ($recon_fx , $pending , $book_todo ) {
156+ my %dates ;
157+ for my $line ($pending -> @*) {
158+ $dates {$line -> {transdate }} //= {};
159+ $dates {$line -> {transdate }}-> {$line -> {source } // ' ' } //= [];
160+ push $dates {$line -> {transdate }}-> {$line -> {source } // ' ' }-> @*, $line ;
161+ }
162+ for my ($date , $sources ) (%dates ) {
163+ for my ($source , $lines ) ($sources -> %*) {
164+ if ($source ) {
165+ my $amount = sum0
166+ map {
167+ $recon_fx ? $_ -> {amount_tc } : $_ -> {amount_bc }
168+ } $lines -> @*;
169+ push $book_todo -> @*, {
170+ amount => $amount ,
171+ post_date => $date ,
172+ source => $source ,
173+ links => $lines ,
174+ };
175+ }
176+ else {
177+ for my $line ($lines -> @*) {
178+ my $amount =
179+ $recon_fx ? $_ -> {amount_tc } : $_ -> {amount_bc };
180+ push $book_todo -> @*, {
181+ amount => $amount ,
182+ post_date => $date ,
183+ links => [ $line ],
184+ };
185+ }
186+ }
187+ }
188+ }
189+ return ;
190+ }
191+
192+ sub _add_pending_items ($self , $wf ) {
193+ my ($pending , $new_recon ) =
194+ _add_pending_payments(
195+ $wf -> context-> param( ' recon_fx' ),
196+ $wf -> context-> param( ' _pending_items' ) );
197+ my $book_todo = $wf -> context-> param( ' _book_todo' );
198+ push $book_todo -> @*, $new_recon -> @*;
199+
200+ # modifies $pending->@* and $book_todo->@*
201+ _adjust_todo_lines( $pending , $book_todo );
202+
203+ # add the remaining lines grouped by source, if they have one
204+ # modifies $book_todo->@*
205+ _add_remaining_lines( $wf -> context-> param( ' recon_fx' ), $pending , $book_todo );
206+
207+ return ;
208+ }
209+
210+ # ####################################
211+ #
212+ # approve
213+ #
214+ # ####################################
215+
84216sub _approve ($self , $wf ) {
85217 $wf -> context-> param( ' approved' , 1 );
86218}
87219
220+ # ####################################
221+ #
222+ # delete
223+ #
224+ # ####################################
225+
88226sub _delete ($self , $wf ) {
89227 $wf -> context-> param( ' deleted' , 1 );
90228}
91229
92- sub _submit ($self , $wf ) {
93- $wf -> context-> param( ' submitted' , 1 );
230+ # ####################################
231+ #
232+ # reconcile
233+ #
234+ # ####################################
235+
236+ sub _reconcile_source_id ( $stmt , $source_id , $book_todo , $book_done ) {
237+ my $lc_source_id = lc ($source_id );
238+ my $candidates = [
239+ grep {
240+ lc ($book_todo -> [$_ ]-> {source }) eq $lc_source_id
241+ and $book_todo -> [$_ ]-> {post_date } eq $stmt -> {post_date }
242+ } 0..$book_todo -> $# *
243+ ];
244+
245+ return unless $candidates -> @*;
246+
247+ if (scalar ($candidates -> @*) == 1) {
248+ my $found = splice $book_todo -> @*, $candidates -> [0], 1;
249+ $found -> {recon_group } = $stmt -> {id };
250+ $stmt -> {recon_group } = $stmt -> {id };
251+ push $book_done -> @*, $found ;
252+ return ;
253+ }
254+
255+ $candidates = [
256+ grep {
257+ $book_todo -> [$_ ]-> {amount } == $stmt -> {amount }
258+ and lc ($book_todo -> [$_ ]-> {source }) eq $lc_source_id
259+ and $book_todo -> [$_ ]-> {post_date } eq $stmt -> {post_date }
260+ } 0..$book_todo -> $# *
261+ ];
262+
263+ return unless $candidates -> @*;
264+
265+ my $found = splice $book_todo -> @*, $candidates -> [0], 1;
266+ $found -> {recon_group } = $stmt -> {id };
267+ $stmt -> {recon_group } = $stmt -> {id };
268+ push $book_done -> @*, $found ;
269+ return ;
94270}
95271
272+ sub _reconcile_no_nource_id ($stmt , $prefix , $book_todo , $book_done ) {
273+ my $candidates = [
274+ grep {
275+ $book_todo -> [$_ ]-> {amount } == $stmt -> {amount }
276+ and $book_todo -> [$_ ]-> {post_date } eq $stmt -> {post_date }
277+ and $book_todo -> [$_ ]-> {source } !~ m / ^\Q $prefix \E /
278+ } 0..$book_todo -> $# *
279+ ];
280+
281+ return unless $candidates -> @*;
282+
283+ my $found = splice $book_todo -> @*, $candidates -> [0], 1;
284+ $found -> {recon_group } = $stmt -> {id };
285+ $stmt -> {recon_group } = $stmt -> {id };
286+ push $book_done -> @*, $found ;
287+ return ;
288+ }
289+
290+ sub _reconcile ($self , $wf ) {
291+ my $stmt_todo = $wf -> context-> param( ' _stmt_todo' );
292+ my $book_todo = $wf -> context-> param( ' _book_todo' );
293+ my $book_done = $wf -> context-> param( ' _book_done' );
294+ my $prefix = $wf -> context-> param( ' _prefix' );
295+
296+ for my $stmt ($stmt_todo -> @*) {
297+ my $source_id ;
298+ if (defined $stmt -> {source_id }) {
299+ if ($stmt -> {source_id } =~ m / ^[0-9] +$ / ) {
300+ $source_id = $prefix . $stmt -> {source_id };
301+ }
302+ elsif ($stmt -> {source_id } ne ' ' ) {
303+ $source_id = $stmt -> {source_id };
304+ }
305+ }
306+
307+ if (defined $source_id ) {
308+ _reconcile_source_id( $stmt , $source_id , $book_todo , $book_done );
309+ return ;
310+ }
311+
312+ _reconcle_no_source_id( $stmt , $prefix , $book_todo , $book_done );
313+ }
314+ }
315+
316+ # ####################################
317+ #
318+ # reject
319+ #
320+ # ####################################
321+
96322sub _reject ($self , $wf ) {
97323 $wf -> context-> param( ' rejected' , 1 );
98324}
99325
326+ # ####################################
327+ #
328+ # submit
329+ #
330+ # ####################################
100331
332+ sub _submit ($self , $wf ) {
333+ $wf -> context-> param( ' submitted' , 1 );
334+ }
101335
1023361;
103337
0 commit comments