Bug 10401: Add ability to merge invoices
Given how easy it is to accidentally receive items from one invoice on
multiple invoices, the ability to merge invoices can be quite handy.
This patch adds that ability to Koha's Acquisitions module.
To test:
1) Apply patch.
2) Run unit test:
> prove t/db_dependent/Acquisition/Invoices.t
3) Create two invoices from the same vendor for merging, and receive at
least one order on each.
4) Do a search on the Invoices page that brings up both the invoices you
created.
5) Check the boxes next to the two invoices.
6) Click "Merge selected invoices."
7) Choose which invoice you want to keep (the default will be the first).
8) Click "Merge."
9) Confirm that the resulting invoice has all the orders you received
listed on it.
10) Sign off.
Signed-off-by: Paola Rossi <paola.rossi@cineca.it>
Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Passes all tests and QA script.
Merged several invoices sucessfully - with and without received
orders, open and closed. Works nicely.
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
This commit is contained in:
parent
481c062fe9
commit
88b46f3422
|
|
@ -72,6 +72,7 @@ BEGIN {
|
|||
&CloseInvoice
|
||||
&ReopenInvoice
|
||||
&DelInvoice
|
||||
&MergeInvoices
|
||||
|
||||
&GetItemnumbersFromOrder
|
||||
|
||||
|
|
@ -2574,6 +2575,31 @@ sub DelInvoice {
|
|||
return;
|
||||
}
|
||||
|
||||
=head3 MergeInvoices
|
||||
|
||||
MergeInvoices($invoiceid, \@sourceids);
|
||||
|
||||
Merge the invoices identified by the IDs in \@sourceids into
|
||||
the invoice identified by $invoiceid.
|
||||
|
||||
=cut
|
||||
|
||||
sub MergeInvoices {
|
||||
my ($invoiceid, $sourceids) = @_;
|
||||
|
||||
return unless $invoiceid;
|
||||
foreach my $sourceid (@$sourceids) {
|
||||
next if $sourceid == $invoiceid;
|
||||
my $source = GetInvoiceDetails($sourceid);
|
||||
foreach my $order (@{$source->{'orders'}}) {
|
||||
$order->{'invoiceid'} = $invoiceid;
|
||||
ModOrder($order);
|
||||
}
|
||||
DelInvoice($source->{'invoiceid'});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ elsif ( $op && $op eq 'mod' ) {
|
|||
ReopenInvoice($invoiceid);
|
||||
} elsif ($input->param('close')) {
|
||||
CloseInvoice($invoiceid);
|
||||
} elsif ($input->param('merge')) {
|
||||
my @sources = $input->param('merge');
|
||||
MergeInvoices($invoiceid, \@sources);
|
||||
}
|
||||
$template->param( modified => 1 );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ use C4::Output;
|
|||
use C4::Acquisition qw/GetInvoices/;
|
||||
use C4::Bookseller qw/GetBookSeller/;
|
||||
use C4::Branch qw/GetBranches/;
|
||||
use C4::Budgets;
|
||||
|
||||
my $input = CGI->new;
|
||||
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
|
||||
|
|
@ -121,6 +122,14 @@ foreach ( sort keys %$branches ) {
|
|||
};
|
||||
}
|
||||
|
||||
my $budgets = GetBudgets();
|
||||
my @budgets_loop;
|
||||
foreach my $budget (@$budgets) {
|
||||
push @budgets_loop, $budget if CanUserUseBudget( $loggedinuser, $budget, $flags );
|
||||
}
|
||||
|
||||
$template->{'VARS'}->{'budgets_loop'} = \@budgets_loop;
|
||||
|
||||
$template->param(
|
||||
do_search => ( $op and $op eq 'do_search' ) ? 1 : 0,
|
||||
invoices => $invoices,
|
||||
|
|
|
|||
|
|
@ -1564,11 +1564,11 @@ div#header_search input.submit {
|
|||
padding : .1em;
|
||||
}
|
||||
|
||||
input[type=submit]:active, input[type=button]:active, button.submit:active {
|
||||
input[type=submit]:active, input[type=button]:active, button.submit:active, a.submit:active {
|
||||
border : 1px inset #999999;
|
||||
}
|
||||
|
||||
input[type=submit], input[type=reset], input[type=button], input.submit, button.submit {
|
||||
input[type=submit], input[type=reset], input[type=button], input.submit, button.submit, a.submit {
|
||||
border: 1px outset #999999;
|
||||
border-top-color: #666;
|
||||
border-left-color: #666;
|
||||
|
|
@ -1589,16 +1589,21 @@ input[type=submit]:disabled,
|
|||
input[type=reset]:disabled,
|
||||
input[type=button]:disabled,
|
||||
input.submit:disabled,
|
||||
button.submit:disabled {
|
||||
button.submit:disabled,
|
||||
a.submit:disabled {
|
||||
color : #999;
|
||||
border : 1px solid #C0C0C0;
|
||||
background : #EEE none;
|
||||
}
|
||||
|
||||
input[type=reset]:active, input[type=button]:active, input.submit:active, button.submit:active {
|
||||
input[type=reset]:active, input[type=button]:active, input.submit:active, button.submit:active, a.submit:active {
|
||||
border : 1px inset #999999;
|
||||
}
|
||||
|
||||
a.submit {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul li input.submit {
|
||||
font-size : 87%;
|
||||
padding : 2px;
|
||||
|
|
@ -2687,3 +2692,16 @@ span.browse-button {
|
|||
top: 50%;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
#merge_invoices {
|
||||
display: none;
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
#merge {
|
||||
margin: 0.5em 0 0 0;
|
||||
}
|
||||
|
||||
#merge_table tr.active td {
|
||||
background-color: #FFFFCC;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,58 @@ $(document).ready(function() {
|
|||
null,null,{ "sType": "title-string" },null,null,null,null
|
||||
],
|
||||
aoColumnDefs: [
|
||||
{ "bSortable": false, "aTargets": [6] }
|
||||
{ "bSortable": false, "aTargets": [0, 7] }
|
||||
]
|
||||
}));
|
||||
|
||||
$('#merge').click(function (ev) {
|
||||
var booksellerid;
|
||||
var mismatch;
|
||||
var invoices = [ ];
|
||||
if ($('.select-invoice:checked').size() < 2) {
|
||||
alert(_("You must select at least two invoices to merge."));
|
||||
return false;
|
||||
}
|
||||
$('.select-invoice:checked').each(function () {
|
||||
var row = $(this).parents('tr');
|
||||
booksellerid = booksellerid || $(row).attr('data-booksellerid');
|
||||
if (booksellerid !== $(row).attr('data-booksellerid')) {
|
||||
mismatch = true;
|
||||
}
|
||||
invoices.push({ 'invoiceid': $(row).attr('data-invoiceid'),
|
||||
'invoicenumber': $(row).find('td:nth-child(2) a').text(),
|
||||
'shipmentdate': $(row).attr('data-shipmentdate'),
|
||||
'billingdate': $(row).attr('data-billingdate'),
|
||||
'shipmentcost': $(row).attr('data-shipmentcost'),
|
||||
'shipment_budgetid': $(row).attr('data-shipment_budgetid'),
|
||||
'closedate': $(row).attr('data-closedate'), });
|
||||
$('#merge_invoice_form').append('<input type="hidden" name="merge" value="' + $(row).attr('data-invoiceid') + '" />');
|
||||
});
|
||||
if (mismatch) {
|
||||
alert(_("All invoices for merging must be from the same vendor"));
|
||||
} else {
|
||||
$('#merge_table tbody').empty();
|
||||
$.each(invoices, function (idx, invoice) {
|
||||
var row = $('<tr data-invoiceid="' + invoice.invoiceid + '"><td>' + invoice.invoicenumber + '</td><td>' + invoice.shipmentdate + '</td><td>' + invoice.billingdate + '</td><td>' + invoice.shipmentcost + '</td></tr>');
|
||||
$(row).appendTo('#merge_table tbody');
|
||||
$(row).click(function () {
|
||||
$('#merge_table tbody tr').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$('#merge_invoicenumber').text(invoice.invoicenumber);
|
||||
$.each(['invoiceid', 'shipmentdate', 'billingdate', 'shipmentcost', 'shipment_budgetid'], function (idx, prop) {
|
||||
$('#merge_' + prop).val(invoice[prop]);
|
||||
});
|
||||
if (invoice.closedate) {
|
||||
$('#merge_status').text(_("Closed on " + invoice.closedate + ""));
|
||||
} else {
|
||||
$('#merge_status').text(_("Open"));
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#merge_table tbody tr:first').click();
|
||||
$('#merge_invoices').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
|
@ -40,6 +89,7 @@ $(document).ready(function() {
|
|||
<table id="resultst">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Invoice no.</th>
|
||||
<th>Vendor</th>
|
||||
<th>Billing date</th>
|
||||
|
|
@ -51,7 +101,8 @@ $(document).ready(function() {
|
|||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH invoice IN invoices %]
|
||||
<tr>
|
||||
<tr data-invoiceid="[% invoice.invoiceid %]" data-booksellerid="[% invoice.booksellerid %]" data-shipmentdate="[% invoice.shipmentdate | $KohaDates %]" data-billingdate="[% invoice.billingdate | $KohaDates %]" data-shipmentcost="[% invoice.shipmentcost %]" data-shipment_budgetid="[% invoice.shipmentcost_budgetid %]" data-closedate="[% invoice.closedate | $KohaDates %]">
|
||||
<td><input type="checkbox" class="select-invoice" value="[% invoice.invoiceid %]"></input></td>
|
||||
<td><a href="/cgi-bin/koha/acqui/invoice.pl?invoiceid=[% invoice.invoiceid %]">[% invoice.invoicenumber %]</a></td>
|
||||
<td><a href="/cgi-bin/koha/acqui/supplier.pl?booksellerid=[% invoice.booksellerid %]">[% invoice.suppliername %]</a></td>
|
||||
<td>
|
||||
|
|
@ -85,6 +136,44 @@ $(document).ready(function() {
|
|||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
<a class="submit" id="merge" href="#merge_invoices">Merge selected invoices</a>
|
||||
<div id="merge_invoices">
|
||||
<form id="merge_invoice_form" action="/cgi-bin/koha/acqui/invoice.pl" method="post">
|
||||
<fieldset class="rows">
|
||||
<ol>
|
||||
<li><h2>Merge invoices</h2></li>
|
||||
<li><table id="merge_table">
|
||||
<thead><tr><th>Invoice no.</th><th>Shipment date</th><th>Billing date</th><th>Shipment cost</th></tr></thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table></li>
|
||||
<li><label for="merge_invoicenumber">Invoice number:</label><span id="merge_invoicenumber"></span></li>
|
||||
<li><label for="merge_shipmentdate">Shipment date:</label>
|
||||
<input type="text" size="10" id="merge_shipmentdate" name="shipmentdate" value="" readonly="readonly" class="datepicker" /></li>
|
||||
|
||||
<li><label for="merge_billingdate">Billing date:</label>
|
||||
<input type="text" size="10" id="merge_billingdate" name="billingdate" value="" readonly="readonly" class="datepicker" /></li>
|
||||
|
||||
<li><label for="merge_shipmentcost">Shipment cost:</label>
|
||||
<input type="text" size="10" id="merge_shipmentcost" name="shipmentcost" value="" /></li>
|
||||
<li><label for="merge_shipment_budgetid">Fund:</label>
|
||||
<select id="merge_shipment_budgetid" name="shipment_budget_id">
|
||||
<option value="">No fund</option>
|
||||
[% FOREACH budget IN budgets_loop %]
|
||||
<option value="[% budget.budget_id %]">
|
||||
[% budget.budget_name %]
|
||||
</option>
|
||||
[% END %]
|
||||
</select></li>
|
||||
|
||||
<li><span class="label">Status:</span> <span id="merge_status"></span></li>
|
||||
<li><input type="submit" value="Merge" /></li>
|
||||
</ol>
|
||||
<input type="hidden" name="op" value="mod" />
|
||||
<input type="hidden" id="merge_invoiceid" name="invoiceid" value="" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
[% ELSE %]
|
||||
<p>Sorry, but there is no results for your search.</p>
|
||||
<p>Search was:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# This Koha test module is a stub!
|
||||
# Add more tests here!!!
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use C4::Bookseller qw( GetBookSellerFromId );
|
||||
use C4::Biblio qw( AddBiblio );
|
||||
|
||||
use Test::More tests => 14;
|
||||
|
||||
BEGIN {
|
||||
use_ok('C4::Acquisition');
|
||||
}
|
||||
|
||||
my $dbh = C4::Context->dbh;
|
||||
$dbh->{AutoCommit} = 0;
|
||||
$dbh->{RaiseError} = 1;
|
||||
|
||||
my $booksellerid = C4::Bookseller::AddBookseller(
|
||||
{
|
||||
name => "my vendor",
|
||||
address1 => "bookseller's address",
|
||||
phone => "0123456",
|
||||
active => 1
|
||||
}
|
||||
);
|
||||
|
||||
my $booksellerinfo = GetBookSellerFromId( $booksellerid );
|
||||
my $basketno = NewBasket($booksellerid, 1);
|
||||
my $basket = GetBasket($basketno);
|
||||
|
||||
my $budgetid = C4::Budgets::AddBudget(
|
||||
{
|
||||
budget_code => "budget_code_test_getordersbybib",
|
||||
budget_name => "budget_name_test_getordersbybib",
|
||||
}
|
||||
);
|
||||
my $budget = C4::Budgets::GetBudget( $budgetid );
|
||||
|
||||
my ($ordernumber1, $ordernumber2, $ordernumber3);
|
||||
my ($biblionumber1, $biblioitemnumber1) = AddBiblio(MARC::Record->new, '');
|
||||
my ($biblionumber2, $biblioitemnumber2) = AddBiblio(MARC::Record->new, '');
|
||||
my ($biblionumber3, $biblioitemnumber3) = AddBiblio(MARC::Record->new, '');
|
||||
( undef, $ordernumber1 ) = C4::Acquisition::NewOrder(
|
||||
{
|
||||
basketno => $basketno,
|
||||
quantity => 2,
|
||||
biblionumber => $biblionumber1,
|
||||
budget_id => $budget->{budget_id},
|
||||
}
|
||||
);
|
||||
|
||||
( undef, $ordernumber2 ) = C4::Acquisition::NewOrder(
|
||||
{
|
||||
basketno => $basketno,
|
||||
quantity => 1,
|
||||
biblionumber => $biblionumber2,
|
||||
budget_id => $budget->{budget_id},
|
||||
}
|
||||
);
|
||||
|
||||
( undef, $ordernumber3 ) = C4::Acquisition::NewOrder(
|
||||
{
|
||||
basketno => $basketno,
|
||||
quantity => 1,
|
||||
biblionumber => $biblionumber3,
|
||||
budget_id => $budget->{budget_id},
|
||||
ecost => 42,
|
||||
rrp => 42,
|
||||
}
|
||||
);
|
||||
|
||||
my $invoiceid1 = AddInvoice(invoicenumber => 'invoice1', booksellerid => $booksellerid, unknown => "unknown");
|
||||
my $invoiceid2 = AddInvoice(invoicenumber => 'invoice2', booksellerid => $booksellerid, unknown => "unknown");
|
||||
|
||||
my ($datereceived, $new_ordernumber) = ModReceiveOrder(
|
||||
$biblionumber1,
|
||||
$ordernumber1,
|
||||
2,
|
||||
undef,
|
||||
12,
|
||||
12,
|
||||
$invoiceid1,
|
||||
42
|
||||
);
|
||||
|
||||
($datereceived, $new_ordernumber) = ModReceiveOrder(
|
||||
$biblionumber2,
|
||||
$ordernumber2,
|
||||
1,
|
||||
undef,
|
||||
5,
|
||||
5,
|
||||
$invoiceid2,
|
||||
42
|
||||
);
|
||||
|
||||
($datereceived, $new_ordernumber) = ModReceiveOrder(
|
||||
$biblionumber3,
|
||||
$ordernumber3,
|
||||
1,
|
||||
undef,
|
||||
12,
|
||||
12,
|
||||
$invoiceid2,
|
||||
42
|
||||
);
|
||||
|
||||
|
||||
my $invoice1 = GetInvoiceDetails($invoiceid1);
|
||||
my $invoice2 = GetInvoiceDetails($invoiceid2);
|
||||
|
||||
is(scalar @{$invoice1->{'orders'}}, 1, 'Invoice1 has only one order');
|
||||
is(scalar @{$invoice2->{'orders'}}, 2, 'Invoice2 has only two orders');
|
||||
|
||||
my @invoices = GetInvoices();
|
||||
cmp_ok(scalar @invoices, '>=', 2, 'GetInvoices returns at least two invoices');
|
||||
|
||||
@invoices = GetInvoices(invoicenumber => 'invoice2');
|
||||
cmp_ok(scalar @invoices, '>=', 1, 'GetInvoices returns at least one invoice when a specific invoice is requested');
|
||||
|
||||
my $invoicesummary1 = GetInvoice($invoiceid1);
|
||||
is($invoicesummary1->{'invoicenumber'}, 'invoice1', 'GetInvoice retrieves correct invoice');
|
||||
is($invoicesummary1->{'invoicenumber'}, $invoice1->{'invoicenumber'}, 'GetInvoice and GetInvoiceDetails retrieve same information');
|
||||
|
||||
ModInvoice(invoiceid => $invoiceid1, invoicenumber => 'invoice11');
|
||||
$invoice1 = GetInvoiceDetails($invoiceid1);
|
||||
is($invoice1->{'invoicenumber'}, 'invoice11', 'ModInvoice changed invoice number');
|
||||
|
||||
is($invoice1->{'closedate'}, undef, 'Invoice is not closed before CloseInvoice call');
|
||||
CloseInvoice($invoiceid1);
|
||||
$invoice1 = GetInvoiceDetails($invoiceid1);
|
||||
isnt($invoice1->{'closedate'}, undef, 'Invoice is closed after CloseInvoice call');
|
||||
ReopenInvoice($invoiceid1);
|
||||
$invoice1 = GetInvoiceDetails($invoiceid1);
|
||||
is($invoice1->{'closedate'}, undef, 'Invoice is open after ReopenInvoice call');
|
||||
|
||||
|
||||
MergeInvoices($invoiceid1, [ $invoiceid2 ]);
|
||||
|
||||
my $mergedinvoice = GetInvoiceDetails($invoiceid1);
|
||||
is(scalar @{$mergedinvoice->{'orders'}}, 3, 'Merged invoice has three orders');
|
||||
|
||||
my $invoiceid3 = AddInvoice(invoicenumber => 'invoice3', booksellerid => $booksellerid, unknown => "unknown");
|
||||
my $invoicecount = GetInvoices();
|
||||
DelInvoice($invoiceid3);
|
||||
@invoices = GetInvoices();
|
||||
is(scalar @invoices, $invoicecount - 1, 'DelInvoice deletes invoice');
|
||||
is(GetInvoice($invoiceid3), undef, 'DelInvoice deleted correct invoice');
|
||||
|
||||
END {
|
||||
$dbh and $dbh->rollback;
|
||||
}
|
||||
Loading…
Reference in New Issue