6bab20278afd91a8191813aa5ad02869ec47c773
[freeside.git] / httemplate / edit / process / part_pkg.cgi
1 <% include( 'elements/process.html',
2               #'debug'             => 1,
3               'table'             => 'part_pkg',
4               'agent_virt'        => 1,
5               'agent_null_right'  => \@agent_null_right,
6               'redirect'          => $redirect_callback,
7               'viewall_dir'       => 'browse',
8               'viewall_ext'       => 'cgi',
9               'edit_ext'          => 'cgi',
10               'precheck_callback' => $precheck_callback,
11               'args_callback'     => $args_callback,
12               'update_svc'        => $update_svc,
13               'process_locale'    => 'pkg',
14               'process_m2m'       => \@process_m2m,
15               'process_o2m'       => \@process_o2m,
16           )
17 %>
18 <%init>
19
20 my $customizing = ( ! $cgi->param('pkgpart') && $cgi->param('pkgnum') );
21
22 my $curuser = $FS::CurrentUser::CurrentUser;
23
24 my $edit_global = 'Edit global package definitions';
25 my $customize   = 'Customize customer package';
26
27 die "access denied"
28   unless $curuser->access_right('Edit package definitions')
29       || $curuser->access_right($edit_global)
30       || ( $customizing && $curuser->access_right($customize) );
31
32 my @agent_null_right = ( $edit_global );
33 push @agent_null_right, $customize if $customizing;
34
35
36 my $precheck_callback = sub {
37   my( $cgi ) = @_;
38
39   my $conf = new FS::Conf;
40
41   foreach (qw( setuptax recurtax disabled )) {
42     $cgi->param($_, '') unless defined $cgi->param($_);
43   }
44
45   return 'Must select a tax class'
46     if $cgi->param('taxclass') eq '(select)';
47
48   my @agents = ();
49   foreach ($cgi->param('agent_type')) {
50     /^(\d+)$/;
51     push @agents, $1 if $1;
52   }
53   return "At least one agent type must be specified."
54     unless scalar(@agents)
55            #wtf? || ( $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ )
56            || $cgi->param('disabled')
57            || $cgi->param('agentnum');
58
59   return '';
60
61 };
62
63 my $custnum = '';
64
65 my $args_callback = sub {
66   my( $cgi, $new ) = @_;
67   
68   my @args = ( 'primary_svc' => scalar($cgi->param('pkg_svc_primary')) );
69
70   ##
71   #options
72   ##
73   
74   $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
75   my $plan = $1;
76   
77   tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
78   my $href = $plans{$plan}->{'fields'};
79   
80   my $error = '';
81   my $options = $cgi->param($plan."__OPTIONS");
82   my @options = split(',', $options);
83   my %options =
84     map { my $optionname = $_;
85           my $param = $plan."__$optionname";
86           my $parser = exists($href->{$optionname}{parse})
87                          ? $href->{$optionname}{parse}
88                          : sub { shift };
89           my $value = join(', ', &$parser($cgi->param($param)));
90           my $check = $href->{$optionname}{check};
91           if ( $check && ! &$check($value) ) {
92             $value = join(', ', $cgi->param($param));
93             $error ||= "Illegal ".
94                          ($href->{$optionname}{name}||$optionname). ": $value";
95           }
96           ( $optionname => $value );
97         }
98         grep { $_ !~ /^report_option_/ }
99         @options;
100
101   foreach my $class ( '', split(',', $cgi->param('taxproductnums') ) ) {
102     my $param = "taxproductnum_$class";
103     my $value = $cgi->param($param);
104
105     if ( $value == -1 ) {
106       my $desc = $cgi->param($param.'_description');
107       # insert a new part_pkg_taxproduct
108       my $engine = FS::TaxEngine->new;
109       my $obj_or_error = $engine->add_taxproduct($desc);
110       if (ref $obj_or_error) {
111         $value = $obj_or_error->taxproductnum;
112         $cgi->param($param, $value); # for error handling
113       } else {
114         die "$obj_or_error (adding tax product)";
115       }
116     }
117
118     $error ||= "Illegal $param: $value"
119       unless ( $value =~ /^\d*$/  );
120     if (length($class)) {
121       $options{"usage_taxproductnum_$class"} = $value;
122     } else {
123       $new->set('taxproductnum', $value);
124     }
125   }
126
127   foreach ( grep $_, $cgi->param('report_option') ) {
128     $error ||= "Illegal optional report class: $_" unless ( $_ =~ /^\d*$/  );
129     $options{"report_option_$_"} = 1;
130   }
131
132   $options{$_} = scalar( $cgi->param($_) )
133     for (qw( setup_fee recur_fee disable_line_item_date_ranges ));
134   
135   push @args, 'options' => \%options;
136
137   ###
138   #part_pkg_currency
139   ###
140
141   my %part_pkg_currency = (
142     map { $_ => scalar($cgi->param($_)) }
143       #grep /._[A-Z]{3}$/, #support other options
144       grep /^(setup|recur)_fee_[A-Z]{3}$/,
145         $cgi->param
146   );
147
148   push @args, 'part_pkg_currency' => \%part_pkg_currency;
149
150   ###
151   # fcc options
152   ###
153   my $fcc_options_string = $cgi->param('fcc_options_string');
154   if ($fcc_options_string) {
155     push @args, 'fcc_options' => decode_json($fcc_options_string);
156   }
157
158   ###
159   #pkg_svc
160   ###
161
162   my @svcparts = map { $_->svcpart } qsearch('part_svc', {});
163   my %pkg_svc    = map { $_ => scalar($cgi->param("pkg_svc$_"  )) } @svcparts;
164   my %hidden_svc = map { $_ => scalar($cgi->param("hidden$_"   )) } @svcparts;
165   my %provision_hold = map { $_ => scalar($cgi->param("provision_hold$_"   )) } @svcparts;
166   my %bulk_skip  = map { $_ => ( $cgi->param("no_bulk_skip$_") eq 'Y'
167                                    ? '' : 'Y'
168                                )
169                                                                   } @svcparts;
170
171   push @args, 'pkg_svc'    => \%pkg_svc,
172               'hidden_svc' => \%hidden_svc,
173               'provision_hold' => \%provision_hold,
174               'bulk_skip'  => \%bulk_skip;
175
176   ###
177   # cust_pkg and custnum_ref (inserts only)
178   ###
179   unless ( $cgi->param('pkgpart') ) {
180     push @args, 'cust_pkg'    => scalar($cgi->param('pkgnum')),
181                 'custnum_ref' => \$custnum;
182   }
183
184   my %part_pkg_vendor;
185   foreach my $param ( $cgi->param ) {
186     if ( $param =~ /^export(\d+)$/ && length($cgi->param($param)) > 0 ) {
187         $part_pkg_vendor{$1} = $cgi->param($param);
188     }
189   }
190   if ( keys %part_pkg_vendor > 0 ) {
191     push @args, 'part_pkg_vendor' => \%part_pkg_vendor;
192   }
193
194   push @args, 'part_pkg_restrict_soft_override' => 1
195     if $cgi->param('part_pkg_restrict_soft_override');
196
197   #warn "args: ".join('/', @args). "\n";
198
199   @args;
200
201 };
202
203 ## update services upon package change.
204 my $update_svc = sub {
205   my $cgi = shift @_;
206   my $new = shift @_;
207   my %args = @_;
208   my $error;
209
210   my @svcs = $new->pkg_svc();
211
212   foreach my $svc_part(@svcs) {
213     my @part_svc_column = qsearch('part_svc_column',{ 'svcpart' => $svc_part->{Hash}->{svcpart}, 'columnflag' => 'P' });
214
215     if ($svc_part->{Hash}->{svcdb} eq "svc_broadband" && (keys $args{fcc_options}) && @part_svc_column ) {
216       ## find provisioned services to update
217       my @svc_svcdb = qsearch({
218         'table'     => 'svc_broadband',
219         'select'    => 'svc_broadband.*, cust_svc.svcpart',
220         'addl_from' => 'LEFT JOIN cust_svc USING (svcnum) LEFT JOIN cust_pkg USING (pkgnum)',
221         'extra_sql' => " WHERE cust_svc.svcpart = '".$svc_part->{Hash}->{svcpart}."' AND cust_pkg.pkgpart = '".$svc_part->{Hash}->{pkgpart}."'",
222       });
223       foreach my $svc (@svc_svcdb) {
224         #my $svc_new = $svc;
225         $svc->{Hash}->{speed_down} = $args{fcc_options}->{broadband_downstream} * 1000;
226         $svc->{Hash}->{speed_up} = $args{fcc_options}->{broadband_upstream} * 1000;
227         $error = $svc->replace();
228       }
229     }
230   }
231   return $error;
232 };
233
234 my $redirect_callback = sub {
235   #my( $cgi, $new ) = @_;
236   return '' unless $custnum;
237   my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
238                ? ''
239                : ';show=packages';
240   #my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
241  
242   #can we link back to the specific customized package?  it would be nice...
243   popurl(3). "view/cust_main.cgi?custnum=$custnum$show;dummy=";
244 };
245
246 #these should probably move to @args above and be processed by part_pkg.pm...
247
248 $cgi->param('tax_override') =~ /^([\d,]+)$/;
249 my (@tax_overrides) = (grep "$_", split (",", $1));
250
251 my @process_m2m = (
252   {
253     'link_table'   => 'part_pkg_taxoverride',
254     'target_table' => 'tax_class',
255     'params'       => \@tax_overrides,
256   },
257   { 'link_table'   => 'part_pkg_discount',
258     'target_table' => 'discount',
259     'params'       => [ map $cgi->param($_),
260                         grep /^discountnum/, $cgi->param
261                       ],
262   },
263   { 'link_table'   => 'part_pkg_link',
264     'target_table' => 'part_pkg',
265     'base_field'   => 'src_pkgpart',
266     'target_field' => 'dst_pkgpart',
267     'hashref'      => { 'link_type' => 'svc', 'hidden' => '' },
268     'params'       => [ map $cgi->param($_),
269                         grep /^svc_dst_pkgpart/, $cgi->param
270                       ],
271   },
272   { 'link_table'   => 'part_pkg_link',
273     'target_table' => 'part_pkg',
274     'base_field'   => 'src_pkgpart',
275     'target_field' => 'dst_pkgpart',
276     'hashref'      => { 'link_type' => 'supp', 'hidden' => '' },
277     'params'       => [ map $cgi->param($_),
278                         grep /^supp_dst_pkgpart/, $cgi->param
279                       ],
280   },
281   map { 
282     my $hidden = $_;
283     { 'link_table'   => 'part_pkg_link',
284       'target_table' => 'part_pkg',
285       'base_field'   => 'src_pkgpart',
286       'target_field' => 'dst_pkgpart',
287       'hashref'      => { 'link_type' => 'bill', 'hidden' => $hidden },
288       'params'       => [ map { $cgi->param($_) }
289                           grep { my $param = "bill_dst_pkgpart__hidden";
290                                  my $digit = '';
291                                  (($digit) = /^bill_dst_pkgpart(\d+)/ ) &&
292                                  $cgi->param("$param$digit") eq $hidden;
293                                }
294                           $cgi->param
295                         ],
296     },
297   } ( '', 'Y' ),
298 );
299
300 foreach my $override_class ($cgi->param) {
301   next unless $override_class =~ /^tax_override_(\w+)$/;
302   my $class = $1;
303
304   my (@tax_overrides) = (grep "$_", split (",", $1))
305     if $cgi->param($override_class) =~ /^([\d,]+)$/;
306
307   push @process_m2m, {
308     'link_table'   => 'part_pkg_taxoverride',
309     'target_table' => 'tax_class',
310     'hashref'      => { 'usage_class' => $class },
311     'params'       => [ @tax_overrides ],
312   };
313
314 }
315
316 my $conf = new FS::Conf;
317
318 my @agents = ();
319 foreach ($cgi->param('agent_type')) {
320   /^(\d+)$/;
321   push @agents, $1 if $1;
322 }
323 push @process_m2m, {
324   'link_table'   => 'type_pkgs',
325   'target_table' => 'agent_type',
326   'params'       => \@agents,
327 };
328
329 my $targets = FS::part_pkg_usageprice->targets;
330 foreach my $amount_param ( grep /^usagepricepart(\d+)_amount$/, $cgi->param ) {
331   $amount_param =~ /^usagepricepart(\d+)_amount$/ or die 'unpossible';
332   my $num = $1;
333   my $amount = $cgi->param($amount_param);
334   if ( ! $amount && ! $cgi->param("usagepricepart${num}_price") ) {
335     #don't add empty rows just because the dropdowns have a value
336     $cgi->param("usagepricepart${num}_$_", '') for qw( currency action target );
337     next;
338   } 
339   my $target = $cgi->param("usagepricepart${num}_target");
340   $amount *= $targets->{$target}{multiplier} if $targets->{$target}{multiplier};
341   $cgi->param($amount_param, $amount);
342 }
343
344 my @process_o2m = (
345   {
346     'table'  => 'part_pkg_usageprice',
347     'fields' => [qw( price currency action target amount )],
348
349   }
350 );
351
352 </%init>