$cvar->{value} = $cvar->{type} eq 'date' ? $act_var->{date_value}
: $cvar->{type} eq 'timestamp' ? $act_var->{timestamp_value}
: $cvar->{type} eq 'number' ? $act_var->{number_value}
+ : $cvar->{type} eq 'customer' ? $act_var->{number_value}
: $cvar->{type} eq 'bool' ? $act_var->{bool_value}
: $act_var->{text_value};
$cvar->{valid} = $valid;
if ($cvar->{type} eq 'number') {
$cvar->{value} = $form->format_amount($myconfig, $cvar->{value} * 1, $cvar->{precision});
+ } elsif ($cvar->{type} eq 'customer') {
+ require SL::DB::Customer;
+ $cvar->{value} = SL::DB::Manager::Customer->find_by(id => $cvar->{value} * 1);
}
}
} elsif ($config->{type} eq 'bool') {
push @values, $value ? 't' : 'f', undef, undef, undef;
+ } elsif ($config->{type} eq 'customer') {
+ push @values, undef, undef, undef, $value * 1;
}
do_statement($form, $sth, $query, @values);
$not = 'NOT' if ($params{filter}->{$name} eq 'no');
push @sub_where, qq|COALESCE(cvar.bool_value, false) = TRUE|;
+ } elsif ($config->{type} eq 'customer') {
+ next unless $params{filter}->{$name};
+
+ push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM customer WHERE name ILIKE ?)|;
+ push @sub_values, "%$params{filter}->{$name}%";
}
if (@sub_where) {
$cfg->{type} eq 'date' ? $ref->{date_value}
: $cfg->{type} eq 'timestamp' ? $ref->{timestamp_value}
: $cfg->{type} eq 'number' ? $form->format_amount($myconfig, $ref->{number_value} * 1, $cfg->{precision})
+ : $cfg->{type} eq 'customer' ? SL::DB::Manager::Customer->find_by(id => 1* $ref->{number_value})->name
: $cfg->{type} eq 'bool' ? ($ref->{bool_value} ? $locale->text('Yes') : $locale->text('No'))
: $ref->{text_value};
}
if ($options->{inline}) {
$source = \$template;
+ } elsif($options->{raw}) {
+ $source = $template;
+
} else {
$source = "templates/webpages/${template}." . $options->{type};
croak "Template file ${source} not found" unless -f $source;
);
my $output;
- my $parser = $self->_template_obj;
- $parser->process($source, \%params, \$output) || croak $parser->error;
+ if (!$options->{raw}) {
+ my $parser = $self->_template_obj;
+ $parser->process($source, \%params, \$output) || croak $parser->error;
+ } else {
+ $output = $$source;
+ }
print $output unless $options->{inline} || $options->{no_output};
will not be sent to the browser. Instead it is only returned to the
caller.
+If C<< $options->{raw}>> is trueish, the function will treat the input as
+already parsed, and will not filter the input through Template. Unlike
+C<inline>, the input is taked as a reference.
+
If C<< $options->{inline} >> is falsish then C<$template> is
interpreted as the name of a template file. It is prefixed with
"templates/webpages/" and postfixed with a file extension based on
--- /dev/null
+package SL::Controller::Customer;
+
+use strict;
+use parent qw(SL::Controller::Base);
+
+use SL::DB::Customer;
+
+# safety
+__PACKAGE__->run_before(sub { $::auth->assert('customer_vendor_edit') });
+
+sub action_ajax_autocomplete {
+ my ($self, %params) = @_;
+
+ my $limit = $::form->{limit} || 20;
+ my $type = $::form->{type} || {};
+ my $query = { ilike => "%$::form->{term}%" };
+ my @filter;
+ push @filter, ($::form->{column})
+ ? ($::form->{column} => $query)
+ : (or => [ customernumber => $query, name => $query ]);
+
+ $self->{customers} = SL::DB::Manager::Customer->get_all(query => [ @filter ], limit => $limit);
+ $self->{value} = $::form->{column} || 'name';
+
+ $self->render('ct/ajax_autocomplete2', { no_layout => 1 });
+}
+
--- /dev/null
+package SL::Controller::Part;
+
+use strict;
+use parent qw(SL::Controller::Base);
+
+use SL::DB::Part;
+
+# safety
+__PACKAGE__->run_before(sub { $::auth->assert('part_service_assembly_edit') });
+
+sub action_ajax_autocomplete {
+ my ($self, %params) = @_;
+
+ my $limit = $::form->{limit} || 20;
+ my $type = $::form->{type} || {};
+ my $query = { ilike => "%$::form->{term}%" };
+ my @filter;
+ push @filter, SL::DB::Manager::Part->type_filter($type);
+ push @filter, ($::form->{column})
+ ? ($::form->{column} => $query)
+ : (or => [ partnumber => $query, description => $query ]);
+
+ $self->{parts} = SL::DB::Manager::Part->get_all(query => [ @filter ], limit => $limit);
+ $self->{value} = $::form->{column} || 'description';
+
+ $self->render('part/ajax_autocomplete', { no_layout => 1 });
+}
+
+
+1;
return join ', ', grep { $_ } $self->street, $self->zipcode, $self->city;
}
+sub displayable_name {
+ my $self = shift;
+
+ return join ' ', grep $_, $self->customernumber, $self->name;
+}
+
1;
translation_payment_terms => 'translation_payment_term',
units => 'unit',
units_language => 'units_language',
+ vendor => 'vendor',
vendortax => 'vendor_tax',
);
use Carp;
use SL::DBUtils;
+use SL::MoreCommon qw(listify);
sub object_class { 'SL::DB::Part' }
__PACKAGE__->make_manager_methods;
sub type_filter {
- my $class = shift;
- my $type = lc(shift || '');
-
- if ($type =~ m/^part/) {
- return (and => [ or => [ assembly => 0, assembly => undef ],
- '!inventory_accno_id' => 0,
- '!inventory_accno_id' => undef,
- ]);
-
- } elsif ($type =~ m/^service/) {
- return (and => [ or => [ assembly => 0, assembly => undef ],
- or => [ inventory_accno_id => 0, inventory_accno_id => undef ],
- ]);
-
- } elsif ($type =~ m/^assembl/) {
- return (assembly => 1);
-
+ my ($class, $type) = @_;
+
+ return () unless $type;
+
+ my @types = listify($type);
+ my @filter;
+
+ for my $type (@types) {
+ if ($type =~ m/^part/) {
+ push @filter, (and => [ or => [ assembly => 0, assembly => undef ],
+ '!inventory_accno_id' => 0,
+ '!inventory_accno_id' => undef,
+ ]);
+ } elsif ($type =~ m/^service/) {
+ push @filter, (and => [ or => [ assembly => 0, assembly => undef ],
+ or => [ inventory_accno_id => 0, inventory_accno_id => undef ],
+ ]);
+ } elsif ($type =~ m/^assembl/) {
+ push @filter, (assembly => 1);
+ }
}
- return ();
+ return @filter ? (or => \@filter) : ();
}
sub get_ordered_qty {
table => 'ar',
columns => [
- id => { type => 'integer', not_null => 1, sequence => 'glid' },
- invnumber => { type => 'text', not_null => 1 },
- transdate => { type => 'date', default => 'now' },
- gldate => { type => 'date', default => 'now' },
- customer_id => { type => 'integer' },
- taxincluded => { type => 'boolean' },
- amount => { type => 'numeric', precision => 5, scale => 15 },
- netamount => { type => 'numeric', precision => 5, scale => 15 },
- paid => { type => 'numeric', precision => 5, scale => 15 },
- datepaid => { type => 'date' },
- duedate => { type => 'date' },
- deliverydate => { type => 'date' },
- invoice => { type => 'boolean', default => 'false' },
- shippingpoint => { type => 'text' },
- terms => { type => 'integer', default => '0' },
- notes => { type => 'text' },
- curr => { type => 'character', length => 3 },
- ordnumber => { type => 'text' },
- employee_id => { type => 'integer' },
- quonumber => { type => 'text' },
- cusordnumber => { type => 'text' },
- intnotes => { type => 'text' },
- department_id => { type => 'integer', default => '0' },
- shipvia => { type => 'text' },
- itime => { type => 'timestamp', default => 'now()' },
- mtime => { type => 'timestamp' },
- cp_id => { type => 'integer' },
- language_id => { type => 'integer' },
- payment_id => { type => 'integer' },
- delivery_customer_id => { type => 'integer' },
- delivery_vendor_id => { type => 'integer' },
- storno => { type => 'boolean', default => 'false' },
- taxzone_id => { type => 'integer' },
- shipto_id => { type => 'integer' },
- type => { type => 'text' },
- dunning_config_id => { type => 'integer' },
- orddate => { type => 'date' },
- quodate => { type => 'date' },
- globalproject_id => { type => 'integer' },
- salesman_id => { type => 'integer' },
- transaction_description => { type => 'text' },
- storno_id => { type => 'integer' },
- marge_total => { type => 'numeric', precision => 5, scale => 15 },
- marge_percent => { type => 'numeric', precision => 5, scale => 15 },
- donumber => { type => 'text' },
+ id => { type => 'integer', not_null => 1, sequence => 'glid' },
+ invnumber => { type => 'text', not_null => 1 },
+ transdate => { type => 'date', default => 'now' },
+ gldate => { type => 'date', default => 'now' },
+ customer_id => { type => 'integer' },
+ taxincluded => { type => 'boolean' },
+ amount => { type => 'numeric', precision => 5, scale => 15 },
+ netamount => { type => 'numeric', precision => 5, scale => 15 },
+ paid => { type => 'numeric', precision => 5, scale => 15 },
+ datepaid => { type => 'date' },
+ duedate => { type => 'date' },
+ deliverydate => { type => 'date' },
+ invoice => { type => 'boolean', default => 'false' },
+ shippingpoint => { type => 'text' },
+ terms => { type => 'integer', default => '0' },
+ notes => { type => 'text' },
+ curr => { type => 'character', length => 3 },
+ ordnumber => { type => 'text' },
+ employee_id => { type => 'integer' },
+ quonumber => { type => 'text' },
+ cusordnumber => { type => 'text' },
+ intnotes => { type => 'text' },
+ department_id => { type => 'integer', default => '0' },
+ shipvia => { type => 'text' },
+ itime => { type => 'timestamp', default => 'now()' },
+ mtime => { type => 'timestamp' },
+ cp_id => { type => 'integer' },
+ language_id => { type => 'integer' },
+ payment_id => { type => 'integer' },
+ delivery_customer_id => { type => 'integer' },
+ delivery_vendor_id => { type => 'integer' },
+ storno => { type => 'boolean', default => 'false' },
+ taxzone_id => { type => 'integer' },
+ shipto_id => { type => 'integer' },
+ type => { type => 'text' },
+ dunning_config_id => { type => 'integer' },
+ orddate => { type => 'date' },
+ quodate => { type => 'date' },
+ globalproject_id => { type => 'integer' },
+ salesman_id => { type => 'integer' },
+ transaction_description => { type => 'text' },
+ storno_id => { type => 'integer' },
+ marge_total => { type => 'numeric', precision => 5, scale => 15 },
+ marge_percent => { type => 'numeric', precision => 5, scale => 15 },
+ donumber => { type => 'text' },
+ invnumber_for_credit_note => { type => 'text' },
],
primary_key_columns => [ 'id' ],
allow_inline_column_values => 1,
foreign_keys => [
+ customer => {
+ class => 'SL::DB::Customer',
+ key_columns => { customer_id => 'id' },
+ },
+
employee => {
class => 'SL::DB::Employee',
key_columns => { employee_id => 'id' },
class => 'SL::DB::Employee',
key_columns => { salesman_id => 'id' },
},
+
+ vendor => {
+ class => 'SL::DB::Vendor',
+ key_columns => { vendor_id => 'id' },
+ },
],
);
'<script type="text/javascript" src="js/jscalendar/calendar.js"></script>',
'<script type="text/javascript" src="js/jscalendar/lang/calendar-de.js"></script>',
'<script type="text/javascript" src="js/jscalendar/calendar-setup.js"></script>',
- '<script type="text/javascript" src="js/part_selection.js"></script>';
+ '<script type="text/javascript" src="js/part_selection.js"></script>',
+ '<script type="text/javascript" src="js/jquery-ui.js"></script>',
+ '<style "type=text/css">@import url("css/ui-lightness/jquery-ui-1.8.12.custom.css")</style>';
push @header, $self->{javascript} if $self->{javascript};
push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] };
push @header, "<script type='text/javascript'>function fokus(){ document.$self->{fokus}.focus(); }</script>" if $self->{fokus};
--- /dev/null
+package SL::Template::Plugin::JSON;
+
+use JSON ();
+use Carp qw(croak);
+use base qw(Template::Plugin);
+
+our $VERSION = "0.06";
+
+sub new {
+ my ($class, $context, $args) = @_;
+
+ my $self = bless {context => $context, json_args => $args }, $class;
+
+ $context->define_vmethod($_, json => sub { $self->json(@_) }) for qw(hash list scalar);
+
+ return $self;
+}
+
+sub json_converter {
+ my ($self, %params) = @_;
+
+ if (!$self->{json}) {
+ $self->{json} = JSON->new->allow_nonref(1);
+
+ my $args = $self->{json_args};
+
+ for my $method (keys %$args) {
+ if ( $self->{json}->can($method) ) {
+ $self->{json}->$method( $args->{$method} );
+ }
+ }
+ }
+
+ return $self->{json};
+}
+
+sub json {
+ my ($self, $value) = @_;
+
+ $self->json_converter->encode($value);
+}
+
+sub json_decode {
+ my ( $self, $value ) = @_;
+
+ $self->json_converter->decode($value);
+}
+
+1;
+
+__END__
) : '');
}
+sub customer_picker {
+ my ($self, $name, $value, %params) = @_;
+ my $name_e = _H($name);
+
+ $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
+ $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
+ $self->javascript(<<JS);
+function autocomplete_customer (selector, column) {
+ \$(function(){ \$(selector).autocomplete({
+ source: function(req, rsp) {
+ \$.ajax({
+ url: 'controller.pl?action=Customer/ajax_autocomplete',
+ dataType: "json",
+ data: {
+ column: column,
+ term: req.term,
+ current: function() { \$('#$name_e').val() },
+ obsolete: 0,
+ },
+ success: function (data){ rsp(data) }
+ });
+ },
+ limit: 20,
+ delay: 50,
+ select: function(event, ui) {
+ \$('#$name_e').val(ui.item.id);
+ \$('#$name_e\_name').val(ui.item.name);
+ },
+ })});
+}
+autocomplete_customer('#$name_e\_name');
+JS
+}
+
sub javascript_tag {
my $self = shift;
my $code = '';
use strict;
+use Scalar::Util qw(blessed);
+
# Parameters:
# 1. The template's file name
# 2. A reference to the Form object
my $form = $self->{form};
my $value;
- if (($get_array || @indices) && (ref $form->{TEMPLATE_ARRAYS} eq 'HASH') && (ref $form->{TEMPLATE_ARRAYS}->{$var} eq 'ARRAY')) {
+ if ($var =~ m/\./) {
+ $value = $form;
+ for my $part (split(m/\./, $var)) {
+ if (ref($value) =~ m/^(?:Form|HASH)$/) {
+ $value = $value->{$part};
+ } elsif (blessed($value) && $value->can($part)) {
+ $value = $value->$part;
+ } else {
+ $value = '';
+ last;
+ }
+ }
+ } elsif (($get_array || @indices) && (ref $form->{TEMPLATE_ARRAYS} eq 'HASH') && (ref $form->{TEMPLATE_ARRAYS}->{$var} eq 'ARRAY')) {
$value = $form->{TEMPLATE_ARRAYS}->{$var};
} else {
$value = $form->{$var};
'timestamp' => $locale->text('Timestamp'),
'bool' => $locale->text('Yes/No (Checkbox)'),
'select' => $locale->text('Selection'),
+ 'customer' => $locale->text('Customer'),
);
-our @types = qw(text textfield number date bool select); # timestamp
+our @types = qw(text textfield number date bool select customer); # timestamp
our @modules = ({ module => 'CT', description => $locale->text('Customers and vendors') },
{ module => 'IC', description => $locale->text('Parts, services and assemblies') },
--- /dev/null
+-- @tag: oe_customer_vendor_fkeys
+-- @encoding: utf-8
+-- @description: Foreign Keys für customer und vendor in oe
+-- @depends: release_2_6_3
+-- @timestamp: 1317380460
+UPDATE oe SET customer_id = NULL WHERE customer_id = 0;
+UPDATE oe SET vendor_id = NULL WHERE vendor_id = 0;
+
+
+ALTER TABLE oe ADD FOREIGN KEY (customer_id) REFERENCES customer(id);
+ALTER TABLE oe ADD FOREIGN KEY (vendor_id) REFERENCES vendor(id);
[%- USE T8 %]
-[% USE HTML %]
+[%- USE HTML %]
+[%- USE L %]
[%- SET var_name = HTML.escape(name_prefix) _ "cvar_" _ HTML.escape(var.name) _ HTML.escape(name_postfix) -%]
[%- ELSIF var.type == 'timestamp' %]
<input name="[% var_name %]" value="[% HTML.escape(var.value) %]">
+[%- ELSIF var.type == 'customer' %]
+[% L.customer_picker(var_name, var.value) %]
+
[%- ELSIF var.type == 'select' %]
<select name="[% var_name %]">
[%- USE T8 %]
[%- USE HTML %]
+[%- USE L %]
[%- BLOCK cvar_name %][% HTML.escape(cvar.name_prefix) _ "cvar_" _ HTML.escape(cvar.var.name) _ HTML.escape(cvar.name_postfix) -%][% END %]
[%- BLOCK cvar_inputs %]
[%- %]
<option[% IF option.value == cvar.value %] selected[% END %]>[% HTML.escape(option.value) %]</option>
[%- END %]
</select>
+[%- ELSIF cvar.var.type == 'customer' %]
+[% render_input_blocks__cvar_name = PROCESS cvar_name %][% L.customer_picker(render_input_blocks__cvar_name, cvar.value) %]
[%- ELSE %]
<input name="[% PROCESS cvar_name %]" value="[% HTML.escape(cvar.value) %]" [%- IF cvar.var.maxlength %] maxlength="[% HTML.escape(cvar.var.maxlength) %]"[% END -%]>
[%- END %]
</select>
<input name="cvar_[% HTML.escape(var.name) %]"[% IF var.maxlength %]maxlength="[% HTML.escape(var.maxlength) %]"[% END %]>
+ [%- ELSIF var.type == 'customer' %]
+ <input name="cvar_[% var.name | html %]">
+
[% ELSIF var.type == 'select' %]
<select name="cvar_[% HTML.escape(var.name) %]">
<option value="" selected>---</option>
--- /dev/null
+[%- USE HTML %][% USE JSON %][
+[%- FOREACH customer = SELF.customers %]
+ {
+ "value": [% customer.${SELF.value}.json %],
+ "label": [% customer.displayable_name.json %],
+ "id": [% customer.id.json %],
+ "customernumber": [% customer.customernumber.json %],
+ "name": [% customer.name.json %]
+ }[% ',' UNLESS loop.last %]
+[%- END %]
+]
--- /dev/null
+[% USE L %]
+[% USE T8 %]
+[% USE LxERP %]
+[% L.javascript_tag('jquery-ui') %]
+<link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.8.12.custom.css" type="text/css" />
+
+<p>Pick a customer</p>
+id: [% L.input_tag('customer_id', '') %]
+nr: [% L.input_tag('customer_customernumber', '') %]
+desc: [% L.input_tag('customer_name', '') %]
+
+<script type='text/javascript'>
+function autocomplete_customer (selector, column) {
+ $(function(){ $(selector).autocomplete({
+ source: function(req, rsp) {
+ $.ajax({
+ url: 'controller.pl?action=Customer/ajax_autocomplete',
+ dataType: "json",
+ data: {
+ column: column,
+ term: req.term,
+ current: function() { $('#customer_id').val() },
+ obsolete: 0,
+ },
+ success: function (data){ rsp(data) }
+ });
+ },
+ limit: 20,
+ delay: 50,
+ select: function(event, ui) {
+ $('#customer_id').val(ui.item.id);
+ $('#customer_customernumber').val(ui.item.customernumber);
+ $('#customer_name').val(ui.item.name);
+ },
+ })});
+}
+//autocomplete_customer('#customer_customernumber', 'customernumber');
+autocomplete_customer('#customer_name', '');
+</script>
+
--- /dev/null
+[%- USE HTML %][% USE JSON %][
+[%- FOREACH part = SELF.parts %]
+[%- ajax_autocomplete__label = part.partnumber _ " " _ part.description %]
+ {
+ "value": [% part.${SELF.value}.json %],
+ "label": [% ajax_autocomplete__label.json %],
+ "id": [% part.id.json %],
+ "partnumber": [% part.partnumber.json %],
+ "description": [% part.description.json %],
+ "type": [% part.type.json %]
+ }[% ',' UNLESS loop.last %]
+[%- END %]
+]
--- /dev/null
+[% USE L %]
+[% USE T8 %]
+[% USE LxERP %]
+[% L.javascript_tag('jquery', 'jquery-ui') %]
+<link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.8.12.custom.css" type="text/css" />
+
+<p>Pick a part</p>
+id: [% L.input_tag('part_id', '') %]
+nr: [% L.input_tag('part_partnumber', '') %]
+desc: [% L.input_tag('part_description', '') %]
+
+<script type='text/javascript'>
+function autocomplete_part (selector, column, type) {
+ $(function(){ $(selector).autocomplete({
+ source: function(req, rsp) {
+ $.ajax({
+ url: 'controller.pl?action=Part/ajax_autocomplete',
+ dataType: "json",
+ data: {
+ column: column,
+ term: req.term,
+ current: function() { $('#part_id').val() },
+ type: type,
+ obsolete: 0,
+ },
+ success: function (data){ rsp(data) }
+ });
+ },
+ limit: 20,
+ delay: 50,
+ select: function(event, ui) {
+ $('#part_id').val(ui.item.id);
+ $('#part_partnumber').val(ui.item.partnumber);
+ $('#part_description').val(ui.item.description);
+ },
+ })});
+}
+autocomplete_part('#part_partnumber', 'partnumber', ['part', 'assembly']);
+autocomplete_part('#part_description', 'description', ['part', 'assembly']);
+</script>