[freeside-commits] branch master updated. 98aa4355cb0a31f97cdbeabb6c190c908ba355a0

Mark Wells mark at 420.am
Mon Mar 26 23:35:07 PDT 2012


The branch, master has been updated
       via  98aa4355cb0a31f97cdbeabb6c190c908ba355a0 (commit)
       via  77977d04ed407c3a44877a83b475a3800a581361 (commit)
       via  9b4209f91ad9e3c4cef7deebfb0180f6faf1d0dc (commit)
       via  5561349c87fdcc646c18010ea57925f90170f321 (commit)
       via  194c05911b1bf4f09b0ce523e994d888b2e983ef (commit)
       via  0318b208bb5edbe05a2969039c22b4203399eaa5 (commit)
       via  0456422b9eaf771e8c27cfb4ef4a0dd7e6f926f8 (commit)
       via  ed9e6fe7b0f2979ef94f6c399853533ffab8e0d2 (commit)
       via  213ad23ac1b8d9449e29522661a428581fcb1ce2 (commit)
      from  f62a68016f32b443084705b0e7851497b308f81d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 98aa4355cb0a31f97cdbeabb6c190c908ba355a0
Merge: f62a680 77977d0
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Mar 26 23:34:44 2012 -0700

    Merge branch '16824'


commit 77977d04ed407c3a44877a83b475a3800a581361
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Mar 26 23:33:09 2012 -0700

    historical package definition feature, part 1, #16824

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 2bc1f19..ee7f2b4 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -694,6 +694,13 @@ sub reason_type_options {
   },
 
   {
+    'key'         => 'part_pkg-lineage',
+    'section'     => '',
+    'description' => 'When editing a package definition, if setup or recur fees are changed, create a new package rather than changing the existing package.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'apacheip',
     #not actually deprecated yet
     #'section'     => 'deprecated',
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index ab853e6..9b21dfc 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1690,6 +1690,8 @@ sub tables_hashref {
         'no_auto',          'char', 'NULL',  1, '', '', 
         'recur_show_zero',  'char', 'NULL',  1, '', '',
         'setup_show_zero',  'char', 'NULL',  1, '', '',
+        'successor',     'int',     'NULL', '', '', '',
+        'family_pkgpart','int',     'NULL', '', '', '',
       ],
       'primary_key' => 'pkgpart',
       'unique' => [],
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 373982b..061001b 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -103,6 +103,13 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =item fcc_ds0s - Optional DS0 equivalency number for FCC form 477
 
+=item successor - Foreign key for the part_pkg that replaced this record.
+If this record is not obsolete, will be null.
+
+=item family_pkgpart - Foreign key for the part_pkg that was the earliest
+ancestor of this record.  If this record is not a successor to another 
+part_pkg, will be equal to pkgpart.
+
 =back
 
 =head1 METHODS
@@ -192,6 +199,16 @@ sub insert {
     return $error;
   }
 
+  # set family_pkgpart
+  if ( $self->get('family_pkgpart') eq '' ) {
+    $self->set('family_pkgpart' => $self->pkgpart);
+    $error = $self->SUPER::replace;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
   my $conf = new FS::Conf;
   if ( $conf->exists('agent_defaultpkg') ) {
     warn "  agent_defaultpkg set; allowing all agents to purchase package"
@@ -294,7 +311,7 @@ sub insert {
       }
   }
 
-  warn "  commiting transaction" if $DEBUG;
+  warn "  committing transaction" if $DEBUG and $oldAutoCommit;
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   '';
@@ -360,6 +377,28 @@ sub replace {
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
+  
+  my $conf = new FS::Conf;
+  if ( $conf->exists('part_pkg-lineage') ) {
+    if ( grep { $options->{options}->{$_} ne $old->option($_, 1) }
+          qw(setup_fee recur_fee) #others? config?
+        ) { 
+    
+      warn "  superseding package" if $DEBUG;
+
+      my $error = $new->supersede($old, %$options);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+      else {
+        warn "  committing transaction" if $DEBUG and $oldAutoCommit;
+        $dbh->commit if $oldAutoCommit;
+        return $error;
+      }
+    }
+    #else nothing
+  }
 
   #plandata shit stays in replace for upgrades until after 2.0 (or edit
   #_upgrade_data)
@@ -501,8 +540,18 @@ sub replace {
         }
       }
   }
+  
+  # propagate changes to certain core fields
+  if ( $conf->exists('part_pkg-lineage') ) {
+    warn "  propagating changes to family" if $DEBUG;
+    my $error = $new->propagate($old);
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
 
-  warn "  commiting transaction" if $DEBUG;
+  warn "  committing transaction" if $DEBUG and $oldAutoCommit;
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
@@ -573,6 +622,8 @@ sub check {
            : $self->ut_agentnum_acl('agentnum', \@null_agentnum_right)
        )
     || $self->ut_numbern('fcc_ds0s')
+    || $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart')
+    || $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart')
     || $self->SUPER::check
   ;
   return $error if $error;
@@ -587,6 +638,76 @@ sub check {
   '';
 }
 
+=item supersede OLD [, OPTION => VALUE ... ]
+
+Inserts this package as a successor to the package OLD.  All options are as
+for C<insert>.  After inserting, disables OLD and sets the new package as its
+successor.
+
+=cut
+
+sub supersede {
+  my ($new, $old, %options) = @_;
+  my $error;
+
+  $new->set('pkgpart' => '');
+  $new->set('family_pkgpart' => $old->family_pkgpart);
+  warn "    inserting successor package\n" if $DEBUG;
+  $error = $new->insert(%options);
+  return $error if $error;
+ 
+  warn "    disabling superseded package\n" if $DEBUG; 
+  $old->set('successor' => $new->pkgpart);
+  $old->set('disabled' => 'Y');
+  $error = $old->SUPER::replace; # don't change its options/pkg_svc records
+  return $error if $error;
+
+  warn "  propagating changes to family" if $DEBUG;
+  $new->propagate($old);
+}
+
+=item propagate OLD
+
+If any of certain fields have changed from OLD to this package, then,
+for all packages in the same lineage as this one, sets those fields 
+to their values in this package.
+
+=cut
+
+my @propagate_fields = (
+  qw( pkg classnum setup_cost recur_cost taxclass
+  setuptax recurtax pay_weight credit_weight
+  )
+);
+
+sub propagate {
+  my $new = shift;
+  my $old = shift;
+  my %fields = (
+    map { $_ => $new->get($_) }
+    grep { $new->get($_) ne $old->get($_) }
+    @propagate_fields
+  );
+
+  my @part_pkg = qsearch('part_pkg', { 
+      'family_pkgpart' => $new->family_pkgpart 
+  });
+  my @error;
+  foreach my $part_pkg ( @part_pkg ) {
+    my $pkgpart = $part_pkg->pkgpart;
+    next if $pkgpart == $new->pkgpart; # don't modify $new
+    warn "    propagating to pkgpart $pkgpart\n" if $DEBUG;
+    foreach ( keys %fields ) {
+      $part_pkg->set($_, $fields{$_});
+    }
+    # SUPER::replace to avoid changing non-core fields
+    my $error = $part_pkg->SUPER::replace;
+    push @error, "pkgpart $pkgpart: $error"
+      if $error;
+  }
+  join("\n", @error);
+}
+
 =item pkg_comment [ OPTION => VALUE... ]
 
 Returns an (internal) string representing this package.  Currently,
@@ -1277,7 +1398,7 @@ sub _rebless {
   }
   return $self if ref($self) =~ /::$plan$/; #already blessed into plan subclass
   my $class = ref($self). "::$plan";
-  warn "reblessing $self into $class" if $DEBUG;
+  warn "reblessing $self into $class" if $DEBUG > 1;
   eval "use $class;";
   die $@ if $@;
   bless($self, $class) unless $@;
@@ -1410,6 +1531,14 @@ sub _upgrade_data { # class method
     die $error if $error;
   }
 
+  # set family_pkgpart on any packages that don't have it
+  @part_pkg = qsearch('part_pkg', { 'family_pkgpart' => '' });
+  foreach my $part_pkg (@part_pkg) {
+    $part_pkg->set('family_pkgpart' => $part_pkg->pkgpart);
+    my $error = $part_pkg->SUPER::replace;
+    die $error if $error;
+  }
+
   my @part_pkg_option = qsearch('part_pkg_option',
     { 'optionname'  => 'unused_credit',
       'optionvalue' => 1,
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 7668060..4ca78d7 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -45,6 +45,7 @@ my $select = '*';
 my $orderby = 'pkgpart';
 my %hash = ();
 my $extra_count = '';
+my $family_pkgpart;
 
 if ( $cgi->param('active') ) {
   $orderby = 'num_active DESC';
@@ -77,6 +78,16 @@ if ( $cgi->param('missing_recur_fee') ) {
                     )";
 }
 
+if ( $cgi->param('family') =~ /^(\d+)$/ ) {
+  $family_pkgpart = $1;
+  push @where, "family_pkgpart = $1";
+  # Hiding disabled or one-time charges and limiting by classnum aren't 
+  # very useful in this mode, so all links should still refer back to the 
+  # non-family-limited display.
+  $cgi->param('showdisabled', 1);
+  $cgi->delete('family');
+}
+
 push @where, FS::part_pkg->curuser_pkgs_sql
   unless $acl_edit_global;
 
@@ -209,6 +220,16 @@ push @fields, sub {
                   $part_pkg->part_pkg_discount;
 
   [
+    ( !$family_pkgpart &&
+      $part_pkg->pkgpart == $part_pkg->family_pkgpart ? () : [
+      {
+        'align'=> 'center',
+        'colspan' => 2,
+        'size' => '-1',
+        'data' => '<b>Show all versions</b>',
+        'link' => $p.'browse/part_pkg.cgi?family='.$part_pkg->family_pkgpart,
+      }
+    ] ),
     [
       { data =>$plan,
         align=>'center',
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index e5edcde..cd07313 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -24,6 +24,8 @@
               'error_callback'        => $error_callback,
               'field_callback'        => $field_callback,
 
+              'onsubmit'              => 'confirm_submit',
+
               'labels' => { 
                             'pkgpart'          => 'Package Definition',
                             'pkg'              => 'Package (customer-visible)',
@@ -66,6 +68,8 @@
                             },
 
                             { field=>'custom',  type=>'hidden' },
+                            { field=>'family_pkgpart', type=>'hidden' },
+                            { field=>'successor', type=>'hidden' },
 
                             { type => 'columnstart' },
                             
@@ -593,7 +597,7 @@ my $javascript = <<'END';
 
     }
 
-    function aux_planchanged(what) {
+    function aux_planchanged(what) { //?
 
       alert('called!');
       var plan = what.options[what.selectedIndex].value;
@@ -609,9 +613,29 @@ my $javascript = <<'END';
 
     }
 
-  </SCRIPT>
 END
 
+my $warning =
+  'Changing the setup or recurring fee will create a new package definition. '.
+  'Continue?';
+              
+if ( $conf->exists('part_pkg-lineage') ) {
+  $javascript .= "
+    function confirm_submit(f) {
+    
+      var fields = Array('setup_fee','recur_fee');
+      for(var i=0; i < fields.length; i++) {
+          if ( f[fields[i]].value != f[fields[i]].defaultValue ) {
+              return confirm('$warning');
+          }
+      }
+      return true;
+    }
+";
+}
+
+$javascript .= '</SCRIPT>';
+
 tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
 
 tie my %plan_labels, 'Tie::IxHash',

commit 9b4209f91ad9e3c4cef7deebfb0180f6faf1d0dc
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Mar 26 19:20:29 2012 -0700

    more contact-aware new customer screen, RT#16819

diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index d2c0cb7..3994313 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -50,7 +50,7 @@
 %  }
 
 <BR>
-<FONT SIZE="+1"><B><% mt('Billing address') |h %></B></FONT>
+<FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
 
 <& cust_main/contact.html,
              'cust_main'    => $cust_main,
@@ -127,9 +127,9 @@ function samechanged(what) {
 </SCRIPT>
 
 <BR>
-<FONT SIZE="+1"><B><% mt('Service address') |h %></B></FONT>
+<FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
 
-(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>><% mt('same as billing address') |h %>)
+<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>><% mt('same as billing address') |h %>
 <& cust_main/contact.html,
              'cust_main' => $cust_main,
              'pre'       => 'ship_',
@@ -138,17 +138,23 @@ function samechanged(what) {
              'style'     => \@ship_style
 &>
 
+<& cust_main/contacts_new.html,
+             'cust_main' => $cust_main,
+&>
+
 %# billing info
 <& cust_main/billing.html, $cust_main,
                'payinfo'        => $payinfo,
                'invoicing_list' => \@invoicing_list,
 &>
+<BR>
 
 % my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
 % if (!$ro_comments || $cust_main->comments) {
 
-    <BR><% mt('Comments') |h %> 
-    <% &ntable("#cccccc") %>
+    <BR>
+    <FONT CLASS="fsinnerbox-title"><% mt('Comments') |h %></FONT>
+    <TABLE CLASS="fsinnerbox">
       <TR>
         <TD>
           <TEXTAREA NAME = "comments"
@@ -204,6 +210,7 @@ function samechanged(what) {
        VALUE   = "<% $custnum ?  emt("Apply changes") : emt("Add Customer") %>"
        onClick = "this.disabled=true; bottomfixup(this.form);"
 >
+<BR><BR>
 </FORM>
 
 <& /elements/footer.html &>
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index bf6c623..18c7ae9 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -34,7 +34,7 @@
 %
 %  my $r = qq!<font color="#ff0000">*</font> !;
 
-  <BR><FONT SIZE="+1"><B><% mt('Billing information') |h %></B></FONT>
+  <BR><FONT CLASS="fsinnerbox-title"><% mt('Billing information') |h %></FONT>
   <% &ntable("#cccccc") %>
 
     <TR>
@@ -580,7 +580,7 @@ function toggle(obj) {
 
   </TABLE>
 
-  <% $r %> <% mt('required fields') |h %> 
+  <% $r %><% mt('required fields') |h %> 
 % } 
 
 <script type="text/javascript">
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
index 4912327..57490b9 100644
--- a/httemplate/edit/cust_main/contact.html
+++ b/httemplate/edit/cust_main/contact.html
@@ -1,4 +1,4 @@
-<% &ntable("#cccccc") %>
+<TABLE CLASS="fsinnerbox">
 
 <TR>
   <TH ALIGN="right"><%$r%><% mt('Contact name (last, first)') |h %></TH>
@@ -14,8 +14,6 @@
 
   <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $opt{ss} %>"></TD>
 % } 
-
-
 </TR>
 
 % if ( $conf->exists('cust-email-high-visibility') && !$pre ) {
@@ -30,100 +28,19 @@
     </TR>
 % }
 
-% unless ( $conf->exists('cust-edit-alt-field-order') ) {
-<TR>
-  <TD ALIGN="right"><% mt('Company') |h %></TD>
-  <TD COLSPAN=7>
-    <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') |h %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<& /elements/location.html,
-             'prefix'       => $pre,
-             'object'       => $cust_main,
-             'onchange'     => $onchange,
-             'disabled'     => $disabled,
-             'style'        => \@style,
-             'same_checked' => $opt{'same_checked'},
-             'geocode'      => $opt{'geocode'},
-             'censustract'  => $opt{'censustract'},
-&>
-
-<TR>
-  <TD ALIGN="right"><% $daytime_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<TR>
-  <TD ALIGN="right"><% $night_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<TR>
-  <TD ALIGN="right"><% $mobile_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<TR>
-  <TD ALIGN="right"><% mt('Fax') |h %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-% } else {
-
-<TR>
-  <TD ALIGN="right"><% $daytime_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
+% unless ( $conf->exists('cust-edit-alt-field-order') ) { #standard order
 
-<TR>
-  <TD ALIGN="right"><% $night_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
+  <& company &>
+  <& location &>
+  <& phones &>
+  <& fax &>
 
-<TR>
-  <TD ALIGN="right"><% $mobile_label %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<& /elements/location.html,
-             'prefix'       => $pre,
-             'object'       => $cust_main,
-             'onchange'     => $onchange,
-             'disabled'     => $disabled,
-             'style'        => \@style,
-             'same_checked' => $opt{'same_checked'},
-             'geocode'      => $opt{'geocode'},
-             'censustract'  => $opt{'censustract'},
-&>
+% } else { #alternate field order
 
-<TR>
-  <TD ALIGN="right"><% mt('Fax') |h %></TD>
-  <TD COLSPAN=5>
-    <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
-
-<TR>
-  <TD ALIGN="right"><% mt('Company') |h %></TD>
-  <TD COLSPAN=7>
-    <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') |h %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-  </TD>
-</TR>
+  <& phones &>
+  <& location &>
+  <& fax &>
+  <& company &>
 
 % }
 
@@ -152,16 +69,88 @@
 </TABLE>
 <%$r%><% mt('required fields') |h %><BR>
 
+<%def company>
+% my $display = ($cust_main->residential_commercial eq 'Commercial')
+%                 ? '' : 'none';
+  <TR ID="<%$pre%>company_row" STYLE="display:<%$display%>">
+    <TD ALIGN="right"><% mt('Company') |h %></TD>
+    <TD COLSPAN=7>
+      <INPUT TYPE="text" NAME="<%$pre%>company" ID="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') |h %>" SIZE=60 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+    </TD>
+  </TR>
+</%def>
+
+<%def location>
+  <& /elements/location.html,
+               'prefix'       => $pre,
+               'object'       => $cust_main,
+               'onchange'     => $onchange,
+               'disabled'     => $disabled,
+               'style'        => \@style,
+               'same_checked' => $opt{'same_checked'},
+               'geocode'      => $opt{'geocode'},
+               'censustract'  => $opt{'censustract'},
+  &>
+</%def>
+
+<%def phones>
+  <TR>
+    <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD>
+    <TD COLSPAN=6>
+
+      <TABLE CELLSPACING=0 CELLPADDING=0>
+        <TR>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $daytime_label %></FONT>
+          </TD>
+          <TD> </TD>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $night_label %></FONT>
+          </TD>
+          <TD> </TD>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $mobile_label %></FONT>
+          </TD>
+        </TR>
+      </TABLE>
+    </TD>
+  </TR>
+</%def>
+
+<%def fax>
+  <TR>
+    <TD ALIGN="right"><% mt('Fax') |h %></TD>
+    <TD COLSPAN=5>
+      <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+    </TD>
+  </TR>
+</%def>
+
+<%once>
+
+my $r = qq!<font color="#ff0000">*</font> !;
+
+</%once>
+<%shared>
+
+my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style,
+    $daytime_label, $night_label, $mobile_label,
+  );
+
+</%shared>
 <%init>
 
-my %opt = @_;
-my $cust_main = $opt{'cust_main'};
-my $pre       = $opt{'pre'};
-my $onchange  = $opt{'onchange'};
-my $disabled  = $opt{'disabled'};
-my @style     = ( $opt{'style'} ? @{ $opt{'style'} } : () );
+%opt = @_;
+$cust_main = $opt{'cust_main'};
+$pre       = $opt{'pre'};
+$onchange  = $opt{'onchange'};
+$disabled  = $opt{'disabled'};
+ at style     = ( $opt{'style'} ? @{ $opt{'style'} } : () );
 
-my $style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
+$style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
 
 my $conf = new FS::Conf;
 
@@ -189,15 +178,15 @@ if ( $conf->exists('cust_main-require_censustract') ) {
   $opt{censustract} ||= $cust_main->censustract;
 }
 
-my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
-                      ? 'Day Phone'
-                      : FS::Msgcat::_gettext('daytime');
-my $night_label   = FS::Msgcat::_gettext('night') =~/^(night)?$/
-                      ? 'Night Phone'
-                      : FS::Msgcat::_gettext('night') || 'Night Phone';
-my $mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
-                      ? 'Mobile Phone'
-                      : FS::Msgcat::_gettext('mobile') || 'Mobile Phone';
+$daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+                   ? 'Day'
+                   : FS::Msgcat::_gettext('daytime');
+$night_label   = FS::Msgcat::_gettext('night') =~/^(night)?$/
+                   ? 'Night'
+                   : FS::Msgcat::_gettext('night') || 'Night';
+$mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
+                   ? 'Mobile'
+                   : FS::Msgcat::_gettext('mobile') || 'Mobile';
 
 my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
                   ? 'Driver’s License'
@@ -206,8 +195,6 @@ my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_st
                         ? 'Driver’s License State'
                         : FS::Msgcat::_gettext('stateid_state') || 'Driver’s License State';
 
-my $r = qq!<font color="#ff0000">*</font> !;
-
 my @invoicing_list = $cust_main->invoicing_list;
 
 my $agentnum = $cust_main->agentnum if $cust_main->custnum;
diff --git a/httemplate/edit/cust_main/contacts_new.html b/httemplate/edit/cust_main/contacts_new.html
new file mode 100644
index 0000000..f59126a
--- /dev/null
+++ b/httemplate/edit/cust_main/contacts_new.html
@@ -0,0 +1,61 @@
+<DIV ID="contacts_div" STYLE="display:<% $display %>">
+<BR>
+<FONT CLASS="fsinnerbox-title">Contacts</FONT>
+<% include('/edit/elements/edit.html',
+     'embed'           => $opt{cust_main},
+     'table'           => 'cust_main',
+     'labels'          => { 'contactnum'  => 'Contact',
+                            #'locationnum' => ' ',
+                          },
+     'fields'          => [
+       { 'field'             => 'contactnum',
+         'type'              => 'contact',
+         'colspan'           => 6,
+         'm2m_method'        => 'cust_contact',
+         'm2m_dstcol'        => 'contactnum',   
+         'm2_label'          => 'Contact',
+         'm2_error_callback' => $m2_error_callback,
+       },
+     ],
+     'agent_virt'      => 1,
+    )
+%>
+</DIV>
+<%init>
+
+my %opt = @_;
+
+my $display = ($opt{'cust_main'}->residential_commercial eq 'Commercial')
+                ? ''
+                : 'none';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $conf = new FS::Conf;
+
+my $m2_error_callback = sub {
+  my($cgi, $object) = @_;
+
+  #process_o2m fields in process/cust_main-contacts.html
+  my @fields = qw( first last title comment );
+  my @gfields = ( '', map "_$_", @fields );
+
+  map {
+        if ( /^contactnum(\d+)$/ ) {
+          my $num = $1;
+          if ( grep $cgi->param("contactnum$num$_"), @gfields ) {
+            my $x = new FS::contact {
+              'contactnum' => scalar($cgi->param("contactnum$num")),
+              map { $_ => scalar($cgi->param("contactnum${num}_$_")) } @fields,
+            };
+            $x;
+          } else {
+            ();
+          }
+        } else {
+          ();
+        }
+      }
+      $cgi->param;
+};
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg.html b/httemplate/edit/cust_main/first_pkg.html
index 2e9f36f..ad118b4 100644
--- a/httemplate/edit/cust_main/first_pkg.html
+++ b/httemplate/edit/cust_main/first_pkg.html
@@ -6,8 +6,8 @@
 %
 % if ( @part_pkg ) {
 
-    <BR><BR>
-    <FONT SIZE="+1"><B><% mt('First package') |h %></B></FONT>
+    <BR>
+    <FONT CLASS="fsinnerbox-title"><% mt('First package') |h %></FONT>
     <% ntable("#cccccc") %>
 
       <TR>
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
index 2d1d978..7ba167b 100644
--- a/httemplate/edit/cust_main/top_misc.html
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -1,4 +1,49 @@
-<% &ntable("#cccccc") %>
+<TABLE CLASS="fsinnerbox">
+
+<TR>
+  <TD ALIGN="right">Residential</TD>
+  <TD><INPUT TYPE     = "radio"
+             NAME     = "residential_commercial"
+             ID       = "residential_commercial_Residential"
+             VALUE    = "Residential"
+             onChange = "rescom_changed(this)"
+       <% $cust_main->residential_commercial eq 'Commercial' ? '' : 'CHECKED' %>
+  ></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Commercial</TD>
+  <TD><INPUT TYPE     = "radio"
+             NAME     = "residential_commercial"
+             ID       = "residential_commercial_Commercial"
+             VALUE    = "Commercial"
+             onChange = "rescom_changed(this)"
+       <% $cust_main->residential_commercial eq 'Commercial' ? 'CHECKED' : '' %>
+  ></TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+  function rescom_changed() {
+    var f = document.CustomerForm;
+
+    if        ( f.residential_commercial_Residential.checked ) {
+      document.getElementById('contacts_div').style.display = 'none';
+    } else { // if ( f.residential_commercial_Commercial.checked ) {
+      document.getElementById('contacts_div').style.display = '';
+    }
+
+    if        ( f.residential_commercial_Residential.checked && ! f.company.value.length ) {
+      document.getElementById('company_row').style.display = 'none'
+    } else { // if ( f.residential_commercial_Commercial.checked ) {
+      document.getElementById('company_row').style.display = '';
+    }
+
+    if        ( f.residential_commercial_Residential.checked && ! f.ship_company.value.length ) {
+      document.getElementById('ship_company_row').style.display = 'none'
+    } else { // if ( f.residential_commercial_Commercial.checked ) {
+      document.getElementById('ship_company_row').style.display = '';
+    }
+  }
+</SCRIPT>
 
 % foreach my $field ($cust_main->virtual_fields) {
     <% $cust_main->pvf($field)->widget('HTML', 'edit',$cust_main->getfield($field)) %>
@@ -144,6 +189,23 @@ my( $cust_main, %opt ) = @_;
 
 my $custnum = $opt{'custnum'};
 
+if ( $cgi->param('error') ) {
+  $cust_main->set('residential_commercial',
+    ($cgi->param('residential_commercial') eq 'Commercial')
+      ? 'Commercial'
+      : 'Residential'
+  );
+} elsif ( $custnum ) { #editing
+  $cust_main->set('residential_commercial',
+    length($cust_main->company)
+      ? 'Commercial'
+      : 'Residential'
+  );
+} else { #new customer
+  #config to default to commercial and/or disable residential when someone needs
+  $cust_main->set('residential_commercial', 'Residential');
+}
+
 my $conf = new FS::Conf;
 
 my $curuser = $FS::CurrentUser::CurrentUser;
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 4e896f2..73faad4 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -111,6 +111,11 @@ Example:
     #we're in a popup (no title/menu/searchboxes)
     'popup' => 1,
 
+    #we're embedded (rows only: no header at all, no html_init, no error
+    # display, no <FORM>, no hidden fields for table name or primary key, no
+    # display of primary key, no submit button, no html_foot, no footer)
+    'embed' => $object, #need to pass the object
+
     ###
     # HTML callbacks
     ###
@@ -186,41 +191,60 @@ Example:
 
 </%doc>
 
-<% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
-              $title,
-              include( '/elements/menubar.html', @menubar ),
-              $opt{'body_etc'},
-           )
-%>
+% unless ( $opt{embed} ) {
+%
+%   my $title = $opt{action}. ' '. ( $opt{name} || $opt{'name_singular'} );
+%
+%   my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
+%   $viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};  
+%
+%   my @menubar;
+%   if ( $opt{'menubar'} ) {
+%     @menubar = @{ $opt{'menubar'} };
+%   } else {
+%     my $items = $opt{'name'} ? $opt{'name'}.'s' : PL($opt{'name_singular'});
+%     @menubar = (
+%       "View all $items" => $viewall_url,
+%     );
+%   }
 
-<% defined($opt{'html_init'}) 
-      ? ( ref($opt{'html_init'})
-            ? &{$opt{'html_init'}}()
-            : $opt{'html_init'}
-        )
-      : ''
-%>
+  <% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
+                $title,
+                include( '/elements/menubar.html', @menubar ),
+                $opt{'body_etc'},
+             )
+  %>
+
+  <% defined($opt{'html_init'}) 
+        ? ( ref($opt{'html_init'})
+              ? &{$opt{'html_init'}}()
+              : $opt{'html_init'}
+          )
+        : ''
+  %>
 
-<% include('/elements/error.html') %>
+  <% include('/elements/error.html') %>
 
 % my $url = $opt{'post_url'} || popurl(1)."process/$table.html";
 
-<FORM NAME   = "edit_topform"
-      METHOD = POST
-      ACTION = "<% $url %>"
-      <% $opt{onsubmit} ? 'onSubmit="return '.$opt{onsubmit}.'(this)"' : '' %>
->
+  <FORM NAME   = "edit_topform"
+        METHOD = POST
+        ACTION = "<% $url %>"
+        <% $opt{onsubmit} ? 'onSubmit="return '.$opt{onsubmit}.'(this)"' : '' %>
+  >
 
-<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>">
-<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $clone ? '' : $object->$pkey() %>">
+  <INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>">
+  <INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $clone ? '' : $object->$pkey() %>">
 
-<FONT SIZE="+1"><B>
-<% ( $opt{labels} && exists $opt{labels}->{$pkey} )
-      ? $opt{labels}->{$pkey}
-      : $pkey
-%>
-</B></FONT>
-#<% ( !$clone && $object->$pkey() ) || "(NEW)" %>
+  <FONT SIZE="+1"><B>
+  <% ( $opt{labels} && exists $opt{labels}->{$pkey} )
+        ? $opt{labels}->{$pkey}
+        : $pkey
+  %>
+  </B></FONT>
+  #<% ( !$clone && $object->$pkey() ) || "(NEW)" %>
+
+% }
 
 % my $tablenum = 0;
 <TABLE ID="TableNumber<% $tablenum++ %>" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
@@ -723,24 +747,28 @@ Example:
       : $opt{'html_bottom'}
 %>
 
-<BR>
+% unless ($opt{'embed'}) {
 
-<INPUT TYPE     = "submit"
-       ID       = "submit"
-       VALUE    = "<% ( !$clone && $object->$pkey() )
-                        ? "Apply changes"
-                        : "Add ". ( $opt{'name'} || $opt{'name_singular'} )
-                   %>"
->
+  <BR>
 
-</FORM>
+  <INPUT TYPE     = "submit"
+         ID       = "submit"
+         VALUE    = "<% ( !$clone && $object->$pkey() )
+                          ? "Apply changes"
+                          : "Add ". ( $opt{'name'} || $opt{'name_singular'} )
+                     %>"
+  >
 
-<% ref( $opt{'html_foot'} )
-      ? &{ $opt{'html_foot'} }( $object )
-      : $opt{'html_foot'}
-%>
+  </FORM>
+
+  <% ref( $opt{'html_foot'} )
+        ? &{ $opt{'html_foot'} }( $object )
+        : $opt{'html_foot'}
+  %>
+
+  <% include("/elements/footer.html") %>
 
-<% include("/elements/footer.html") %>
+% }
 <%init>
 
 my(%opt) = @_;
@@ -756,116 +784,113 @@ my $fields = $opt{'fields'}
              || [ grep { $_ ne $pkey } fields($table) ];
 #my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields;
 
-#$m->comp('/elements/handle_uri_query');
-if ( $cgi->param('redirect') ) {
-  my $session = $cgi->param('redirect');
-  my $pref = $curuser->option("redirect$session");
-  die "unknown redirect session $session\n" unless length($pref);
-  $cgi = new CGI($pref);
-}
-
-&{$opt{'begin_callback'}}( $cgi, $fields, \%opt )
-  if $opt{'begin_callback'};
-
-my %qsearch = (
-    'table'     => $table,
-    'extra_sql' => ( $opt{'agent_virt'}
-                       ? ' AND '. $curuser->agentnums_sql(
-                                    'null_right' => $opt{'agent_null_right'}
-                                  )
-                       : ''
-                   ),
-);
-
-my $mode;
-my $object;
+my( $mode, $object);
 my $clone = '';
-if ( $cgi->param('error') ) {
+if ( $opt{'embed'} ) {
 
-  $mode = 'error';
+  $object = $opt{'embed'};
+  $mode = $cgi->param('error')
+            ? 'error'
+            : $object->$pkey()
+              ? 'edit'
+              : 'new';
 
-  $object = $class->new( {
-    map { $_ => scalar($cgi->param($_)) } fields($table)
-  });
+} else {
 
-  &{$opt{'error_callback'}}( $cgi, $object, $fields, \%opt )
-    if $opt{'error_callback'};
+  #$m->comp('/elements/handle_uri_query');
+  if ( $cgi->param('redirect') ) {
+    my $session = $cgi->param('redirect');
+    my $pref = $curuser->option("redirect$session");
+    die "unknown redirect session $session\n" unless length($pref);
+    $cgi = new CGI($pref);
+  }
 
-} elsif ( $cgi->param('clone') =~ /^(\d+)$/ ) {
+  &{$opt{'begin_callback'}}( $cgi, $fields, \%opt )
+    if $opt{'begin_callback'};
+
+  my %qsearch = (
+      'table'     => $table,
+      'extra_sql' => ( $opt{'agent_virt'}
+                         ? ' AND '. $curuser->agentnums_sql(
+                                      'null_right' => $opt{'agent_null_right'}
+                                    )
+                         : ''
+                     ),
+  );
 
-  $mode = 'clone';
+  if ( $cgi->param('error') ) {
 
-  $clone = $1;
+    $mode = 'error';
 
-  $qsearch{'extra_sql'} = ' AND '. $opt{'agent_clone_extra_sql'}
-    if $opt{'agent_clone_extra_sql'};
+    $object = $class->new( {
+      map { $_ => scalar($cgi->param($_)) } fields($table)
+    });
 
-  $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } })
-    or die "$pkey $clone not found in $table";
+    &{$opt{'error_callback'}}( $cgi, $object, $fields, \%opt )
+      if $opt{'error_callback'};
 
-  &{$opt{'clone_callback'}}( $cgi, $object, $fields, \%opt )
-    if $opt{'clone_callback'};
+  } elsif ( $cgi->param('clone') =~ /^(\d+)$/ ) {
 
-  #$object->$pkey('');
+    $mode = 'clone';
 
-  $opt{action} ||= 'Add';
+    $clone = $1;
 
-} elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing
+    $qsearch{'extra_sql'} = ' AND '. $opt{'agent_clone_extra_sql'}
+      if $opt{'agent_clone_extra_sql'};
 
-  $mode = 'edit';
+    $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } })
+      or die "$pkey $clone not found in $table";
 
-  my $value;
-  if ( $cgi->param($pkey) ) {
-    $value = $cgi->param($pkey)
-  } else { 
-    my( $query ) = $cgi->keywords;
-    $value = $query;
-  }
-  $value =~ /^(\d+)$/ or die "unparsable $pkey";
-  $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $1 } })
-    or die "$pkey $1 not found in $table";
-  
-  warn "$table $pkey => $1"
-    if $opt{'debug'};
+    &{$opt{'clone_callback'}}( $cgi, $object, $fields, \%opt )
+      if $opt{'clone_callback'};
 
-  &{$opt{'edit_callback'}}( $cgi, $object, $fields, \%opt )
-    if $opt{'edit_callback'};
+    #$object->$pkey('');
 
-} else { #adding
+    $opt{action} ||= 'Add';
 
-  $mode = 'new';
+  } elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing
 
-  my $hashref = $opt{'new_hashref_callback'}
-                  ? &{$opt{'new_hashref_callback'}}
-                  : {};
+    $mode = 'edit';
 
-  $object = $opt{'new_object_callback'}
-              ? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt )
-              : $class->new( $hashref );
+    my $value;
+    if ( $cgi->param($pkey) ) {
+      $value = $cgi->param($pkey)
+    } else { 
+      my( $query ) = $cgi->keywords;
+      $value = $query;
+    }
+    $value =~ /^(\d+)$/ or die "unparsable $pkey";
+    $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $1 } })
+      or die "$pkey $1 not found in $table";
 
-  &{$opt{'new_callback'}}( $cgi, $object, $fields, \%opt )
-    if $opt{'new_callback'};
+    warn "$table $pkey => $1"
+      if $opt{'debug'};
 
-}
+    &{$opt{'edit_callback'}}( $cgi, $object, $fields, \%opt )
+      if $opt{'edit_callback'};
 
-&{$opt{'end_callback'}}( $cgi, $object, $fields, \%opt )
-  if $opt{'end_callback'};
+  } else { #adding
 
-$opt{action} ||= $object->$pkey() ? 'Edit' : 'Add';
+    $mode = 'new';
 
-my $title = $opt{action}. ' '. ( $opt{name} || $opt{'name_singular'} );
+    my $hashref = $opt{'new_hashref_callback'}
+                    ? &{$opt{'new_hashref_callback'}}
+                    : {};
 
-my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
-$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};  
+    $object = $opt{'new_object_callback'}
+                ? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt )
+                : $class->new( $hashref );
+
+    &{$opt{'new_callback'}}( $cgi, $object, $fields, \%opt )
+      if $opt{'new_callback'};
+
+  }
+
+  &{$opt{'end_callback'}}( $cgi, $object, $fields, \%opt )
+    if $opt{'end_callback'};
+
+  $opt{action} ||= $object->$pkey() ? 'Edit' : 'Add';
 
-my @menubar = ();
-if ( $opt{'menubar'} ) {
-  @menubar = @{ $opt{'menubar'} };
-} else {
-  my $items = $opt{'name'} ? $opt{'name'}.'s' : PL($opt{'name_singular'});
-  @menubar = (
-    "View all $items" => $viewall_url,
-  );
 }
 
 </%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 994f9b7..44fbb4f 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -302,4 +302,17 @@ if ( $new->custnum eq '' or $duplicate_of ) {
   
 }
 
+unless ( $error ) { #XXX i guess i should be transactional... all in the insert
+                    # or replace call
+  my @contact_fields = qw( classnum first last title comment emailaddress );
+  foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
+    push @contact_fields, 'phonetypenum'.$phone_type->phonetypenum;
+  }
+
+  $error = $new->process_o2m( 'table'  => 'contact',
+                              'fields' => \@contact_fields,
+                              'params' => scalar($cgi->Vars),
+                            );
+}
+
 </%init>

commit 5561349c87fdcc646c18010ea57925f90170f321
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 18:51:23 2012 -0700

    better display/edit of contacts on customer view, RT#16819

diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html
index 155490f..9a03410 100644
--- a/httemplate/view/cust_main/contacts_new.html
+++ b/httemplate/view/cust_main/contacts_new.html
@@ -25,12 +25,6 @@
           <TD BGCOLOR="#FFFFFF"><% $contact_phone->phonenum |h %></TD>
 %       }
 
-%       if ( $contact->comment ) {
-          <TD ALIGN="right">   Comment</TD>
-          <TD BGCOLOR="#FFFFFF"><% $contact->comment |h %></TD>
-
-%       }
-
       </TR>
 %   }
 </TABLE>

commit 194c05911b1bf4f09b0ce523e994d888b2e983ef
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 18:48:44 2012 -0700

    enhance contacts: contact classes, RT#16819

diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index 3efa232..b3e5353 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -4,6 +4,24 @@
 
   <TABLE>
     <TR>
+%     if ( @contact_class ) {
+        <TD>
+          <SELECT NAME="<%$name%>_classnum" <% $onchange %>>
+            <OPTION VALUE="">
+%           my $classnum = scalar($cgi->param($name.'_classnum'))
+%                            || $contact->classnum;
+%           foreach my $contact_class (@contact_class) {
+              <OPTION VALUE="<% $contact_class->classnum %>"
+                 <% ($contact_class->classnum == $classnum) ? 'SELECTED' : '' %>
+              ><% $contact_class->classname |h %>
+%           }
+          <SELECT><BR>
+          <FONT SIZE="-1">Type</FONT>
+        </TD>
+%     } else {
+        <INPUT TYPE="hidden" NAME="<%$name%>_classnum" VALUE="">
+%     }
+%
 %     foreach my $field ( @fields ) {
 %
 %       my $value = '';
@@ -60,6 +78,8 @@ if ( $opt{'onchange'} ) {
   $onchange = 'onChange="'. $onchange. '"';
 }
 
+my @contact_class = qsearch('contact_class', { 'disabled' => '' });
+
 my $contact;
 if ( $curr_value ) {
   $contact = qsearchs('contact', { 'contactnum' => $curr_value } );
@@ -81,7 +101,7 @@ foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
   next if $phone_type->typename eq 'Home';
   my $f = 'phonetypenum'.$phone_type->phonetypenum;
   $label{$f} = $phone_type->typename. ' phone';
-  $size{$f} = $first++ ? 11 : 15;
+  $size{$f} = $first++ ? 10 : 15;
 }
 
 $label{'comment'} = 'Comment';

commit 0318b208bb5edbe05a2969039c22b4203399eaa5
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 18:47:36 2012 -0700

    enhance contacts: contact classes, RT#16819

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 60b0b5f..7143c72 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -303,6 +303,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::discount_plan;
   use FS::tower;
   use FS::tower_sector;
+  use FS::contact_class;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {

commit 0456422b9eaf771e8c27cfb4ef4a0dd7e6f926f8
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 18:47:07 2012 -0700

    better display/edit of contacts on customer view, RT#16819

diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 069d5b6..7d1a156 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -8,6 +8,7 @@ use base qw( FS::cust_main::Packages FS::cust_main::Status
              FS::cust_main::Billing_Discount
              FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin
              FS::geocode_Mixin
+             FS::o2m_Common
              FS::Record
            );
 use vars qw( $DEBUG $me $conf
diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html
new file mode 100644
index 0000000..bae58bd
--- /dev/null
+++ b/httemplate/edit/cust_main-contacts.html
@@ -0,0 +1,100 @@
+<% include('elements/edit.html',
+     'name_singular'   => 'customer contacts', #yes, we're editing all of them
+     'table'           => 'cust_main',
+     'post_url'       => popurl(1). 'process/cust_main-contacts.html',
+     'labels'          => { 'custnum'     => ' ', #XXX supress this line entirely, its being redundant
+                            'contactnum'  => 'Contact',
+                            #'locationnum' => ' ',
+                          },
+     'fields'          => [
+       { 'field'             => 'contactnum',
+         'type'              => 'contact',
+         'colspan'           => 6,
+         'm2m_method'        => 'cust_contact',
+         'm2m_dstcol'        => 'contactnum',   
+         'm2_label'          => 'Contact',
+         'm2_error_callback' => $m2_error_callback,
+       },
+     ],
+     #'new_callback'    => $new_callback,
+     #'edit_callback'   => $edit_callback,
+     #'error_callback'  => $error_callback,
+     'agent_virt'      => 1,
+     'menubar'          => [], #remove "view all" link
+
+     #XXX it would be nice if this could instead be after the error but before
+     # the table
+     'html_init'        => include('/elements/small_custview.html',
+                                     $custnum,
+                                     $conf->config('countrydefault') || 'US',
+                                     1, #no balance
+                                  ),
+   )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $conf = new FS::Conf;
+
+my $custnum;
+if ( $cgi->param('error') ) {
+  $custnum = scalar($cgi->param('custnum'));
+
+  die "access denied"
+    unless $curuser->access_right(($custnum ? 'Edit' : 'New'). ' customer'); #contacts?
+
+} elsif ( $cgi->keywords ) { #editing
+  $custnum = ($cgi->keywords)[0];
+
+  die "access denied"
+    unless $curuser->access_right('Edit customer');
+
+} else { #new customer
+
+  #this doesn't really work here, we're an edit only
+  die "guru meditation #32";
+
+  die "access denied"
+    unless $curuser->access_right('New customer');
+
+}
+
+#my $new_callback = sub {
+#  my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+#};
+
+#my $edit_callback = sub {
+# my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+#};
+
+#my $error_callback = sub {
+#  my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+#};
+
+my $m2_error_callback = sub {
+  my($cgi, $object) = @_;
+
+  #process_o2m fields in process/cust_main-contacts.html
+  my @fields = qw( first last title comment );
+  my @gfields = ( '', map "_$_", @fields );
+
+  map {
+        if ( /^contactnum(\d+)$/ ) {
+          my $num = $1;
+          if ( grep $cgi->param("contactnum$num$_"), @gfields ) {
+            my $x = new FS::contact {
+              'contactnum' => scalar($cgi->param("contactnum$num")),
+              map { $_ => scalar($cgi->param("contactnum${num}_$_")) } @fields,
+            };
+            $x;
+          } else {
+            ();
+          }
+        } else {
+          ();
+        }
+      }
+      $cgi->param;
+};
+
+</%init>
diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html
new file mode 100644
index 0000000..ed874a5
--- /dev/null
+++ b/httemplate/edit/process/cust_main-contacts.html
@@ -0,0 +1,20 @@
+<% include('elements/process.html',
+     'table'          => 'cust_main',
+     'error_redirect' => popurl(3). 'edit/cust_main-contacts.html?',
+     'agent_virt'     => 1,
+     'skip_process'   => 1, #we don't want to make any changes to cust_main
+     'process_o2m' => {
+       'table'  => 'contact',
+       'fields' => \@contact_fields,
+     },
+     'redirect' => popurl(3). 'view/cust_main.cgi?',
+   )
+%>
+<%init>
+
+my @contact_fields = qw( classnum first last title comment emailaddress );
+foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
+  push @contact_fields, 'phonetypenum'.$phone_type->phonetypenum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 636bae0..12b3bd9 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -62,6 +62,10 @@ Example:
                       'fields' => [qw( fieldname fieldname2 )],
                     },
 
+   'skip_process' => 0, #boolean, if set true, will skip the main table
+                        #add/edit processing and only run any linked table
+                        #process_ items
+
    #checks CGI params and whatever else before much else runs
    #return an error string or empty for no error
    'precheck_callback' => sub { my( $cgi ) = @_; },
@@ -204,62 +208,71 @@ my $new;
 my $new_pkey = '';
 foreach my $value ( @values ) {
 
-  $new = $class->new( \%hash );
+  if ($opt{'skip_process'}) {
+
+    $new = $old;
+    $new_pkey = $old_pkey;
+
+  } else {
+
+    $new = $class->new( \%hash );
 
-  $new->$bfield($value) if $bfield;
+    $new->$bfield($value) if $bfield;
 
-  if ($old && exists($opt{'copy_on_empty'})) {
-    foreach my $field (@{$opt{'copy_on_empty'}}) {
-      $new->set($field, $old->get($field))
-        unless scalar($cgi->param($field));
+    if ($old && exists($opt{'copy_on_empty'})) {
+      foreach my $field (@{$opt{'copy_on_empty'}}) {
+        $new->set($field, $old->get($field))
+          unless scalar($cgi->param($field));
+      }
     }
-  }
 
-  if ( $opt{'agent_virt'} ) {
+    if ( $opt{'agent_virt'} ) {
 
-    if ( ! $new->agentnum
-         && (    ! $opt{'agent_null_right'}
-              || ! $curuser->access_right($opt{'agent_null_right'})
-            )
-       )
-    {
+      if ( ! $new->agentnum
+           && (    ! $opt{'agent_null_right'}
+                || ! $curuser->access_right($opt{'agent_null_right'})
+              )
+         )
+      {
 
-      $error ||= 'Select an agent';
+        $error ||= 'Select an agent';
 
-    } else {
+      } else {
 
-      die "illegal agentnum"
-        unless $curuser->agentnums_href->{$new->agentnum}
-            or $curuser->access_right('View customers of all agents')
-            or $opt{'agent_null_right'}
-               && ! $new->agentnum
-               && $curuser->access_right($opt{'agent_null_right'});
+        die "illegal agentnum"
+          unless $curuser->agentnums_href->{$new->agentnum}
+              or $curuser->access_right('View customers of all agents')
+              or $opt{'agent_null_right'}
+                 && ! $new->agentnum
+                 && $curuser->access_right($opt{'agent_null_right'});
 
-    }
+      }
 
-  }
+    }
 
-  $error ||= $new->check;
+    $error ||= $new->check;
 
-  my @args = ();
-  if ( !$error && $opt{'args_callback'} ) {
-    @args = &{ $opt{'args_callback'} }( $cgi, $new );
-  }
+    my @args = ();
+    if ( !$error && $opt{'args_callback'} ) {
+      @args = &{ $opt{'args_callback'} }( $cgi, $new );
+    }
 
-  if ( !$error && $opt{'debug'} ) {
-    warn "$me updating record in $table table using $class class\n";
-    warn Dumper(\%hash);
-    warn "with args: \n". Dumper(\@args) if @args;
-  }
+    if ( !$error && $opt{'debug'} ) {
+      warn "$me updating record in $table table using $class class\n";
+      warn Dumper(\%hash);
+      warn "with args: \n". Dumper(\@args) if @args;
+    }
 
-  if ( !$error ) {
-    if ( $old_pkey ) {
-      $error = $new->replace($old, @args);
-    } else {
-      $error = $new->insert(@args);
+    if ( !$error ) {
+      if ( $old_pkey ) {
+        $error = $new->replace($old, @args);
+      } else {
+        $error = $new->insert(@args);
+      }
+      $new_pkey = $new->getfield($pkey);
     }
-    $new_pkey = $new->getfield($pkey);
-  }
+
+  } #unless $opt{'skip_process'}
 
   if ( !$error && $opt{'process_m2m'} ) {
 
@@ -344,5 +357,4 @@ foreach my $value ( @values ) {
   last if $error;
 
 }
-
 </%init>
diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css
index 79c98cd..44a4a3c 100644
--- a/httemplate/elements/freeside.css
+++ b/httemplate/elements/freeside.css
@@ -115,7 +115,7 @@ a.fstab {
          padding-right:12px;*/
          padding-left:4px;
          padding-right:4px;
-         font-size:16px;
+         font-size:18px;
          font-weight:bold;
          text-decoration:none;
          overflow:visible;
@@ -148,7 +148,7 @@ a.fstabselected {
          padding-right:12px;*/
          padding-left:4px;
          padding-right:4px;
-         font-size:16px;
+         font-size:18px;
          font-weight:bold;
          text-decoration:none;
          overflow:visible;
@@ -220,6 +220,32 @@ div.fstabcontainer {
   font-weight:bold;
 }
 
+.fsinnerbox {
+  background-color:#cccccc;
+  padding:2px;
+  -moz-box-shadow:  1px 1px 2px #666666;
+  -webkit-box-shadow:  1px 1px 2px #666666;
+  box-shadow: 1px 1px 2px #666666;
+  filter: progid:DXImageTransform.Microsoft.Shadow(color='#666666', Direction=135, Strength=2);
+}
+
+.fsinnerbox-title {
+  font-size:110%;
+  font-weight:bold;
+  background-color:#cccccc;
+  padding:2px;
+         -moz-border-radius-topleft:8px;
+         -moz-border-radius-topright:8px;
+         -webkit-border-radius-topleft:8px;
+         -webkit-border-radius-topright:8px;
+         border-radius-topleft:8px;
+         border-radius-topright:8px;
+  -moz-box-shadow:  1px 0px 1px #999999;
+  -webkit-box-shadow:  1px 0px 1px #999999;
+  box-shadow: 1px 0px 1px #999999;
+  filter: progid:DXImageTransform.Microsoft.Shadow(color='#999999', Direction=90, Strength=1);
+}
+
 .background {
   background-color:#f8f8f8;
 }
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index dcadf99..fda4db0 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -150,6 +150,11 @@ function areyousure(href, message) {
 
   </TD>
 </TR>
+<TR>
+  <TD COLSPAN = 2>
+    <& cust_main/contacts_new.html, $cust_main &>
+  </TD>
+</TR>
 </TABLE>
 
 % }
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index f1add6f..522c6db 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -1,4 +1,4 @@
-<% mt('Billing information') |h %> 
+<FONT CLASS="fsinnerbox-title"><% mt('Billing information') |h %></FONT>
 %# If we can't see the unencrypted card, then bill now is an exercise in
 %# frustration (without some sort of job queue magic to send it to a secure
 %# machine, anyway)
@@ -6,14 +6,14 @@
 %      && ! $cust_main->is_encrypted($cust_main->payinfo)
 %   ) { 
 %#  (<A HREF="<% $p %>misc/bill.cgi?<% $cust_main->custnum %>"><% mt('Bill now') |h %></A>)
-  (<& /elements/bill.html,
-                custnum   => $cust_main->custnum,
-                label     => emt('Bill now'),
-                url       => $p.'view/cust_main.cgi?'.$cust_main->custnum,
-   &>)
+  <& /elements/bill.html,
+       custnum   => $cust_main->custnum,
+       label     => emt('Bill now'),
+       url       => $p.'view/cust_main.cgi?'.$cust_main->custnum,
+  &>
 % } 
 
-<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+<TABLE CLASS="fsinnerbox">
 
 %( my $balance = $cust_main->balance )
 %  =~ s/^(\-?)(.*)$/<FONT SIZE=+1>$1<\/FONT>$money_char$2/;
@@ -285,7 +285,7 @@
 % }
 
 
-</TABLE></TD></TR></TABLE>
+</TABLE>
 <%once>
 
 my $paystate_label = FS::Msgcat::_gettext('paystate');
diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html
index 68e3b17..b3e52b5 100644
--- a/httemplate/view/cust_main/contacts.html
+++ b/httemplate/view/cust_main/contacts.html
@@ -5,8 +5,8 @@
 % foreach my $which ( '', 'ship_' ) {
 %   my $pre = $cust_main->get("${which}last") ? $which : '';
 
-<% $which{$which} %> <% mt('address') |h %>
-<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+<FONT CLASS="fsinnerbox-title"><% $which{$which} %> <% mt('address') |h %></FONT>
+<TABLE CLASS="fsinnerbox">
 <TR>
   <TD ALIGN="right"><% mt('Contact name') |h %></TD>
   <TD COLSPAN=5 BGCOLOR="#ffffff">
@@ -17,18 +17,23 @@
     <TD BGCOLOR="#ffffff"><% $cust_main->masked('ss') || '&nbsp' %></TD>
 % } 
 </TR>
+
 % if ( $conf->exists('cust-email-high-visibility') && $which eq '') {
-<TR>
-  <TD ALIGN="right"><% mt('Email invoices') |h %></TD>
-  <TD BGCOLOR="#ffff00">
-    <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
-  </TD>
-</TR>
+  <TR>
+    <TD ALIGN="right"><% mt('Email invoices') |h %></TD>
+    <TD BGCOLOR="#ffff00">
+      <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
+    </TD>
+  </TR>
 % }
-<TR>
-  <TD ALIGN="right"><% mt('Company') |h %></TD>
-  <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") |h %></TD>
-</TR>
+
+% if ( $cust_main->get("${pre}company") ) {
+  <TR>
+    <TD ALIGN="right"><% mt('Company') |h %></TD>
+    <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") |h %></TD>
+  </TR>
+% }
+
 <TR>
   <TD ALIGN="right"><% mt('Address') |h %></TD>
   <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") |h %></TD>
@@ -74,42 +79,30 @@
   &>
 % }
 
-<TR>
-  <TD ALIGN="right"><% $daytime_label %></TD>
-  <TD COLSPAN=3 BGCOLOR="#ffffff">
-    <& /elements/phonenumber.html,
-                  $cust_main->get("${pre}daytime"),
-                  'callable'=>1,
-                  'calling_list_exempt'=>$cust_main->calling_list_exempt,
-    &>
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% $night_label %></TD>
-  <TD COLSPAN=3 BGCOLOR="#ffffff">
-    <& /elements/phonenumber.html,
-                  $cust_main->get("${pre}night"),
-                  'callable'=>1,
-                  'calling_list_exempt'=>$cust_main->calling_list_exempt,
-    &>
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% $mobile_label %></TD>
-  <TD COLSPAN=3 BGCOLOR="#ffffff">
-    <& /elements/phonenumber.html,
-                  $cust_main->get("${pre}mobile"),
-                  'callable'=>1,
-                  'calling_list_exempt'=>$cust_main->calling_list_exempt,
-    &>
-  </TD>
-</TR>
-<TR>
-  <TD ALIGN="right"><% mt('Fax') |h %></TD>
-  <TD COLSPAN=3 BGCOLOR="#ffffff">
-    <% $cust_main->get("${pre}fax") || '&nbsp' %>
-  </TD>
-</TR>
+% foreach my $phone (grep $cust_main->get($pre.$_), qw( daytime night mobile )){
+
+  <TR>
+    <TD ALIGN="right"><% $phone_label{$phone} %></TD>
+    <TD COLSPAN=3 BGCOLOR="#ffffff">
+      <& /elements/phonenumber.html,
+                    $cust_main->get($pre.$phone),
+                    'callable'=>1,
+                    'calling_list_exempt'=>$cust_main->calling_list_exempt,
+      &>
+    </TD>
+  </TR>
+
+% }
+
+% if ( $cust_main->get("${pre}fax") ) {
+  <TR>
+    <TD ALIGN="right"><% mt('Fax') |h %></TD>
+    <TD COLSPAN=3 BGCOLOR="#ffffff">
+      <% $cust_main->get("${pre}fax") || '&nbsp' %>
+    </TD>
+  </TR>
+% }
+
 % if ( $which eq '' && $conf->exists('show_stateid') ) { 
   <TR>
     <TD ALIGN="right"><% $stateid_label %></TD>
@@ -118,23 +111,31 @@
     <TD BGCOLOR="#ffffff"><% $cust_main->stateid_state || '&nbsp' %></TD>
   </TR>
 % } 
-</TABLE></TD></TR></TABLE>
+
+</TABLE>
 % if ( $which ne 'ship_' ) {
 <BR>
 % }
 % } 
-<& contacts_new.html, $cust_main &>
 <%once>
 
-my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
-                      ? 'Day Phone'
-                      : FS::Msgcat::_gettext('daytime');
-my $night_label   = FS::Msgcat::_gettext('night') =~ /^(night)?$/
-                      ? 'Night Phone'
-                      : FS::Msgcat::_gettext('night');
-my $mobile_label = FS::Msgcat::_gettext('mobile') =~ /^(mobile)?$/
-                      ? 'Mobile Phone'
-                      : FS::Msgcat::_gettext('Mobile');
+my %phone_label = (
+
+  'daytime' => ( FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+                   ? 'Day Phone'
+                   : FS::Msgcat::_gettext('daytime')
+               ),
+
+  'night'   => ( FS::Msgcat::_gettext('night') =~ /^(night)?$/
+                   ? 'Night Phone'
+                   : FS::Msgcat::_gettext('night')
+               ),
+
+  'mobile'  => ( FS::Msgcat::_gettext('mobile') =~ /^(mobile)?$/
+                   ? 'Mobile Phone'
+                   : FS::Msgcat::_gettext('Mobile')
+               ),
+);
 
 my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
                       ? 'Driver’s License'
diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html
index bd812c7..155490f 100644
--- a/httemplate/view/cust_main/contacts_new.html
+++ b/httemplate/view/cust_main/contacts_new.html
@@ -1,17 +1,44 @@
-% if ( @contacts ) {
 <BR>
-Contacts
-<% ntable("#cccccc",2) %>
+<FONT CLASS="fsinnerbox-title">Contacts</FONT>
+<A HREF="<%$p%>/edit/cust_main-contacts.html?<% $cust_main->custnum %>">Edit contacts</A>
+<TABLE CLASS="fsinnerbox">
 %   foreach my $contact ( @contacts ) {
+%     #XXX maybe this should be a table with alternating colors instead
       <TR>
-        <TD ALIGN="right">Contact</TD>
+        <TD ALIGN="right"><% $contact->contact_classname %> Contact</TD>
         <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD>
+
+%       my @contact_email = $contact->contact_email;
+%       if (@contact_email) {
+          <TD ALIGN="right">   Email</TD>
+          <TD BGCOLOR="#FFFFFF"><% join(', ', map $_->emailaddress, @contact_email) %></TD>
+%       }
+
+%       foreach my $phone_type (@phone_type) {
+%         my $contact_phone =
+%           qsearchs('contact_phone', {
+%                      'contactnum'   => $contact->contactnum,
+%                      'phonetypenum' => $phone_type->phonetypenum,
+%                   })
+%           or next;
+          <TD ALIGN="right">   <% $phone_type->typename %> phone</TD>
+          <TD BGCOLOR="#FFFFFF"><% $contact_phone->phonenum |h %></TD>
+%       }
+
+%       if ( $contact->comment ) {
+          <TD ALIGN="right">   Comment</TD>
+          <TD BGCOLOR="#FFFFFF"><% $contact->comment |h %></TD>
+
+%       }
+
       </TR>
 %   }
 </TABLE>
+<%once>
 
-% }
+my @phone_type = qsearch({table=>'phone_type', order_by=>'weight'});
 
+</%once>
 <%init>
 
 my( $cust_main ) = @_;
diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html
index 28414ef..2953287 100644
--- a/httemplate/view/cust_main/misc.html
+++ b/httemplate/view/cust_main/misc.html
@@ -1,4 +1,4 @@
-<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+<TABLE CLASS="fsinnerbox">
 
 <TR>
   <TD ALIGN="right"><% mt('Customer number') |h %></TD>
@@ -150,7 +150,7 @@
     <% $cust_main->pvf($_)->widget('HTML', 'view', $cust_main->getfield($_)) %>
 % }
 
-</TABLE></TD></TR></TABLE>
+</TABLE>
 <%init>
 
 my( $cust_main ) = @_;

commit ed9e6fe7b0f2979ef94f6c399853533ffab8e0d2
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 16:54:23 2012 -0700

    basics customer view is default in 3.0+

diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index b263f07..5d5cc12 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -498,7 +498,7 @@ sub access_right {
 
 Returns the default customer view for this user, from the 
 "default_customer_view" user preference, the "cust_main-default_view" config,
-or the hardcoded default, "jumbo" (may change to "basics" in the near future).
+or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
 
 =cut
 

commit 213ad23ac1b8d9449e29522661a428581fcb1ce2
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Mar 24 16:53:24 2012 -0700

    basics customer view is default in 3.0+

diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index 075733a..b263f07 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -507,7 +507,7 @@ sub default_customer_view {
 
   $self->option('default_customer_view')
     || $conf->config('cust_main-default_view')
-    || 'jumbo'; #'basics' in 1.9.1?
+    || 'basics'; #s/jumbo/basics/ starting with 3.0
 
 }
 

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Conf.pm                  |    7 ++
 FS/FS/Schema.pm                |    2 +
 FS/FS/part_pkg.pm              |  135 +++++++++++++++++++++++++++++++++++++++-
 httemplate/browse/part_pkg.cgi |   21 ++++++
 httemplate/edit/part_pkg.cgi   |   28 ++++++++-
 5 files changed, 188 insertions(+), 5 deletions(-)




More information about the freeside-commits mailing list