+sub date { goto &gldate }
+
+sub post {
+  my ($self) = @_;
+
+  my @errors = $self->validate;
+  croak t8("Errors in GL transaction:") . "\n" . join("\n", @errors) . "\n" if scalar @errors;
+
+  # make sure all the defaults are set:
+  require SL::DB::Employee;
+  my $employee_id = SL::DB::Manager::Employee->current->id;
+  $self->type(undef);
+  $self->employee_id($employee_id) unless defined $self->employee_id || defined $self->employee;
+  $self->ob_transaction('f') unless defined $self->ob_transaction;
+  $self->cb_transaction('f') unless defined $self->cb_transaction;
+  $self->gldate(DateTime->today_local) unless defined $self->gldate; # should user even be allowed to set this manually?
+  $self->transdate(DateTime->today_local) unless defined $self->transdate;
+
+  $self->db->with_transaction(sub {
+    $self->save;
+
+    if ($::instance_conf->get_datev_check_on_gl_transaction) {
+      my $datev = SL::DATEV->new(
+        dbh      => $self->dbh,
+        trans_id => $self->id,
+      );
+
+      $datev->generate_datev_data;
+
+      if ($datev->errors) {
+         die join "\n", t8('DATEV check returned errors:'), $datev->errors;
+      }
+    }
+
+    require SL::DB::History;
+    SL::DB::History->new(
+      trans_id    => $self->id,
+      snumbers    => 'gltransaction_' . $self->id,
+      employee_id => $employee_id,
+      addition    => 'POSTED',
+      what_done   => 'gl transaction',
+    )->save;
+
+    1;
+  }) or die t8("Error when saving: #1", $self->db->error);
+
+  return $self;
+}
+
+sub add_chart_booking {
+  my ($self, %params) = @_;
+
+  require SL::DB::Chart;
+  die "add_chart_booking needs a transdate" unless $self->transdate;
+  die "add_chart_booking needs taxincluded" unless defined $self->taxincluded;
+  die "chart missing"  unless $params{chart} && ref($params{chart}) eq 'SL::DB::Chart';
+  die t8('Booking needs at least one debit and one credit booking!')
+    unless $params{debit} or $params{credit}; # must exist and not be 0
+  die t8('Cannot have a value in both Debit and Credit!')
+    if defined($params{debit}) and defined($params{credit});
+
+  my $chart = $params{chart};
+
+  my $dec = delete $params{dec} // 2;
+
+  my ($netamount,$taxamount) = (0,0);
+  my $amount = $params{credit} // $params{debit}; # only one can exist
+
+  croak t8('You cannot use a negative amount with debit/credit!') if $amount < 0;
+
+  require SL::DB::Tax;
+
+  my $ct        = $chart->get_active_taxkey($self->deliverydate // $self->transdate);
+  my $chart_tax = ref $ct eq 'SL::DB::TaxKey' ? $ct->tax : undef;
+
+  my $tax = defined($params{tax_id})        ? SL::DB::Manager::Tax->find_by(id => $params{tax_id}) # 1. user param
+          : ref $chart_tax eq 'SL::DB::Tax' ? $chart_tax                                           # automatic tax
+          : SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00);                              # no tax
+
+  die "No valid tax found. User input:" . $params{tax_id} unless ref $tax eq 'SL::DB::Tax';
+
+  if ( $tax and $tax->rate != 0 ) {
+    ($netamount, $taxamount) = Form->calculate_tax($amount, $tax->rate, $self->taxincluded, $dec);
+  } else {
+    $netamount = $amount;
+  };
+
+  if ( $params{debit} ) {
+    $amount    *= -1;
+    $netamount *= -1;
+    $taxamount *= -1;
+  };
+
+  next unless $netamount; # skip entries with netamount 0
+
+  # initialise transactions if it doesn't exist yet
+  $self->transactions([]) unless $self->transactions;
+
+  require SL::DB::AccTransaction;
+  $self->add_transactions( SL::DB::AccTransaction->new(
+    chart_id       => $chart->id,
+    chart_link     => $chart->link,
+    amount         => $netamount,
+    taxkey         => $tax->taxkey,
+    tax_id         => $tax->id,
+    transdate      => $self->transdate,
+    source         => $params{source} // '',
+    memo           => $params{memo}   // '',
+    ob_transaction => $self->ob_transaction,
+    cb_transaction => $self->cb_transaction,
+    project_id     => $params{project_id},
+  ));
+
+  # only add tax entry if amount is >= 0.01, defaults to 2 decimals
+  if ( $::form->round_amount(abs($taxamount), $dec) > 0 ) {
+    my $tax_chart = $tax->chart;
+    if ( $tax->chart ) {
+      $self->add_transactions(SL::DB::AccTransaction->new(
+                                chart_id       => $tax_chart->id,
+                                chart_link     => $tax_chart->link,
+                                amount         => $taxamount,
+                                taxkey         => $tax->taxkey,
+                                tax_id         => $tax->id,
+                                transdate      => $self->transdate,
+                                ob_transaction => $self->ob_transaction,
+                                cb_transaction => $self->cb_transaction,
+                                source         => $params{source} // '',
+                                memo           => $params{memo}   // '',
+                                project_id     => $params{project_id},
+                              ));
+    };
+  };
+  return $self;
+};
+
+sub validate {
+  my ($self) = @_;
+
+  my @errors;
+
+  if ( $self->transactions && scalar @{ $self->transactions } ) {
+    my $debit_count  = map { $_->amount } grep { $_->amount > 0 } @{ $self->transactions };
+    my $credit_count = map { $_->amount } grep { $_->amount < 0 } @{ $self->transactions };
+
+    if ( $debit_count > 1 && $credit_count > 1 ) {
+      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. ' .
+                       'Due to known problems involving accounting software kivitendo does not allow these.');
+    } elsif ( $credit_count == 0 && $debit_count == 0 ) {
+      push @errors, t8('Booking needs at least one debit and one credit booking!');
+    } else {
+      # transactions formally ok, now check for out of balance:
+      my $sum = sum map { $_->amount } @{ $self->transactions };
+      # compare rounded amount to 0, to get around floating point problems, e.g.
+      # $sum = -2.77555756156289e-17
+      push @errors, t8('Out of balance transaction!') unless $::form->round_amount($sum,5) == 0;
+    };
+  } else {
+    push @errors, t8('Empty transaction!');
+  };
+
+  # fields enforced by interface
+  push @errors, t8('Reference missing!')   unless $self->reference;
+  push @errors, t8('Description missing!') unless $self->description;
+
+  # date checks
+  push @errors, t8('Transaction Date missing!') unless $self->transdate && ref($self->transdate) eq 'DateTime';
+
+  if ( $self->transdate ) {
+    if ( $::form->date_closed( $self->transdate, \%::myconfig) ) {
+      if ( !$self->id ) {
+        push @errors, t8('Cannot post transaction for a closed period!')
+      } else {
+        push @errors, t8('Cannot change transaction in a closed period!')
+      };
+    };
+
+    push @errors, t8('Cannot post transaction above the maximum future booking date!')
+      if $::form->date_max_future($self->transdate, \%::myconfig);
+  }
+
+  return @errors;
+}
+