+ qty => $params{qty} || 5,
+ );
+ $item->assign_attributes(%params) if %params;
+ return $item;
+}
+
+sub _create_two_items {
+ my ($record_type) = @_;
+
+ my $part1 = new_part(description => 'Testpart 1',
+ sellprice => 12,
+ )->save;
+ my $part2 = new_part(description => 'Testpart 2',
+ sellprice => 10,
+ )->save;
+ my $item1 = _create_item(record_type => $record_type, part => $part1, qty => 5);
+ my $item2 = _create_item(record_type => $record_type, part => $part2, qty => 8);
+ return [ $item1, $item2 ];
+}
+
+sub create_project {
+ my (%params) = @_;
+ my $project = SL::DB::Project->new(
+ projectnumber => delete $params{projectnumber} // 1,
+ description => delete $params{description} // "Test project",
+ active => 1,
+ valid => 1,
+ project_status_id => SL::DB::Manager::ProjectStatus->find_by(name => "running")->id,
+ project_type_id => SL::DB::Manager::ProjectType->find_by(description => "Standard")->id,
+ )->save;
+ $project->assign_attributes(%params) if %params;
+ return $project;
+}
+
+sub create_department {
+ my (%params) = @_;
+
+ my $department = SL::DB::Department->new(
+ 'description' => delete $params{description} // 'Test Department',
+ )->save;
+
+ $department->assign_attributes(%params) if %params;
+ return $department;
+
+}
+
+sub create_ap_transaction {
+ my (%params) = @_;
+
+ my $vendor = delete $params{vendor};
+ if ( $vendor ) {
+ die "vendor missing or not a SL::DB::Vendor object" unless ref($vendor) eq 'SL::DB::Vendor';
+ } else {
+ # use default SL/Dev vendor if it exists, or create a new one
+ $vendor = SL::DB::Manager::Vendor->find_by(name => 'Testlieferant') // new_vendor->save;
+ };
+
+ my $taxincluded = $params{taxincluded} // 1;
+ delete $params{taxincluded};
+
+ my $bookings = delete $params{bookings};
+ # default bookings
+ unless ( $bookings ) {
+ my $chart_postage = SL::DB::Manager::Chart->find_by(description => 'Porto');
+ my $chart_telephone = SL::DB::Manager::Chart->find_by(description => 'Telefon');
+ $bookings = [
+ {
+ chart => $chart_postage,
+ amount => 1000,
+ },
+ {
+ chart => $chart_telephone,
+ amount => $taxincluded ? 1190 : 1000,
+ },
+ ]
+ };
+
+ # optional params:
+ my $project_id = delete $params{globalproject_id};
+
+ # if amount or netamount are given, then it compares them to the final values, and dies if they don't match
+ my $expected_amount = delete $params{amount};
+ my $expected_netamount = delete $params{netamount};
+
+ my $dec = delete $params{dec} // 2;
+
+ my $today = DateTime->today_local;
+ my $transdate = delete $params{transdate} // $today;
+ die "transdate hat to be DateTime object" unless ref($transdate) eq 'DateTime';
+
+ my $gldate = delete $params{gldate} // $today;
+ die "gldate hat to be DateTime object" unless ref($gldate) eq 'DateTime';
+
+ my $ap_chart = delete $params{ap_chart} // SL::DB::Manager::Chart->find_by( accno => '1600' );
+ die "no ap_chart found or not an AP chart" unless $ap_chart and $ap_chart->link eq 'AP';
+
+ my $ap_transaction = SL::DB::PurchaseInvoice->new(
+ vendor_id => $vendor->id,
+ invoice => 0,
+ transactions => [],
+ globalproject_id => $project_id,
+ invnumber => delete $params{invnumber} // 'test ap_transaction',
+ notes => delete $params{notes} // 'test ap_transaction',
+ transdate => $transdate,
+ gldate => $gldate,
+ taxincluded => $taxincluded,
+ taxzone_id => $vendor->taxzone_id, # taxzone_id shouldn't have any effect on ap transactions
+ currency_id => $::instance_conf->get_currency_id,
+ type => undef, # isn't set for ap
+ employee_id => SL::DB::Manager::Employee->current->id,
+ );
+ # assign any parameters that weren't explicitly handled above, e.g. itime
+ $ap_transaction->assign_attributes(%params) if %params;
+
+ foreach my $booking ( @{$bookings} ) {
+ my $chart = delete $booking->{chart};
+ die "illegal chart" unless ref($chart) eq 'SL::DB::Chart';
+
+ my $tax = _transaction_tax_helper($booking, $chart, $transdate); # will die if tax can't be found
+
+ $ap_transaction->add_ap_amount_row(
+ amount => $booking->{amount}, # add_ap_amount_row expects the user input amount, does its own calculate_tax
+ chart => $chart,
+ tax_id => $tax->id,
+ project_id => $booking->{project_id},
+ );
+ }
+
+ my $acc_trans_sum = sum map { $_->amount } grep { $_->chart_link =~ 'AP_amount' } @{$ap_transaction->transactions};
+ # $main::lxdebug->message(0, sprintf("accno: %s amount: %s chart_link: %s\n",
+ # $_->amount,
+ # $_->chart->accno,
+ # $_->chart_link
+ # )) foreach @{$ap_transaction->transactions};
+
+ # determine netamount and amount from the transactions that were added via bookings
+ $ap_transaction->netamount( -1 * sum map { $_->amount } grep { $_->chart_link =~ 'AP_amount' } @{$ap_transaction->transactions} );
+ # $main::lxdebug->message(0, sprintf('found netamount %s', $ap_transaction->netamount));
+
+ my $taxamount = -1 * sum map { $_->amount } grep { $_->chart_link =~ /tax/ } @{$ap_transaction->transactions};
+ $ap_transaction->amount( $ap_transaction->netamount + $taxamount );
+ # additional check, add up all transactions before AP-transaction is added
+ my $refamount = -1 * sum map { $_->amount } @{$ap_transaction->transactions};
+ die "refamount = $refamount, ap_transaction->amount = " . $ap_transaction->amount unless $refamount == $ap_transaction->amount;
+
+ # if amount or netamount were passed as params, check if the values are still
+ # the same after recalculating them from the acc_trans entries
+ if (defined $expected_amount) {
+ die "amount doesn't match acc_trans amounts: $expected_amount != " . $ap_transaction->amount unless $expected_amount == $ap_transaction->amount;
+ }
+ if (defined $expected_netamount) {
+ die "netamount doesn't match acc_trans netamounts: $expected_netamount != " . $ap_transaction->netamount unless $expected_netamount == $ap_transaction->netamount;
+ }
+
+ $ap_transaction->create_ap_row(chart => $ap_chart);
+ $ap_transaction->save;
+ # $main::lxdebug->message(0, sprintf("created ap_transaction with invnumber %s and trans_id %s",
+ # $ap_transaction->invnumber,
+ # $ap_transaction->id));
+ return $ap_transaction;
+}
+
+sub create_ar_transaction {
+ my (%params) = @_;
+
+ my $customer = delete $params{customer};
+ if ( $customer ) {
+ die "customer missing or not a SL::DB::Customer object" unless ref($customer) eq 'SL::DB::Customer';
+ } else {
+ # use default SL/Dev vendor if it exists, or create a new one
+ $customer = SL::DB::Manager::Customer->find_by(name => 'Testkunde') // new_customer->save;
+ };
+
+ my $taxincluded = $params{taxincluded} // 1;
+ delete $params{taxincluded};
+
+ my $bookings = delete $params{bookings};
+ # default bookings
+ unless ( $bookings ) {
+ my $chart_19 = SL::DB::Manager::Chart->find_by(accno => '8400');
+ my $chart_7 = SL::DB::Manager::Chart->find_by(accno => '8300');
+ my $chart_0 = SL::DB::Manager::Chart->find_by(accno => '8200');
+ $bookings = [
+ {
+ chart => $chart_19,
+ amount => $taxincluded ? 119 : 100,
+ },
+ {
+ chart => $chart_7,
+ amount => $taxincluded ? 107 : 100,
+ },
+ {
+ chart => $chart_0,
+ amount => 100,
+ },
+ ]
+ };
+
+ # optional params:
+ my $project_id = delete $params{globalproject_id};
+
+ # if amount or netamount are given, then it compares them to the final values, and dies if they don't match
+ my $expected_amount = delete $params{amount};
+ my $expected_netamount = delete $params{netamount};
+
+ my $dec = delete $params{dec} // 2;
+
+ my $today = DateTime->today_local;
+ my $transdate = delete $params{transdate} // $today;
+ die "transdate hat to be DateTime object" unless ref($transdate) eq 'DateTime';
+
+ my $gldate = delete $params{gldate} // $today;
+ die "gldate hat to be DateTime object" unless ref($gldate) eq 'DateTime';
+
+ my $ar_chart = delete $params{ar_chart} // SL::DB::Manager::Chart->find_by( accno => '1400' );
+ die "no ar_chart found or not an AR chart" unless $ar_chart and $ar_chart->link eq 'AR';
+
+ my $ar_transaction = SL::DB::Invoice->new(
+ customer_id => $customer->id,
+ invoice => 0,
+ transactions => [],
+ globalproject_id => $project_id,
+ invnumber => delete $params{invnumber} // 'test ar_transaction',
+ notes => delete $params{notes} // 'test ar_transaction',
+ transdate => $transdate,
+ gldate => $gldate,
+ taxincluded => $taxincluded,
+ taxzone_id => $customer->taxzone_id, # taxzone_id shouldn't have any effect on ar transactions
+ currency_id => $::instance_conf->get_currency_id,
+ type => undef, # isn't set for ar
+ employee_id => SL::DB::Manager::Employee->current->id,