|
1 |
use strict;
|
|
2 |
|
|
3 |
use lib 't';
|
|
4 |
|
|
5 |
use Test::More;
|
|
6 |
|
|
7 |
use SL::Controller::BankTransaction;
|
|
8 |
use SL::DB::AccTransaction;
|
|
9 |
use SL::DB::BankTransaction;
|
|
10 |
use SL::DB::BankTransactionAccTrans;
|
|
11 |
use SL::DB::Chart;
|
|
12 |
use SL::DB::Currency;
|
|
13 |
use SL::DB::Employee;
|
|
14 |
use SL::DB::Exchangerate;
|
|
15 |
use SL::DB::InvoiceItem;
|
|
16 |
use SL::DB::PurchaseInvoice;
|
|
17 |
use SL::DB::TaxZone;
|
|
18 |
use SL::DB::ValidityToken;
|
|
19 |
use SL::Dev::CustomerVendor qw(:ALL);
|
|
20 |
use SL::Dev::Part qw(:ALL);
|
|
21 |
use SL::Dev::Payment qw(:ALL);
|
|
22 |
use SL::Form;
|
|
23 |
use SL::Helper::Number qw(_format_number);
|
|
24 |
use SL::InstanceConfiguration;
|
|
25 |
|
|
26 |
use HTML::Query;
|
|
27 |
use Support::TestSetup;
|
|
28 |
|
|
29 |
my $part;
|
|
30 |
my $vendor;
|
|
31 |
my $usd;
|
|
32 |
my $taxzone;
|
|
33 |
my $fxgain;
|
|
34 |
my $fxloss;
|
|
35 |
my $payment_terms;
|
|
36 |
my $dt;
|
|
37 |
|
|
38 |
sub reset_db {
|
|
39 |
SL::DB::Manager::BankTransactionAccTrans->delete_all(all => 1);
|
|
40 |
SL::DB::Manager::BankTransaction->delete_all(all => 1);
|
|
41 |
SL::DB::Manager::AccTransaction->delete_all(all => 1);
|
|
42 |
SL::DB::Manager::InvoiceItem->delete_all(all => 1);
|
|
43 |
SL::DB::Manager::PurchaseInvoice->delete_all(all => 1);
|
|
44 |
SL::DB::Manager::Exchangerate->delete_all(all => 1);
|
|
45 |
$usd->delete;
|
|
46 |
$vendor->delete;
|
|
47 |
$part->delete;
|
|
48 |
$payment_terms->delete;
|
|
49 |
}
|
|
50 |
|
|
51 |
sub init_db {
|
|
52 |
SL::DB::Manager::Exchangerate->delete_all(all => 1);
|
|
53 |
$vendor = new_vendor->save;
|
|
54 |
$part = new_part->save;
|
|
55 |
$usd = SL::DB::Manager::Currency->find_by(name => 'USD') // SL::DB::Currency->new(name => 'USD')->save;
|
|
56 |
$taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland');
|
|
57 |
$fxgain = SL::DB::Manager::Chart->find_by(accno => '2660');
|
|
58 |
$fxloss = SL::DB::Manager::Chart->find_by(accno => '2150');
|
|
59 |
$payment_terms = create_payment_terms();
|
|
60 |
$dt = DateTime->new(year => 1999, month => 1, day => 12);
|
|
61 |
|
|
62 |
|
|
63 |
SL::DB::Default->get->update_attributes(
|
|
64 |
fxgain_accno_id => $fxgain->id,
|
|
65 |
fxloss_accno_id => $fxloss->id,
|
|
66 |
)->load;
|
|
67 |
# reload
|
|
68 |
$::instance_conf = SL::InstanceConfiguration->new;
|
|
69 |
|
|
70 |
}
|
|
71 |
|
|
72 |
package MockDispatcher {
|
|
73 |
sub end_request { die "END_OF_REQUEST" }
|
|
74 |
};
|
|
75 |
$::dispatcher = bless { }, "MockDispatcher";
|
|
76 |
|
|
77 |
# make a pseudo request to an old bin/mozilla style entry point
|
|
78 |
# captures STDOUT, STDERR and the return values of the called action
|
|
79 |
#
|
|
80 |
# the given form should be set up like it would be in Dispatcher after read_cgi_input
|
|
81 |
sub make_request {
|
|
82 |
my ($script, $form, $action) = @_;
|
|
83 |
|
|
84 |
my ($out, $err, @ret);
|
|
85 |
|
|
86 |
package main {
|
|
87 |
local $SIG{__WARN__} = sub {
|
|
88 |
# ignore spurious warnings, TAP::Harness calls this warnings enabled
|
|
89 |
};
|
|
90 |
|
|
91 |
require "bin/mozilla/$script.pl";
|
|
92 |
|
|
93 |
open(my $out_fh, '>', \$out) or die;
|
|
94 |
open(my $err_fh, '>', \$err) or die;
|
|
95 |
|
|
96 |
local *STDOUT = $out_fh;
|
|
97 |
local *STDERR = $err_fh;
|
|
98 |
|
|
99 |
local $::form = Form->new;
|
|
100 |
$::form->{$_} = $form->{$_} for keys %$form;
|
|
101 |
$::form->{script} = $script.'.pl'; # usually set by dispatcher, needed for checks in update_exchangerate
|
|
102 |
local $ENV{REQUEST_URI} = "http://localhost/$script.pl"; # needed for Form::redirect_header
|
|
103 |
|
|
104 |
no strict "refs";
|
|
105 |
eval {
|
|
106 |
no warnings;
|
|
107 |
@ret = &{ "::$action" }();
|
|
108 |
1;
|
|
109 |
} or do { my $err = $@;
|
|
110 |
die unless $err =~ /^END_OF_REQUEST/;
|
|
111 |
@ret = (1);
|
|
112 |
}
|
|
113 |
}
|
|
114 |
return ($out, $err, @ret);
|
|
115 |
}
|
|
116 |
|
|
117 |
sub form_from_html {
|
|
118 |
my ($html) = @_;
|
|
119 |
my $q = HTML::Query->new(text => $html);
|
|
120 |
|
|
121 |
my %form;
|
|
122 |
for my $input ($q->query('#form input')->get_elements()) {
|
|
123 |
next if !$input->attr('name') || $input->attr('disabled');
|
|
124 |
$form{ $input->attr('name') } = $input->attr('value') // "";
|
|
125 |
}
|
|
126 |
for my $select ($q->query('#form select')->get_elements()) {
|
|
127 |
my $name = $select->attr('name');
|
|
128 |
my ($selected_option) = (
|
|
129 |
grep({ $_->tag eq 'option' && $_->attr('selected') } $select->content_list),
|
|
130 |
grep({ $_->tag eq 'option' } $select->content_list)
|
|
131 |
);
|
|
132 |
|
|
133 |
$form{ $name } = $selected_option->attr('value') // $selected_option->as_text
|
|
134 |
if $selected_option;
|
|
135 |
}
|
|
136 |
|
|
137 |
%form;
|
|
138 |
}
|
|
139 |
|
|
140 |
######## main test code #######
|
|
141 |
|
|
142 |
Support::TestSetup::login();
|
|
143 |
init_db();
|
|
144 |
|
|
145 |
{
|
|
146 |
my $description = "simple purchase invoice";
|
|
147 |
# vendor 1
|
|
148 |
# part 1
|
|
149 |
my $currency = 'USD';
|
|
150 |
my $exchangerate = 2.5;
|
|
151 |
my $payment_exchangerate = 1.5;
|
|
152 |
|
|
153 |
my %form;
|
|
154 |
|
|
155 |
# make new invoice
|
|
156 |
my ($out, $err, @ret) = make_request('ir', { type => 'invoice' }, 'add');
|
|
157 |
is $ret[0], 1, "new purchase invoice";
|
|
158 |
%form = form_from_html($out);
|
|
159 |
|
|
160 |
# set invnumber and currency
|
|
161 |
$form{invnumber} = $description;
|
|
162 |
$form{currency} = $currency;
|
|
163 |
|
|
164 |
# update
|
|
165 |
($out, $err, @ret) = make_request('ir', \%form, 'update');
|
|
166 |
is $ret[0], 1, "update purchase invoice with currency";
|
|
167 |
%form = form_from_html($out);
|
|
168 |
|
|
169 |
# set part and exchangerate
|
|
170 |
$form{exchangerate} = _format_number($exchangerate, -2);
|
|
171 |
$form{partnumber_1} = $part->partnumber;
|
|
172 |
|
|
173 |
# update
|
|
174 |
($out, $err, @ret) = make_request('ir', \%form, 'update');
|
|
175 |
is $ret[0], 1, "update purchase invoice with part and exchangerate";
|
|
176 |
%form = form_from_html($out);
|
|
177 |
|
|
178 |
# now set par, exchangerate and payments - this will cause the part to be loaded with the lastcost translated into USD
|
|
179 |
$form{paid_1} = _format_number($part->lastcost / $exchangerate, -2); # lastcost = 5€ = 2$
|
|
180 |
$form{exchangerate_1} = _format_number($payment_exchangerate, -2);
|
|
181 |
|
|
182 |
($out, $err, @ret) = make_request('ir', \%form, 'post');
|
|
183 |
is $ret[0], 1, "posting '$description' does not generate error";
|
|
184 |
warn $err if $err;
|
|
185 |
ok $out =~ /ir\.pl\?action=edit&id=(\d+)/, "posting '$description' returns redirect to id";
|
|
186 |
my $id = $1;
|
|
187 |
|
|
188 |
($out, $err, @ret) = make_request('ir', { id => $id }, 'edit');
|
|
189 |
is $ret[0], 1, "'$description' did not cause an error";
|
|
190 |
warn $err if $err;
|
|
191 |
|
|
192 |
my $q = HTML::Query->new(text => $out);
|
|
193 |
is $q->query('input[name=paid_1]')->size, 1, "out '$description' contains paid_1";
|
|
194 |
is $q->query('input[name=paid_1]')->first->attr('value'), '2,00', "out '$description' paid_1 is 2,00 (the dollar amount, not the internal 5.00€)";
|
|
195 |
|
|
196 |
is $q->query('#ui-tabs-basic-data tr.invtotal th')->first->as_text, 'Summe', "'$description' - total present";
|
|
197 |
is $q->query('#ui-tabs-basic-data tr.invtotal td')->first->as_text, '2,38', "'$description' - total should be 2.00 * 1.19 = 2.38";
|
|
198 |
is $q->query('#ui-tabs-basic-data input[name=oldtotalpaid]')->first->attr('value'), '2', "'$description' - totalpaid should be 2,00 in dollar, not the internal value";
|
|
199 |
};
|
|
200 |
|
|
201 |
{
|
|
202 |
my $description = "ap transaction from redmine #563";
|
|
203 |
# 20 on 4710 Verpackungsmaterial
|
|
204 |
# 20 payment on 1000 kasse
|
|
205 |
my $currency = 'USD';
|
|
206 |
my $exchangerate = 1.1;
|
|
207 |
my $payment_exchangerate = 1.3;
|
|
208 |
my $chart = SL::DB::Manager::Chart->find_by(accno => '4710');
|
|
209 |
my %form;
|
|
210 |
|
|
211 |
# make new ap transaction
|
|
212 |
my ($out, $err, @ret) = make_request('ap', { }, 'add');
|
|
213 |
is $ret[0], 1, "new ap transaction";
|
|
214 |
%form = form_from_html($out);
|
|
215 |
|
|
216 |
# set chart, amount, currency, invnumber
|
|
217 |
$form{AP_amount_chart_id_1} = $chart->id;
|
|
218 |
$form{amount_1} = 20;
|
|
219 |
$form{currency} = 'USD';
|
|
220 |
$form{invnumber} = $description;
|
|
221 |
|
|
222 |
# make new ap transaction
|
|
223 |
($out, $err, @ret) = make_request('ap', \%form, 'update');
|
|
224 |
is $ret[0], 1, "update ap transaction with currency";
|
|
225 |
%form = form_from_html($out);
|
|
226 |
|
|
227 |
# add exchangerate and payments
|
|
228 |
$form{paid_1} = 20;
|
|
229 |
$form{exchangerate} = _format_number($exchangerate, -2);
|
|
230 |
$form{exchangerate_1} = _format_number($payment_exchangerate, -2);
|
|
231 |
|
|
232 |
($out, $err, @ret) = make_request('ap', \%form, 'post');
|
|
233 |
is $ret[0], 1, "posting '$description' did not cause an error";
|
|
234 |
|
|
235 |
my $invoice = SL::DB::Manager::PurchaseInvoice->find_by(invnumber => $description);
|
|
236 |
ok $invoice, "posting '$description' can be found in the database";
|
|
237 |
|
|
238 |
($out, $err, @ret) = make_request('ap', { id => $invoice->id }, 'edit');
|
|
239 |
is $ret[0], 1, "loading '$description' did not cause an error";
|
|
240 |
warn $err if $err;
|
|
241 |
|
|
242 |
my $q = HTML::Query->new(text => $out);
|
|
243 |
is $q->query('input[name=paid_1]')->size, 1, "out '$description' contains paid_1";
|
|
244 |
is $q->query('input[name=paid_1]')->first->attr('value'), '20,00', "out '$description' paid_1 is 20 (the dollar amount, not the internal amount)";
|
|
245 |
}
|
|
246 |
|
|
247 |
{
|
|
248 |
my $testname = 'ap_transaction_fx_gain_fees';
|
|
249 |
my $usd_amount = 83300;
|
|
250 |
my $fx_rate = 2;
|
|
251 |
my $fx_rate_bank = 1.75;
|
|
252 |
my $eur_amount = $usd_amount * $fx_rate;
|
|
253 |
|
|
254 |
my $netamount = $eur_amount;
|
|
255 |
my $amount = $eur_amount;
|
|
256 |
my $buysell = 'sell';
|
|
257 |
my $eur_payment = $usd_amount * $fx_rate_bank;
|
|
258 |
my $usd_payment = $eur_payment / $fx_rate_bank; # for rounding issues
|
|
259 |
|
|
260 |
my $bank = SL::DB::Manager::Chart->find_by(description => 'Bank');
|
|
261 |
my $bank_account = SL::DB::Manager::BankAccount->find_by(chart_id => $bank->id) // SL::DB::BankAccount->new(
|
|
262 |
account_number => '123',
|
|
263 |
bank_code => '123',
|
|
264 |
iban => '123',
|
|
265 |
bic => '123',
|
|
266 |
bank => '123',
|
|
267 |
chart_id => $bank->id,
|
|
268 |
name => $bank->description,
|
|
269 |
)->save;
|
|
270 |
|
|
271 |
my $ex = SL::DB::Manager::Exchangerate->find_by(currency_id => $usd->id, transdate => $dt)
|
|
272 |
|| SL::DB::Exchangerate->new(currency_id => $usd->id, transdate => $dt);
|
|
273 |
$ex->update_attributes($buysell => $fx_rate);
|
|
274 |
|
|
275 |
my $ap_chart = SL::DB::Manager::Chart->get_first(query => [ link => 'AP' ], sort_by => 'accno');
|
|
276 |
my $ap_amount_chart = SL::DB::Manager::Chart->get_first(query => [ link => 'AP_amount' ], sort_by => 'accno');
|
|
277 |
my $invoice = SL::DB::PurchaseInvoice->new(
|
|
278 |
invoice => 0,
|
|
279 |
invnumber => $testname,
|
|
280 |
amount => $amount,
|
|
281 |
netamount => $netamount,
|
|
282 |
transdate => $dt,
|
|
283 |
taxincluded => 0,
|
|
284 |
vendor_id => $vendor->id,
|
|
285 |
taxzone_id => $vendor->taxzone_id,
|
|
286 |
currency_id => $usd->id,
|
|
287 |
transactions => [],
|
|
288 |
notes => 'ap_transaction_fx',
|
|
289 |
);
|
|
290 |
$invoice->add_ap_amount_row(
|
|
291 |
amount => $netamount,
|
|
292 |
chart => $ap_amount_chart,
|
|
293 |
tax_id => 0,
|
|
294 |
);
|
|
295 |
|
|
296 |
$invoice->create_ap_row(chart => $ap_chart);
|
|
297 |
$invoice->save;
|
|
298 |
my $ap_transaction_fx = $invoice;
|
|
299 |
|
|
300 |
# check exchangerate
|
|
301 |
is($ap_transaction_fx->currency->name , 'USD' , "$testname: USD currency");
|
|
302 |
is($ap_transaction_fx->get_exchangerate , '2.00000' , "$testname: fx rate record");
|
|
303 |
my $bt = create_bank_transaction(record => $ap_transaction_fx,
|
|
304 |
bank_chart_id => $bank->id,
|
|
305 |
transdate => $dt,
|
|
306 |
valutadate => $dt,
|
|
307 |
amount => $eur_payment,
|
|
308 |
exchangerate => $fx_rate_bank,
|
|
309 |
) or die "Couldn't create bank_transaction";
|
|
310 |
|
|
311 |
local $::form = Form->new;
|
|
312 |
$::form->{invoice_ids} = {
|
|
313 |
$bt->id => [ $ap_transaction_fx->id ]
|
|
314 |
};
|
|
315 |
$::form->{"book_fx_bank_fees_" . $bt->id . "_" . $ap_transaction_fx->id} = 1;
|
|
316 |
$::form->{"exchangerate_" . $bt->id . "_" . $ap_transaction_fx->id} = "1,75"; # will be parsed
|
|
317 |
$::form->{"currency_id_" . $bt->id . "_" . $ap_transaction_fx->id} = $usd->id;
|
|
318 |
|
|
319 |
my ($stdout, $stderr, @result);
|
|
320 |
{
|
|
321 |
open(my $out_fh, '>', \$stdout) or die;
|
|
322 |
open(my $err_fh, '>', \$stderr) or die;
|
|
323 |
|
|
324 |
local *STDOUT = $out_fh;
|
|
325 |
local *STDERR = $err_fh;
|
|
326 |
my $bt_controller = SL::Controller::BankTransaction->new;
|
|
327 |
@result = $bt_controller->action_save_invoices;
|
|
328 |
};
|
|
329 |
ok !$stderr, "ap_transaction '$testname' can be booked with BackTransaction controller";
|
|
330 |
|
|
331 |
$invoice = SL::DB::Manager::PurchaseInvoice->find_by(invnumber => $testname);
|
|
332 |
|
|
333 |
# now load with old code
|
|
334 |
my ($out, $err, @ret) = make_request('ap', { id => $invoice->id }, 'edit');
|
|
335 |
is $ret[0], 1, "loading '$testname' did not cause an error";
|
|
336 |
warn $err if $err;
|
|
337 |
|
|
338 |
my $q = HTML::Query->new(text => $out);
|
|
339 |
is $q->query('input[name=paid_1]')->size, 1, "out '$testname' contains paid_1";
|
|
340 |
is $q->query('input[name=paid_1]')->first->attr('value'), _format_number($usd_payment, -2), "out '$testname' paid_1 is $usd_payment (the dollar amount, not the internal amount)";
|
|
341 |
}
|
|
342 |
|
|
343 |
reset_db();
|
|
344 |
done_testing();
|
|
345 |
|
|
346 |
1;
|
fx_transactions: test cases basierend auf #563
t/fxtransaction/ap_transactions.t enthält ein Mini-Framework für
bin/mozilla Integration-Tests. Grobes Muster:
my ($out, $err, @ret) = make_request($script, \%form_params, $action);
%form = form_from_html($out);
Wird hier benutzt um simple Flows durch das programm zu simulieren.