]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/RecordLinks.pm
restart apache2 in postinst
[mfinanz.git] / SL / Controller / RecordLinks.pm
1 package SL::Controller::RecordLinks;
2
3 use strict;
4
5 use parent qw(SL::Controller::Base);
6
7 use List::Util qw(first);
8
9 use SL::DB::Helper::Mappings;
10 use SL::DB::Order;
11 use SL::DB::DeliveryOrder;
12 use SL::DB::Invoice;
13 use SL::DB::Letter;
14 use SL::DB::PurchaseInvoice;
15 use SL::DB::Reclamation;
16 use SL::DB::RecordLink;
17 use SL::DB::RequirementSpec;
18 use SL::DBUtils qw(like);
19 use SL::DB::ShopOrder;
20 use SL::JSON;
21 use SL::Locale::String;
22 use SL::Presenter::Record qw(grouped_record_list);
23
24 use Rose::Object::MakeMethods::Generic
25 (
26   scalar => [ qw(object object_model object_id link_type link_direction link_type_desc) ],
27 );
28
29 __PACKAGE__->run_before('check_auth');
30 __PACKAGE__->run_before('check_object_params', only => [ qw(ajax_list ajax_delete ajax_add_select_type ajax_add_filter ajax_add_list ajax_add_do) ]);
31 __PACKAGE__->run_before('check_link_params',   only => [ qw(                                                           ajax_add_list ajax_add_do) ]);
32
33 my %link_type_defaults = (
34   filter            => 'type_filter',
35   project           => 'globalproject',
36   description       => 'transaction_description',
37   description_title => t8('Transaction description'),
38   date              => 'transdate',
39 );
40
41 my @link_type_specifics = (
42   { title => t8('Requirement spec'),            type => 'requirement_spec',            model => 'RequirementSpec', number => 'id', project => 'project', description => 'title', date => undef, filter => 'working_copy_filter', },
43   { title => t8('Shop Order'),                  type => 'shop_order',                  model => 'ShopOrder',       number => 'shop_ordernumber', date => 'order_date', project => undef, description => undef, },
44   { title => t8('Sales quotation'),             type => 'sales_quotation',             model => 'Order',           number => 'quonumber',     },
45   { title => t8('Sales Order Intake'),          type => 'sales_order_intake',          model => 'Order',           number => 'ordnumber',     },
46   { title => t8('Sales Order'),                 type => 'sales_order',                 model => 'Order',           number => 'ordnumber',     },
47   { title => t8('Sales delivery order'),        type => 'sales_delivery_order',        model => 'DeliveryOrder',   number => 'donumber',      },
48   { title => t8('RMA delivery order'),          type => 'rma_delivery_order',          model => 'DeliveryOrder',   number => 'rdonumber',     },
49   { title => t8('Sales Reclamation'),           type => 'sales_reclamation',           model => 'Reclamation',     number => 'record_number', },
50   { title => t8('Sales Invoice'),               type => 'invoice',                     model => 'Invoice',         number => 'invnumber',     },
51   { title => t8('Request for Quotation'),       type => 'request_quotation',           model => 'Order',           number => 'quonumber',     },
52   { title => t8('Purchase Quotation Intake'),   type => 'purchase_quotation_intake',   model => 'Order',           number => 'quonumber',     },
53   { title => t8('Purchase Order'),              type => 'purchase_order',              model => 'Order',           number => 'ordnumber',     },
54   { title => t8('Purchase Order Confirmation'), type => 'purchase_order_confirmation', model => 'Order',           number => 'ordnumber',     },
55   { title => t8('Purchase delivery order'),     type => 'purchase_delivery_order',     model => 'DeliveryOrder',   number => 'donumber',      },
56   { title => t8('Supplier delivery order'),     type => 'supplier_delivery_order',     model => 'DeliveryOrder',   number => 'sdonumber',     },
57   { title => t8('Purchase Reclamation'),        type => 'purchase_reclamation',        model => 'Reclamation',     number => 'record_number', },
58   { title => t8('Purchase Invoice'),            type => 'purchase_invoice',            model => 'PurchaseInvoice', number => 'invnumber',     },
59   { title => t8('Letter'),                      type => 'letter',                      model => 'Letter',          number => 'letternumber', description => 'subject', description_title => t8('Subject'), date => 'date', project => undef },
60   { title => t8('Email'),                       type => 'email_journal',               model => 'EmailJournal',    number => 'id',           description => 'subject', description_title => t8('Subject'), project => undef, date => 'sent_on', },
61   { title => t8('AR Transaction'),              type => 'ar_transaction',              model => 'Invoice',         number => 'invnumber',     },
62   { title => t8('AP Transaction'),              type => 'ap_transaction',              model => 'PurchaseInvoice', number => 'invnumber',     },
63   { title => t8('Dunning'),                     type => 'dunning',                     model => 'Dunning',         number => 'dunning_id',   project => undef, description => undef, },
64   { title => t8('GL Transaction'),              type => 'gl_transaction',              model => 'GLTransaction',   number => 'reference',    project => undef },
65 );
66
67 my @link_types = map { +{ %link_type_defaults, %{ $_ } } } @link_type_specifics;
68
69 #
70 # actions
71 #
72
73 sub action_ajax_list {
74   my ($self) = @_;
75
76   my %order_centric_params = (
77     with_myself           => $::instance_conf->get_record_links_from_order_with_myself,
78     with_sales_quotations => $::instance_conf->get_record_links_from_order_with_quotations
79   );
80
81   eval {
82     my $linked_records = $::instance_conf->get_always_record_links_from_order
83                        ?  $self->object->sales_order_centric_linked_records(%order_centric_params)
84                        :  $self->object->linked_records(direction => 'both', recursive => 1, save_path => 1);
85
86     push @{ $linked_records }, $self->object->sepa_export_items if $self->object->can('sepa_export_items');
87
88     my $output         = grouped_record_list(
89       $linked_records,
90       with_columns      => [ qw(record_link_direction) ],
91       edit_record_links => 1,
92       object_model      => $self->object_model,
93       object_id         => $self->object_id,
94     );
95     $self->render(\$output, { layout => 0, process => 0 });
96
97     1;
98   } or do {
99     $self->render('generic/error', { layout => 0 }, label_error => $@);
100   };
101 }
102
103 sub action_ajax_delete {
104   my ($self) = @_;
105
106   foreach my $str (@{ $::form->{record_links_delete} || [] }) {
107     my ($from_table, $from_id, $to_table, $to_id) = split m/__/, $str, 4;
108     $from_id *= 1;
109     $to_id   *= 1;
110
111     next if !$from_table || !$from_id || !$to_table || !$to_id;
112
113     SL::DB::Manager::RecordLink->delete_all(where => [
114       from_table => $from_table,
115       from_id    => $from_id,
116       to_table   => $to_table,
117       to_id      => $to_id,
118     ]);
119   }
120
121   $self->action_ajax_list;
122 }
123
124 sub action_ajax_add_filter {
125   my ($self) = @_;
126
127   my $presenter = $self->presenter;
128
129   my @link_type_select = map { [ $_->{type}, $_->{title} ] } @link_types;
130   my @projects         = map { [ $_->id, $_->presenter->project(display => 'inline', style => 'both', no_link => 1) ] } @{ SL::DB::Manager::Project->get_all_sorted };
131   my $is_sales         = $self->object->can('customer_id') && $self->object->customer_id;
132   my $is_purchase      = $self->object->can('vendor_id')   && $self->object->vendor_id;
133
134   $self->render(
135     'record_links/add_filter',
136     { layout          => 0 },
137     is_sales          => $is_sales,
138     is_purchase       => $is_purchase,
139     DEFAULT_LINK_TYPE => $is_sales ? 'sales_quotation' : $is_purchase ? 'request_quotation' : 'email_journal',
140     LINK_TYPES        => \@link_type_select,
141     PROJECTS          => \@projects,
142   );
143 }
144
145 sub action_ajax_add_list {
146   my ($self) = @_;
147
148   my $class       = 'SL::DB::'          . $self->link_type_desc->{model};
149   my $manager     = 'SL::DB::Manager::' . $self->link_type_desc->{model};
150   my $vc          = !($class->can('customer_id') || $class->can('vendor_id')) ? undef
151                   : $self->link_type =~ m/shop|sales_|^invoice|requirement_spec|letter|^ar_/ ? 'customer'
152                   : 'vendor';
153   my $project     = $self->link_type_desc->{project};
154   my $project_id  = "${project}_id";
155   my $description = $self->link_type_desc->{description};
156   my $filter      = $self->link_type_desc->{filter};
157   my $number      = $self->link_type_desc->{number};
158
159   my @where = $filter && $manager->can($filter) ? $manager->$filter($self->link_type) : ();
160   push @where, ("${vc}.${vc}number"     => { ilike => like($::form->{vc_number}) })               if $vc && $::form->{vc_number};
161   push @where, ("${vc}.name"            => { ilike => like($::form->{vc_name}) })                 if $vc && $::form->{vc_name};
162   push @where, ($description            => { ilike => like($::form->{transaction_description}) }) if $::form->{transaction_description};
163   push @where, ($project_id             => $::form->{globalproject_id})                           if $::form->{globalproject_id} && $class->can($project_id);
164
165   if ($::form->{number}) {
166     my $col_type = ref $class->meta->column($number);
167     if ($col_type =~ /^Rose::DB::Object::Metadata::Column::(?:Integer|Serial)$/) {
168       push @where, ($number => $::form->{number});
169     } elsif ($col_type =~ /^Rose::DB::Object::Metadata::Column::Text$/) {
170       push @where, ($number => { ilike => like($::form->{number}) });
171     }
172   }
173
174   my @with_objects = ();
175   push @with_objects, $vc      if $vc;
176   push @with_objects, $project if $class->can($project_id);
177
178   # show the newest records first (should be better for 80% of the cases TODO sortable click
179   my $objects = $manager->get_all(where => \@where, with_objects => \@with_objects, sort_by => 'itime',  sort_dir => 'ASC');
180   my $output  = $self->render(
181     'record_links/add_list',
182     { output => 0 },
183     OBJECTS            => $objects,
184     vc                 => $vc,
185     number_column      => $self->link_type_desc->{number},
186     description_column => $description,
187     description_title  => $self->link_type_desc->{description_title},
188     project_column     => $project,
189     date_column        => $self->link_type_desc->{date},
190   );
191
192   my %result = ( count => scalar(@{ $objects }), html => $output );
193
194   $self->render(\to_json(\%result), { type => 'json', process => 0 });
195 }
196
197 sub action_ajax_add_do {
198   my ($self, %params) = @_;
199
200   my $object_side = $self->link_direction eq 'from' ? 'from' : 'to';
201   my $link_side   = $object_side          eq 'from' ? 'to'   : 'from';
202   my $link_table  = SL::DB::Helper::Mappings::get_table_for_package($self->link_type_desc->{model});
203
204   foreach my $link_id (@{ $::form->{link_id} || [] }) {
205     # Check for existing reverse connections in order to avoid loops.
206     my @props = (
207       "${link_side}_table"   => $self->object->meta->table,
208       "${link_side}_id"      => $self->object_id,
209       "${object_side}_table" => $link_table,
210       "${object_side}_id"    => $link_id,
211     );
212
213     my $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
214     next if $existing;
215
216     # Check for existing connections in order to avoid duplicates.
217     @props = (
218       "${object_side}_table" => $self->object->meta->table,
219       "${object_side}_id"    => $self->object_id,
220       "${link_side}_table"   => $link_table,
221       "${link_side}_id"      => $link_id,
222     );
223
224     $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
225
226     SL::DB::RecordLink->new(@props)->save if !$existing;
227   }
228
229   $self->action_ajax_list;
230 }
231
232
233 #
234 # filters
235 #
236
237 sub check_object_params {
238   my ($self) = @_;
239
240   my %models = map { ($_->{model} => 1 ) } @link_types;
241
242   $self->object_id(   $::form->{object_id});
243   $self->object_model($::form->{object_model});
244
245   die "Invalid object_model or object_id" if !$self->object_id || !$models{$self->object_model};
246
247   my $model = 'SL::DB::' . $self->object_model;
248   $self->object($model->new(id => $self->object_id)->load || die "Record not found");
249
250   return 1;
251 }
252
253 sub check_link_params {
254   my ($self) = @_;
255
256   $self->link_type(     $::form->{link_type});
257   $self->link_type_desc((first { $_->{type} eq $::form->{link_type} } @link_types)                || die "Invalid link_type");
258   $self->link_direction($::form->{link_direction} =~ m/^(?:from|to)$/ ? $::form->{link_direction} :  die "Invalid link_direction");
259
260   return 1;
261 }
262
263 sub check_auth {
264   $::auth->assert('record_links');
265 }
266
267 1;