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 @@
} %]
+ [% text('Include in Report') %] |
+
+
[% 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')
} %] |
@@ -280,14 +304,14 @@
name = 'col_transdate'
type = 'checkbox'
value = '1'
- label = text('Invoice Date') #'
+ label = text('Invoice Date')
checked = 'CHECKED'
} %]
[% 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')
} %] |
@@ -354,13 +378,13 @@
name = 'col_due_date'
type = 'checkbox'
value = '1'
- label = text('Due Date') #'
+ label = text('Due Date')
} %]
[% 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')
} %] |
diff --git a/UI/journal/journal_entry.html b/UI/journal/journal_entry.html
index 8d3f4f6fb8..bd2ef75519 100644
--- a/UI/journal/journal_entry.html
+++ b/UI/journal/journal_entry.html
@@ -12,7 +12,7 @@
action="[% form.script %]">
- [% form.title %] |
+ [% form.title %] |
@@ -119,10 +119,25 @@
+
+ [% IF 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] with ID [_2]', form.reversing_reference, form.reversing);
+ ELSE ;
+ text('This transaction will reverse transaction [_1] with ID [_2]', form.reversing_reference, 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/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/old/bin/aa.pl b/old/bin/aa.pl
index 0addb0da12..bfeadd610f 100644
--- a/old/bin/aa.pl
+++ b/old/bin/aa.pl
@@ -167,26 +167,94 @@ 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;
+
+ delete $form->{workflow_id};
+ $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->{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' ) {
$invnumber = "vinumber";
}
- $form->{sequence_select} = $form->sequence_dropdown($invnumber)
- 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;
- &form_footer;
+ my $readonly = $form->{reversing} or $form->{approved};
+ &form_header( readonly => $readonly );
+ &form_footer( readonly => $readonly );
}
@@ -376,8 +444,10 @@ 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"' : '';
+ my $readonly_headers = $form->{approved} ? 'readonly="readonly"' : ''; # not read only unless approved
$form->generate_selects(\%myconfig) unless $form->{"select$form->{ARAP}"};
@@ -392,6 +462,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;
@@ -481,7 +552,7 @@ sub form_header {
}
$form->{notes} //= '';
$notes =
-qq||;
+qq||;
$form->{intnotes} //= '';
$intnotes =
qq||;
@@ -520,7 +591,7 @@ sub form_header {
$employee = qq|
|
- |
+ |
@@ -546,7 +617,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' ) {
@@ -652,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
@@ -660,16 +733,20 @@ sub form_header {
|
|
+ value="| . ($form->{description} // '') . qq|" $readonly_headers /> |
|
+ | .
+ ($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| |
$employee
|
-
+ |
$form->{sequence_select} |
@@ -678,15 +755,15 @@ sub form_header {
|
- |
+ |
|
- |
+ |
|
- {duedate} $readonly> |
+ {duedate} $readonly_headers> |
|
@@ -757,7 +834,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| | |;
@@ -799,7 +876,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"} =
@@ -1005,8 +1082,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'
@@ -1024,6 +1099,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} }
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/gl.pl b/old/bin/gl.pl
index e1c851a8d8..ac78da7dc7 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};
+ $form->{reversing_reference} = $form->{reference};
+ 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->{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/bin/io.pl b/old/bin/io.pl
index 5a49610210..1752c63228 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;
@@ -167,7 +167,8 @@ sub display_row {
$column_data{ship} =
qq||
. $locale->text('Ship')
- . qq| | |;
+ . qq||;
+ $readonly = '';
}
if ( $form->{type} eq "purchase_order" ) {
push @column_index, "ship";
@@ -175,6 +176,7 @@ sub display_row {
qq||
. $locale->text('Recd')
. qq| | |;
+ $readonly = '';
}
for (qw(projectnumber partsgroup)) {
@@ -419,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|
| |