]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/PayPostingImport.pm
Merge branch 'master' of http://wagnertech.de/git/mfinanz
[mfinanz.git] / SL / Controller / PayPostingImport.pm
1 package SL::Controller::PayPostingImport;
2 use strict;
3 use parent qw(SL::Controller::Base);
4
5 use SL::File;
6 use SL::Helper::DateTime;
7 use SL::Helper::Flash qw(flash_later);
8 use SL::Locale::String qw(t8);
9
10 use Carp;
11 use Text::CSV_XS;
12
13 __PACKAGE__->run_before('check_auth');
14
15
16 sub action_upload_pay_postings {
17   my ($self, %params) = @_;
18
19   $self->setup_pay_posting_action_bar;
20
21   # new closedto
22   my $today = DateTime->now();
23   $today->subtract(months => 1);
24
25   my $dt = DateTime->last_day_of_month(year  => $today->year, month => $today->month);
26
27   my $new_closedto = $dt->to_kivitendo();
28   $self->render('pay_posting_import/form', title => $::locale->text('Import Pay Postings'), closedto => $new_closedto);
29 }
30
31 sub action_import_datev_pay_postings {
32   my ($self, %params) = @_;
33
34   die t8("missing file for action import") unless ($::form->{file});
35
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/);
42
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';
46
47   if (parse_and_import($self)) {
48     flash_later('info', t8("All pay postings successfully imported."));
49   }
50   if ($::form->{set_closedto} && _set_closedto($self)) {
51     flash_later('info', t8("Books closed until:") . ' ' . $::form->{closedto});
52   }
53   $self->setup_pay_posting_action_bar;
54   $self->render('pay_posting_import/form', title => $::locale->text('Imported Pay Postings'));
55 }
56
57 sub parse_and_import {
58   my $self     = shift;
59
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} $!";
62   # Read/parse CSV
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);
65
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);
73
74       # check valid soll/haben kennzeichen
75       croak("No valid debit/credit sign") unless $row->[1] =~ m/^(S|H)$/;
76
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;
82
83       croak("Something wrong with date conversion") unless $dt_to_kivi;
84
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);
89
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';
92
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 . '%' } ]);
97       }
98
99       my $amount = $::form->parse_amount({ numberformat => '1000,00' }, $row->[0]);
100
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,
107           imported       => 1,
108           taxincluded    => 1,
109         )->add_chart_booking(
110           chart  => $credit,
111           credit => $amount,
112           source => $::form->{ATTACHMENTS}{file}{filename},
113         )->add_chart_booking(
114           chart  => $debit,
115           debit  => $amount,
116           source => $::form->{ATTACHMENTS}{file}{filename},
117       )->post;
118
119       push @{ $self->{gl_trans} }, $current_transaction;
120
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},
129                       );
130       }
131     }
132
133     1;
134
135   }) or do { die t8("Cannot add Booking, reason: #1 DB: #2 ", $@, SL::DB->client->error) };
136 }
137
138
139 sub _set_closedto {
140   my $self     = shift;
141   die "no date:" . $::form->{closedto} unless $::form->{closedto};
142
143   my $defaults   = SL::DB::Default->get;
144
145   $defaults->closedto(DateTime->from_kivitendo($::form->{closedto}));
146   $defaults->save || die "Cannot save closedto!";
147
148   return 1;
149 }
150
151 sub check_auth {
152   $::auth->assert('general_ledger');
153 }
154
155 sub setup_pay_posting_action_bar {
156   my ($self) = @_;
157
158   for my $bar ($::request->layout->get('actionbar')) {
159     $bar->add(
160       action => [
161         $::locale->text('Import'),
162         submit    => [ '#form', { action => 'PayPostingImport/import_datev_pay_postings' } ],
163         accesskey => 'enter',
164       ],
165     );
166   }
167 }
168
169 1;
170
171 __END__
172
173 =pod
174
175 =encoding utf8
176
177 =head1 NAME
178
179 SL::Controller::PayPostingImport
180 Controller for importing pay postings.
181 Currently only DATEV format is supported.
182
183
184 =head1 FUNCTIONS
185
186 =over 2
187
188 =item C<action_upload_pay_postings>
189
190 Simple upload form. HTML Form allows only CSV files.
191
192
193 =item C<action_import_datev_pay_postings>
194
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
197
198 =item C<parse_and_import>
199
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.
204
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
208 will be aborted
209
210 =back