Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Fri, 24 Jan 2014 08:39:53 +0000 (00:39 -0800)
committerIvan Kohler <ivan@freeside.biz>
Fri, 24 Jan 2014 08:39:53 +0000 (00:39 -0800)
13 files changed:
FS/FS/Record.pm
FS/FS/Schema.pm
FS/FS/cdr.pm
FS/FS/cdr/aapt.pm [new file with mode: 0644]
FS/FS/invoice_conf.pm
httemplate/edit/cust_main.cgi
httemplate/edit/cust_main/bottomfixup.js
httemplate/edit/invoice_conf.html
httemplate/edit/process/quick-cust_pkg.cgi
httemplate/elements/location.html
httemplate/elements/standardize_locations.js
httemplate/graph/cust_bill_pkg.cgi
httemplate/misc/confirm-censustract.html

index 39a3920..ff8a0cc 100644 (file)
@@ -1849,7 +1849,7 @@ sub batch_import {
 
   my( $type, $header, $sep_char,
       $fixedlength_format, $xml_format, $asn_format,
-      $row_callback, @fields );
+      $parser_opt, $row_callback, @fields );
 
   my $postinsert_callback = '';
   $postinsert_callback = $param->{'postinsert_callback'}
@@ -1882,6 +1882,11 @@ sub batch_import {
         ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
         : '';
 
+    $parser_opt =
+      $param->{'format_parser_opts'}
+        ? $param->{'format_parser_opts'}{ $param->{'format'} }
+        : {};
+
     $xml_format =
       $param->{'format_xml_formats'}
         ? $param->{'format_xml_formats'}{ $param->{'format'} }
@@ -1936,18 +1941,17 @@ sub batch_import {
 
     if ( $type eq 'csv' ) {
 
-      my %attr = ( 'binary' => 1, );
-      $attr{sep_char} = $sep_char if $sep_char;
-      $parser = new Text::CSV_XS \%attr;
+      $parser_opt->{'binary'} = 1;
+      $parser_opt->{'sep_char'} = $sep_char if $sep_char;
+      $parser = Text::CSV_XS->new($parser_opt);
 
     } elsif ( $type eq 'fixedlength' ) {
 
       eval "use Parse::FixedLength;";
       die $@ if $@;
-      $parser = Parse::FixedLength->new($fixedlength_format);
+      $parser = Parse::FixedLength->new($fixedlength_format, $parser_opt);
 
-    }
-    else {
+    } else {
       die "Unknown file type $type\n";
     }
 
index 0447892..17fa2d8 100644 (file)
@@ -6369,21 +6369,9 @@ sub tables_hashref {
         'latexnotes',           'text',     'NULL', '', '', '',
         'latexfooter',          'text',     'NULL', '', '', '',
         'latexsummary',         'text',     'NULL', '', '', '',
-        'latexcoupon',          'text',     'NULL', '', '', '',
         'latexsmallfooter',     'text',     'NULL', '', '', '',
         'latexreturnaddress',   'text',     'NULL', '', '', '',
-        'latextopmargin',       'varchar',  'NULL', 16, '', '',
-        'latexheadsep',         'varchar',  'NULL', 16, '', '',
-        'latexaddresssep',      'varchar',  'NULL', 16, '', '',
-        'latextextheight',      'varchar',  'NULL', 16, '', '',
-        'latexextracouponspace','varchar',  'NULL', 16, '', '',
-        'latexcouponfootsep',   'varchar',  'NULL', 16, '', '',
-        'latexcouponamountenclosedsep', 'varchar',  'NULL', 16, '', '',
-        'latexcoupontoaddresssep',      'varchar',  'NULL', 16, '', '',
-        'latexverticalreturnaddress',      'char',  'NULL',  1, '', '',
-        'latexcouponaddcompanytoaddress',  'char',  'NULL',  1, '', '',
-        'logo_png',             'blob',     'NULL', '', '', '',
-        'logo_eps',             'blob',     'NULL', '', '', '',
+        'with_latexcoupon',     'char',     'NULL', '1', '', '',
         'lpr',                  'varchar',  'NULL', $char_d, '', '',
       ],
       'primary_key'  => 'confnum',
index 5658be9..80b31bf 100644 (file)
@@ -1657,9 +1657,15 @@ my %import_options = (
           keys %cdr_info
     },
 
-  'format_row_callbacks' => { map { $_ => $cdr_info{$_}->{'row_callback'}; }
-                                  keys %cdr_info
-                            },
+  'format_row_callbacks' =>
+    { map { $_ => $cdr_info{$_}->{'row_callback'}; }
+          keys %cdr_info
+    },
+
+  'format_parser_opts' =>
+    { map { $_ => $cdr_info{$_}->{'parser_opt'}; }
+          keys %cdr_info
+    },
 );
 
 sub _import_options {
diff --git a/FS/FS/cdr/aapt.pm b/FS/FS/cdr/aapt.pm
new file mode 100644 (file)
index 0000000..600a192
--- /dev/null
@@ -0,0 +1,126 @@
+package FS::cdr::aapt;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw ( %info );
+use FS::cdr qw(_cdr_date_parser_maker);
+
+my %CURRENCY = ( #Table 2.1.3
+  1 => 'AUD',
+  2 => 'USD',
+  3 => 'AUD',
+);
+
+my %UNIT_SCALE = ( #Table 2.1.4
+  100 => 1,     # seconds
+  101 => 0.1,   # tenths
+  120 => 60,    # minutes
+  130 => 3600,  # hours
+  #(irrelevant, because we don't yet support these usage types, but still)
+  200 => 1,     # bytes
+  201 => 2**10, # KB
+  202 => 2**20, # MB
+  203 => 2**30, # GB
+  401 => 2**10 * 1000, # "decimal MB"
+  402 => 2**20 * 1000, # "decimal GB"
+  451 => 1,     # Kbps--conveniently the same as our base unit
+  452 => 1000,  # Mbps (decimal)
+);
+
+%info = (
+  'name'          => 'AAPT CTOP',
+  'weight'        => 600,
+  'header'        => 1,
+  'type'          => 'fixedlength',
+  'row_callback'  => sub { $DB::single = 1; }, #XXX
+  'parser_opt'    => { trim => 1 },
+  'fixedlength_format' => [qw(
+    record_type:6:1:6
+    transaction_id:20R:7:26
+    product_id:6R:27:32
+    usage_type:6R:33:38
+    id_type:6R:39:44
+    id_value:48R:45:92
+    trans_time:14:93:106
+    sec_time:14:107:120
+    target:24R:121:144
+    origin:24R:145:168
+    rated_units:10R:169:178
+    rated_price:18R:179:196
+    jurisdiction:18R:197:214
+    fnn:18R:215:232
+    foreign_amount:18R:233:250
+    currency:6R:251:256
+    recipient:10R:257:266
+    completion:3R:267:269
+    record_id:22R:270:291
+    raw_units:10R:292:301
+    raw_unittype:6R:302:307
+    rated_unittype:6R:308:313
+    base_amount:18R:314:331
+    second_units:10R:332:341
+    third_units:10R:342:351
+    special1:255:352:606
+    special2:96:607:702
+    service_type:6:703:708
+    sec_id_type:6:709:714
+    sec_id_value:48:715:762
+    unused:238:763:1000
+  )],
+  'import_fields' => [
+    sub {                   # record type
+      my ($cdr, $data, $conf, $param) = @_;
+      $param->{skiprow} = 1 if $data ne 'PWTDET'; # skip non-detail records
+    },
+    '',                     # transaction ID
+    '',                     # product ID (CPRD)
+    'calltypenum',          # usage ID (CUSG)
+    sub {                   # ID type
+      my ($cdr, $data, $conf, $param) = @_;
+      if ($data != 1) {
+        warn "AAPT: service ID type is not telephone number.\n";
+        $param->{skiprow} = 1;
+      }
+    },
+    'charged_party',        # ID value (phone number, if ID type = 1)
+    _cdr_date_parser_maker('startdate'),  # trans date/time
+    '',                     # secondary date (unused?)
+    'dst',                  # Target (B-party)
+    'src',                  # Origin (A-party)
+    'billsec',              # Rated units (may need unit scaling)
+    sub {                   # Amount charged
+      my ($cdr, $data) = @_;
+      $cdr->set('upstream_price', sprintf('%.5f', $data/100));
+    },
+    'dcontext',             # Jurisdiction code; we use dcontext for this
+    '',                     # Full National Number (unused?)
+    '',                     # "Foreign Amount"
+    sub {                   # Currency
+      my ($cdr, $data) = @_;
+      $cdr->set('upstream_currency', $CURRENCY{$data});
+    },
+    '',                     # Reseller account number
+    '',                     # Completion status
+    'uniqueid',             # CTOP Record ID
+    'duration',             # Raw units
+    sub {                   # Raw unit type
+      my ($cdr, $data) = @_;
+      if (exists($UNIT_SCALE{$data})) {
+        $cdr->set('duration',
+          sprintf('%.0f', $cdr->get('duration') * $UNIT_SCALE{$data})
+        );
+      }
+    },
+    sub {                   # Rated unit type
+      my ($cdr, $data) = @_;
+      if (exists($UNIT_SCALE{$data})) {
+        $cdr->set('billsec',
+          sprintf('%.0f', $cdr->get('billsec') * $UNIT_SCALE{$data})
+        );
+      }
+    },
+    # trailing fields we don't care about
+  ], #import_fields
+);
+
+1;
index 043cab0..da448b8 100644 (file)
@@ -57,38 +57,9 @@ and supports the FS::Conf interface.  The following fields are supported:
 
 =item latexreturnaddress - return address (LaTeX)
 
-=item latexcoupon - payment coupon section (LaTeX)
-
 =item latexsmallfooter - footer for pages after the first (LaTeX)
 
-=item latextopmargin - top margin
-
-=item latexheadsep - distance from bottom of header to top of body
-
-=item latexaddresssep - distance from top of body to customer address
-
-=item latextextheight - maximum height of invoice body text
-
-=item latexextracouponspace - additional footer space to allow for coupon
-
-=item latexcouponfootsep - distance from bottom of coupon content to top
-of page footer
-
-=item latexcouponamountenclosedsep - distance from coupon balance line to
-"Amount Enclosed" box
-
-=item latexcoupontoaddresssep - distance from "Amount Enclosed" box to 
-coupon mailing address
-
-=item latexverticalreturnaddress - 'Y' to place the return address below 
-the company logo rather than beside it
-
-=item latexcouponaddcompanytoaddress - 'Y' to add the company name to the 
-address on the payment coupon
-
-=item logo_png - company logo, as a PNG, for HTML invoices
-
-=item logo_eps - company logo, as an EPS, for LaTeX invoices
+=item with_latexcoupon - 'Y' to print the payment coupon (LaTeX)
 
 =item lpr - command to print the invoice (passed on stdin as a PDF)
 
@@ -109,6 +80,12 @@ L<"insert">.
 
 sub table { 'invoice_conf'; }
 
+# fields (prefixed with 'with_') that turn on certain conf variables 
+# (set them to their conf values, rather than to null)
+my %flags = (
+  latexcoupon => 1
+);
+
 =item insert
 
 Adds this record to the database.  If there is an error, returns the error,
@@ -198,8 +175,10 @@ sub check {
 
   my $error = 
     $self->ut_numbern('confnum')
+    # core properties
     || $self->ut_number('modenum')
     || $self->ut_textn('locale')
+    # direct overrides of conf variables
     || $self->ut_anything('notice_name')
     || $self->ut_anything('subject')
     || $self->ut_anything('htmlnotes')
@@ -209,21 +188,10 @@ sub check {
     || $self->ut_anything('latexnotes')
     || $self->ut_anything('latexfooter')
     || $self->ut_anything('latexsummary')
-    || $self->ut_anything('latexcoupon')
     || $self->ut_anything('latexsmallfooter')
     || $self->ut_anything('latexreturnaddress')
-    || $self->ut_textn('latextopmargin')
-    || $self->ut_textn('latexheadsep')
-    || $self->ut_textn('latexaddresssep')
-    || $self->ut_textn('latextextheight')
-    || $self->ut_textn('latexextracouponspace')
-    || $self->ut_textn('latexcouponfootsep')
-    || $self->ut_textn('latexcouponamountenclosedsep')
-    || $self->ut_textn('latexcoupontoaddresssep')
-    || $self->ut_flag('latexverticalreturnaddress')
-    || $self->ut_flag('latexcouponaddcompanytoaddress')
-    || $self->ut_anything('logo_png')
-    || $self->ut_anything('logo_eps')
+    # flags
+    || $self->ut_flag('with_latexcoupon')
   ;
   return $error if $error;
 
@@ -243,6 +211,13 @@ sub _config {
   if ( $key =~ /^invoice_(.*)$/ ) {
     $colname = $1;
   }
+  if ( $flags{$colname} and !$self->get("with_$colname") ) {
+    # then a flag field is defined, and the flag is off, so act as though
+    # the config entry doesn't exist
+    # (currently only used for "latexcoupon", to allow invoice modes
+    # where the coupon is not printed)
+    return undef;
+  }
   if ( length($self->get($colname)) ) {
     return FS::conf->new({ 'name'   => $key,
                            'value'  => $self->get($colname) });
index 480047c..629c808 100755 (executable)
@@ -53,6 +53,8 @@
     <& /elements/location.html,
         object => $cust_main->bill_location,
         prefix => 'bill_',
+        enable_censustract => 1,
+        enable_district => 1,
         enable_coords => 1,
     &>
     <& cust_main/after_bill_location.html, $cust_main &>
index ecfcb3c..5a8835f 100644 (file)
@@ -7,7 +7,7 @@ my $company_longitude = $conf->config('company_longitude');
 
 my @fixups = ('copy_payby_fields', 'standardize_locations');
 
-push @fixups, 'confirm_censustract'
+push @fixups, 'confirm_censustract_bill', 'confirm_censustract_ship'
     if $conf->exists('cust_main-require_censustract');
 
 my $uniqueness = $conf->config('cust_main-check_unique');
@@ -101,14 +101,17 @@ function copyelement(from, to) {
 }
 
 % # the value in pre+'censustract' is the confirmed censustract; if it's set,
-% # do nothing here
-function confirm_censustract() {
+% # and the user hasn't changed it manually, skip this
+function confirm_censustract(pre) {
   var cf = document.CustomerForm;
-  var pre = cf.elements['same'].checked ? 'bill_' : 'ship_';
-  if ( cf.elements[pre+'censustract'].value == '' ) {
+  if ( cf.elements[pre+'censustract'].value == '' ||
+         cf.elements[pre+'enter_censustract'].value != 
+         cf.elements[pre+'censustract'].value )
+  {
     var address_info = form_address_info();
     address_info[pre+'latitude']  = cf.elements[pre+'latitude'].value;
     address_info[pre+'longitude'] = cf.elements[pre+'longitude'].value;
+    address_info['prefix'] = pre;
     OLpostAJAX(
         '<%$p%>/misc/confirm-censustract.html',
         'q=' + encodeURIComponent(JSON.stringify(address_info)),
@@ -121,14 +124,22 @@ function confirm_censustract() {
         0);
   } else submit_continue();
 }
+function confirm_censustract_bill() {
+  confirm_censustract('bill_');
+}
 
-%# called from confirm-censustract.html
-function set_censustract(tract, year) {
+function confirm_censustract_ship() {
   var cf = document.CustomerForm;
-  var pre = 'ship_';
   if ( cf.elements['same'].checked ) {
-    pre = 'bill_';
+    submit_continue();
+  } else {
+    confirm_censustract('ship_');
   }
+}
+
+%# called from confirm-censustract.html
+function set_censustract(tract, year, pre) {
+  var cf = document.CustomerForm;
   cf.elements[pre + 'censustract'].value = tract;
   cf.elements[pre + 'censusyear'].value = year;
   submit_continue();
index b7b3a4e..7122653 100644 (file)
@@ -49,14 +49,8 @@ my @fields = (
         'Footer',
         'Summary header',
         'Return address',
-        'Coupon',
         'Small footer',
-        'Top margin',
-        'Header separation',
-        'Address separation',
-        'Text height',
-        'Coupon height',
-        'Footer separation',
+        'Enable coupon',
       ),
 
       { type  => 'columnnext' },
@@ -65,19 +59,8 @@ my @fields = (
       { field => 'latexfooter',         %textarea },
       { field => 'latexsummary',        %textarea },
       { field => 'latexreturnaddress',  %textarea },
-      { field => 'latexcoupon',         %textarea },
       { field => 'latexsmallfooter',    %textarea },
-      { field => 'latextopmargin',                size => 16 },
-      { field => 'latexheadsep',                  size => 16 },
-      { field => 'latexaddresssep',               size => 16 },
-      { field => 'latextextheight',               size => 16 },
-      { field => 'latexextracouponspace',         size => 16 },
-      { field => 'latexcouponfootsep',            size => 16 },
-      # are these still used?
-      #{ field => 'latexcouponamountenclosedsep',  size => 16 },
-      #{ field => 'latexverticalreturnaddress',    type => 'checkbox' },
-      #{ field => 'latexcouponaddcompanytoaddress',type => 'checkbox' },
-      # logo -- implement if someone really needs it...
+      { field => 'with_latexcoupon', type => 'checkbox', value => 'Y' },
 
       { type  => 'columnnext' },
       { type  => 'title', value => 'HTML' },
@@ -104,20 +87,12 @@ my %labels = (
     latexfooter
     latexsummary
     latexreturnaddress
-    latexcoupon
+    with_latexcoupon
     latexsmallfooter
-    latextopmargin
-    latexheadsep
-    latexaddresssep
-    latextextheight
-    latexextracouponspace
-    latexcouponfootsep
     htmlnotes
     htmlfooter
     htmlsummary
     htmlreturnaddress
-    logo_png
-    logo_eps
   ) ),
 
 ); 
@@ -211,7 +186,6 @@ sub edit_callback {
     or die "invoice mode $modenum not found";
   $object->set('modename', $invoice_mode->modename);
   $object->set('agentnum', $invoice_mode->agentnum);
-
 }
 
 sub menubar_opt_callback {
index 95c9243..c3ab6fe 100644 (file)
@@ -134,7 +134,7 @@ my %hash = (
 $hash{'custnum'} = $cust_main->custnum if $cust_main;
 
 my @cust_pkg_usageprice = ();
-foreach my $quantity_param ( grep ( $cgi->param($_) && $cgi->param($_) > 0 ),
+foreach my $quantity_param ( grep { $cgi->param($_) && $cgi->param($_) > 0 }
                                grep /^usagepricenum(\d+)_quantity$/,
                                  $cgi->param
                            )
index a820bf2..799531e 100644 (file)
@@ -213,7 +213,8 @@ Example:
   <TD ALIGN="right">Census&nbsp;tract</TD>
   <TD COLSPAN=8>
     <INPUT TYPE="text" SIZE=15
-           NAME="enter_censustract" 
+           ID="<% $pre %>enter_censustract" 
+           NAME="<% $pre %>enter_censustract" 
            VALUE="<% $object->censustract |h %>">
     <% '(automatic)' %>
   </TD>
@@ -250,7 +251,8 @@ Example:
   var clear_coords_ids = [
     '<%$pre%>latitude',
     '<%$pre%>longitude',
-    'enter_censustract',
+    '<%$pre%>enter_censustract',
+    '<%$pre%>censustract',
     '<%$pre%>district'
   ];
   function clear_coords() {
index cea59b8..2d1fc74 100644 (file)
@@ -11,26 +11,14 @@ function form_address_info() {
 % if ( $billship ) {
   returnobj['same'] = cf.elements['same'].checked;
 % }
-% if ( $withcensus ) {
-% # "entered" censustract always goes with the ship_ address if there is one
-%   if ( $billship ) {
-    returnobj['ship_censustract'] = cf.elements['enter_censustract'].value;
-%   } else { # there's only a package address, so it's just "censustract"
-    returnobj['censustract'] = cf.elements['enter_censustract'].value;
-%   }
-% }
 % for my $pre (@prefixes) {
-  if ( <% $pre eq 'ship_' ? 1 : 0 %> && returnobj['same'] ) {
-%   # special case: don't include any ship_ fields, and move the entered
-%   # censustract over to bill_.
-    returnobj['bill_censustract'] = returnobj['ship_censustract'];
-    delete returnobj['ship_censustract'];
-  } else {
 %   # normal case
 %   for my $field (qw(address1 address2 city state zip country)) {
     returnobj['<% $pre %><% $field %>'] = cf.elements['<% $pre %><% $field %>'].value;
 %   } #for $field
-  } // if returnobj['same']
+%   if ( $withcensus ) {
+    returnobj['<% $pre %>censustract'] = cf.elements['<% $pre %>enter_censustract'].value;
+%   }
 % } #foreach $pre
 
   return returnobj;
@@ -181,18 +169,11 @@ function confirm_manual_address() {
 %# not much to do in this case, just confirm the censustract
 % if ( $withcensus ) {
   var cf = document.<% $formname %>;
-%   if ( $billship ) {
-  if ( cf.elements['same'] && cf.elements['same'].checked ) {
-    cf.elements['bill_censustract'].value =
-      cf.elements['enter_censustract'].value;
-  } else {
-    cf.elements['ship_censustract'].value =
-      cf.elements['enter_censustract'].value;
-  }
-%   } else {
-  cf.elements['censustract'].value = cf.elements['enter_censustract'].value;
+%   foreach my $pre (@prefixes) {
+  cf.elements['<% $pre %>censustract'].value =
+    cf.elements['<% $pre %>enter_censustract'].value;
 %   }
-% }
+% } # $withcensus
   post_standardization();
 }
 
index edc339d..151b295 100644 (file)
@@ -254,12 +254,17 @@ foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->a
 
     } elsif ( $cgi->param('class_agg_break') eq 'breakdown' ) {
 
-      # if we're working with report options, @classnums here contains 
-      # arrays of multiple classnums
       for (my $i = 0; $i < scalar @classnums; $i++) {
-        my $row_classnum = join(',', @{ $classnums[$i] });
-        my $row_classname = join(', ', @{ $classnames[$i] });
-        my $not_row_classnum = join(',', @{ $not_classnums[$i] });
+        my $row_classnum = $classnums[$i];
+        my $row_classname = $classnames[$i];
+        my $not_row_classnum = '';
+        if ( $class_param eq 'report_optionnum' ) {
+          # if we're working with report options, @classnums here contains 
+          # arrays of multiple classnums
+          $row_classnum = join(',', @$row_classnum);
+          $row_classname = join(', ', @$row_classname);
+          $not_row_classnum = join(',', @{ $not_classnums[$i] });
+        }
         foreach my $component ( @components ) {
 
           push @items, 'cust_bill_pkg';
index 9e0af49..880cade 100644 (file)
@@ -6,6 +6,9 @@ Census tract error
 Confirm census tract
 % }
 </B><BR>
+<% $location{address1} %> <% $location{address2} %><BR>
+<% $location{city} %>, <% $location{state} %> <% $location{zip} %><BR>
+<BR>
 % my $querystring = "census_year=$year&latitude=".$cache->get('latitude').'&longitude='.$cache->get('longitude');
 <A HREF="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?<% $querystring %>"
    TARGET="_blank">Map service module location</A><BR>
@@ -29,13 +32,14 @@ Confirm census tract
   <TR>
     <TD ALIGN="center">
       <BUTTON TYPE="button"
-              onclick="set_censustract('<% $old_tract %>', '<% $year %>')">
+              onclick="set_censustract('<% $old_tract %>', '<% $year %>', '<% $pre %>')">
       <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract
       </BUTTON>
     </TD>
+%   if ( !$error ) {
     <TD ALIGN="center">
       <BUTTON TYPE="button"
-              onclick="set_censustract('<% $new_tract %>', '<% $year %>')">
+              onclick="set_censustract('<% $new_tract %>', '<% $year %>', '<% $pre %>')">
       <IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract
       </BUTTON>
     </TD>
@@ -47,6 +51,14 @@ Confirm census tract
       </BUTTON>
     </TD>
   </TR>
+%   } else { # don't show a button to use the calculated value
+    <TD COLSPAN=1 ALIGN="center">
+      <BUTTON TYPE="button" onclick="submit_abort()">
+      <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+      </BUTTON>
+    </TD>
+  </TR>
+%   }
 </TABLE></CENTER>
 <%init>
 
@@ -61,11 +73,7 @@ warn $cgi->param('q') if $DEBUG;
 my $q = decode_json($cgi->param('q'))
   or die "bad argument '".$cgi->param('q')."'";
 
-my $pre = '';
-if ($q->{billship}) {
-  # this still isn't quite correct
-  $pre = $q->{'same'} ? 'bill_' : 'ship_';
-}
+my $pre = $q->{prefix} || '';
 my %location = (
   map { $_ => $q->{$pre.$_} }
     qw( company address1 address2 city state zip country latitude longitude )