fix several UI nits in FCC 477 report configuration, #24047
[freeside.git] / FS / FS / Report / FCC_477.pm
1 package FS::Report::FCC_477;
2 use base qw( FS::Report );
3
4 use strict;
5 use vars qw( @upload @download @technology @part2aoption @part2boption
6              %states
7            );
8 use FS::Record qw( dbh );
9
10 use Tie::IxHash;
11 use Storable;
12
13 our $DEBUG = 0;
14
15 =head1 NAME
16
17 FS::Report::FCC_477 - Routines for FCC Form 477 reports
18
19 =head1 SYNOPSIS
20
21 =head1 BUGS
22
23 Documentation.
24
25 =head1 SEE ALSO
26
27 =cut
28
29 @upload = qw(
30  <200kbps
31  200-768kbps
32  768kbps-1.5mbps
33  1.5-3mpbs
34  3-6mbps
35  6-10mbps
36  10-25mbps
37  25-100mbps
38  >100mbps
39 );
40
41 @download = qw(
42  200-768kbps
43  768kbps-1.5mbps
44  1.5-3mbps
45  3-6mbps
46  6-10mbps
47  10-25mbps
48  25-100mbps
49  >100mbps
50 );
51
52 @technology = (
53   'Asymmetric xDSL',
54   'Symmetric xDSL',
55   'Other Wireline',
56   'Cable Modem',
57   'Optical Carrier',
58   'Satellite',
59   'Terrestrial Fixed Wireless',
60   'Terrestrial Mobile Wireless',
61   'Electric Power Line',
62   'Other Technology',
63 );
64
65 @part2aoption = (
66  'LD carrier',
67  'owned loops',
68  'unswitched UNE loops',
69  'UNE-P',
70  'UNE-P replacement',
71  'FTTP',
72  'coax',
73  'wireless',
74 );
75
76 @part2boption = (
77  'nomadic',
78  'copper',
79  'FTTP',
80  'coax',
81  'wireless',
82  'other broadband',
83 );
84
85 #from the select at http://www.ffiec.gov/census/default.aspx
86 #though this is now in the database, also
87 %states = (
88   '01' => 'ALABAMA (AL)',
89   '02' => 'ALASKA (AK)',
90   '04' => 'ARIZONA (AZ)',
91   '05' => 'ARKANSAS (AR)',
92   '06' => 'CALIFORNIA (CA)',
93   '08' => 'COLORADO (CO)',
94
95   '09' => 'CONNECTICUT (CT)',
96   '10' => 'DELAWARE (DE)',
97   '11' => 'DISTRICT OF COLUMBIA (DC)',
98   '12' => 'FLORIDA (FL)',
99   '13' => 'GEORGIA (GA)',
100   '15' => 'HAWAII (HI)',
101
102   '16' => 'IDAHO (ID)',
103   '17' => 'ILLINOIS (IL)',
104   '18' => 'INDIANA (IN)',
105   '19' => 'IOWA (IA)',
106   '20' => 'KANSAS (KS)',
107   '21' => 'KENTUCKY (KY)',
108
109   '22' => 'LOUISIANA (LA)',
110   '23' => 'MAINE (ME)',
111   '24' => 'MARYLAND (MD)',
112   '25' => 'MASSACHUSETTS (MA)',
113   '26' => 'MICHIGAN (MI)',
114   '27' => 'MINNESOTA (MN)',
115
116   '28' => 'MISSISSIPPI (MS)',
117   '29' => 'MISSOURI (MO)',
118   '30' => 'MONTANA (MT)',
119   '31' => 'NEBRASKA (NE)',
120   '32' => 'NEVADA (NV)',
121   '33' => 'NEW HAMPSHIRE (NH)',
122
123   '34' => 'NEW JERSEY (NJ)',
124   '35' => 'NEW MEXICO (NM)',
125   '36' => 'NEW YORK (NY)',
126   '37' => 'NORTH CAROLINA (NC)',
127   '38' => 'NORTH DAKOTA (ND)',
128   '39' => 'OHIO (OH)',
129
130   '40' => 'OKLAHOMA (OK)',
131   '41' => 'OREGON (OR)',
132   '42' => 'PENNSYLVANIA (PA)',
133   '44' => 'RHODE ISLAND (RI)',
134   '45' => 'SOUTH CAROLINA (SC)',
135   '46' => 'SOUTH DAKOTA (SD)',
136
137   '47' => 'TENNESSEE (TN)',
138   '48' => 'TEXAS (TX)',
139   '49' => 'UTAH (UT)',
140   '50' => 'VERMONT (VT)',
141   '51' => 'VIRGINIA (VA)',
142   '53' => 'WASHINGTON (WA)',
143
144   '54' => 'WEST VIRGINIA (WV)',
145   '55' => 'WISCONSIN (WI)',
146   '56' => 'WYOMING (WY)',
147   '72' => 'PUERTO RICO (PR)',
148 );
149
150 sub restore_fcc477map {
151   my $key = shift;
152   FS::Record::scalar_sql('',"select formvalue from fcc477map where formkey = ?",$key);
153 }
154
155 sub save_fcc477map {
156   my $key = shift;
157   my $value = shift;
158
159   local $SIG{HUP} = 'IGNORE';
160   local $SIG{INT} = 'IGNORE';
161   local $SIG{QUIT} = 'IGNORE';
162   local $SIG{TERM} = 'IGNORE';
163   local $SIG{TSTP} = 'IGNORE';
164   local $SIG{PIPE} = 'IGNORE';
165
166   my $oldAutoCommit = $FS::UID::AutoCommit;
167   local $FS::UID::AutoCommit = 0;
168   my $dbh = dbh;
169
170   my $sql = "delete from fcc477map where formkey = ?";
171   my $sth = dbh->prepare($sql) or die dbh->errstr;
172   $sth->execute($key) or do {
173     warn "WARNING: Error removing FCC 477 form defaults: " . $sth->errstr;
174     $dbh->rollback if $oldAutoCommit;
175   };
176
177   $sql = "insert into fcc477map (formkey,formvalue) values (?,?)";
178   $sth = dbh->prepare($sql) or die dbh->errstr;
179   $sth->execute($key,$value) or do {
180     warn "WARNING: Error setting FCC 477 form defaults: " . $sth->errstr;
181     $dbh->rollback if $oldAutoCommit;
182   };
183
184   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
185
186   '';
187 }
188
189 sub parse_technology_option {
190   my $cgi = shift;
191   my $save = shift;
192   my @result = ();
193   my $i = 0;
194   for (my $i = 0; $i < scalar(@technology); $i++) {
195     my $value = $cgi->param("part1_technology_option_$i"); #lame
196     save_fcc477map("part1_technology_option_$i",$value) 
197         if $save && $value =~ /^\d+$/;
198     push @result, $value =~ /^\d+$/ ? $value : 0;
199   }
200   return (@result);
201 }
202
203 sub statenum2state {
204   my $num = shift;
205   $states{$num};
206 }
207 ### everything above this point is unmaintained ###
208
209
210 =head1 THE "NEW" REPORT (October 2014 and later)
211
212 =head2 METHODS
213
214 =over 4
215
216 =cut
217
218 # functions for internal use
219
220 sub join_optionnames {
221   join(' ', map { join_optionname($_) } @_);
222 }
223
224 sub join_optionnames_int {
225   join(' ', map { join_optionname_int($_) } @_);
226 }
227
228 sub join_optionname {
229   # Returns a FROM phrase to join a specific option into the query (via 
230   # part_pkg).  The option value will appear as a field with the same name
231   # as the option.
232   my $name = shift;
233   "LEFT JOIN (SELECT pkgpart, optionvalue AS $name FROM part_pkg_fcc_option".
234     " WHERE fccoptionname = '$name') AS t_$name".
235     " ON (part_pkg.pkgpart = t_$name.pkgpart)";
236 }
237
238 sub join_optionname_int {
239   # Returns a FROM phrase to join a specific option into the query (via 
240   # part_pkg) and cast it to integer..  Note this does not convert nulls
241   # to zero.
242   my $name = shift;
243   "LEFT JOIN (SELECT pkgpart, CAST(optionvalue AS int) AS $name
244    FROM part_pkg_fcc_option".
245     " WHERE fccoptionname = '$name') AS t_$name".
246     " ON (part_pkg.pkgpart = t_$name.pkgpart)";
247 }
248
249 sub dbaname {
250   # Returns an sql expression for the DBA name
251   "COALESCE( deploy_zone.dbaname,
252      (SELECT value FROM conf WHERE conf.name = 'company_name'
253                              AND (conf.agentnum = deploy_zone.agentnum
254                                   OR conf.agentnum IS NULL)
255                              ORDER BY conf.agentnum IS NOT NULL DESC
256                              LIMIT 1)
257      ) AS dbaname"
258 }
259
260 sub active_on {
261   # Returns a condition to limit packages to those that were setup before a 
262   # certain date, and not canceled before that date.
263   #
264   # (Strictly speaking this should also exclude suspended packages but 
265   # "suspended as of some past date" is a complicated query.)
266   my $date = shift;
267   "cust_pkg.setup <= $date AND ".
268   "(cust_pkg.cancel IS NULL OR cust_pkg.cancel > $date)";
269 }
270
271 sub is_fixed_broadband {
272   "is_broadband::int = 1 AND technology::int IN( 10, 11, 12, 20, 30, 40, 41, 42, 50, 60, 70, 90, 0 )"
273 }
274
275 sub is_mobile_broadband {
276   "is_broadband::int = 1 AND technology::int IN( 80, 81, 82, 83, 84, 85, 86, 87, 88)"
277 }
278
279 =item report SECTION, OPTIONS
280
281 Returns the report section SECTION (see the C<parts> method for section 
282 name strings) as an arrayref of arrayrefs.  OPTIONS may contain the following:
283
284 - date: a timestamp value. Packages that were active on that date will be 
285 counted.
286
287 - agentnum: limit to packages with this agent.
288
289 - detail: if true, the report will contain an additional column which contains
290 the keys of all objects aggregated in the row.
291
292 - ignore_quantity: if true, package quantities will be ignored (only distinct
293 packages will be counted).
294
295 =cut
296
297 sub report {
298   my $class = shift;
299   my $section = shift;
300   my %opt = @_;
301
302   my $method = $section.'_sql';
303   die "Report section '$section' is not implemented\n"
304     unless $class->can($method);
305   my $statement = $class->$method(%opt);
306
307   warn $statement if $DEBUG;
308   my $sth = dbh->prepare($statement);
309   $sth->execute or die $sth->errstr;
310   $sth->fetchall_arrayref;
311 }
312
313 sub fbd_sql {
314   my $class = shift;
315   my %opt = @_;
316   my $date = $opt{date} || time;
317   my $agentnum = $opt{agentnum};
318
319   my @select = (
320     'censusblock',
321     dbaname(),
322     'technology',
323     'CASE WHEN is_consumer IS NOT NULL THEN 1 ELSE 0 END',
324     'adv_speed_down',
325     'adv_speed_up',
326     'CASE WHEN is_business IS NOT NULL THEN 1 ELSE 0 END',
327     'cir_speed_down',
328     'cir_speed_up',
329   );
330   push @select, 'blocknum' if $opt{detail};
331
332   my $from = 'deploy_zone_block
333     JOIN deploy_zone USING (zonenum)
334     JOIN agent USING (agentnum)';
335   my @where = (
336     "zonetype = 'B'",
337     "active_date  < $date",
338     "(expire_date > $date OR expire_date IS NULL)",
339   );
340   push @where, "agentnum = $agentnum" if $agentnum;
341
342   my $order_by = 'censusblock, agentnum, technology, is_consumer, is_business';
343
344   "SELECT ".join(', ', @select) . "
345   FROM $from
346   WHERE ".join(' AND ', @where)."
347   ORDER BY $order_by
348   ";
349 }
350
351 sub fbs_sql {
352   my $class = shift;
353   my %opt = @_;
354   my $date = $opt{date} || time;
355   my $agentnum = $opt{agentnum};
356   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
357
358   my @select = (
359     'cust_location.censustract',
360     'technology',
361     'broadband_downstream',
362     'broadband_upstream',
363     "SUM($q)",
364     "SUM(COALESCE(is_consumer,0) * $q)",
365   );
366   push @select, "array_to_string(array_agg(pkgnum), ',')" if $opt{detail};
367
368   my $from =
369     'cust_pkg
370       JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
371       JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
372       JOIN part_pkg USING (pkgpart) '.
373       join_optionnames_int(qw(
374         is_broadband technology 
375         is_consumer
376         )).
377       join_optionnames(qw(broadband_downstream broadband_upstream))
378   ;
379   my @where = (
380     active_on($date),
381     is_fixed_broadband()
382   );
383   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
384   my $group_by = 'cust_location.censustract, technology, '.
385                    'broadband_downstream, broadband_upstream ';
386   my $order_by = $group_by;
387
388   "SELECT ".join(', ', @select) . "
389   FROM $from
390   WHERE ".join(' AND ', @where)."
391   GROUP BY $group_by
392   ORDER BY $order_by
393   ";
394
395 }
396
397 sub fvs_sql {
398   my $class = shift;
399   my %opt = @_;
400   my $date = $opt{date} || time;
401   my $agentnum = $opt{agentnum};
402   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
403
404   my @select = (
405     'cust_location.censustract',
406     # VoIP indicator (0 for non-VoIP, 1 for VoIP)
407     'COALESCE(is_voip, 0)',
408     # number of lines/subscriptions
409     "SUM($q * (CASE WHEN is_voip = 1 THEN 1 ELSE phone_lines END))",
410     # consumer grade lines/subscriptions
411     "SUM($q * COALESCE(is_consumer,0) * (CASE WHEN is_voip = 1 THEN voip_sessions ELSE phone_lines END))",
412   );
413   push @select, "array_to_string(array_agg(pkgnum), ',')" if $opt{detail};
414
415   my $from = 'cust_pkg
416     JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
417     JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
418     JOIN part_pkg USING (pkgpart) '.
419     join_optionnames_int(qw(
420       is_phone is_voip is_consumer phone_lines voip_sessions
421       ))
422   ;
423
424   my @where = (
425     active_on($date),
426     "(is_voip = 1 OR is_phone = 1)",
427   );
428   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
429   my $group_by = 'cust_location.censustract, COALESCE(is_voip, 0)';
430   my $order_by = $group_by;
431
432   "SELECT ".join(', ', @select) . "
433   FROM $from
434   WHERE ".join(' AND ', @where)."
435   GROUP BY $group_by
436   ORDER BY $order_by
437   ";
438
439 }
440
441 sub lts_sql {
442   my $class = shift;
443   my %opt = @_;
444   my $date = $opt{date} || time;
445   my $agentnum = $opt{agentnum};
446   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
447
448   my @select = (
449     "state.fips",
450     "SUM($q * phone_vges)",
451     "SUM($q * phone_circuits)",
452     "SUM($q * phone_lines)",
453     "SUM($q * (CASE WHEN is_broadband = 1 THEN phone_lines ELSE 0 END))",
454     "SUM($q * (CASE WHEN is_consumer = 1 AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END))",
455     "SUM($q * (CASE WHEN is_consumer = 1 AND phone_longdistance = 1 THEN phone_lines ELSE 0 END))",
456     "SUM($q * (CASE WHEN is_consumer IS NULL AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END))",
457     "SUM($q * (CASE WHEN is_consumer IS NULL AND phone_longdistance = 1 THEN phone_lines ELSE 0 END))",
458     "SUM($q * (CASE WHEN phone_localloop = 'owned' THEN phone_lines ELSE 0 END))",
459     "SUM($q * (CASE WHEN phone_localloop = 'leased' THEN phone_lines ELSE 0 END))",
460     "SUM($q * (CASE WHEN phone_localloop = 'resale' THEN phone_lines ELSE 0 END))",
461     "SUM($q * (CASE WHEN media = 'Fiber' THEN phone_lines ELSE 0 END))",
462     "SUM($q * (CASE WHEN media = 'Cable Modem' THEN phone_lines ELSE 0 END))",
463     "SUM($q * (CASE WHEN media = 'Fixed Wireless' THEN phone_lines ELSE 0 END))",
464   );
465   push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail};
466
467   my $from =
468     'cust_pkg
469       JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
470       JOIN state USING (country, state)
471       JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
472       JOIN part_pkg USING (pkgpart) '.
473       join_optionnames_int(qw(
474         is_phone is_broadband
475         phone_vges phone_circuits phone_lines
476         is_consumer phone_longdistance
477         )).
478       join_optionnames('media', 'phone_localloop')
479   ;
480   my @where = (
481     active_on($date),
482     "is_phone = 1",
483   );
484   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
485   my $group_by = 'state.fips';
486   my $order_by = $group_by;
487
488   "SELECT ".join(', ', @select) . "
489   FROM $from
490   WHERE ".join(' AND ', @where)."
491   GROUP BY $group_by
492   ORDER BY $order_by
493   ";
494 }
495
496 sub voip_sql {
497   my $class = shift;
498   my %opt = @_;
499   my $date = $opt{date} || time;
500   my $agentnum = $opt{agentnum};
501   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
502
503   my @select = (
504     "state.fips",
505     # OTT, OTT + consumer
506     "SUM($q * (CASE WHEN (voip_lastmile IS NULL) THEN 1 ELSE 0 END))",
507     "SUM($q * (CASE WHEN (voip_lastmile IS NULL AND is_consumer = 1) THEN 1 ELSE 0 END))",
508     # non-OTT: total, consumer, broadband bundle, media types
509     "SUM($q * (CASE WHEN (voip_lastmile = 1) THEN 1 ELSE 0 END))",
510     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND is_consumer = 1) THEN 1 ELSE 0 END))",
511     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND is_broadband = 1) THEN 1 ELSE 0 END))",
512     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Copper') THEN 1 ELSE 0 END))",
513     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Cable Modem') THEN 1 ELSE 0 END))",
514     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Fiber') THEN 1 ELSE 0 END))",
515     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Fixed Wireless') THEN 1 ELSE 0 END))",
516     "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media NOT IN('Copper', 'Fiber', 'Cable Modem', 'Fixed Wireless') ) THEN 1 ELSE 0 END))",
517   );
518   push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail};
519
520   my $from =
521     'cust_pkg
522       JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
523       JOIN state USING (country, state)
524       JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
525       JOIN part_pkg USING (pkgpart) '.
526       join_optionnames_int(
527         qw( is_voip is_broadband is_consumer voip_lastmile)
528       ).
529       join_optionnames('media')
530   ;
531   my @where = (
532     active_on($date),
533     "is_voip = 1",
534   );
535   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
536   my $group_by = 'state.fips';
537   my $order_by = $group_by;
538
539   "SELECT ".join(', ', @select) . "
540   FROM $from
541   WHERE ".join(' AND ', @where)."
542   GROUP BY $group_by
543   ORDER BY $order_by
544   ";
545 }
546
547 sub mbs_sql {
548   my $class = shift;
549   my %opt = @_;
550   my $date = $opt{date} || time;
551   my $agentnum = $opt{agentnum};
552   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
553
554   my @select = (
555     'state.fips',
556     'broadband_downstream',
557     'broadband_upstream',
558     "SUM($q)",
559     "SUM(COALESCE(is_consumer, 0) * $q)",
560   );
561   push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail};
562
563   my $from =
564     'cust_pkg
565       JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
566       JOIN state USING (country, state)
567       JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
568       JOIN part_pkg USING (pkgpart) '.
569       join_optionnames_int(qw(
570         is_broadband technology
571         is_consumer
572         )).
573       join_optionnames(qw(broadband_downstream broadband_upstream))
574   ;
575   my @where = (
576     active_on($date),
577     is_mobile_broadband()
578   );
579   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
580   my $group_by = 'state.fips, broadband_downstream, broadband_upstream ';
581   my $order_by = $group_by;
582
583   "SELECT ".join(', ', @select) . "
584   FROM $from
585   WHERE ".join(' AND ', @where)."
586   GROUP BY $group_by
587   ORDER BY $order_by
588   ";
589 }
590
591 sub mvs_sql {
592   my $class = shift;
593   my %opt = @_;
594   my $date = $opt{date} || time;
595   my $agentnum = $opt{agentnum};
596   my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)';
597
598   my @select = (
599     'state.fips',
600     "SUM($q)",
601     "SUM($q * COALESCE(mobile_direct,0))",
602   );
603   push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail};
604
605   my $from =
606     'cust_pkg
607       JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum)
608       JOIN state USING (country, state)
609       JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
610       JOIN part_pkg USING (pkgpart) '.
611       join_optionnames_int(qw( is_mobile mobile_direct) )
612   ;
613   my @where = (
614     active_on($date),
615     'is_mobile = 1'
616   );
617   push @where, "cust_main.agentnum = $agentnum" if $agentnum;
618   my $group_by = 'state.fips';
619   my $order_by = $group_by;
620
621   "SELECT ".join(', ', @select) . "
622   FROM $from
623   WHERE ".join(' AND ', @where)."
624   GROUP BY $group_by
625   ORDER BY $order_by
626   ";
627 }
628
629 =item parts
630
631 Returns a Tie::IxHash reference of the internal short names used for the 
632 report sections ('fbd', 'mbs', etc.) to the full names.
633
634 =cut
635
636 tie our %parts, 'Tie::IxHash', (
637   fbd   => 'Fixed Broadband Deployment',
638   fbs   => 'Fixed Broadband Subscription',
639   fvs   => 'Fixed Voice Subscription',
640   lts   => 'Local Exchange Telephone Subscription',
641   voip  => 'Interconnected VoIP Subscription',
642   mbd   => 'Mobile Broadband Deployment',
643   mbsa  => 'Mobile Broadband Service Availability',
644   mbs   => 'Mobile Broadband Subscription',
645   mvd   => 'Mobile Voice Deployment',
646   mvs   => 'Mobile Voice Subscription',
647 );
648
649 sub parts {
650   Storable::dclone(\%parts);
651 }
652
653 =item part_table SECTION
654
655 Returns the name of the primary table that's aggregated in the report section 
656 SECTION. The last column of the report returned by the L</report> method is 
657 a comma-separated list of record numbers, in this table, that are included in
658 the report line item.
659
660 =cut
661
662 sub part_table {
663   my ($class, $part) = @_;
664   if ($part eq 'fbd') {
665     return 'deploy_zone_block';
666   } else {
667     return 'cust_pkg';
668   } # add other cases as we add more of the deployment/availability reports
669 }
670
671 1;