1 package SL::DB::Object;
 
   6 use English qw(-no_match_vars);
 
   8 use Rose::DB::Object::Constants qw();
 
   9 use List::MoreUtils qw(any pairwise);
 
  10 use List::Util qw(first);
 
  13 use SL::DB::Helper::Attr;
 
  14 use SL::DB::Helper::Metadata;
 
  15 use SL::DB::Helper::Manager;
 
  16 use SL::DB::Helper::Presenter;
 
  17 use SL::DB::Object::Hooks;
 
  19 use base qw(Rose::DB::Object);
 
  21 my @rose_reserved_methods = qw(
 
  22   db dbh delete DESTROY error init_db _init_db insert load meta meta_class
 
  23   not_found save update import
 
  26 my %db_to_presenter_mapping = (
 
  27   Customer        => 'CustomerVendor',
 
  28   PurchaseInvoice => 'Invoice',
 
  29   Vendor          => 'CustomerVendor',
 
  30   GLTransaction   => 'GL',
 
  35   my $self  = $class->SUPER::new();
 
  37   $self->_assign_attributes(@_) if $self;
 
  43   my $class_or_self = shift;
 
  44   my $class         = ref($class_or_self) || $class_or_self;
 
  45   my $type          = $class =~ m/::Auth/ ? 'KIVITENDO_AUTH' : 'KIVITENDO';
 
  47   return SL::DB::create(undef, $type);
 
  51   return 'SL::DB::Helper::Metadata';
 
  54 sub _get_manager_class {
 
  55   my $class_or_self = shift;
 
  56   my $class         = ref($class_or_self) || $class_or_self;
 
  58   return $class->meta->convention_manager->auto_manager_class_name($class);
 
  61 my %text_column_types = (text => 1, char => 1, varchar => 1);
 
  63 sub assign_attributes {
 
  67   my $pk         = ref($self)->meta->primary_key;
 
  68   delete @attributes{$pk->column_names} if $pk;
 
  69   delete @attributes{@rose_reserved_methods};
 
  71   return $self->_assign_attributes(%attributes);
 
  74 sub _assign_attributes {
 
  78   my %types      = map { $_->name => $_->type } ref($self)->meta->columns;
 
  80   # Special case for *_as_man_days / *_as_man_days_string /
 
  81   # *_as_man_days_unit: the _unit variation must always be called
 
  82   # after the non-unit methods.
 
  83   my @man_days_attributes = grep { m/_as_man_days(?:_string)?$/ } keys %attributes;
 
  84   foreach my $attribute (@man_days_attributes) {
 
  85     my $value = delete $attributes{$attribute};
 
  86     $self->$attribute(defined($value) && ($value eq '') ? undef : $value);
 
  89   while (my ($attribute, $value) = each %attributes) {
 
  90     my $type = lc($types{$attribute} || 'text');
 
  91     $value   = $type eq 'boolean'                ? ($value ? 't' : 'f')
 
  92              : $text_column_types{$type}         ? $value
 
  93              : defined($value) && ($value eq '') ? undef
 
  95     $self->$attribute($value);
 
 101 sub update_attributes {
 
 104   $self->assign_attributes(@_)->save;
 
 109 sub update_collection {
 
 110   my ($self, $attribute, $entries) = @_;
 
 112   my $self_primary_key = "" . ($self->meta->primary_key_columns)[0];
 
 114   croak "\$self hasn't been saved yet" if !$self->$self_primary_key;
 
 116   my $relationship = first { $_->name eq $attribute } @{ $self->meta->relationships };
 
 118   croak "No relationship found for attribute '$attribute'" if !$relationship;
 
 120   my @primary_key_columns = $relationship->class->meta->primary_key_columns;
 
 122   croak "Classes with multiple primary key columns are not supported" if scalar(@primary_key_columns) > 1;
 
 124   my $class             = $relationship->class;
 
 125   my $manager_class     = "SL::DB::Manager::" . substr($class, 8);
 
 126   my $other_primary_key = "" . $primary_key_columns[0];
 
 127   my $column_map        = $relationship->column_map;
 
 128   my @new_entries       = @{ $entries          // [] };
 
 129   my @existing_entries  = @{ $self->$attribute // [] };
 
 130   my @to_delete         = grep { my $value = $_->$other_primary_key; !any { $_->{$other_primary_key} == $value } @new_entries } @existing_entries;
 
 132   $_->delete for @to_delete;
 
 134   foreach my $entry (@new_entries) {
 
 135     if (!$entry->{$other_primary_key}) {
 
 136       my $new_instance = $class->new(%{ $entry });
 
 138       foreach my $self_attribute (keys %{ $column_map }) {
 
 139         my $other_attribute = $column_map->{$self_attribute};
 
 140         $new_instance->$other_attribute($self->$self_attribute);
 
 148     my $existing = first { $_->$other_primary_key == $entry->{$other_primary_key} } @existing_entries;
 
 149     $existing->update_attributes(%{ $entry }) if $existing;
 
 156   return $self->$sub(@_);
 
 164   $check    = $check->($self) if ref($check) eq 'CODE';
 
 166   return $check ? $self->$sub(@_) : $self;
 
 169 sub get_first_conflicting {
 
 170   my ($self, @attributes) = @_;
 
 172   my $primary_key         = ($self->meta->primary_key)[0];
 
 173   my @where               = map { ($_ => $self->$_) } @attributes;
 
 175   push @where, ("!$primary_key" => $self->$primary_key) if $self->$primary_key;
 
 177   return $self->_get_manager_class->get_first(where => [ and => \@where ]);
 
 180 # These three functions cannot sit in SL::DB::Object::Hooks because
 
 181 # mixins don't deal well with super classes (SUPER is the current
 
 182 # package's super class, not $self's).
 
 184   my ($self, @args) = @_;
 
 186   SL::DB::Object::Hooks::run_hooks($self, 'before_load');
 
 187   my $result = $self->SUPER::load(@args);
 
 188   SL::DB::Object::Hooks::run_hooks($self, 'after_load', $result);
 
 194   my ($self, @args) = @_;
 
 198   $self->db->with_transaction(sub {
 
 199     SL::DB::Object::Hooks::run_hooks($self, 'before_save');
 
 200     $result = $self->SUPER::save(@args);
 
 201     SL::DB::Object::Hooks::run_hooks($self, 'after_save', $result);
 
 204   }) || die $self->db->error;
 
 210   my ($self, @args) = @_;
 
 214   $self->db->with_transaction(sub {
 
 215     SL::DB::Object::Hooks::run_hooks($self, 'before_delete');
 
 216     $result = $self->SUPER::delete(@args);
 
 217     SL::DB::Object::Hooks::run_hooks($self, 'after_delete', $result);
 
 220   }) || die $self->db->error;
 
 226   my $class_or_self = shift;
 
 228   my $class         = ref($class_or_self) || $class_or_self;
 
 229   my $cache         = $::request->cache("::SL::DB::Object::object_cache::${class}");
 
 231   croak "Missing ID" unless @ids;
 
 233   my @missing_ids = grep { !exists $cache->{$_} } @ids;
 
 235   return $cache->{$ids[0]} if !@missing_ids;
 
 237   croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
 
 239   my $primary_key = $class->meta->primary_key_columns->[0]->name;
 
 240   my $objects     = $class->_get_manager_class->get_all(where => [ $primary_key => \@missing_ids ]);
 
 242   $cache->{$_->$primary_key} = $_ for @{ $objects};
 
 244   return $cache->{$ids[0]};
 
 247 sub invalidate_cached {
 
 248   my ($class_or_self, @ids) = @_;
 
 249   my $class                 = ref($class_or_self) || $class_or_self;
 
 251   if (ref($class_or_self) && !@ids) {
 
 252     croak "Caching can only be used with classes with exactly one primary key column" if 1 != scalar(@{ $class->meta->primary_key_columns });
 
 254     my $primary_key = $class->meta->primary_key_columns->[0]->name;
 
 255     @ids            = ($class_or_self->$primary_key);
 
 258   delete @{ $::request->cache("::SL::DB::Object::object_cache::${class}") }{ @ids };
 
 260   return $class_or_self;
 
 263 my %_skip_fields_when_cloning = map { ($_ => 1) } qw(itime mtime);
 
 265 sub clone_and_reset {
 
 267   my $class               = ref $self;
 
 268   my $cloning             = Rose::DB::Object::Constants::STATE_CLONING();
 
 269   local $self->{$cloning} = 1;
 
 271   my $meta                = $class->meta;
 
 272   my @accessors           = $meta->column_accessor_method_names;
 
 273   my @mutators            = $meta->column_mutator_method_names;
 
 275     grep     { $_->[0] && $_->[1] && !$_skip_fields_when_cloning{ $_->[0] } }
 
 276     pairwise { no warnings qw(once); [ $a, $b] } @accessors, @mutators;
 
 278   my $clone = $class->new(map { my $method = $_->[0]; ($_->[1] => $self->$method) } @column_names);
 
 280   # Blank all primary and unique key columns
 
 282     $meta->primary_key_column_mutator_names,
 
 283     map { my $uk = $_; map { $meta->column_mutator_method_name($_) } ($uk->columns) } ($meta->unique_keys)
 
 286   $clone->$_(undef) for @keys;
 
 288   # Also copy db object, if any
 
 289   $clone->db($self->{db}) if $self->{db};
 
 297   my $class =  ref $self;
 
 298   $class    =~ s{^SL::DB::}{};
 
 299   $class    =  "SL::Presenter::" . ($db_to_presenter_mapping{$class} // $class);
 
 301   return SL::DB::Helper::Presenter->new($class, $self);
 
 309       my $column_name = $_->name;
 
 310       my $value       = $self->$column_name;
 
 311       $value          = !defined($value) ? undef : "${value}";
 
 313     } $self->meta->columns
 
 327 SL::DB::Object: Base class for all of our model classes
 
 331 This is the base class from which all other model classes are
 
 332 derived. It contains functionality and settings required for all model
 
 335 Several functions (e.g. C<make_manager_class>, C<init_db>) in this
 
 336 class are used for setting up the classes / base classes used for all
 
 337 model instances. They overwrite the functions from
 
 344 =item assign_attributes %attributes
 
 346 =item _assign_attributes %attributes
 
 348 Assigns all elements from C<%attributes> to the columns by calling
 
 349 their setter functions. The difference between the two functions is
 
 350 that C<assign_attributes> protects primary key columns while
 
 351 C<_assign_attributes> doesn't.
 
 353 Both functions handle values that are empty strings by replacing them
 
 354 with C<undef> for non-text columns. This allows the calling functions
 
 355 to use data from HTML forms as the input for C<assign_attributes>
 
 356 without having to remove empty strings themselves (think of
 
 357 e.g. select boxes with an empty option which should be turned into
 
 358 C<NULL> in the database).
 
 360 =item update_attributes %attributes
 
 362 Assigns the attributes from C<%attributes> by calling the
 
 363 C<assign_attributes> function and saves the object afterwards. Returns
 
 366 =item C<update_collection $attribute, $entries, %params>
 
 368 Updates a one-to-many relationship named C<$attribute> to match the
 
 369 entries in C<$entries>. C<$entries> is supposed to be an array ref of
 
 372 For each hash ref in C<$entries> that does not contain a field for the
 
 373 relationship's primary key column, this function creates a new entry
 
 374 in the database with its attributes set to the data in the entry.
 
 376 For each hash ref in C<$entries> that contains a field for the
 
 377 relationship's primary key column, this function looks up the
 
 378 corresponding entry in C<$self->$attribute> & updates its
 
 379 attributes with the data in the entry.
 
 381 All objects in C<$self->$attribute> for which no corresponding
 
 382 entry exists in C<$entries> are deleted by calling the object's
 
 385 In all cases the relationship itself C<$self->$attribute> is not
 
 388 =item _get_manager_class
 
 390 Returns the manager package for the object or class that it is called
 
 391 on. Can be used from methods in this package for getting the actual
 
 394 =item C<call_sub $name, @args>
 
 396 Calls the sub C<$name> on C<$self> with the arguments C<@args> and
 
 397 returns its result. This is meant for situations in which the sub's
 
 398 name is a composite, e.g.
 
 400   my $chart_id = $buchungsgruppe->call_sub(($is_sales ? "income" : "expense") . "_accno_id_${taxzone_id}");
 
 402 =item C<call_sub_if $name, $check, @args>
 
 404 Calls the sub C<$name> on C<$self> with the arguments C<@args> if
 
 405 C<$check> is trueish. If C<$check> is a code reference then it will be
 
 406 called with C<$self> as the only argument and its result determines
 
 407 whether or not C<$name> is called.
 
 409 Returns the sub's result if the check is positive and C<$self>
 
 412 =item C<get_first_conflicting @attributes>
 
 414 Returns the first object for which all properties listed in
 
 415 C<@attributes> equal those in C<$self> but which is not C<$self>. Can
 
 416 be used to check whether or not an object's columns are unique before
 
 417 saving or during validation.
 
 419 =item C<load_cached @ids>
 
 421 Loads objects from the database which haven't been cached before and
 
 422 caches them for the duration of the current request (see
 
 423 L<SL::Request/cache>).
 
 425 If you know in advance that you will likely need all objects of a
 
 426 particular type then you can pre-cache them by calling the manager's
 
 427 C<cache_all> function. For example, if you expect to need all unit
 
 428 objects, you can use C<SL::DB::Manager::Unit-E<gt>cache_all> before
 
 429 you start the actual work. Later you can use
 
 430 C<SL::DB::Unit-E<gt>load_cached> to retrieve individual objects and be
 
 431 sure that they're already cached.
 
 433 This method can be called both as an instance method and a class
 
 434 method. It loads objects for the corresponding class (e.g. both
 
 435 C<SL::DB::Part-E<gt>load_cached(…)> and
 
 436 C<$some_part-E<gt>load_cached(…)> will load parts).
 
 438 Currently only classes with a single primary key column are supported.
 
 440 Returns the cached object for the first ID.
 
 442 =item C<invalidate_cached @ids>
 
 444 Deletes all cached instances of this class (see L</load_cached>) for
 
 447 If called as an instance method without further arguments then the
 
 450 Returns the object/class it was called on.
 
 452 =item C<clone_and_reset>
 
 454 This works similar to L<Rose::DB::Object::Helpers/clone_and_reset>: it
 
 455 returns a cloned instance of C<$self>. All primary and unique key
 
 456 fields have been reset.
 
 458 The difference between Rose's and this function is that this function
 
 459 will also skip setting the following fields if such columns exist for
 
 460 C<$self>: C<itime>, C<mtime>.
 
 464 Returns a proxy wrapper that will dispatch all method calls to the presenter
 
 465 with the same name as the class of the involking object.
 
 467 For the full documentation about its capabilites see
 
 468 L<SL::DB::Helper::Presenter>
 
 470 =item C<as_debug_info>
 
 472 Returns a hash containing solely the essentials for dumping it with
 
 473 L<LXDebug/dump>. The returned hash consists of the column names with
 
 474 associated column values in stringified form.
 
 480 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>