# This file has been auto-generated only because it didn't exist.
# Feel free to modify it at will; it will not be overwritten automatically.

package SL::DB::Manager::PriceRuleItem;

use strict;

use SL::DB::Helper::Manager;
use base qw(SL::DB::Helper::Manager);

sub object_class { 'SL::DB::PriceRuleItem' }

__PACKAGE__->make_manager_methods;

use SL::Locale::String qw(t8);
use List::Util qw(first);

use SL::DB::CustomVariableConfig;

my %ops = (
  'num'  => { eq => '=', le => '<=', ge => '>=' },
  'date' => { eq => '=', lt => '<', gt => '>' },
);

my @types = (
  { type => 'customer',   description => t8('Customer'),           customer => 1, vendor => 0, data_type => 'int',  data => sub { $_[0]->customer->id }, },
  { type => 'vendor',     description => t8('Vendor'),             customer => 0, vendor => 1, data_type => 'int',  data => sub { $_[0]->vendor->id }, },
  { type => 'business',   description => t8('Type of Business'),   customer => 1, vendor => 1, data_type => 'int',  data => sub { $_[0]->customervendor->business_id }, exclude_nulls => 1 },
  { type => 'reqdate',    description => t8('Reqdate'),            customer => 1, vendor => 1, data_type => 'date', data => sub { $_[0]->reqdate }, ops => 'date' },
  { type => 'transdate',  description => t8('Transdate'),          customer => 1, vendor => 1, data_type => 'date', data => sub { $_[0]->transdate }, ops => 'date' },
  { type => 'part',       description => t8('Part'),               customer => 1, vendor => 1, data_type => 'int',  data => sub { $_[1]->part->id }, },
  { type => 'pricegroup', description => t8('Pricegroup'),         customer => 1, vendor => 1, data_type => 'int',  data => sub { $_[1]->pricegroup_id }, exclude_nulls => 1 },
  { type => 'partsgroup', description => t8('Partsgroup'),         customer => 1, vendor => 1, data_type => 'int',  data => sub { $_[1]->part->partsgroup_id }, exclude_nulls => 1 },
  { type => 'qty',        description => t8('Qty'),                customer => 1, vendor => 1, data_type => 'num',  data => sub { $_[1]->qty }, ops => 'num' },
);

# text, textfield, htmlfield, bool are not supported
our %price_rule_type_by_cvar_type = (
  select    => 'text',
  customer  => 'int',
  vendor    => 'int',
  part      => 'int',
  number    => 'num',
  date      => 'date',
  text      => undef,
  textfield => undef,
  htmlfield => undef,
  bool      => undef,
);


# ITEM.part.cvar_by_name(var.config.name)

sub not_matching_sql_and_values {
  my ($class, %params) = @_;

  die 'must be called with a customer/vendor type' unless $params{type};
  my @args = @params{'record', 'record_item'};

  my (@tokens, @values);

  for my $def (@types, cached_cvar_types()) {
    my $type = $def->{type};
    next unless $def->{$params{type}};

    my $value = $def->{data}->(@args);

    my $type_token = $def->{cvar_config} ? "custom_variable_configs_id = '$def->{cvar_config}'" : "type = '$type'";

    if ($def->{exclude_nulls} && !defined $value) {
      push @tokens, $type_token;
    } else {
      my @sub_tokens;
      if ($def->{ops}) {
        my $ops = $ops{$def->{ops}};

        for (keys %$ops) {
          push @sub_tokens, "op = '$_' AND NOT ? $ops->{$_} value_$def->{data_type}";
          push @values, $value;
        }
      } else {
        push @sub_tokens, "NOT value_$def->{data_type} = ?";
        push @values, $value;
      }

      push @tokens, "$type_token AND (@{[ join(' OR ', map qq|($_)|, @sub_tokens) ]})";
    }
  }

  return join(' OR ', map "($_)", @tokens), @values;
}

sub cached_cvar_types {
  my $cache = $::request->cache("SL::DB::PriceRuleItem::cvar_types", []);

  @$cache = generate_cvar_types() if !@$cache;
  @$cache
}

# we only generate cvar types for cvar price_rules that are actually used to keep the query smaller
# these are cached per request
sub generate_cvar_types {
  my @supported_cvar_modules = qw(CT Contacts IC Projects ShipTo);

  my $cvar_configs = SL::DB::Manager::CustomVariableConfig->get_all(query => [ module => \@supported_cvar_modules ]);

  my @types;

  my %ops_by_cvar_type = (
    number    => 'num',
    date      => 'date',
  );

  for my $config (@$cvar_configs) {
    # cvars can be pretty complicated, but most of that luckily doesn't affect price rule filtering:
    #   - editable flags are copied to submodule entries - so those need to be preferred
    #   - cvars may be invalid - but in that case they just won't get crated so we won't find any
    #   - cvars may be restricted to partgroups, but again, in that case we simply won't find any
    #   - cvars may have different types of values () just like price_rule_items, but the ->value
    #     accessor already handles that, so we only need to set the price_rule type accordingly


    my %data_by_module = (
      IC => sub {
        raw_value(
          $config->processed_flags->{editable}
            ? $_[1]->cvar_by_name($config->name)->value
            : $_[1]->part->cvar_by_name($config->name)->value
        );
      },
      CT => sub {
        raw_value(
          $_[0]->customervendor->cvar_by_name($config->name)->value
        );
      },
      Projects => sub {
        raw_value(
          $_[1]->project ? $_[1]->project->cvar_by_name($config->name)->value :
          $_[0]->globalproject ? $_[0]->globalproject->cvar_by_name($config->name)->value : undef
        );
      },
      Contacts => sub {
        raw_value(
          $_[0]->contact ? $_[0]->contact->cvar_by_name($config->name)->value : undef
        );
      },
      ShipTo => sub {
        raw_value(
          $_[0]->custom_shipto ? $_[0]->custom_shipto->cvar_by_name($config->name)->value :
          $_[0]->shipto ? $_[0]->shipto->cvar_by_name($config->name)->value : undef
        );
      },
    );

    my $data_type;
    if (exists $price_rule_type_by_cvar_type{$config->type}) {
      # known but undef typedefs are ignored.
      # those cvar configs are not supported and can not be used in price rules
      $data_type = $price_rule_type_by_cvar_type{$config->type} or next;
    } else {
      die "cvar type @{[$config->type]} " . $config->description . " is not supported in price rules";
    }

    my $ops = $ops_by_cvar_type{$config->type};

    push @types, {
      type          => "cvar_" . $config->id,
      description   => $config->description,
      customer      => 1,
      vendor        => 1,
      data_type     => $data_type,
      data          => $data_by_module{$config->module},
      exclude_nulls => 1,
      cvar_config   => $config->id,
      ops           => $ops,
    };
  }

  @types;
}

sub raw_value {
  my ($value) = @_;
  return if !defined $value;
  return $value->id if (ref $value) =~ /Part|Customer|Contact|Vendor|Project/;
  return $value if (ref $value) =~ /DateTime/;
  die "reference value unsupported for binding to price_rules, got ref " . ref $value if ref $value;
  $value;
}

sub get_all_types {
  my ($class, $vc) = @_;

  $vc
  ? [ map { [ $_->{type}, $_->{description} ] } grep { $_->{$vc} } @types ]
  : [ map { [ $_->{type}, $_->{description} ] } @types ]
}

sub get_type {
  grep { $_->{type} eq $_[1] } @types
}

sub filter_match {
  my ($self, $type, $value) = @_;

  my $type_def = first { $_->{type} eq $type } @types;

  if (!$type_def->{ops}) {
    my $evalue   = $::form->get_standard_dbh->quote($value);
    return "value_$type_def->{data_type} = $evalue";
  } elsif ($type_def->{ops} eq 'date') {
    my $date_value   = $::form->get_standard_dbh->quote(DateTime->from_kivitendo($value));
    return "
      (value_$type_def->{data_type} > $date_value AND op = 'lt') OR
      (value_$type_def->{data_type} < $date_value AND op = 'gt') OR
      (value_$type_def->{data_type} = $date_value AND op = 'eq')
    ";
  } elsif ($type_def->{ops} eq 'num') {
    my $num_value   = $::form->get_standard_dbh->quote($::form->parse_amount(\%::myconfig, $value));
    return "
      (value_$type_def->{data_type} >= $num_value AND op = 'le') OR
      (value_$type_def->{data_type} <= $num_value AND op = 'ge') OR
      (value_$type_def->{data_type} =  $num_value AND op = 'eq')
    ";
  }
}


1;
