Revision 697fe153
Von Sven Schöling vor etwa 2 Jahren hinzugefügt
SL/DB/Manager/PriceRuleItem.pm | ||
---|---|---|
{ 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' },
|
||
{ type => 'cvar', description => t8('Custom Variables'), customer => 1, vendor => 1, data_type => 'int', data => sub { $_[1]->part->cvar_by_name('articlegroup')->id }, exclude_nulls => 1 },
|
||
);
|
||
|
||
# ITEM.part.cvar_by_name(var.config.name)
|
||
... | ... | |
# 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 $cvar_configs = SL::DB::Manager::CustomVariableConfig->get_all(query => [
|
||
id => [ \"(select distinct custom_variable_configs_id from price_rule_items where custom_variable_configs_id is not null)" ]
|
||
]);
|
||
my $cvar_configs = SL::DB::Manager::CustomVariableConfig->get_all;
|
||
|
||
my @types;
|
||
|
||
# text, textfield, bool are not supported
|
||
my %price_rule_type_by_cvar_type = (
|
||
select => 'text',
|
||
customer => 'int',
|
||
vendor => 'int',
|
||
part => 'int',
|
||
number => 'num',
|
||
date => 'date',
|
||
);
|
||
|
||
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
|
||
);
|
||
},
|
||
);
|
||
|
||
my $data_type = $price_rule_type_by_cvar_type{$config->type} or
|
||
die "cvar type @{[$config->type]} 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 => 'text',
|
||
data => sub {
|
||
$config->processed_flags->{editable}
|
||
? $_[1]->cvar_by_name($config->name)->value
|
||
: $_[1]->part->cvar_by_name($config->name)->value
|
||
},
|
||
data_type => $data_type,
|
||
data => $data_by_module{$config->module},
|
||
exclude_nulls => 1,
|
||
cvar_config => $config->id,
|
||
} if $config->module eq 'IC' && $config->type eq 'select';
|
||
|
||
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) = @_;
|
||
|
t/db/price_rule.t | ||
---|---|---|
|
||
use SL::Dev::ALL qw(:ALL);
|
||
use SL::DB::PriceRule;
|
||
use SL::DB::Project;
|
||
use SL::DB::CustomVariableConfig;
|
||
|
||
Support::TestSetup::login();
|
||
... | ... | |
description => "test",
|
||
type => "select",
|
||
options => "A##B##C##D",
|
||
default_value => "D",
|
||
flags => "editable=0",
|
||
searchable => 0,
|
||
includeable => 0,
|
||
... | ... | |
description => "test2",
|
||
type => "select",
|
||
options => "A##B##C##D",
|
||
default_value => "D",
|
||
flags => "editable=1",
|
||
searchable => 0,
|
||
includeable => 0,
|
||
... | ... | |
}
|
||
|
||
|
||
|
||
# k, now for a more broad test:
|
||
#
|
||
# we can have these modules in cvars:
|
||
# - CT
|
||
# - Contact
|
||
# - IC
|
||
# - Project
|
||
#
|
||
# and the cvars themselves can have these types:
|
||
# - select
|
||
# - customer
|
||
# - vendor
|
||
# - part
|
||
# - integer
|
||
# - number
|
||
# - date
|
||
# - timestamp
|
||
#
|
||
# ...with the numeric and date ones also having comparison ops
|
||
#
|
||
#
|
||
# to be matched against all different record/record items
|
||
#
|
||
#
|
||
# testing all of that is too much, so this will do some combinations:
|
||
# 1. a cvar config
|
||
# 2. a price_rule that uses both
|
||
# 3. record + record item that either uses that or not
|
||
# 4. expected behaviour
|
||
{
|
||
sub test {
|
||
my ($price_rule, $record, $record_item, $comment, $expected_match) = @_;
|
||
|
||
# needed to clear cvar caches in price rule implementation
|
||
$::request->{_cache} = {};
|
||
|
||
my $matching_rules = SL::DB::Manager::PriceRule->get_all_matching(record => $record, record_item => $record_item);
|
||
my @does_match = grep { $_->{name} eq $price_rule->name } @$matching_rules;
|
||
|
||
if ($expected_match) {
|
||
ok(@does_match && $price_rule->name eq $does_match[0]->name, "$comment - expected match, got @does_match");
|
||
} else {
|
||
ok(!@does_match, "$comment - expected no match, got @does_match");
|
||
}
|
||
}
|
||
|
||
{
|
||
reset_db();
|
||
|
||
my $name = "before critical customer date";
|
||
|
||
my $config = SL::DB::CustomVariableConfig->new(
|
||
module => 'CT',
|
||
type => 'date',
|
||
name => $name,
|
||
description => $name,
|
||
searchable => 0,
|
||
includeable => 0,
|
||
included_by_default => 0,
|
||
)->save->load;
|
||
|
||
my $price_rule = SL::DB::PriceRule->new(
|
||
name => $name,
|
||
price => 1,
|
||
type => "customer",
|
||
items => [
|
||
SL::DB::PriceRuleItem->new(
|
||
custom_variable_configs => $config,
|
||
value_date => DateTime->new(year => 2022, month => 12, day => 9),
|
||
op => "lt",
|
||
type => "cvar",
|
||
),
|
||
],
|
||
)->save;
|
||
|
||
my $order = create_sales_order()->save->load;
|
||
my $item = $order->items_sorted->[0];
|
||
|
||
test($price_rule, $order, $item, $name, 0);
|
||
|
||
$order->customer->cvar_by_name($name)->value(DateTime->new(year => 2022, month => 12, day => 12));
|
||
$order->customer->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- too late", 0);
|
||
|
||
$order->customer->cvar_by_name($name)->value(DateTime->new(year => 2022, month => 12, day => 5));
|
||
$order->customer->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- early", 1);
|
||
}
|
||
|
||
{
|
||
reset_db();
|
||
|
||
my $name = "contact number equals 1234";
|
||
|
||
my $config = SL::DB::CustomVariableConfig->new(
|
||
module => 'Contacts',
|
||
type => 'number',
|
||
name => $name,
|
||
description => $name,
|
||
searchable => 0,
|
||
includeable => 0,
|
||
included_by_default => 0,
|
||
)->save->load;
|
||
|
||
my $price_rule = SL::DB::PriceRule->new(
|
||
name => $name,
|
||
price => 1,
|
||
type => "customer",
|
||
items => [
|
||
SL::DB::PriceRuleItem->new(
|
||
custom_variable_configs => $config,
|
||
value_num => 1234,
|
||
op => "eq",
|
||
type => "cvar",
|
||
),
|
||
],
|
||
)->save;
|
||
|
||
my $order = create_sales_order()->save->load;
|
||
my $item = $order->items_sorted->[0];
|
||
|
||
test($price_rule, $order, $item, "$name -- no contact", 0);
|
||
|
||
$order->contact(SL::DB::Contact->new)->save;
|
||
|
||
test($price_rule, $order, $item, "$name -- null", 0);
|
||
|
||
$order->contact->cvar_by_name($name)->value(45);
|
||
$order->contact->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- not matching", 0);
|
||
|
||
$order->contact->cvar_by_name($name)->value(1234);
|
||
$order->contact->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- matching", 1);
|
||
}
|
||
|
||
{
|
||
reset_db();
|
||
|
||
my $name = "project part matches";
|
||
|
||
my $config = SL::DB::CustomVariableConfig->new(
|
||
module => 'Projects',
|
||
type => 'part',
|
||
name => $name,
|
||
description => $name,
|
||
searchable => 0,
|
||
includeable => 0,
|
||
included_by_default => 0,
|
||
)->save->load;
|
||
|
||
my $part = new_part()->save;
|
||
|
||
my $price_rule = SL::DB::PriceRule->new(
|
||
name => $name,
|
||
price => 1,
|
||
type => "customer",
|
||
items => [
|
||
SL::DB::PriceRuleItem->new(
|
||
custom_variable_configs => $config,
|
||
value_int => $part->id,
|
||
type => "cvar",
|
||
),
|
||
],
|
||
)->save;
|
||
|
||
my $project1 = SL::DB::Project->new(
|
||
project_type => SL::DB::Manager::ProjectType->find_by(description => 'Standard'),
|
||
project_status => SL::DB::Manager::ProjectStatus->find_by(name => 'running'),
|
||
)->save->load;
|
||
|
||
my $order = create_sales_order()->save->load;
|
||
my $item = $order->items_sorted->[0];
|
||
|
||
test($price_rule, $order, $item, "$name -- no project", 0);
|
||
|
||
$order->globalproject($project1)->save;
|
||
|
||
test($price_rule, $order, $item, "$name -- global project, but no value", 0);
|
||
|
||
$order->globalproject->cvar_by_name($name)->value($item->part);
|
||
$order->globalproject->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- global project, not matching", 0);
|
||
|
||
$order->globalproject->cvar_by_name($name)->value($part);
|
||
$order->globalproject->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- global project, matching", 1);
|
||
|
||
my $project2 = SL::DB::Project->new(
|
||
project_type => SL::DB::Manager::ProjectType->find_by(description => 'Standard'),
|
||
project_status => SL::DB::Manager::ProjectStatus->find_by(name => 'running'),
|
||
)->save->load;
|
||
|
||
$item->project($project2)->save;
|
||
|
||
test($price_rule, $order, $item, "$name -- item project, but no value", 0);
|
||
|
||
$item->project->cvar_by_name($name)->value($item->part);
|
||
$item->project->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- item project, not matching", 0);
|
||
|
||
$item->project->cvar_by_name($name)->value($part);
|
||
$item->project->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- item project, matching", 1);
|
||
}
|
||
|
||
{
|
||
reset_db();
|
||
|
||
my $name = "part customer matches";
|
||
|
||
my $config = SL::DB::CustomVariableConfig->new(
|
||
module => 'IC',
|
||
type => 'customer',
|
||
name => $name,
|
||
description => $name,
|
||
searchable => 0,
|
||
includeable => 0,
|
||
included_by_default => 0,
|
||
)->save->load;
|
||
|
||
my $customer = new_customer()->save->load;
|
||
|
||
my $price_rule = SL::DB::PriceRule->new(
|
||
name => $name,
|
||
price => 1,
|
||
type => "vendor",
|
||
items => [
|
||
SL::DB::PriceRuleItem->new(
|
||
custom_variable_configs => $config,
|
||
value_int => $customer->id,
|
||
type => "cvar",
|
||
),
|
||
],
|
||
)->save;
|
||
|
||
my $order = create_purchase_order()->save->load;
|
||
my $item = $order->items_sorted->[0];
|
||
|
||
test($price_rule, $order, $item, "$name -- no value", 0);
|
||
|
||
$item->part->cvar_by_name($name)->value(new_customer());
|
||
$item->part->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- not matching", 0);
|
||
|
||
$item->part->cvar_by_name($name)->value($customer);
|
||
$item->part->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- matching", 1);
|
||
}
|
||
|
||
{
|
||
reset_db();
|
||
|
||
my $name = "part number with default value 15 matches 15";
|
||
|
||
my $config = SL::DB::CustomVariableConfig->new(
|
||
module => 'IC',
|
||
type => 'number',
|
||
name => $name,
|
||
description => $name,
|
||
default_value => 15,
|
||
searchable => 0,
|
||
includeable => 0,
|
||
included_by_default => 0,
|
||
)->save->load;
|
||
|
||
my $price_rule = SL::DB::PriceRule->new(
|
||
name => $name,
|
||
price => 1,
|
||
type => "customer",
|
||
items => [
|
||
SL::DB::PriceRuleItem->new(
|
||
custom_variable_configs => $config,
|
||
value_num => 15,
|
||
op => "eq",
|
||
type => "cvar",
|
||
),
|
||
],
|
||
)->save;
|
||
|
||
my $order = create_sales_order()->save->load;
|
||
my $item = $order->items_sorted->[0];
|
||
|
||
test($price_rule, $order, $item, "$name -- default value", 1);
|
||
|
||
$item->part->cvar_by_name($name)->value(20);
|
||
$item->part->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- not matching", 0);
|
||
|
||
$item->part->cvar_by_name($name)->value(15);
|
||
$item->part->cvar_by_name($name)->save;
|
||
test($price_rule, $order, $item, "$name -- matching", 1);
|
||
}
|
||
}
|
||
|
||
|
||
done_testing();
|
Auch abrufbar als: Unified diff
CVar + PriceRule: Manager Logik und Tests
Implementiert sind jetzt die CVar typen:
- select
- part
- customer
- vendor
- number
- date
nicht unterstützt sind:
- text
- textfield
- bool
unterstützt werden alle module:
- IC
- CT (customer und vendor, unabhängig ob der Beleg customer oder vendor
hat)
- Projects (am recorditem.project oder record.globalproject als fallback)
- Contacts (nur wenn direkt im record gesetzt)