Revision 0fed2b9a
Von G. Richardson vor mehr als 5 Jahren hinzugefügt
SL/DB/GLTransaction.pm | ||
---|---|---|
5 | 5 |
use SL::DB::MetaSetup::GLTransaction; |
6 | 6 |
use SL::Locale::String qw(t8); |
7 | 7 |
use List::Util qw(sum); |
8 |
use SL::DATEV; |
|
9 |
use Carp; |
|
10 |
use Data::Dumper; |
|
8 | 11 |
|
9 | 12 |
# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. |
10 | 13 |
__PACKAGE__->meta->make_manager_class; |
... | ... | |
57 | 60 |
|
58 | 61 |
sub date { goto &gldate } |
59 | 62 |
|
63 |
sub post { |
|
64 |
my ($self) = @_; |
|
65 |
|
|
66 |
my @errors = $self->validate; |
|
67 |
croak t8("Errors in GL transaction:") . "\n" . join("\n", @errors) . "\n" if scalar @errors; |
|
68 |
|
|
69 |
# make sure all the defaults are set: |
|
70 |
require SL::DB::Employee; |
|
71 |
my $employee_id = SL::DB::Manager::Employee->current->id; |
|
72 |
$self->type(undef); |
|
73 |
$self->employee_id($employee_id) unless defined $self->employee_id || defined $self->employee; |
|
74 |
$self->ob_transaction('f') unless defined $self->ob_transaction; |
|
75 |
$self->cb_transaction('f') unless defined $self->cb_transaction; |
|
76 |
$self->gldate(DateTime->today_local) unless defined $self->gldate; # should user even be allowed to set this manually? |
|
77 |
$self->transdate(DateTime->today_local) unless defined $self->transdate; |
|
78 |
|
|
79 |
$self->db->with_transaction(sub { |
|
80 |
$self->save; |
|
81 |
|
|
82 |
if ($::instance_conf->get_datev_check_on_gl_transaction) { |
|
83 |
my $datev = SL::DATEV->new( |
|
84 |
dbh => $self->dbh, |
|
85 |
trans_id => $self->id, |
|
86 |
); |
|
87 |
|
|
88 |
$datev->generate_datev_data; |
|
89 |
|
|
90 |
if ($datev->errors) { |
|
91 |
die join "\n", t8('DATEV check returned errors:'), $datev->errors; |
|
92 |
} |
|
93 |
} |
|
94 |
|
|
95 |
require SL::DB::History; |
|
96 |
SL::DB::History->new( |
|
97 |
trans_id => $self->id, |
|
98 |
snumbers => 'gltransaction_' . $self->id, |
|
99 |
employee_id => $employee_id, |
|
100 |
addition => 'POSTED', |
|
101 |
what_done => 'gl transaction', |
|
102 |
)->save; |
|
103 |
|
|
104 |
1; |
|
105 |
}) or die t8("Error when saving: #1", $self->db->error); |
|
106 |
|
|
107 |
return $self; |
|
108 |
} |
|
109 |
|
|
110 |
sub add_chart_booking { |
|
111 |
my ($self, %params) = @_; |
|
112 |
|
|
113 |
require SL::DB::Chart; |
|
114 |
die "add_chart_booking needs a transdate" unless $self->transdate; |
|
115 |
die "add_chart_booking needs taxincluded" unless defined $self->taxincluded; |
|
116 |
die "chart missing" unless $params{chart} && ref($params{chart}) eq 'SL::DB::Chart'; |
|
117 |
die t8('Booking needs at least one debit and one credit booking!') |
|
118 |
unless $params{debit} or $params{credit}; # must exist and not be 0 |
|
119 |
die t8('Cannot have a value in both Debit and Credit!') |
|
120 |
if defined($params{debit}) and defined($params{credit}); |
|
121 |
|
|
122 |
my $chart = $params{chart}; |
|
123 |
|
|
124 |
my $dec = delete $params{dec} // 2; |
|
125 |
|
|
126 |
my ($netamount,$taxamount) = (0,0); |
|
127 |
my $amount = $params{credit} // $params{debit}; # only one can exist |
|
128 |
|
|
129 |
croak t8('You cannot use a negative amount with debit/credit!') if $amount < 0; |
|
130 |
|
|
131 |
require SL::DB::Tax; |
|
132 |
my $tax = SL::DB::Manager::Tax->find_by(id => $params{tax_id}) |
|
133 |
// croak "Can't find tax with id " . $params{tax_id}; |
|
134 |
|
|
135 |
if ( $tax and $tax->rate != 0 ) { |
|
136 |
($netamount, $taxamount) = Form->calculate_tax($amount, $tax->rate, $self->taxincluded, $dec); |
|
137 |
} else { |
|
138 |
$netamount = $amount; |
|
139 |
}; |
|
140 |
|
|
141 |
if ( $params{debit} ) { |
|
142 |
$amount *= -1; |
|
143 |
$netamount *= -1; |
|
144 |
$taxamount *= -1; |
|
145 |
}; |
|
146 |
|
|
147 |
next unless $netamount; # skip entries with netamount 0 |
|
148 |
|
|
149 |
# initialise transactions if it doesn't exist yet |
|
150 |
$self->transactions([]) unless $self->transactions; |
|
151 |
|
|
152 |
require SL::DB::AccTransaction; |
|
153 |
$self->add_transactions( SL::DB::AccTransaction->new( |
|
154 |
chart_id => $chart->id, |
|
155 |
chart_link => $chart->link, |
|
156 |
amount => $netamount, |
|
157 |
taxkey => $tax->taxkey, |
|
158 |
tax_id => $tax->id, |
|
159 |
transdate => $self->transdate, |
|
160 |
source => $params{source} // '', |
|
161 |
memo => $params{memo} // '', |
|
162 |
ob_transaction => $self->ob_transaction, |
|
163 |
cb_transaction => $self->cb_transaction, |
|
164 |
project_id => $params{project_id}, |
|
165 |
)); |
|
166 |
|
|
167 |
# only add tax entry if amount is >= 0.01, defaults to 2 decimals |
|
168 |
if ( $::form->round_amount(abs($taxamount), $dec) > 0 ) { |
|
169 |
my $tax_chart = $tax->chart; |
|
170 |
if ( $tax->chart ) { |
|
171 |
$self->add_transactions(SL::DB::AccTransaction->new( |
|
172 |
chart_id => $tax_chart->id, |
|
173 |
chart_link => $tax_chart->link, |
|
174 |
amount => $taxamount, |
|
175 |
taxkey => $tax->taxkey, |
|
176 |
tax_id => $tax->id, |
|
177 |
transdate => $self->transdate, |
|
178 |
ob_transaction => $self->ob_transaction, |
|
179 |
cb_transaction => $self->cb_transaction, |
|
180 |
source => $params{source} // '', |
|
181 |
memo => $params{memo} // '', |
|
182 |
project_id => $params{project_id}, |
|
183 |
)); |
|
184 |
}; |
|
185 |
}; |
|
186 |
return $self; |
|
187 |
}; |
|
188 |
|
|
189 |
sub validate { |
|
190 |
my ($self) = @_; |
|
191 |
|
|
192 |
my @errors; |
|
193 |
|
|
194 |
if ( $self->transactions && scalar @{ $self->transactions } ) { |
|
195 |
my $debit_count = map { $_->amount } grep { $_->amount > 0 } @{ $self->transactions }; |
|
196 |
my $credit_count = map { $_->amount } grep { $_->amount < 0 } @{ $self->transactions }; |
|
197 |
|
|
198 |
if ( $debit_count > 1 && $credit_count > 1 ) { |
|
199 |
push @errors, t8('Split entry detected. The values you have entered will result in an entry with more than one position on both debit and credit. ' . |
|
200 |
'Due to known problems involving accounting software kivitendo does not allow these.'); |
|
201 |
} elsif ( $credit_count == 0 && $debit_count == 0 ) { |
|
202 |
push @errors, t8('Booking needs at least one debit and one credit booking!'); |
|
203 |
} else { |
|
204 |
# transactions formally ok, now check for out of balance: |
|
205 |
my $sum = sum map { $_->amount } @{ $self->transactions }; |
|
206 |
# compare rounded amount to 0, to get around floating point problems, e.g. |
|
207 |
# $sum = -2.77555756156289e-17 |
|
208 |
push @errors, t8('Out of balance transaction!') unless $::form->round_amount($sum,5) == 0; |
|
209 |
}; |
|
210 |
} else { |
|
211 |
push @errors, t8('Empty transaction!'); |
|
212 |
}; |
|
213 |
|
|
214 |
# fields enforced by interface |
|
215 |
push @errors, t8('Reference missing!') unless $self->reference; |
|
216 |
push @errors, t8('Description missing!') unless $self->description; |
|
217 |
|
|
218 |
# date checks |
|
219 |
push @errors, t8('Transaction Date missing!') unless $self->transdate && ref($self->transdate) eq 'DateTime'; |
|
220 |
|
|
221 |
if ( $self->transdate ) { |
|
222 |
if ( $::form->date_closed( $self->transdate, \%::myconfig) ) { |
|
223 |
if ( !$self->id ) { |
|
224 |
push @errors, t8('Cannot post transaction for a closed period!') |
|
225 |
} else { |
|
226 |
push @errors, t8('Cannot change transaction in a closed period!') |
|
227 |
}; |
|
228 |
}; |
|
229 |
|
|
230 |
push @errors, t8('Cannot post transaction above the maximum future booking date!') |
|
231 |
if $::form->date_max_future($self->transdate, \%::myconfig); |
|
232 |
} |
|
233 |
|
|
234 |
return @errors; |
|
235 |
} |
|
236 |
|
|
60 | 237 |
1; |
238 |
|
|
239 |
__END__ |
|
240 |
|
|
241 |
=pod |
|
242 |
|
|
243 |
=encoding UTF-8 |
|
244 |
|
|
245 |
=head1 NAME |
|
246 |
|
|
247 |
SL::DB::GLTransaction: Rose model for GL transactions (table "gl") |
|
248 |
|
|
249 |
=head1 FUNCTIONS |
|
250 |
|
|
251 |
=over 4 |
|
252 |
|
|
253 |
=item C<post> |
|
254 |
|
|
255 |
Takes an unsaved but initialised GLTransaction object and saves it, but first |
|
256 |
validates the object, sets certain defaults (e.g. employee), and then also runs |
|
257 |
various checks, writes history, runs DATEV check, ... |
|
258 |
|
|
259 |
Returns C<$self> on success and dies otherwise. The whole process is run inside |
|
260 |
a transaction. If it fails then nothing is saved to or changed in the database. |
|
261 |
A new transaction is only started if none are active. |
|
262 |
|
|
263 |
Example of posting a GL transaction from scratch: |
|
264 |
|
|
265 |
my $tax_0 = SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00); |
|
266 |
my $gl_transaction = SL::DB::GLTransaction->new( |
|
267 |
taxincluded => 1, |
|
268 |
description => 'bar', |
|
269 |
reference => 'bla', |
|
270 |
transdate => DateTime->today_local, |
|
271 |
)->add_chart_booking( |
|
272 |
chart => SL::DB::Manager::Chart->find_by( description => 'Kasse' ), |
|
273 |
credit => 100, |
|
274 |
tax_id => $tax_0->id, |
|
275 |
)->add_chart_booking( |
|
276 |
chart => SL::DB::Manager::Chart->find_by( description => 'Bank' ), |
|
277 |
debit => 100, |
|
278 |
tax_id => $tax_0->id, |
|
279 |
)->post; |
|
280 |
|
|
281 |
=item C<add_chart_booking %params> |
|
282 |
|
|
283 |
Adds an acc_trans entry to an existing GL transaction, depending on the tax it |
|
284 |
will also automatically create the tax entry. The GL transaction already needs |
|
285 |
to have certain values, e.g. transdate, taxincluded, ... |
|
286 |
|
|
287 |
Mandatory params are |
|
288 |
|
|
289 |
=over 2 |
|
290 |
|
|
291 |
=item * chart as an RDBO object |
|
292 |
|
|
293 |
=item * tax_id |
|
294 |
|
|
295 |
=item * either debit OR credit (positive values) |
|
296 |
|
|
297 |
=back |
|
298 |
|
|
299 |
Optional params: |
|
300 |
|
|
301 |
=over 2 |
|
302 |
|
|
303 |
=item * dec - number of decimals to round to, defaults to 2 |
|
304 |
|
|
305 |
=item * source |
|
306 |
|
|
307 |
=item * memo |
|
308 |
|
|
309 |
=item * project_id |
|
310 |
|
|
311 |
=back |
|
312 |
|
|
313 |
All other values are taken directly from the GL transaction. |
|
314 |
|
|
315 |
For an example, see C<post>. |
|
316 |
|
|
317 |
After adding an acc_trans entry the GL transaction shouldn't be modified (e.g. |
|
318 |
values affecting the acc_trans entries, such as transdate or taxincluded |
|
319 |
shouldn't be changed). There is currently no method for recalculating the |
|
320 |
acc_trans entries after they were added. |
|
321 |
|
|
322 |
Return C<$self>, so it allows chaining. |
|
323 |
|
|
324 |
=item C<validate> |
|
325 |
|
|
326 |
Runs various checks to see if the GL transaction is ready to be C<post>ed. |
|
327 |
|
|
328 |
Will return an array of error strings if any necessary conditions aren't met. |
|
329 |
|
|
330 |
=back |
|
331 |
|
|
332 |
=head1 TODO |
|
333 |
|
|
334 |
Nothing here yet. |
|
335 |
|
|
336 |
=head1 AUTHOR |
|
337 |
|
|
338 |
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>, |
|
339 |
G. Richardson E<lt>grichardson@kivitec.deE<gt> |
|
340 |
|
|
341 |
=cut |
Auch abrufbar als: Unified diff
GLTransaction - Dialogbuchungen per Rose erstellen
neue Methoden in GLTransaction zum Erstellen von DialogbuchungenAn einigen Stellen im Code werden Dialogbuchungen per Hand erstellt,
inkl. Steuern, das soll hiermit vereinheitlicht und vereinfacht
werden.
Acc_trans-Einträge können nun mit wenigen Parametern zu Dialogbuchungen
hinzugefügt werden, die Parameter orientieren sich dabei an den Werten,
wie sie auch an der Oberfläche eingegeben werden (Konto, Soll/Haben,
Steuer). Dabei werden einige der Werte aus der GLTransaction
automatisch übernommen.
Beim Buchen wird eine neue Transaktion gestartet, die Buchung wird
validiert und es wird ein Historieneintrag erstellt.