1 package SL::Controller::PayPostingImport;
3 use parent qw(SL::Controller::Base);
6 use SL::Helper::DateTime;
7 use SL::Helper::Flash qw(flash_later);
8 use SL::Locale::String qw(t8);
13 __PACKAGE__->run_before('check_auth');
16 sub action_upload_pay_postings {
17 my ($self, %params) = @_;
19 $self->setup_pay_posting_action_bar;
22 my $today = DateTime->now();
23 $today->subtract(months => 1);
25 my $dt = DateTime->last_day_of_month(year => $today->year, month => $today->month);
27 my $new_closedto = $dt->to_kivitendo();
28 $self->render('pay_posting_import/form', title => $::locale->text('Import Pay Postings'), closedto => $new_closedto);
31 sub action_import_datev_pay_postings {
32 my ($self, %params) = @_;
34 die t8("missing file for action import") unless ($::form->{file});
36 my $filename= $::form->{ATTACHMENTS}{file}{filename};
37 # check name and first fields of CSV data
38 die t8("Wrong file name, expects name like: DTVF_*_LOHNBUCHUNG*.csv") unless $filename =~ /^DTVF_.*_LOHNBUCHUNGEN_LUG.*\.csv$/;
39 die t8("not a valid DTVF file, expected first field in A1 'DTVF'") unless ($::form->{file} =~ m/^('|")?DTVF/);
40 die t8("not a valid DTVF file, expected field header start with 'Umsatz; (..) ;Konto;Gegenkonto'")
41 unless ($::form->{file} =~ m/Umsatz;S\/H;;;;;Konto;Gegenkonto.*;;Belegdatum;Belegfeld 1;Belegfeld 2;;Buchungstext/);
43 # check if file is already imported
44 my $acc_trans_doc = SL::DB::Manager::AccTransaction->get_first(query => [ source => $filename ]);
45 die t8("Already imported: ") . $acc_trans_doc->source if ref $acc_trans_doc eq 'SL::DB::AccTransaction';
47 if (parse_and_import($self)) {
48 flash_later('info', t8("All pay postings successfully imported."));
50 if ($::form->{set_closedto} && _set_closedto($self)) {
51 flash_later('info', t8("Books closed until:") . ' ' . $::form->{closedto});
53 $self->setup_pay_posting_action_bar;
54 $self->render('pay_posting_import/form', title => $::locale->text('Imported Pay Postings'));
57 sub parse_and_import {
60 my $csv = Text::CSV_XS->new ({ binary => 0, auto_diag => 1, sep_char => ";" });
61 open (my $fh, "<:encoding(cp1252)", \$::form->{file}) or die "cannot open $::form->{file} $!";
63 # Umsatz S/H Konto Gegenkonto (ohne BU-Schlüssel) Belegdatum Belegfeld 1 Belegfeld 2 Buchungstext
64 my $year = substr($csv->getline($fh)->[12], 0, 4);
66 # whole import or nothing
67 my $current_transaction;
68 SL::DB->client->with_transaction(sub {
69 while (my $row = $csv->getline($fh)) {
70 next unless $row->[0] =~ m/\d/;
71 my ($credit, $debit, $dt_to_kivi, $length, $accno_credit, $accno_debit,
72 $department_name, $department);
74 # check valid soll/haben kennzeichen
75 croak("No valid debit/credit sign") unless $row->[1] =~ m/^(S|H)$/;
77 # check transaction date can be 4 or 3 digit (leading 0 omitted)
78 $length = length $row->[9] == 4 ? 2 : 1;
79 $dt_to_kivi = DateTime->new(year => $year,
80 month => substr ($row->[9], -2),
81 day => substr($row->[9],0, $length))->to_kivitendo;
83 croak("Something wrong with date conversion") unless $dt_to_kivi;
85 $accno_credit = $row->[1] eq 'S' ? $row->[7] : $row->[6];
86 $accno_debit = $row->[1] eq 'S' ? $row->[6] : $row->[7];
87 $credit = SL::DB::Manager::Chart->find_by(accno => $accno_credit);
88 $debit = SL::DB::Manager::Chart->find_by(accno => $accno_debit);
90 croak("No such Chart $accno_credit") unless ref $credit eq 'SL::DB::Chart';
91 croak("No such Chart $accno_debit") unless ref $debit eq 'SL::DB::Chart';
93 # optional KOST1 - KOST2 ?
94 $department_name = $row->[36];
95 if ($department_name) {
96 $department = SL::DB::Manager::Department->get_first(where => [ description => { ilike => $department_name . '%' } ]);
99 my $amount = $::form->parse_amount({ numberformat => '1000,00' }, $row->[0]);
101 $current_transaction = SL::DB::GLTransaction->new(
102 employee_id => $::form->{employee_id},
103 transdate => $dt_to_kivi,
104 description => $row->[13],
105 reference => $row->[13],
106 department_id => ref $department eq 'SL::DB::Department' ? $department->id : undef,
109 )->add_chart_booking(
112 source => $::form->{ATTACHMENTS}{file}{filename},
113 )->add_chart_booking(
116 source => $::form->{ATTACHMENTS}{file}{filename},
119 push @{ $self->{gl_trans} }, $current_transaction;
121 if ($::instance_conf->get_doc_storage) {
122 my $file = SL::File->save(object_id => $current_transaction->id,
123 object_type => 'gl_transaction',
124 mime_type => 'text/csv',
125 source => 'uploaded',
126 file_type => 'attachment',
127 file_name => $::form->{ATTACHMENTS}{file}{filename},
128 file_contents => $::form->{file},
135 }) or do { die t8("Cannot add Booking, reason: #1 DB: #2 ", $@, SL::DB->client->error) };
141 die "no date:" . $::form->{closedto} unless $::form->{closedto};
143 my $defaults = SL::DB::Default->get;
145 $defaults->closedto(DateTime->from_kivitendo($::form->{closedto}));
146 $defaults->save || die "Cannot save closedto!";
152 $::auth->assert('general_ledger');
155 sub setup_pay_posting_action_bar {
158 for my $bar ($::request->layout->get('actionbar')) {
161 $::locale->text('Import'),
162 submit => [ '#form', { action => 'PayPostingImport/import_datev_pay_postings' } ],
163 accesskey => 'enter',
179 SL::Controller::PayPostingImport
180 Controller for importing pay postings.
181 Currently only DATEV format is supported.
188 =item C<action_upload_pay_postings>
190 Simple upload form. HTML Form allows only CSV files.
193 =item C<action_import_datev_pay_postings>
195 Does some sanity checks for the CSV file according to the expected DATEV data structure
196 If successful calls the parse_and_import function
198 =item C<parse_and_import>
200 Internal function for parsing and importing every line of the CSV data as a GL Booking.
201 Adds the attribute imported for the GL Booking.
202 If a chart which uses a tax automatic is assigned the tax will be calculated with the
203 'tax_included' option, which defaults to the DATEV format.
205 Furthermore adds the original CSV filename for every AccTransaction and puts the CSV in every GL Booking
206 if the feature DMS is active.
207 If a Chart is missing or any kind of different error occurs the whole import including the DMS addition