From defe17b6678893bf4bb9cd219ef3aa739073dccf Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 7 Aug 2022 20:12:42 +0200 Subject: [PATCH 01/17] Track reversal of GL transactions --- UI/journal/journal_entry.html | 40 +++++-- old/bin/gl.pl | 122 +++++++++++++++++++-- old/lib/LedgerSMB/GL.pm | 4 +- sql/changes/1.10/reversed-transactions.sql | 12 ++ sql/changes/LOADORDER | 1 + sql/modules/Roles.sql | 1 + 6 files changed, 158 insertions(+), 22 deletions(-) create mode 100644 sql/changes/1.10/reversed-transactions.sql diff --git a/UI/journal/journal_entry.html b/UI/journal/journal_entry.html index 8d3f4f6fb8..d67214fd60 100644 --- a/UI/journal/journal_entry.html +++ b/UI/journal/journal_entry.html @@ -12,7 +12,7 @@ action="[% form.script %]"> - + @@ -119,10 +119,25 @@
[% form.title %][% form.title %]
+ + [% IF form.reversed_by ; + text('This transaction has been reversed by transaction [_1]', form.reversed_by); + END %] +
+ + [% IF form.reversing ; + IF form.approved ; + text('This transaction reverses transaction [_1]', form.reversing); + ELSE ; + text('This transaction will reverse transaction [_1]', form.reversing); + END ; + END %] + + - + - +
@@ -271,7 +287,7 @@ value = displayrow.debit_fx name = "debit_fx_$INDEX" type = "text" - readonly = form.approved + readonly = (form.approved || form.reversing) size = 12 id = "deb_fx_$INDEX" } %] @@ -282,7 +298,7 @@ value = displayrow.credit_fx name = "credit_fx_$INDEX" type = "text" - readonly = form.approved + readonly = (form.approved || form.reversing) size = 12 id = "cre_fx_$INDEX" } %] @@ -313,7 +329,7 @@


[% FOREACH hidden IN hiddens.keys; diff --git a/old/bin/gl.pl b/old/bin/gl.pl index e1c851a8d8..630c0461a9 100644 --- a/old/bin/gl.pl +++ b/old/bin/gl.pl @@ -52,7 +52,7 @@ package lsmb_legacy; use LedgerSMB::Legacy_Util; use LedgerSMB::Num2text; -require "old/bin/arap.pl"; +require "old/bin/arap.pl"; # for: Schedule action # end of main @@ -107,7 +107,6 @@ sub approve { } } - sub new { for my $row (0 .. $form->{rowcount}){ for my $fld(qw(accno projectnumber acc debit credit source memo)){ @@ -148,6 +147,90 @@ sub add { } +sub _reverse_amounts { + # swap debits and credits + for my $rownum (0 .. $form->{rowcount}) { + my $credit = $form->{"credit_$rownum"}; + my $credit_fx = $form->{"credit_fx_$rownum"}; + $form->{"credit_$rownum"} = $form->{"debit_$rownum"}; + $form->{"credit_fx_$rownum"} = $form->{"debit_fx_$rownum"}; + $form->{"debit_$rownum"} = $credit; + $form->{"debit_fx_$rownum"} = $credit_fx; + } +} + +sub reverse { + $form->{title} = "Reverse"; + + &create_links; # runs GL->transaction() + _reverse_amounts(); + + $form->{reversing} = delete $form->{id}; + delete $form->{approved}; + + display_form(); +} + + +sub post_reversing { + # we should save only the reference, sequence, transdate, description and notes. + + $form->error( + $locale->text('Cannot post transaction for a closed period!') ) + if ( $transdate and $form->is_closed( $transdate ) ); + if (not $form->{id}) { + do { + local $form->{id} = $form->{reversing}; + + # save data we want to use from the posted form, + # not from the reversed transaction. + local $form->{reversing}; + local $form->{notes}; + local $form->{description}; + local $form->{transdate}; + local $form->{reference}; + local $form->{approved}; + + &create_links; # create_links overwrites 'reversing' + }; + + # Why do I not need _reverse_amounts here??? + # _reverse_amounts(); + GL->post_transaction( \%myconfig, \%$form, $locale); + + my $query = q{UPDATE transactions SET reversing = ? WHERE id = ?}; + $form->{dbh}->do( + $query, + {}, + $form->{reversing}, + $form->{id}) + or $form->dberror($query); + } + else { + my $query = <<~'QUERY'; + UPDATE gl + SET reference = ?, + description = ?, + transdate = ?, + notes = ? + WHERE id = ? + QUERY + + $form->{dbh}->do( + $query, + {}, + $form->{reference}, + $form->{description}, + $form->{transdate}, + $form->{notes}, + + $form->{id}) + or $form->dberror($query); + } + + display_form(); +} + sub display_form { $form->{separate_duties} = $form->get_setting('separate_duties'); @@ -203,6 +286,7 @@ sub display_form 'callback' => $form->{callback}, 'form_id' => $form->{form_id}, 'separate_duties' => $form->{separate_duties}, + 'reversing' => $form->{reversing} ); @@ -237,6 +321,9 @@ sub display_form class => 'post' }, { action => 'edit_and_save', value => $locale->text('Save Draft') }, + { action => 'post_reversing', + value => ($form->{separate_duties} + ? $locale->text('Save') : $locale->text('Post')), }, { action => 'save_temp', value => $locale->text('Save Template') }, { action => 'save_as_new', @@ -250,26 +337,33 @@ sub display_form ); %a = (); - $a{'save_temp'} = 1; + $a{'save_temp'} = not $form->{reversing}; if ( $form->{id}) { - for ( 'new', 'save_as_new', 'schedule', 'copy_to_new' ) { + for ( 'new', 'save_as_new', 'copy_to_new' ) { $a{$_} = 1; } + for ( 'schedule' ) { + $a{$_} = not $form->{reversing}; + } if (!$form->{approved} && !$form->{batch_id}) { # Need to check for draft_modify and draft_post if ($form->is_allowed_role(['draft_post'])) { $a{approve} = 1; } if ($form->is_allowed_role(['draft_modify'])) { - $a{edit_and_save} = 1; + $a{edit_and_save} = not $form->{reversing}; + $a{post_reversing} = $form->{reversing}; } - $a{update} = 1; + $a{update} = not $form->{reversing}; } } else { - $a{'update'} = 1; if ( not $form->is_closed( $transdate ) ) { - for ( 'post', 'schedule' ) { $a{$_} = 1 } + for ( 'post' ) { $a{$_} = not $form->{reversing} } + for ( 'post_reversing' ) { $a{$_} = $form->{reversing} } + } + for ( 'update', 'schedule' ) { + $a{$_} = not $form->{reversing}; } } @@ -287,6 +381,18 @@ sub display_form grep { $a{$_->{action}} } @buttons; } + unless ($form->{reversed_by}) { + if ($form->{approved}) { + push @buttons, { + name => 'action', + value => 'reverse', + text => $locale->text('Reverse'), + type => 'submit', + class => 'submit', + }; + } + } + $form->{recurringset}=0; if ( $form->{recurring} ) { $form->{recurringset}=1; diff --git a/old/lib/LedgerSMB/GL.pm b/old/lib/LedgerSMB/GL.pm index f96a9cea34..13d4b0a4ae 100644 --- a/old/lib/LedgerSMB/GL.pm +++ b/old/lib/LedgerSMB/GL.pm @@ -269,8 +269,8 @@ sub transaction { @{$form->{currencies}} = (LedgerSMB::Setting->new(%$form))->get_currencies; - $query = qq|SELECT g.* - FROM gl g + $query = qq|SELECT g.*, t.reversing, t.reversed_by + FROM gl g JOIN transactions_reversal t on g.id = t.id WHERE g.id = ?|; $sth = $dbh->prepare($query) || $form->dberror($dbh->errstr); diff --git a/sql/changes/1.10/reversed-transactions.sql b/sql/changes/1.10/reversed-transactions.sql new file mode 100644 index 0000000000..5c4e7a850d --- /dev/null +++ b/sql/changes/1.10/reversed-transactions.sql @@ -0,0 +1,12 @@ + +alter table transactions + add column reversing int; + +create unique index transactions_reversing_idx + on transactions ( reversing ) where reversing is not null; + +create view transactions_reversal as + select t.*, (select i.id from transactions i + where i.approved and i.reversing = t.id) as reversed_by + from transactions t; + diff --git a/sql/changes/LOADORDER b/sql/changes/LOADORDER index 4a4176f378..bea5a15beb 100644 --- a/sql/changes/LOADORDER +++ b/sql/changes/LOADORDER @@ -175,3 +175,4 @@ mc/delete-migration-validation-data.sql 1.11/country-config.sql 1.11/workflow-user.sql 1.11/enable-barcode-on-invoices.sql +1.10/reversed-transactions.sql diff --git a/sql/modules/Roles.sql b/sql/modules/Roles.sql index bee8d4e64f..f8151bccdc 100644 --- a/sql/modules/Roles.sql +++ b/sql/modules/Roles.sql @@ -1334,6 +1334,7 @@ SELECT lsmb__grant_perms('base_user', obj, 'ALL') 'user_preference_id_seq', 'user_preference', 'status', 'recurring', 'recurringemail', 'recurringprint', 'transactions', + 'transactions_reversal', 'ac_tax_form', 'invoice_tax_form', 'lsmb_sequence']) obj; -- transactions table needs to be better locked down in 1.5 From cb2c3b2f5700ebc25ed11ec941247876616aae90 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Tue, 9 Aug 2022 21:09:49 +0200 Subject: [PATCH 02/17] Retrieve new reversal columns for AR/AP On transaction retrieval, also retrieve the new reversal related columns 'reversing' and 'reversed_by'. This works both for invoices and transactions. --- old/lib/LedgerSMB/Form.pm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/old/lib/LedgerSMB/Form.pm b/old/lib/LedgerSMB/Form.pm index 036bba265f..e895bd7567 100644 --- a/old/lib/LedgerSMB/Form.pm +++ b/old/lib/LedgerSMB/Form.pm @@ -1954,7 +1954,6 @@ sub create_links { } } } - $sth->finish; my $arap = ( $vc eq 'customer' ) ? 'ar' : 'ap'; @@ -1970,17 +1969,17 @@ sub create_links { a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes, a.intnotes, ce.name AS $vc, - a.amount_tc AS oldinvtotal, - case when a.amount_tc = 0 then 0 - else a.amount_bc / a.amount_tc end as exchangerate, + a.amount_tc AS oldinvtotal, + case when a.amount_tc = 0 then 0 + else a.amount_bc / a.amount_tc end as exchangerate, a.person_id as employee_id, e.name AS employee, c.language_code, a.ponumber, a.reverse, - a.approved, ctf.default_reportable, - a.description, a.on_hold, a.crdate, - ns.location_id as locationid, a.is_return, $seq, - t.workflow_id + a.approved, ctf.default_reportable, + a.description, a.on_hold, a.crdate, + ns.location_id as locationid, a.is_return, $seq, + t.workflow_id, t.reversing, t.reversed_by FROM $arap a - JOIN transactions t ON t.id = a.id + JOIN transactions_reversal t ON t.id = a.id JOIN entity_credit_account c ON (a.entity_credit_account = c.id) JOIN entity ce ON (ce.id = c.entity_id) From eedf9974e13026459afc42ad8b98857b930a190f Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Tue, 9 Aug 2022 21:16:13 +0200 Subject: [PATCH 03/17] Reversal tracking and posting for AR/AP transactions Note that the regular term is a transaction whereas the reversing "thing" is a credit/debit note. The reverse of those are transactions again. --- old/bin/aa.pl | 84 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/old/bin/aa.pl b/old/bin/aa.pl index 0addb0da12..ad332f7d79 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -167,12 +167,81 @@ sub edit { } else { $form->error("Unknown AR/AP selection value: $form->{ARAP}"); } + } + + &display_form; +} +sub reverse { + $form->{title} = $locale->text('Add'); + $form->{invnumber} .= '-VOID'; + + &create_links; + + $form->{reversing} = delete $form->{id}; + delete $form->{approved}; + $form->{reverse} = $form->{reverse} ? 0 : 1; + $form->{paidaccounts} = 0; + if ($form->{reverse}){ + if ($form->{ARAP} eq 'AR'){ + $form->{subtype} = 'credit_note'; + $form->{type} = 'transaction'; + } elsif ($form->{ARAP} eq 'AP'){ + $form->{subtype} = 'debit_note'; + $form->{type} = 'transaction'; + } else { + $form->error("Unknown AR/AP selection value: $form->{ARAP}"); + } } &display_form; } +sub post_reversing { + # we should save only the reference, sequence, transdate, description and notes; + # get the rest from the transaction being reversed. + + $form->error( + $locale->text('Cannot post transaction for a closed period!') ) + if ( $transdate and $form->is_closed( $transdate ) ); + if (not $form->{id}) { + do { + local $form->{id} = $form->{reversing}; + + # save data we want to use from the posted form, + # not from the reversed transaction. + local $form->{reversing}; + local $form->{reverse}; + local $form->{notes}; + local $form->{description}; + local $form->{transdate}; + local $form->{reference}; + local $form->{approved}; + + &create_links; # create_links overwrites 'reversing' + }; + + AA->post_transaction( \%myconfig, \%$form ); + my $query = q{UPDATE transactions SET reversing = ? WHERE id = ?}; + $form->{dbh}->do( + $query, + {}, + $form->{reversing}, + $form->{id}) + or $form->dberror($query); + } + else { + my $query = <<~'QUERY'; + UPDATE gl + SET reference = ?, + description = ?, + transdate = ?, + notes = ? + WHERE id = ? + QUERY + } +} + sub display_form { my $invnumber = "sinumber"; if ( $form->{vc} eq 'vendor' ) { @@ -546,7 +615,7 @@ sub form_header { qw(batch_id approved id printed emailed sort oldtransdate audittrail recurring checktax reverse subtype entity_control_code tax_id meta_number default_reportable - address city zipcode state country workflow_id) + address city zipcode state country workflow_id reversing) ); if ( $form->{vc} eq 'customer' ) { @@ -664,6 +733,10 @@ sub form_header { + | . + ($form->{reversing} ? qq|| . ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) .q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + qq| $employee @@ -799,7 +872,7 @@ sub form_header { } my $tax_base = $form->{invtotal}; - foreach my $item ( split / /, $form->{taxaccounts} ) { + foreach my $item ( split / /, $form->{taxaccounts} ) { $form->{"calctax_$item"} = ( $form->{"calctax_$item"} ) ? "checked" : ""; $form->{"tax_$item"} = @@ -1024,6 +1097,13 @@ sub form_footer { tooltip => ($action->short_help ? $locale->maketext($action->short_help) : '') }; } + ###TODO: Move "reversing" state to the workflow! + if ($form->{reversing}) { + delete $button{$_} for (qw(schedule update save_temp edit_and_save)); + } + if (not $form->{approved}) { + delete $button{reverse}; + } for ( sort { $button{$a}->{ndx} <=> $button{$b}->{ndx} } From 9889a4669776d9f6e907e35d27810a4a7cfebd16 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Tue, 9 Aug 2022 22:35:34 +0200 Subject: [PATCH 04/17] Mostly read-only interface for transaction reversal entry --- old/bin/aa.pl | 14 ++++++++------ .../{1.10 => 1.11}/reversed-transactions.sql | 0 sql/changes/LOADORDER | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) rename sql/changes/{1.10 => 1.11}/reversed-transactions.sql (100%) diff --git a/old/bin/aa.pl b/old/bin/aa.pl index ad332f7d79..524c4695cd 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -194,7 +194,7 @@ sub reverse { } } - &display_form; + &display_form( readonly => 1 ); } sub post_reversing { @@ -243,6 +243,7 @@ sub post_reversing { } sub display_form { + my %args = @_; my $invnumber = "sinumber"; if ( $form->{vc} eq 'vendor' ) { $invnumber = "vinumber"; @@ -254,8 +255,8 @@ sub display_form { $form->generate_selects(\%myconfig); $form->open_form; AA->get_files($form, $locale); - &form_header; - &form_footer; + &form_header( readonly => $args{readonly} ); + &form_footer( readonly => $args{readonly} ); } @@ -445,8 +446,9 @@ sub create_links { } sub form_header { + my %args = @_; my $min_lines = $form->get_setting('min_empty') // 0; - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = ($args{readonly} or $form->{approved}) ? 'readonly="readonly"' : ''; $form->generate_selects(\%myconfig) unless $form->{"select$form->{ARAP}"}; @@ -589,7 +591,7 @@ sub form_header { $employee = qq| - + @@ -830,7 +832,7 @@ sub form_header { if($form->{"taxformcheck_$i"} or ($form->{default_reportable} and ($i == $form->{rowcount}))) { $taxchecked=qq|CHECKED="CHECKED"|; - + $taxchecked.=q| disabled="disabled"| if $readonly; } $taxformcheck=qq||; diff --git a/sql/changes/1.10/reversed-transactions.sql b/sql/changes/1.11/reversed-transactions.sql similarity index 100% rename from sql/changes/1.10/reversed-transactions.sql rename to sql/changes/1.11/reversed-transactions.sql diff --git a/sql/changes/LOADORDER b/sql/changes/LOADORDER index bea5a15beb..2057b938b3 100644 --- a/sql/changes/LOADORDER +++ b/sql/changes/LOADORDER @@ -175,4 +175,4 @@ mc/delete-migration-validation-data.sql 1.11/country-config.sql 1.11/workflow-user.sql 1.11/enable-barcode-on-invoices.sql -1.10/reversed-transactions.sql +1.11/reversed-transactions.sql From 355c545ecdd0b4eb90c020abda25ca664d2cad88 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Thu, 13 Oct 2022 22:26:33 +0200 Subject: [PATCH 05/17] Address review comments regarding retention of "transdate" --- old/bin/aa.pl | 1 - old/bin/gl.pl | 1 - 2 files changed, 2 deletions(-) diff --git a/old/bin/aa.pl b/old/bin/aa.pl index 524c4695cd..b6bd88c565 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -214,7 +214,6 @@ sub post_reversing { local $form->{reverse}; local $form->{notes}; local $form->{description}; - local $form->{transdate}; local $form->{reference}; local $form->{approved}; diff --git a/old/bin/gl.pl b/old/bin/gl.pl index 630c0461a9..b761eba992 100644 --- a/old/bin/gl.pl +++ b/old/bin/gl.pl @@ -187,7 +187,6 @@ sub post_reversing { local $form->{reversing}; local $form->{notes}; local $form->{description}; - local $form->{transdate}; local $form->{reference}; local $form->{approved}; From c4fbc2103eb408e54433d8a9cfd8110fa431cf27 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Wed, 2 Nov 2022 11:48:52 +0100 Subject: [PATCH 06/17] Read-only rendering for reversing transactions --- old/bin/ir.pl | 6 ++++-- old/bin/is.pl | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/old/bin/ir.pl b/old/bin/ir.pl index 4d8d0431aa..33df759d6d 100644 --- a/old/bin/ir.pl +++ b/old/bin/ir.pl @@ -283,7 +283,8 @@ sub prepare_invoice { } sub form_header { - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = + ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; $form->{nextsub} = 'update'; $status_div_id = 'AP-invoice'; @@ -536,7 +537,8 @@ sub form_header { } sub form_footer { - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = + ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; my $manual_tax; if ($form->{approved}){ $manual_tax = diff --git a/old/bin/is.pl b/old/bin/is.pl index e9fc6a2057..b1f9f5ad2b 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -282,7 +282,8 @@ sub prepare_invoice { } sub form_header { - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = + ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; $form->{nextsub} = 'update'; $form->{ARAP} = 'AR'; $form->generate_selects(\%myconfig) unless $form->{selectAR}; @@ -631,7 +632,8 @@ sub void { } sub form_footer { - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = + ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; my $manual_tax; if ($form->{approved}){ $manual_tax = From d103ecbf7506b8aeb2eed5f6475202efb3134b4a Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sat, 22 Apr 2023 23:20:39 +0200 Subject: [PATCH 07/17] Reversal tracking and posting for AR/AP invoices --- old/bin/ir.pl | 24 ++++++++++++++++++++++++ old/bin/is.pl | 4 ++++ old/lib/LedgerSMB/IR.pm | 4 ++-- old/lib/LedgerSMB/IS.pm | 4 ++-- workflows/ar-ap.actions.xml | 6 ++++++ workflows/ar-ap.conditions.xml | 5 +++++ workflows/ar-ap.workflow.xml | 7 +++++++ 7 files changed, 50 insertions(+), 4 deletions(-) diff --git a/old/bin/ir.pl b/old/bin/ir.pl index 33df759d6d..4c48f92695 100644 --- a/old/bin/ir.pl +++ b/old/bin/ir.pl @@ -459,6 +459,10 @@ sub form_header {
+ | . + ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + qq| @@ -536,6 +540,26 @@ sub form_header { } } +sub reverse { + $form->{reverse} = 1; + $form->{paidaccounts} = 1; + if ($form->{paid_1}){ + warn $locale->text( + 'Payments associated with voided invoice may need to be reversed.' + ); + delete $form->{paid_1}; + } + $form->{reversing} = delete $form->{id}; + + my $wf = $form->{_wire}->get('workflows') + ->fetch_workflow( 'AR/AP', $form->{workflow_id} ); + $wf->execute_action( $form->{action} ); + + delete $form->{workflow_id}; + &post_as_new; +} + + sub form_footer { my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; diff --git a/old/bin/is.pl b/old/bin/is.pl index b1f9f5ad2b..d7047795c1 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -516,6 +516,10 @@ sub form_header {
+ | . + ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + qq| $employee diff --git a/old/lib/LedgerSMB/IR.pm b/old/lib/LedgerSMB/IR.pm index 2d598fc77a..8952a2a5f0 100644 --- a/old/lib/LedgerSMB/IR.pm +++ b/old/lib/LedgerSMB/IR.pm @@ -155,9 +155,9 @@ sub post_invoice { $sth->execute || $form->dberror($query); ( $form->{id} ) = $sth->fetchrow_array; - $query = q|UPDATE transactions SET workflow_id = ? WHERE id = ? AND workflow_id IS NULL|; + $query = q|UPDATE transactions SET workflow_id = ?, reversing = ? WHERE id = ? AND workflow_id IS NULL|; $sth = $dbh->prepare($query); - $sth->execute( $form->{workflow_id}, $form->{id} ) + $sth->execute( $form->{workflow_id}, $form->{reversing}, $form->{id} ) || $form->dberror($query); } diff --git a/old/lib/LedgerSMB/IS.pm b/old/lib/LedgerSMB/IS.pm index 2392cf6e0d..ec68c3e73b 100644 --- a/old/lib/LedgerSMB/IS.pm +++ b/old/lib/LedgerSMB/IS.pm @@ -759,9 +759,9 @@ sub post_invoice { ( $form->{id} ) = $sth->fetchrow_array; - $query = q|UPDATE transactions SET workflow_id = ? WHERE id = ? AND workflow_id IS NULL|; + $query = q|UPDATE transactions SET workflow_id = ?, reversing = ? WHERE id = ? AND workflow_id IS NULL|; $sth = $dbh->prepare($query); - $sth->execute( $form->{workflow_id}, $form->{id} ) + $sth->execute( $form->{workflow_id}, $form->{reversing}, $form->{id} ) || $form->dberror($query); } diff --git a/workflows/ar-ap.actions.xml b/workflows/ar-ap.actions.xml index 0c186ab05f..bfe6b9ebff 100644 --- a/workflows/ar-ap.actions.xml +++ b/workflows/ar-ap.actions.xml @@ -135,6 +135,12 @@ TODO! Check workflow when 'separate duties' is false! text="Void" history-text="Voided" class="LedgerSMB::Workflow::Action::Null" /> + + + + + diff --git a/workflows/ar-ap.workflow.xml b/workflows/ar-ap.workflow.xml index eff87cf21f..d00f52c6a0 100644 --- a/workflows/ar-ap.workflow.xml +++ b/workflows/ar-ap.workflow.xml @@ -67,6 +67,9 @@ + + + @@ -91,5 +94,9 @@ + + + + From 180afd888bb21200082e5f0f647156a5abfc194c Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 11:42:24 +0200 Subject: [PATCH 08/17] Fixes from testing --- old/bin/aa.pl | 6 +++--- old/lib/LedgerSMB/Form.pm | 7 ++++--- workflows/ar-ap.actions.xml | 6 +++--- workflows/ar-ap.conditions.xml | 4 ++-- workflows/ar-ap.workflow.xml | 3 +-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/old/bin/aa.pl b/old/bin/aa.pl index b6bd88c565..4dd0239e97 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -178,6 +178,7 @@ sub reverse { &create_links; + delete $form->{workflow_id}; $form->{reversing} = delete $form->{id}; delete $form->{approved}; $form->{reverse} = $form->{reverse} ? 0 : 1; @@ -247,7 +248,7 @@ sub display_form { if ( $form->{vc} eq 'vendor' ) { $invnumber = "vinumber"; } - $form->{sequence_select} = $form->sequence_dropdown($invnumber) + $form->{sequence_select} = $form->sequence_dropdown($invnumber, $args{readonly}) unless $form->{id} and ($form->{vc} eq 'vendor'); $form->{format} = $form->get_setting('format') unless $form->{format}; $form->close_form; @@ -462,6 +463,7 @@ sub form_header { ->create_workflow( 'AR/AP', Workflow::Context->new( 'batch-id' => $form->{batch_id}, + 'table_name' => lc($form->{ARAP}), is_transaction => 1 ) ); $form->{workflow_id} = $wf->id; @@ -1079,8 +1081,6 @@ sub form_footer { {text=> $locale->text('Transaction'), value => 'transaction'}, ] }; - %button; - $wf->context->param( _is_closed => $form->is_closed( $transdate ) ); %button_types = ( print => 'lsmb/PrintButton' diff --git a/old/lib/LedgerSMB/Form.pm b/old/lib/LedgerSMB/Form.pm index e895bd7567..a21ad45194 100644 --- a/old/lib/LedgerSMB/Form.pm +++ b/old/lib/LedgerSMB/Form.pm @@ -3120,7 +3120,7 @@ sub get_batch_description { } -=item sequence_dropdown(setting_key) +=item sequence_dropdown(setting_key, readonly) This function returns the HTML code for a dropdown box for a given setting key. It is not generally to be used with code on new templates. @@ -3128,10 +3128,11 @@ key. It is not generally to be used with code on new templates. =cut sub sequence_dropdown{ - my ($self, $setting_key) = @_; + my ($self, $setting_key, $readonly) = @_; return undef if $self->{id} and ($setting_key ne 'sinumber'); my @sequences = LedgerSMB::Setting::Sequence->list($setting_key); - my $retval = qq|\n|; $retval .= qq||; for my $seq (@sequences){ diff --git a/workflows/ar-ap.actions.xml b/workflows/ar-ap.actions.xml index bfe6b9ebff..6d443376ba 100644 --- a/workflows/ar-ap.actions.xml +++ b/workflows/ar-ap.actions.xml @@ -143,19 +143,19 @@ TODO! Check workflow when 'separate duties' is false! class="LedgerSMB::Workflow::Action::Null" /> diff --git a/workflows/ar-ap.conditions.xml b/workflows/ar-ap.conditions.xml index 8e8dc41536..b4015538b4 100644 --- a/workflows/ar-ap.conditions.xml +++ b/workflows/ar-ap.conditions.xml @@ -11,8 +11,8 @@ class="Workflow::Condition::Evaluate" /> - - + + - - + From 9f678963ede561d81bd4f0263b37973261ee30bc Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 17:29:22 +0200 Subject: [PATCH 09/17] Readonly inputs for reversed transactions, except specific headers & footers --- old/bin/aa.pl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/old/bin/aa.pl b/old/bin/aa.pl index 4dd0239e97..161ef530d8 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -195,7 +195,7 @@ sub reverse { } } - &display_form( readonly => 1 ); + &display_form; } sub post_reversing { @@ -243,20 +243,18 @@ sub post_reversing { } sub display_form { - my %args = @_; my $invnumber = "sinumber"; if ( $form->{vc} eq 'vendor' ) { $invnumber = "vinumber"; } - $form->{sequence_select} = $form->sequence_dropdown($invnumber, $args{readonly}) - unless $form->{id} and ($form->{vc} eq 'vendor'); $form->{format} = $form->get_setting('format') unless $form->{format}; $form->close_form; $form->generate_selects(\%myconfig); $form->open_form; AA->get_files($form, $locale); - &form_header( readonly => $args{readonly} ); - &form_footer( readonly => $args{readonly} ); + my $readonly = $form->{reversing} or $form->{approved}; + &form_header( readonly => $readonly ); + &form_footer( readonly => $readonly ); } @@ -449,6 +447,7 @@ sub form_header { my %args = @_; my $min_lines = $form->get_setting('min_empty') // 0; my $readonly = ($args{readonly} or $form->{approved}) ? 'readonly="readonly"' : ''; + my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; # not read only unless approved $form->generate_selects(\%myconfig) unless $form->{"select$form->{ARAP}"}; @@ -553,7 +552,7 @@ sub form_header { } $form->{notes} //= ''; $notes = -qq||; +qq||; $form->{intnotes} //= ''; $intnotes = qq||; @@ -724,6 +723,8 @@ sub form_header { ponumber)); $myconfig{dateformat} //= ''; $employee //= ''; + $form->{sequence_select} = $form->sequence_dropdown($invnumber, $readonly_headers) + unless $form->{id} and ($form->{vc} eq 'vendor'); $form->{sequence_select} //= ''; print qq| $exchangerate @@ -732,7 +733,7 @@ sub form_header { + value="| . ($form->{description} // '') . qq|" $readonly_headers />
@@ -745,7 +746,7 @@ sub form_header { $employee - + $form->{sequence_select} @@ -754,15 +755,15 @@ sub form_header { - + - + - {duedate} $readonly> + {duedate} $readonly_headers> From c6ab476829e8d88482ae118a93744012c9966640 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 17:46:42 +0200 Subject: [PATCH 10/17] Readonly inputs for reversed invoices, except specific headers & footers --- old/bin/io.pl | 2 +- old/bin/ir.pl | 14 ++++++++------ old/bin/is.pl | 14 ++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/old/bin/io.pl b/old/bin/io.pl index 5a49610210..99a092865c 100644 --- a/old/bin/io.pl +++ b/old/bin/io.pl @@ -142,7 +142,7 @@ sub approve { } sub display_row { - my $readonly = $form->{approved} ? 'readonly="readonly"' : ''; + my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; my $numrows = shift; my $min_lines = $form->get_setting('min_empty') // 0; my $lsmb_module; diff --git a/old/bin/ir.pl b/old/bin/ir.pl index 4c48f92695..1097758738 100644 --- a/old/bin/ir.pl +++ b/old/bin/ir.pl @@ -285,6 +285,7 @@ sub prepare_invoice { sub form_header { my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; + my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; $form->{nextsub} = 'update'; $status_div_id = 'AP-invoice'; @@ -455,7 +456,7 @@ sub form_header { + value="| . $form->{description} . qq|" $readonly_headers /> @@ -467,7 +468,7 @@ sub form_header { - @@ -481,15 +482,15 @@ sub form_header { - + - + - + @@ -563,6 +564,7 @@ sub reverse { sub form_footer { my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; + my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; my $manual_tax; if ($form->{approved}){ $manual_tax = @@ -599,7 +601,7 @@ sub form_footer { } $rows = ( $rows > $introws ) ? $rows : $introws; $notes = -qq||; +qq||; $intnotes = qq||; $tax = ""; diff --git a/old/bin/is.pl b/old/bin/is.pl index d7047795c1..5fd3aeadca 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -284,6 +284,7 @@ sub prepare_invoice { sub form_header { my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; + my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; $form->{nextsub} = 'update'; $form->{ARAP} = 'AR'; $form->generate_selects(\%myconfig) unless $form->{selectAR}; @@ -502,7 +503,7 @@ sub form_header { + value="| . $form->{description} . qq|" $readonly_headers /> @@ -525,7 +526,7 @@ sub form_header { $employee - + @@ -534,15 +535,15 @@ sub form_header { - + - + - + @@ -638,6 +639,7 @@ sub void { sub form_footer { my $readonly = ($form->{reversing} or $form->{approved}) ? 'readonly="readonly"' : ''; + my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; my $manual_tax; if ($form->{approved}){ $manual_tax = @@ -675,7 +677,7 @@ sub form_footer { $rows = ( $rows > $introws ) ? $rows : $introws; $form->{notes} //= ''; $notes = - qq||; + qq||; $form->{intnotes} //= ''; $intnotes = qq||; From fb08a3895e3df5fabdce7021d7c36cfdfbadef46 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 18:09:46 +0200 Subject: [PATCH 11/17] Add -VOID suffix to voiding invoices --- old/bin/arap.pl | 2 ++ old/bin/is.pl | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/old/bin/arap.pl b/old/bin/arap.pl index 4213b34efb..63e57f96d6 100644 --- a/old/bin/arap.pl +++ b/old/bin/arap.pl @@ -318,9 +318,11 @@ sub add_transaction { } sub post_as_new { + my %args = @_; $form->{old_workflow_id} = $form->{workflow_id}; for (qw(id printed emailed workflow_id invnumber)) { delete $form->{$_} } + $form->{invnumber} = $args{invnumber} // ''; my $wf = $form->{_wire}->get('workflows') ->create_workflow( 'AR/AP' ); diff --git a/old/bin/is.pl b/old/bin/is.pl index 5fd3aeadca..74db33a9a3 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -617,7 +617,7 @@ sub void { "Can't void a voided invoice!" )); } - $form->{invnumber} .= '-VOID'; + my $invnumber = $form->{invnumber} . '-VOID'; $form->{reverse} = 1; $form->{paidaccounts} = 1; if ($form->{paid_1}){ @@ -633,7 +633,7 @@ sub void { $wf->execute_action( $form->{action} ); delete $form->{workflow_id}; - &post_as_new; + &post_as_new( invnumber => $invnumber ); } sub form_footer { From 09ddee05de06ef1548e67ae68155607ba55bd67e Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 21:55:55 +0200 Subject: [PATCH 12/17] Prevent readonly rendering of orders --- old/bin/io.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/old/bin/io.pl b/old/bin/io.pl index 99a092865c..a6aa1505be 100644 --- a/old/bin/io.pl +++ b/old/bin/io.pl @@ -167,7 +167,8 @@ sub display_row { $column_data{ship} = qq||; + . qq||; + $readonly = ''; } if ( $form->{type} eq "purchase_order" ) { push @column_index, "ship"; @@ -175,6 +176,7 @@ sub display_row { qq||; + $readonly = ''; } for (qw(projectnumber partsgroup)) { From b9490b83d43b76bb21849dc0bf98f55631b21a9c Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sun, 23 Apr 2023 21:57:09 +0200 Subject: [PATCH 13/17] Reversing a credit/debit invoice should result in a sales/purchase invoice... --- old/bin/ir.pl | 2 +- old/bin/is.pl | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/old/bin/ir.pl b/old/bin/ir.pl index 1097758738..021306eb24 100644 --- a/old/bin/ir.pl +++ b/old/bin/ir.pl @@ -542,7 +542,7 @@ sub form_header { } sub reverse { - $form->{reverse} = 1; + $form->{reverse} = not $form->{reverse}; $form->{paidaccounts} = 1; if ($form->{paid_1}){ warn $locale->text( diff --git a/old/bin/is.pl b/old/bin/is.pl index 74db33a9a3..566f4f1b05 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -612,13 +612,8 @@ sub form_header { } sub void { - if ($form->{invnumber} =~ /-VOID$/){ - $form->error($locale->text( - "Can't void a voided invoice!" - )); - } my $invnumber = $form->{invnumber} . '-VOID'; - $form->{reverse} = 1; + $form->{reverse} = not $form->{reverse}; $form->{paidaccounts} = 1; if ($form->{paid_1}){ warn $locale->text( From 088adafc8bfb04230a8fe6e63ce4f27491b4a13a Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sat, 29 Apr 2023 00:06:31 +0200 Subject: [PATCH 14/17] Save the reversal status for AR/AP/GL transactions --- old/lib/LedgerSMB/AA.pm | 4 ++-- old/lib/LedgerSMB/GL.pm | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/old/lib/LedgerSMB/AA.pm b/old/lib/LedgerSMB/AA.pm index d59bb9f367..06305afccd 100644 --- a/old/lib/LedgerSMB/AA.pm +++ b/old/lib/LedgerSMB/AA.pm @@ -366,9 +366,9 @@ sub post_transaction { $sth = $dbh->prepare($query) or $form->dberror($query); $sth->execute(@queryargs) or $form->dberror($query); ($form->{id}) = $sth->fetchrow_array() or $form->dberror($query); - $query = q|UPDATE transactions SET workflow_id = ? WHERE id = ? AND workflow_id IS NULL|; + $query = q|UPDATE transactions SET workflow_id = ?, reversing = ? WHERE id = ? AND workflow_id IS NULL|; $sth = $dbh->prepare($query); - $sth->execute( $form->{workflow_id}, $form->{id} ) + $sth->execute( $form->{workflow_id}, $form->{reversing}, $form->{id} ) || $form->dberror($query); if (defined $form->{approved}) { diff --git a/old/lib/LedgerSMB/GL.pm b/old/lib/LedgerSMB/GL.pm index 13d4b0a4ae..3df396529a 100644 --- a/old/lib/LedgerSMB/GL.pm +++ b/old/lib/LedgerSMB/GL.pm @@ -119,6 +119,11 @@ sub post_transaction { || $form->dberror($query); ( $form->{id} ) = $sth->fetchrow_array(); + $query = q|UPDATE transactions SET reversing = ? WHERE id = ? AND workflow_id IS NULL|; + $sth = $dbh->prepare($query); + $form->{reversing} ||= undef; # convert empty string to NULL + $sth->execute( $form->{reversing}, $form->{id} ) + || $form->dberror($query); } ( $null, $department_id ) = From a34e60ae0653f62ef6c0905bed55cd07953429e6 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sat, 29 Apr 2023 00:18:15 +0200 Subject: [PATCH 15/17] Implement searching for voided transactions/invoices While at it, harmonize the AR/AP and GL search pages. --- UI/Reports/filters/gl.html | 93 +++++++++------ UI/Reports/filters/invoice_search.html | 108 +++++++++++------- lib/LedgerSMB/Report/GL.pm | 4 +- lib/LedgerSMB/Report/Invoices/Transactions.pm | 5 +- lib/LedgerSMB/Report/Voided_Option.pm | 62 ++++++++++ sql/modules/Report.sql | 36 +++++- xt/42-report.pg | 8 +- 7 files changed, 228 insertions(+), 88 deletions(-) create mode 100644 lib/LedgerSMB/Report/Voided_Option.pm diff --git a/UI/Reports/filters/gl.html b/UI/Reports/filters/gl.html index 828dad0be1..838f5fb3c7 100644 --- a/UI/Reports/filters/gl.html +++ b/UI/Reports/filters/gl.html @@ -111,10 +111,47 @@ } %] - + + + + +
+ | . $form->sequence_dropdown('vinumber') . qq|
| . $locale->text('State') . qq|
| . $form->sequence_dropdown('sinumber') . qq|| . $form->sequence_dropdown('sinumber', $readonly_headers) . qq|
| . $locale->text('Ship') - . qq|| . $locale->text('Recd') . qq|
[% text('Include in Report') %][% text('Transaction status') %] + + + + + + + + - - - - - - - - + +
+ [% PROCESS input element_data = { + name = 'is_approved' + type = 'radio' + value = 'All' + checked = 'CHECKED' + }; %] [% text('All'); %][% PROCESS input element_data = { + name = 'is_approved' + type = 'radio' + value = 'Y' + }; %] [% text('Approved'); %] + [% PROCESS input element_data = { + name = 'is_approved' + type = 'radio' + value = 'N' + }; %] [% text('Unapproved'); %]
[% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'All' + checked = 'CHECKED' + }; %] [% text('All'); %][% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'Y' + }; %] [% text('Reversed'); %] + [% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'N' + }; %] [% text('Non-reversed'); %]
[% PROCESS input element_data = { @@ -125,7 +162,7 @@ value="X" checked = "checked" } %] [% text('All') %] + [% PROCESS input element_data = { name="category" id="category-asset" @@ -133,7 +170,7 @@ type="radio" value="A" } %] [% text('Asset') %] + [% PROCESS input element_data = { name="category" id="category-liability" @@ -141,7 +178,7 @@ type="radio" value="L" } %] [% text('Liability') %] + [% PROCESS input element_data = { name="category" id="category-equity" @@ -149,7 +186,7 @@ type="radio" value="Q" } %] [% text('Equity') %] + [% PROCESS input element_data = { name="category" id="category-income" @@ -157,7 +194,7 @@ type="radio" value="I" } %] [% text('Income') %] + [% PROCESS input element_data = { name="category" id="category-expense" @@ -165,29 +202,16 @@ type="radio" value="E" } %] [% text('Expense') %] -
[% PROCESS input element_data = { - name = 'is_approved' - type = 'radio' - value = 'Y' - checked = 'CHECKED' - }; %] [% text('Approved'); %] - [% PROCESS input element_data = { - name = 'is_approved' - type = 'radio' - value = 'N' - }; %] [% text('Unapproved'); %] - [% PROCESS input element_data = { - name = 'is_approved' - type = 'radio' - value = 'All' - }; %] [% text('All'); %]
+
[% text('Include in Report') %] + -
- - - + + - - + + - - + + - -
[% PROCESS input element_data = { name="col_id" class="checkbox" @@ -222,8 +246,8 @@ value="1" checked="checked" } %] [% text('Description') %]
[% PROCESS input element_data = { name="col_debits" class="checkbox" @@ -256,8 +280,8 @@ type="checkbox" value="1" } %] [% text('FX Credit') %]
[% PROCESS input element_data = { name="col_source" class="checkbox" @@ -283,8 +307,8 @@ type="checkbox" value="1" } %] [% text('Till') %]
[% PROCESS input element_data = { name="col_accno" class="checkbox" @@ -316,13 +340,10 @@ type="checkbox" value="1" } %] [% text('Subtotal') %]
- diff --git a/UI/Reports/filters/invoice_search.html b/UI/Reports/filters/invoice_search.html index a9743cc3b5..399d260a1e 100644 --- a/UI/Reports/filters/invoice_search.html +++ b/UI/Reports/filters/invoice_search.html @@ -167,44 +167,39 @@ } %] [% PROCESS date_row %] - - [% text('Invoice Status') %] - [% label_pos = 1; PROCESS input element_data = { - label = text('All') - type = 'radio' - name = 'on_hold' - id = 'on_hold-all' - value = '' - checked = 'CHECKED' - }; - PROCESS input element_data = { - label = text('Active') - type = 'radio' - name = 'on_hold' - id = 'on_hold-active' - value = '0' - }; - PROCESS input element_data = { - label = text('On Hold') #' - type = 'radio' - name = 'on_hold' - id = 'on_hold-hold' - value = '1' - }; %] - - -[% text('Include in Report') %] - -   - + [% text('Invoice Status') %] + + + + + + @@ -226,7 +222,6 @@ type = 'radio' value = 'Y' label = text('Approved') - checked = 'CHECKED' }; %] + + + + + +
[% label_pos = 1; PROCESS input element_data = { + label = text('Active') + type = 'radio' + name = 'on_hold' + id = 'on_hold-active' + value = '0' + }; %][% PROCESS input element_data = { + label = text('On Hold') + type = 'radio' + name = 'on_hold' + id = 'on_hold-hold' + value = '1' + }; %][% PROCESS input element_data = { + label = text('All') + type = 'radio' + name = 'on_hold' + id = 'on_hold-all' + value = '' + checked = 'CHECKED' + }; %]
[% PROCESS input element_data = { name = 'oc_state' type = 'radio' value = 'open' - checked = 'CHECKED' label = text('Open') } %] [% PROCESS input element_data = { @@ -218,6 +213,7 @@ type = 'radio' value = 'all' label = text('All') + checked = 'CHECKED' } %]
[% PROCESS input element_data = { name = 'is_approved' @@ -239,8 +234,37 @@ type = 'radio' value = 'All' label = text('All') + checked = 'CHECKED' }; %]
[% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'Y' + label = text('Voided') + }; %][% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'N' + label = text('Unvoided') + }; %][% PROCESS input element_data = { + name = 'is_voided' + type = 'radio' + value = 'All' + label = text('All') + checked = 'CHECKED' + }; %]
+ + + + [% text('Include in Report') %] + + @@ -280,14 +304,14 @@ name = 'col_transdate' type = 'checkbox' value = '1' - label = text('Invoice Date') #' + label = text('Invoice Date') checked = 'CHECKED' } %] @@ -354,13 +378,13 @@ name = 'col_due_date' type = 'checkbox' value = '1' - label = text('Due Date') #' + label = text('Due Date') } %] diff --git a/lib/LedgerSMB/Report/GL.pm b/lib/LedgerSMB/Report/GL.pm index d247a67320..cfa32f8764 100644 --- a/lib/LedgerSMB/Report/GL.pm +++ b/lib/LedgerSMB/Report/GL.pm @@ -31,7 +31,9 @@ use LedgerSMB::Report; use Moose; use namespace::autoclean; extends 'LedgerSMB::Report'; -with 'LedgerSMB::Report::Dates', 'LedgerSMB::Report::Approval_Option'; +with 'LedgerSMB::Report::Dates', + 'LedgerSMB::Report::Approval_Option', + 'LedgerSMB::Report::Voided_Option'; =head1 PROPERTIES diff --git a/lib/LedgerSMB/Report/Invoices/Transactions.pm b/lib/LedgerSMB/Report/Invoices/Transactions.pm index e0f108713e..191ae0298d 100644 --- a/lib/LedgerSMB/Report/Invoices/Transactions.pm +++ b/lib/LedgerSMB/Report/Invoices/Transactions.pm @@ -19,7 +19,8 @@ extends 'LedgerSMB::Report'; with 'LedgerSMB::Report::Dates', 'LedgerSMB::Report::Approval_Option', - 'LedgerSMB::Report::OpenClosed_Option'; + 'LedgerSMB::Report::OpenClosed_Option', + 'LedgerSMB::Report::Voided_Option'; =head1 DESCRIPTION @@ -187,7 +188,7 @@ has '+order_by' => (default => 'transdate'); =back -=head1 INTERNLS +=head1 INTERNALS =head2 columns diff --git a/lib/LedgerSMB/Report/Voided_Option.pm b/lib/LedgerSMB/Report/Voided_Option.pm new file mode 100644 index 0000000000..29dcc85a56 --- /dev/null +++ b/lib/LedgerSMB/Report/Voided_Option.pm @@ -0,0 +1,62 @@ +package LedgerSMB::Report::Voided_Option; + +=head1 NAME + +LedgerSMB::Report::Voided_Option - Report interface for voided selection + +=head1 DESCRIPTION + +This moose role adds an Cd user-settable flag (Y/N/All) +which is mapped to a boolean (True/False/Null). Null is used to request all. + +=head1 ADDED PROPERTIES + +=head2 is_voided string + +Y, N, All + +=head2 voided bool + +mapped from is_voided + +=cut + +use Moose::Role; +use namespace::autoclean; + +has is_voided => (is => 'ro', isa => 'Str', + default => 'Y'); +has voided => (is => 'ro', lazy => 1, + builder => '_voided'); + +my $_voided_map = { + Y => 1, + N => 0, + All => undef +}; + +=head1 METHODS + +This module does not declare any (public) methods. + +=cut + + +sub _voided { + my $self = shift; + die 'Bad approval code: ' . $self->is_voided + unless exists $_voided_map->{$self->is_voided}; + return $_voided_map->{$self->is_voided} +} + +=head1 LICENSE AND COPYRIGHT + +Copyright (C) 2016-2023 The LedgerSMB Core Team + +This file is licensed under the GNU General Public License version 2, or at your +option any later version. A copy of the license should have been included with +your software. + +=cut + +1; diff --git a/sql/modules/Report.sql b/sql/modules/Report.sql index 02a589be86..3a2f5588ba 100644 --- a/sql/modules/Report.sql +++ b/sql/modules/Report.sql @@ -247,10 +247,16 @@ CREATE TYPE gl_report_item AS ( business_units int[] ); -CREATE OR REPLACE FUNCTION report__gl +DROP FUNCTION IF EXISTS report__gl (in_reference text, in_accno text, in_category char(1), in_source text, in_memo text, in_description text, in_from_date date, in_to_date date, in_approved bool, in_from_amount numeric, in_to_amount numeric, +in_business_units int[]); + +CREATE OR REPLACE FUNCTION report__gl +(in_reference text, in_accno text, in_category char(1), +in_source text, in_memo text, in_description text, in_from_date date, +in_to_date date, in_approved bool, in_voided bool, in_from_amount numeric, in_to_amount numeric, in_business_units int[]) RETURNS SETOF gl_report_item STABLE AS $$ @@ -332,6 +338,11 @@ FOR retval IN or (in_approved is false and (g.approved is false or ac.approved is false))) + AND (in_voided is null + or in_voided is not distinct from (exists (select 1 + from transactions t + where t.approved + and t.reversing = g.id))) AND (in_from_amount IS NULL OR abs(ac.amount_bc) >= in_from_amount) AND (in_to_amount IS NULL @@ -542,6 +553,14 @@ DROP FUNCTION IF EXISTS report__aa_transactions in_shipvia text, in_from_date date, in_to_date date, in_on_hold bool, in_taxable bool, in_tax_account_id int, in_open bool, in_closed bool, in_approved bool); +DROP FUNCTION IF EXISTS report__aa_transactions +(in_entity_class int, in_account_id int, in_entity_name text, + in_meta_number text, + in_employee_id int, in_manager_id int, in_invnumber text, in_ordnumber text, + in_ponumber text, in_source text, in_description text, in_notes text, + in_shipvia text, in_from_date date, in_to_date date, in_on_hold bool, + in_taxable bool, in_tax_account_id int, in_open bool, in_closed bool, + in_approved bool, in_partnumber text); CREATE OR REPLACE FUNCTION report__aa_transactions (in_entity_class int, in_account_id int, in_entity_name text, in_meta_number text, @@ -549,7 +568,7 @@ CREATE OR REPLACE FUNCTION report__aa_transactions in_ponumber text, in_source text, in_description text, in_notes text, in_shipvia text, in_from_date date, in_to_date date, in_on_hold bool, in_taxable bool, in_tax_account_id int, in_open bool, in_closed bool, - in_approved bool, in_partnumber text) + in_approved bool, in_voided bool, in_partnumber text) RETURNS SETOF aa_transactions_line AS $$ BEGIN @@ -642,19 +661,26 @@ SELECT a.id, a.invoice, eeca.id, eca.meta_number::text, eeca.name, OR ($20 IS TRUE AND ( a.force_closed IS NOT TRUE AND abs(p.due) > 0.005) IS NOT TRUE) ) + AND ( + $22 IS NULL + OR $22 IS NOT DISTINCT FROM (EXISTS (SELECT 1 + FROM transactions t + WHERE t.approved + AND t.reversing = a.id)) + ) AND -- by partnumber - ($22 IS NULL + ($23 IS NULL OR a.id IN ( select i.trans_id FROM invoice i JOIN parts p ON i.parts_id = p.id - WHERE p.partnumber = $22)) + WHERE p.partnumber = $23)) $sql$ USING in_entity_class, in_account_id, in_entity_name, in_meta_number, in_employee_id, in_manager_id, in_invnumber, in_ordnumber, in_ponumber, in_source, in_description, in_notes, in_shipvia, in_from_date, in_to_date, in_on_hold, in_taxable, in_tax_account_id, in_open, in_closed, - in_approved, in_partnumber; + in_approved, in_voided, in_partnumber; END $$ LANGUAGE PLPGSQL; diff --git a/xt/42-report.pg b/xt/42-report.pg index bbde212b74..c367f448e9 100644 --- a/xt/42-report.pg +++ b/xt/42-report.pg @@ -20,9 +20,9 @@ BEGIN; -- SELECT has_function('business_type__list','{}'::text[]); SELECT has_function('report__aa_outstanding',ARRAY['integer','integer','text','text','integer','integer[]','text','boolean','date','date','text','integer']); SELECT has_function('report__aa_outstanding_details',ARRAY['integer','integer','text','text','integer','integer[]','text','boolean','date','date','text','integer']); - SELECT has_function('report__aa_transactions',ARRAY['integer','integer','text','text','integer','integer','text','text','text','text','text','text','text','date','date','boolean','boolean','integer','boolean','boolean','boolean','text']); + SELECT has_function('report__aa_transactions',ARRAY['integer','integer','text','text','integer','integer','text','text','text','text','text','text','text','date','date','boolean','boolean','integer','boolean','boolean','boolean','boolean','text']); SELECT has_function('report__general_balance',ARRAY['date','date']); - SELECT has_function('report__gl',ARRAY['text','text','character','text','text','text','date','date','boolean','numeric','numeric','integer[]']); + SELECT has_function('report__gl',ARRAY['text','text','character','text','text','text','date','date','boolean','boolean','numeric','numeric','integer[]']); SELECT has_function('report__incoming_cogs_line',ARRAY['date','date','text','text']); SELECT has_function('report__invoice_aging_detail',ARRAY['integer','integer','integer','text','date','integer[]','boolean','text']); SELECT has_function('report__invoice_aging_summary',ARRAY['integer','integer','integer','text','date','integer[]','boolean','text']); @@ -50,6 +50,7 @@ COPY acc_trans(trans_id,chart_id,amount_bc,curr,amount_tc,transdate,approved,ent '1899-12-31'::date, -- from_date '1900-01-01'::date, -- to_date NULL, -- approved + NULL, -- voided NULL, NULL, -- from_amount, to_amount NULL -- business_units ); @@ -65,6 +66,7 @@ COPY acc_trans(trans_id,chart_id,amount_bc,curr,amount_tc,transdate,approved,ent NULL, -- from_date '1900-01-01'::date, -- to_date NULL, -- approved + NULL, -- voided NULL, NULL, -- from_amount, to_amount NULL -- business_units ); @@ -80,6 +82,7 @@ COPY acc_trans(trans_id,chart_id,amount_bc,curr,amount_tc,transdate,approved,ent '1900-01-01'::date, -- from_date '1900-01-02'::date, -- to_date NULL, -- approved + NULL, -- voided NULL, NULL, -- from_amount, to_amount NULL -- business_units ); @@ -95,6 +98,7 @@ COPY acc_trans(trans_id,chart_id,amount_bc,curr,amount_tc,transdate,approved,ent '1900-01-02'::date, -- from_date '1900-01-03'::date, -- to_date NULL, -- approved + NULL, -- voided NULL, NULL, -- from_amount, to_amount NULL -- business_units ); From e43b51f795bf9c5ad7eac18e22400aa60da6fac4 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sat, 29 Apr 2023 22:46:27 +0200 Subject: [PATCH 16/17] Mention reversed transactions by reference+ID instead of ID alone --- UI/journal/journal_entry.html | 6 +-- old/bin/aa.pl | 4 +- old/bin/gl.pl | 1 + old/bin/ir.pl | 4 +- old/bin/is.pl | 4 +- old/lib/LedgerSMB/Form.pm | 3 +- old/lib/LedgerSMB/GL.pm | 2 +- sql/changes/1.11/reversed-transactions.sql | 31 +++++++++++--- sql/modules/triggers.sql | 50 +++++++++++++--------- 9 files changed, 69 insertions(+), 36 deletions(-) diff --git a/UI/journal/journal_entry.html b/UI/journal/journal_entry.html index d67214fd60..bd2ef75519 100644 --- a/UI/journal/journal_entry.html +++ b/UI/journal/journal_entry.html @@ -121,15 +121,15 @@
[% PROCESS input element_data = { name = 'col_running_number' @@ -259,20 +283,20 @@ name = 'col_invnumber' type = 'checkbox' value = '1' - label = text('Invoice Number') #' + label = text('Invoice Number') checked = 'CHECKED' } %] [% PROCESS input element_data = { name = 'col_ordnumber' type = 'checkbox' value = '1' - label = text('Order Number') #' + label = text('Order Number') } %] [% PROCESS input element_data = { name = 'col_ponumber' type = 'checkbox' value = '1' - label = text('PO Number') #' + label = text('PO Number') } %]
[% PROCESS input element_data = { name = 'col_business_units' type = 'checkbox' value = '1' - label = text('Business Units') #' + label = text('Business Units') } %] [% PROCESS input element_data = { name = 'col_entity_name' @@ -339,7 +363,7 @@ name = 'col_last_payment' type = 'checkbox' value = '1' - label = text('Date Paid') #' + label = text('Date Paid') } %]
[% PROCESS input element_data = { name = 'col_due' type = 'checkbox' value = '1' - label = text('Amount Due') #' + label = text('Amount Due') checked = 'CHECKED' } %] [% PROCESS input element_data = { @@ -381,13 +405,13 @@ name = 'col_shipping_point' type = 'checkbox' value = '1' - label = text('Shipping Point') #' + label = text('Shipping Point') } %] [% PROCESS input element_data = { name = 'col_ship_via' type = 'checkbox' value = '1' - label = text('Ship Via') #' + label = text('Ship Via') } %]
[% IF form.reversed_by ; - text('This transaction has been reversed by transaction [_1]', form.reversed_by); + text('This transaction has been reversed by transaction [_1] with ID [_2]', form.reversed_by_reference, form.reversed_by); END %]
[% IF form.reversing ; IF form.approved ; - text('This transaction reverses transaction [_1]', form.reversing); + text('This transaction reverses transaction [_1] with ID [_2]', form.reversing_reference, form.reversing); ELSE ; - text('This transaction will reverse transaction [_1]', form.reversing); + text('This transaction will reverse transaction [_1] with ID [_2]', form.reversing_reference, form.reversing); END ; END %] diff --git a/old/bin/aa.pl b/old/bin/aa.pl index 161ef530d8..bfeadd610f 100644 --- a/old/bin/aa.pl +++ b/old/bin/aa.pl @@ -738,8 +738,8 @@ sub form_header {
| . - ($form->{reversing} ? qq|| . ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) .q|
| : '') . - ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + ($form->{reversing} ? qq|| . ($form->{approved} ? $locale->text('This transaction reverses transaction [_1] with ID [_2]', $form->{reversing_reference}, $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) .q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1] with ID [_2]', $form->{reversed_by_reference}, $form->{reversed_by}) . q|| : '') . qq| diff --git a/old/bin/gl.pl b/old/bin/gl.pl index b761eba992..ac78da7dc7 100644 --- a/old/bin/gl.pl +++ b/old/bin/gl.pl @@ -166,6 +166,7 @@ sub reverse { _reverse_amounts(); $form->{reversing} = delete $form->{id}; + $form->{reversing_reference} = $form->{reference}; delete $form->{approved}; display_form(); diff --git a/old/bin/ir.pl b/old/bin/ir.pl index 021306eb24..57129173bd 100644 --- a/old/bin/ir.pl +++ b/old/bin/ir.pl @@ -461,8 +461,8 @@ sub form_header {
| . - ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . - ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1] with ID [_2]', $form->{reversing_reference}, $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1] with ID [_2]', $form->{reversed_by_reference}, $form->{reversed_by}) . q|| : '') . qq| diff --git a/old/bin/is.pl b/old/bin/is.pl index 566f4f1b05..abd9e84d41 100644 --- a/old/bin/is.pl +++ b/old/bin/is.pl @@ -518,8 +518,8 @@ sub form_header {
| . - ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1]', $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . - ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1]', $form->{reversed_by}) . q|| : '') . + ($form->{reversing} ? qq||. ($form->{approved} ? $locale->text('This transaction reverses transaction [_1] with ID [_2]', $form->{reversing_reference}, $form->{reversing}) : $locale->text('This transaction will reverse transaction [_1]', $form->{reversing})) . q|
| : '') . + ($form->{reversed_by} ? qq| | . $locale->text('This transaction is reversed by transaction [_1] with ID [_2]', $form->{reversed_by_reference}, $form->{reversed_by}) . q|| : '') . qq| diff --git a/old/lib/LedgerSMB/Form.pm b/old/lib/LedgerSMB/Form.pm index a21ad45194..6192f178a5 100644 --- a/old/lib/LedgerSMB/Form.pm +++ b/old/lib/LedgerSMB/Form.pm @@ -1977,7 +1977,8 @@ sub create_links { a.approved, ctf.default_reportable, a.description, a.on_hold, a.crdate, ns.location_id as locationid, a.is_return, $seq, - t.workflow_id, t.reversing, t.reversed_by + t.workflow_id, t.reversing, t.reversing_reference, + t.reversed_by, t.reversed_by_reference FROM $arap a JOIN transactions_reversal t ON t.id = a.id JOIN entity_credit_account c diff --git a/old/lib/LedgerSMB/GL.pm b/old/lib/LedgerSMB/GL.pm index 3df396529a..22bdae2daf 100644 --- a/old/lib/LedgerSMB/GL.pm +++ b/old/lib/LedgerSMB/GL.pm @@ -274,7 +274,7 @@ sub transaction { @{$form->{currencies}} = (LedgerSMB::Setting->new(%$form))->get_currencies; - $query = qq|SELECT g.*, t.reversing, t.reversed_by + $query = qq|SELECT g.*, t.reversing, t.reversing_reference, t.reversed_by, t.reversed_by_reference FROM gl g JOIN transactions_reversal t on g.id = t.id WHERE g.id = ?|; diff --git a/sql/changes/1.11/reversed-transactions.sql b/sql/changes/1.11/reversed-transactions.sql index 5c4e7a850d..7e60929900 100644 --- a/sql/changes/1.11/reversed-transactions.sql +++ b/sql/changes/1.11/reversed-transactions.sql @@ -1,12 +1,33 @@ alter table transactions - add column reversing int; + add column reversing int, + add column reference text; + +update transactions t + set reference = ar.invnumber + from ar + where t.id = ar.id; + +update transactions t + set reference = ap.invnumber + from ap + where t.id = ap.id; + +update transactions t + set reference = gl.reference + from gl + where t.id = gl.id; + create unique index transactions_reversing_idx on transactions ( reversing ) where reversing is not null; create view transactions_reversal as - select t.*, (select i.id from transactions i - where i.approved and i.reversing = t.id) as reversed_by - from transactions t; - + select t.*, + i.id as reversed_by, i.reference as reversed_by_reference, + j.reference as reversing_reference + from transactions t + left join transactions i + on t.id = i.reversing + left join transactions j + on t.reversing = j.id; diff --git a/sql/modules/triggers.sql b/sql/modules/triggers.sql index f7bbc8e89e..d7eb1b02fa 100644 --- a/sql/modules/triggers.sql +++ b/sql/modules/triggers.sql @@ -5,28 +5,38 @@ set client_min_messages = 'warning'; BEGIN; -CREATE OR REPLACE FUNCTION track_global_sequence() - RETURNS trigger AS +CREATE OR REPLACE FUNCTION track_global_sequence() RETURNS trigger AS $BODY$ + DECLARE + t_new_reference text; + t_old_reference text; BEGIN - IF tg_op = 'INSERT' THEN - INSERT INTO transactions (id, table_name, transdate, approved) - VALUES (new.id, TG_RELNAME, new.transdate, new.approved); - ELSEIF tg_op = 'UPDATE' THEN - IF new.id = old.id - AND new.approved = old.approved - AND new.transdate = old.transdate THEN - return new; - ELSE - UPDATE transactions SET id = new.id, - approved = new.approved, - transdate = new.transdate - WHERE id = old.id; - END IF; - ELSE - DELETE FROM transactions WHERE id = old.id; - END IF; - RETURN new; + if tg_relname in ('ar','ap') then + t_new_reference := new.invnumber; + t_old_reference := old.invnumber; + else + t_new_reference := new.reference; + t_old_reference := old.reference; + end if; + IF tg_op = 'INSERT' THEN + INSERT INTO transactions (id, table_name, transdate, approved, reference) + VALUES (new.id, TG_RELNAME, new.transdate, new.approved, t_new_reference); + ELSEIF tg_op = 'UPDATE' THEN + IF new.id <> old.id + OR new.approved <> old.approved + OR new.transdate <> old.transdate + OR t_new_reference <> t_old_reference THEN + UPDATE transactions + SET id = new.id, + approved = new.approved, + transdate = new.transdate, + reference = t_new_reference + WHERE id = old.id; + END IF; + ELSE + DELETE FROM transactions WHERE id = old.id; + END IF; + RETURN new; END; $BODY$ LANGUAGE plpgsql; From 1bd698b341b1968ad6603fa8ac9086ed146fb4f1 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Sat, 29 Apr 2023 22:47:59 +0200 Subject: [PATCH 17/17] On read-only lines, don't display the "delete line" button --- old/bin/io.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/old/bin/io.pl b/old/bin/io.pl index a6aa1505be..1752c63228 100644 --- a/old/bin/io.pl +++ b/old/bin/io.pl @@ -421,7 +421,7 @@ sub display_row { if ($form->{"partnumber_$i"}){ $column_data{deleteline} = qq|
|; - if (not $form->{approved}) { + if (not $form->{approved} and not $readonly) { $column_data{deleteline} .= qq|