customer classification, RT#6376
authorivan <ivan>
Thu, 29 Oct 2009 01:08:33 +0000 (01:08 +0000)
committerivan <ivan>
Thu, 29 Oct 2009 01:08:33 +0000 (01:08 +0000)
26 files changed:
FS/FS.pm
FS/FS/Schema.pm
FS/FS/category_Common.pm [new file with mode: 0644]
FS/FS/class_Common.pm [new file with mode: 0644]
FS/FS/cust_category.pm [new file with mode: 0644]
FS/FS/cust_class.pm [new file with mode: 0644]
FS/FS/cust_main.pm
FS/FS/pkg_category.pm
FS/FS/pkg_class.pm
FS/MANIFEST
FS/t/cust_category.t [new file with mode: 0644]
FS/t/cust_class.t [new file with mode: 0644]
httemplate/browse/cust_category.html [new file with mode: 0644]
httemplate/browse/cust_class.html [new file with mode: 0644]
httemplate/browse/part_pkg_report_option.html
httemplate/browse/pkg_category.html
httemplate/browse/pkg_class.html
httemplate/edit/cust_category.html [new file with mode: 0644]
httemplate/edit/cust_class.html [new file with mode: 0644]
httemplate/edit/elements/category_Common.html [new file with mode: 0644]
httemplate/edit/elements/class_Common.html [new file with mode: 0644]
httemplate/edit/pkg_category.html
httemplate/edit/pkg_class.html
httemplate/edit/process/cust_category.html [new file with mode: 0644]
httemplate/edit/process/cust_class.html [new file with mode: 0644]
httemplate/elements/menu.html

index 7ce9741..9ec2602 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -73,6 +73,10 @@ L<FS::m2name_Common> - Base class for tables with a related table listing names
 
 L<FS::option_Common> - Base class for option sub-classes
 
 
 L<FS::option_Common> - Base class for option sub-classes
 
+L<FS::class_Common> - Base class for classification classes
+
+L<FS::category_Common> - Base class for category (grooups of classifications) classes
+
 L<FS::conf> - Configuration value class
 
 L<FS::payinfo_Mixin>  - Mixin class for records in tables that contain payinfo.
 L<FS::conf> - Configuration value class
 
 L<FS::payinfo_Mixin>  - Mixin class for records in tables that contain payinfo.
@@ -234,6 +238,10 @@ L<FS::cust_main_Mixin> - Mixin class for records that contain fields from cust_m
 
 L<FS::cust_main_invoice> - Invoice destination class
 
 
 L<FS::cust_main_invoice> - Invoice destination class
 
+L<FS::cust_class> - Customer classification class
+
+L<FS::cust_category> - Customer category class
+
 L<FS::cust_main_exemption> - Customer tax exemption class
 
 L<FS::cust_main_note> - Customer note class
 L<FS::cust_main_exemption> - Customer tax exemption class
 
 L<FS::cust_main_note> - Customer note class
index 2b0ea90..faafcc6 100644 (file)
@@ -685,6 +685,7 @@ sub tables_hashref {
         'custnum',  'serial',  '',     '', '', '', 
         'agentnum', 'int',  '',     '', '', '', 
         'agent_custid', 'varchar', 'NULL', $char_d, '', '',
         'custnum',  'serial',  '',     '', '', '', 
         'agentnum', 'int',  '',     '', '', '', 
         'agent_custid', 'varchar', 'NULL', $char_d, '', '',
+        'classnum', 'int', 'NULL', '', '', '',
         'custbatch', 'varchar', 'NULL', $char_d, '', '',
 #        'titlenum', 'int',  'NULL',   '', '', '', 
         'last',     'varchar', '',     $char_d, '', '', 
         'custbatch', 'varchar', 'NULL', $char_d, '', '',
 #        'titlenum', 'int',  'NULL',   '', '', '', 
         'last',     'varchar', '',     $char_d, '', '', 
@@ -752,7 +753,8 @@ sub tables_hashref {
       'unique' => [ [ 'agentnum', 'agent_custid' ] ],
       #'index' => [ ['last'], ['company'] ],
       'index' => [
       'unique' => [ [ 'agentnum', 'agent_custid' ] ],
       #'index' => [ ['last'], ['company'] ],
       'index' => [
-                   [ 'agentnum' ], [ 'refnum' ], [ 'custbatch' ],
+                   [ 'agentnum' ], [ 'refnum' ], [ 'classnum' ],
+                   [ 'custbatch' ],
                    [ 'referral_custnum' ],
                    [ 'payby' ], [ 'paydate' ],
                    [ 'archived' ],
                    [ 'referral_custnum' ],
                    [ 'payby' ], [ 'paydate' ],
                    [ 'archived' ],
@@ -841,6 +843,30 @@ sub tables_hashref {
       'index' => [ [ 'custnum' ], [ '_date' ], ],
     },
 
       'index' => [ [ 'custnum' ], [ '_date' ], ],
     },
 
+    'cust_category' => {
+      'columns' => [
+        'categorynum',   'serial',  '', '', '', '', 
+        'categoryname',  'varchar', '', $char_d, '', '', 
+        'weight',         'int', 'NULL',  '', '', '',
+        'disabled',      'char', 'NULL',   1, '', '', 
+      ],
+      'primary_key' => 'categorynum',
+      'unique' => [],
+      'index' => [ ['disabled'] ],
+    },
+
+    'cust_class' => {
+      'columns' => [
+        'classnum',    'serial',   '',      '', '', '', 
+        'classname',   'varchar',  '', $char_d, '', '', 
+        'categorynum', 'int',  'NULL',      '', '', '', 
+        'disabled',    'char', 'NULL',       1, '', '', 
+      ],
+      'primary_key' => 'classnum',
+      'unique' => [],
+      'index' => [ ['disabled'] ],
+    },
+
     'cust_main_exemption' => {
       'columns' => [
         'exemptionnum', 'serial', '',      '', '', '',
     'cust_main_exemption' => {
       'columns' => [
         'exemptionnum', 'serial', '',      '', '', '',
diff --git a/FS/FS/category_Common.pm b/FS/FS/category_Common.pm
new file mode 100644 (file)
index 0000000..c239a78
--- /dev/null
@@ -0,0 +1,87 @@
+package FS::category_Common;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch );
+
+=head1 NAME
+
+FS::category_Common - Base class for category (group of classifications) classes
+
+=head1 SYNOPSIS
+
+use base qw( FS::category_Common );
+use FS::class_table; #should use this
+
+#optional for non-standard names
+sub _class_table { 'table_name'; } #default is to replace s/category/class/
+
+=head1 DESCRIPTION
+
+FS::category_Common is a base class for classes which provide a categorization
+(group of classifications) for other classes, such as pkg_category or
+cust_category.
+
+=item delete
+
+Deletes this category from the database.  Only categories with no associated
+classifications can be deleted.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  return "Can't delete a ". $self->table.
+         " with ". $self->_class_table. " records!"
+    if qsearch( $self->_class_table, { 'categorynum' => $self->categorynum } );
+
+  $self->SUPER::delete;
+}
+
+=item check
+
+Checks all fields to make sure this is a valid package category.  If there is an
+error, returns the error, otherwise returns false.  Called by the insert and
+replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  $self->ut_numbern('categorynum')
+    or $self->ut_text('categoryname')
+    or $self->ut_snumbern('weight')
+    or $self->ut_enum('disabled', [ '', 'Y' ])
+    or $self->SUPER::check;
+
+}
+
+=back
+
+=cut
+
+#defaults
+
+use vars qw( $_class_table );
+sub _class_table {
+  return $_class_table if $_class_table;
+  my $self = shift;
+  $_class_table = $self->table;
+  $_class_table =~ s/category/cclass/ # s/_category$/_class/
+    or die "can't determine an automatic class table for $_class_table";
+  $_class_table;
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/class_Common.pm b/FS/FS/class_Common.pm
new file mode 100644 (file)
index 0000000..5ee8208
--- /dev/null
@@ -0,0 +1,143 @@
+package FS::class_Common;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::class_Common - Base class for classification classes
+
+=head1 SYNOPSIS
+
+use base qw( FS::class_Common );
+use FS::category_table; #should use this
+
+#required
+sub _target_table { 'table_name'; }
+
+#optional for non-standard names
+sub _target_column { 'classnum'; } #default is classnum
+sub _category_table { 'table_name'; } #default is to replace s/class/category/
+
+=head1 DESCRIPTION
+
+FS::class_Common is a base class for classes which provide a classification for
+other classes, such as pkg_class or cust_class.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new classification.  To add the classfication to the database, see
+L<"insert">.
+
+=cut
+
+=item insert
+
+Adds this classification to the database.  If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Deletes this classification from the database.  Only classifications with no
+associated target objects can be deleted.  If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  return "Can't delete a ". $self->table.
+         " with ". $self->_target_table. " records!"
+    if qsearch( $self->_target_table,
+                { $self->_target_column => $self->classnum }
+              );
+
+  $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid package classification.  If
+there is an error, returns the error, otherwise returns false.  Called by the
+insert and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  $self->ut_numbern('classnum')
+    or $self->ut_text('classname')
+    or $self->ut_foreign_keyn( 'categorynum',
+                               $self->_category_table,
+                               'categorynum',
+                             )
+    or $self->ut_enum('disabled', [ '', 'Y' ] )
+    or $self->SUPER::check;
+
+}
+
+=item category
+
+Returns the category record associated with this class, or false if there is
+none.
+
+=cut
+
+sub category {
+  my $self = shift;
+  qsearchs($self->_category_table, { 'categorynum' => $self->categorynum } );
+}
+
+=item categoryname
+
+Returns the category name associated with this class, or false if there
+is none.
+
+=cut
+
+sub categoryname {
+  my $category = shift->category;
+  $category ? $category->categoryname : '';
+}
+
+#required
+sub _target_table {
+  my $self = shift;
+  die "_target_table unspecified for $self";
+}
+
+#defaults
+
+sub _target_column { 'classnum'; }
+
+use vars qw( $_category_table );
+sub _category_table {
+  return $_category_table if $_category_table;
+  my $self = shift;
+  $_category_table = $self->table;
+  $_category_table =~ s/class/category/ # s/_class$/_category/
+    or die "can't determine an automatic category table for $_category_table";
+  $_category_table;
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::category_Common>, L<FS::pkg_class>, L<FS::cust_class>
+
+=cut
+
+1;
diff --git a/FS/FS/cust_category.pm b/FS/FS/cust_category.pm
new file mode 100644 (file)
index 0000000..636b1d3
--- /dev/null
@@ -0,0 +1,97 @@
+package FS::cust_category;
+
+use strict;
+use base qw( FS::category_Common );
+use FS::cust_class;
+
+=head1 NAME
+
+FS::cust_category - Object methods for cust_category records
+
+=head1 SYNOPSIS
+
+  use FS::cust_category;
+
+  $record = new FS::cust_category \%hash;
+  $record = new FS::cust_category { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_category object represents a customer category.  Every customer
+class (see L<FS::cust_class>) has, optionally, a customer category.
+FS::cust_category inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item categorynum
+
+primary key
+
+=item categoryname
+
+Text name of this package category
+
+=item weight
+
+Weight
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer category.  To add the customer category to the database,
+see L<"insert">.
+
+=cut
+
+sub table { 'cust_category'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_class>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_class.pm b/FS/FS/cust_class.pm
new file mode 100644 (file)
index 0000000..a811be7
--- /dev/null
@@ -0,0 +1,120 @@
+package FS::cust_class;
+
+use strict;
+use base qw( FS::class_Common );
+use FS::cust_main;
+use FS::cust_category;
+
+=head1 NAME
+
+FS::cust_class - Object methods for cust_class records
+
+=head1 SYNOPSIS
+
+  use FS::cust_class;
+
+  $record = new FS::cust_class \%hash;
+  $record = new FS::cust_class { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pkg_class object represents an customer class.  Every customer (see
+L<FS::cust_main>) has, optionally, a customer class. FS::cust_class inherits
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+primary key
+
+=item classname
+
+Text name of this customer class
+
+=item categorynum
+
+Number of associated cust_category (see L<FS::cust_category>)
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer class.  To add the customer class to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'cust_class'; }
+sub _target_table { 'cust_main'; }
+
+=item insert
+
+Adds this customer class to the database.  If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Delete this customer class from the database.  Only customer classes with no
+associated customers can be deleted.  If there is an error, returns
+the error, otherwise returns false.
+
+=item replace [ OLD_RECORD ]
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid customer class.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=item cust_category
+
+=item category
+
+Returns the cust_category record associated with this class, or false if there
+is none.
+
+=cut
+
+sub cust_category {
+  my $self = shift;
+  $self->category;
+}
+
+=item categoryname
+
+Returns the category name associated with this class, or false if there
+is none.
+
+=cut
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::Record>
+
+=cut
+
+1;
index 700e15a..2c2984f 100644 (file)
@@ -45,6 +45,7 @@ use FS::cust_refund;
 use FS::part_referral;
 use FS::cust_main_county;
 use FS::cust_location;
 use FS::part_referral;
 use FS::cust_main_county;
 use FS::cust_location;
+use FS::cust_class;
 use FS::cust_main_exemption;
 use FS::cust_tax_adjustment;
 use FS::tax_rate;
 use FS::cust_main_exemption;
 use FS::cust_tax_adjustment;
 use FS::tax_rate;
@@ -1537,6 +1538,7 @@ sub check {
     || $self->ut_number('agentnum')
     || $self->ut_textn('agent_custid')
     || $self->ut_number('refnum')
     || $self->ut_number('agentnum')
     || $self->ut_textn('agent_custid')
     || $self->ut_number('refnum')
+    || $self->ut_foreign_keyn('classnum', 'cust_class', 'classnum')
     || $self->ut_textn('custbatch')
     || $self->ut_name('last')
     || $self->ut_name('first')
     || $self->ut_textn('custbatch')
     || $self->ut_name('last')
     || $self->ut_name('first')
index 0beaf1c..1502971 100644 (file)
@@ -1,11 +1,12 @@
 package FS::pkg_category;
 
 use strict;
 package FS::pkg_category;
 
 use strict;
+use base qw( FS::category_Common );
 use vars qw( @ISA $me $DEBUG );
 use FS::Record qw( qsearch dbh );
 use vars qw( @ISA $me $DEBUG );
 use FS::Record qw( qsearch dbh );
+use FS::pkg_class;
 use FS::part_pkg;
 
 use FS::part_pkg;
 
-@ISA = qw( FS::Record );
 $DEBUG = 0;
 $me = '[FS::pkg_category]';
 
 $DEBUG = 0;
 $me = '[FS::pkg_category]';
 
@@ -36,11 +37,21 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =over 4
 
 
 =over 4
 
-=item categorynum - primary key (assigned automatically for new package categoryes)
+=item categorynum
 
 
-=item categoryname - Text name of this package category
+primary key (assigned automatically for new package categoryes)
 
 
-=item disabled - Disabled flag, empty or 'Y'
+=item categoryname
+
+Text name of this package category
+
+=item weight
+
+Weight
+
+=item disabled
+
+Disabled flag, empty or 'Y'
 
 =back
 
 
 =back
 
@@ -50,8 +61,8 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =item new HASHREF
 
 
 =item new HASHREF
 
-Creates a new package category.  To add the package category to the database, see
-L<"insert">.
+Creates a new package category.  To add the package category to the database,
+see L<"insert">.
 
 =cut
 
 
 =cut
 
@@ -64,22 +75,11 @@ error, otherwise returns false.
 
 =item delete
 
 
 =item delete
 
-Deletes this package category from the database.  Only package categoryes with no
-associated package definitions can be deleted.  If there is an error, returns
-the error, otherwise returns false.
-
-=cut
-
-sub delete {
-  my $self = shift;
-
-  return "Can't delete an pkg_category with pkg_class records!"
-    if qsearch( 'pkg_class', { 'categorynum' => $self->categorynum } );
-
-  $self->SUPER::delete;
-}
+Deletes this package category from the database.  Only package categoryes with
+no associated package definitions can be deleted.  If there is an error,
+returns the error, otherwise returns false.
 
 
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ]
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
@@ -90,18 +90,6 @@ Checks all fields to make sure this is a valid package category.  If there is an
 error, returns the error, otherwise returns false.  Called by the insert and
 replace methods.
 
 error, returns the error, otherwise returns false.  Called by the insert and
 replace methods.
 
-=cut
-
-sub check {
-  my $self = shift;
-
-  $self->ut_numbern('categorynum')
-  or $self->ut_text('categoryname')
-  or $self->ut_snumber('weight')
-  or $self->SUPER::check;
-
-}
-
 # _ upgrade_data
 #
 # Used by FS::Upgrade to migrate to a new database.
 # _ upgrade_data
 #
 # Used by FS::Upgrade to migrate to a new database.
@@ -136,7 +124,7 @@ sub _upgrade_data {
 
 =head1 SEE ALSO
 
 
 =head1 SEE ALSO
 
-L<FS::Record>, L<FS::part_pkg>, schema.html from the base documentation.
+L<FS::category_Common>, L<FS::Record>
 
 =cut
 
 
 =cut
 
index 254282f..51d0455 100644 (file)
@@ -1,13 +1,11 @@
 package FS::pkg_class;
 
 use strict;
 package FS::pkg_class;
 
 use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs qsearch );
+use FS::class_Common;
+use base qw( FS::class_Common );
 use FS::part_pkg;
 use FS::pkg_category;
 
 use FS::part_pkg;
 use FS::pkg_category;
 
-@ISA = qw( FS::Record );
-
 =head1 NAME
 
 FS::pkg_class - Object methods for pkg_class records
 =head1 NAME
 
 FS::pkg_class - Object methods for pkg_class records
@@ -35,13 +33,21 @@ from FS::Record.  The following fields are currently supported:
 
 =over 4
 
 
 =over 4
 
-=item classnum - primary key (assigned automatically for new package classes)
+=item classnum
+
+primary key (assigned automatically for new package classes)
+
+=item classname
+
+Text name of this package class
 
 
-=item classname - Text name of this package class
+=item categorynum
 
 
-=item categorynum - Number of associated pkg_category (see L<FS::pkg_category>)
+Number of associated pkg_category (see L<FS::pkg_category>)
 
 
-=item disabled - Disabled flag, empty or 'Y'
+=item disabled
+
+Disabled flag, empty or 'Y'
 
 =back
 
 
 =back
 
@@ -57,6 +63,7 @@ L<"insert">.
 =cut
 
 sub table { 'pkg_class'; }
 =cut
 
 sub table { 'pkg_class'; }
+sub _target_table { 'part_pkg'; }
 
 =item insert
 
 
 =item insert
 
@@ -69,18 +76,7 @@ Deletes this package class from the database.  Only package classes with no
 associated package definitions can be deleted.  If there is an error, returns
 the error, otherwise returns false.
 
 associated package definitions can be deleted.  If there is an error, returns
 the error, otherwise returns false.
 
-=cut
-
-sub delete {
-  my $self = shift;
-
-  return "Can't delete an pkg_class with part_pkg records!"
-    if qsearch( 'part_pkg', { 'classnum' => $self->classnum } );
-
-  $self->SUPER::delete;
-}
-
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ]
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
@@ -91,20 +87,10 @@ Checks all fields to make sure this is a valid package class.  If there is an
 error, returns the error, otherwise returns false.  Called by the insert and
 replace methods.
 
 error, returns the error, otherwise returns false.  Called by the insert and
 replace methods.
 
-=cut
-
-sub check {
-  my $self = shift;
-
-  $self->ut_numbern('classnum')
-  or $self->ut_text('classname')
-  or $self->ut_foreign_keyn('categorynum', 'pkg_category', 'categorynum')
-  or $self->SUPER::check;
-
-}
-
 =item pkg_category
 
 =item pkg_category
 
+=item category
+
 Returns the pkg_category record associated with this class, or false if there
 is none.
 
 Returns the pkg_category record associated with this class, or false if there
 is none.
 
@@ -112,7 +98,7 @@ is none.
 
 sub pkg_category {
   my $self = shift;
 
 sub pkg_category {
   my $self = shift;
-  qsearchs('pkg_category', { 'categorynum' => $self->categorynum } );
+  $self->category;
 }
 
 =item categoryname
 }
 
 =item categoryname
@@ -120,22 +106,14 @@ sub pkg_category {
 Returns the category name associated with this class, or false if there
 is none.
 
 Returns the category name associated with this class, or false if there
 is none.
 
-=cut
-
-sub categoryname {
-  my $pkg_category = shift->pkg_category;
-  $pkg_category->categoryname if $pkg_category;
-}
-
 =back
 
 =head1 BUGS
 
 =head1 SEE ALSO
 
 =back
 
 =head1 BUGS
 
 =head1 SEE ALSO
 
-L<FS::Record>, L<FS::part_pkg>, schema.html from the base documentation.
+L<FS::part_pkg>, L<FS::Record>
 
 =cut
 
 1;
 
 =cut
 
 1;
-
index f5511f0..d4e80e6 100644 (file)
@@ -453,3 +453,7 @@ FS/cust_attachment.pm
 t/cust_attachment.t
 FS/cust_statement.pm
 t/cust_statement.t
 t/cust_attachment.t
 FS/cust_statement.pm
 t/cust_statement.t
+FS/cust_class.pm
+t/cust_class.t
+FS/cust_category.pm
+t/cust_category.t
diff --git a/FS/t/cust_category.t b/FS/t/cust_category.t
new file mode 100644 (file)
index 0000000..8cb0cd0
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_category;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_class.t b/FS/t/cust_class.t
new file mode 100644 (file)
index 0000000..ef7e82f
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/cust_category.html b/httemplate/browse/cust_category.html
new file mode 100644 (file)
index 0000000..4468d2f
--- /dev/null
@@ -0,0 +1,32 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Customer categories',
+                 'html_init'   => $html_init,
+                 'name'        => 'customer categories',
+                 'disableable' => 1,
+                 'disabled_statuspos' => 2,
+                 'query'       => { 'table'     => 'cust_category',
+                                    'hashref'   => {},
+                                    'extra_sql' => 'ORDER BY categorynum',
+                                  },
+                 'count_query' => $count_query,
+                 'header'      => [ '#', 'Category' ],
+                 'fields'      => [ 'categorynum', 'categoryname' ],
+                 'links'       => [ $link, $link ],
+             )
+%>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init = 
+  qq!<A HREF="${p}browse/cust_class.html">Customer classes</A><BR><BR>!.
+  'Customer categories define groups of customer classes.<BR><BR>'.
+  qq!<A HREF="${p}edit/cust_category.html"><I>Add a customer category</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM cust_category';
+
+my $link = [ $p.'edit/cust_category.html?', 'categorynum' ];
+
+</%init>
diff --git a/httemplate/browse/cust_class.html b/httemplate/browse/cust_class.html
new file mode 100644 (file)
index 0000000..da303cf
--- /dev/null
@@ -0,0 +1,45 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Customer classes',
+                 'html_init'   => $html_init,
+                 'name'        => 'customer classes',
+                 'disableable' => 1,
+                 'disabled_statuspos' => 2,
+                 'query'       => { 'table'     => 'cust_class',
+                                    'hashref'   => {},
+                                    'extra_sql' => 'ORDER BY classnum',
+                                  },
+                 'count_query' => $count_query,
+                 'header'      => $header,
+                 'fields'      => $fields,
+                 'links'       => $links,
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init = 
+  'Customer classes define groups of customer for reporting.<BR><BR>'.
+  qq!<A HREF="${p}edit/cust_class.html"><I>Add a customer class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM cust_class';
+
+my $link = [ $p.'edit/cust_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links  = [ $link, $link ];
+
+my $cat_query = 'SELECT COUNT(*) FROM cust_class where categorynum IS NOT NULL';
+my $sth = dbh->prepare($cat_query)
+  or die "Error preparing $cat_query: ". dbh->errstr;
+$sth->execute
+  or die "Error executing $cat_query: ". $sth->errstr;
+if ($sth->fetchrow_arrayref->[0]) {
+  push @$header, 'Category';
+  push @$fields, 'categoryname';
+  push @$links,  $link;
+}
+
+</%init>
index 90540bc..9745b13 100644 (file)
@@ -20,7 +20,7 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
 my $html_init = 
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
 my $html_init = 
-  'Package optional report classes define groups of packages, for reporting purposes.'.
+  'Package optional report classes define optional groups of packages for reporting only.'.
   qq!<BR><BR><A HREF="${p}edit/part_pkg_report_option.html"><I>Add a class</I></A><BR><BR>!;
 
 my $link = [ $p.'edit/part_pkg_report_option.html?', 'num' ];
   qq!<BR><BR><A HREF="${p}edit/part_pkg_report_option.html"><I>Add a class</I></A><BR><BR>!;
 
 my $link = [ $p.'edit/part_pkg_report_option.html?', 'num' ];
index e85c0dd..2223445 100644 (file)
@@ -22,8 +22,7 @@ die "access denied"
 
 my $html_init = 
   qq!<A HREF="${p}browse/pkg_class.html">Package classes</A><BR><BR>!.
 
 my $html_init = 
   qq!<A HREF="${p}browse/pkg_class.html">Package classes</A><BR><BR>!.
-  'Package categories define groups of package classes, for reporting and '.
-  'convenience purposes.<BR><BR>'.
+  'Package categories define groups of package classes.<BR><BR>'.
   qq!<A HREF="${p}edit/pkg_category.html"><I>Add a package category</I></A><BR><BR>!;
 
 my $count_query = 'SELECT COUNT(*) FROM pkg_category';
   qq!<A HREF="${p}edit/pkg_category.html"><I>Add a package category</I></A><BR><BR>!;
 
 my $count_query = 'SELECT COUNT(*) FROM pkg_category';
index 75969db..7097c86 100644 (file)
@@ -20,8 +20,8 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
 my $html_init = 
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
 my $html_init = 
-  'Package classes define groups of packages, for reporting and '.
-  'convenience purposes.<BR><BR>'.
+  'Package classes define groups of packages, for taxation, ordering '.
+  'convenience and reporting.<BR><BR>'.
   qq!<A HREF="${p}edit/pkg_class.html"><I>Add a package class</I></A><BR><BR>!;
 
 my $count_query = 'SELECT COUNT(*) FROM pkg_class';
   qq!<A HREF="${p}edit/pkg_class.html"><I>Add a package class</I></A><BR><BR>!;
 
 my $count_query = 'SELECT COUNT(*) FROM pkg_class';
diff --git a/httemplate/edit/cust_category.html b/httemplate/edit/cust_category.html
new file mode 100644 (file)
index 0000000..18a6189
--- /dev/null
@@ -0,0 +1,5 @@
+<% include( 'elements/category_Common.html',
+              'name'   => 'Customer Category',
+              'table'  => 'cust_category',
+          )
+%>
diff --git a/httemplate/edit/cust_class.html b/httemplate/edit/cust_class.html
new file mode 100644 (file)
index 0000000..fdb58e6
--- /dev/null
@@ -0,0 +1,5 @@
+<% include( 'elements/class_Common.html',
+              'name'   => 'Customer Class',
+              'table'  => 'cust_class',
+          )
+%>
diff --git a/httemplate/edit/elements/category_Common.html b/httemplate/edit/elements/category_Common.html
new file mode 100644 (file)
index 0000000..8bbdcd1
--- /dev/null
@@ -0,0 +1,24 @@
+<% include( 'edit.html',
+              'fields' => [
+                            'categoryname',
+                            'weight',
+                            { field=>'disabled', type=>'checkbox', value=>'Y', },
+                          ],
+              'labels' => { 
+                            'categorynum'  => 'Category number',
+                            'categoryname' => 'Category name',
+                            'weight'       => 'Weight',
+                            'disabled'     => 'Disable category',
+                          },
+              'viewall_dir' => 'browse',
+              %opt,
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html
new file mode 100644 (file)
index 0000000..b5f4939
--- /dev/null
@@ -0,0 +1,32 @@
+<% include( 'edit.html',
+              'fields' => [
+                            'classname',
+                            (scalar(@category)
+                              ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' }
+                              : { field=>'categorynum', type=>'hidden' }
+                            ),
+                            { field=>'disabled', type=>'checkbox', value=>'Y', },
+                          ],
+              'labels' => { 
+                            'classnum'    => 'Class number',
+                            'classname'   => 'Class name',
+                            'categorynum' => 'Category',
+                            'disabled'    => 'Disable class',
+                          },
+              'viewall_dir' => 'browse',
+              %opt,
+           )
+          
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %opt = @_;
+
+my $table = $opt{'table'};
+( my $category_table = $table ) =~ s/class/category/ or die;
+
+my @category = qsearch($category_table, { 'disabled' => '' });
+</%init>
index a07dc58..a244bd5 100644 (file)
@@ -1,24 +1,5 @@
-<% include( 'elements/edit.html',
+<% include( 'elements/category_Common.html',
               'name'   => 'Package Category',
               'table'  => 'pkg_category',
               'name'   => 'Package Category',
               'table'  => 'pkg_category',
-              'fields' => [
-                            'categoryname',
-                            'weight',
-                            { field=>'disabled', type=>'checkbox', value=>'Y', },
-                          ],
-              'labels' => { 
-                            'categorynum'  => 'Category number',
-                            'categoryname' => 'Category name',
-                            'weight'       => 'Weight',
-                            'disabled'  => 'Disable category',
-                          },
-              'viewall_dir' => 'browse',
-           )
-          
+          )
 %>
 %>
-<%init>
-
-die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-
-</%init>
index 26bc8ba..4f7a729 100644 (file)
@@ -1,28 +1,5 @@
-<% include( 'elements/edit.html',
+<% include( 'elements/class_Common.html',
               'name'   => 'Package Class',
               'table'  => 'pkg_class',
               'name'   => 'Package Class',
               'table'  => 'pkg_class',
-              'fields' => [
-                            'classname',
-                            (scalar(@category)
-                              ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' }
-                              : { field=>'categorynum', type=>'hidden' }
-                            ),
-                            { field=>'disabled', type=>'checkbox', value=>'Y', },
-                          ],
-              'labels' => { 
-                            'classnum'    => 'Class number',
-                            'classname'   => 'Class name',
-                            'categorynum' => 'Category',
-                            'disabled'    => 'Disable class',
-                          },
-              'viewall_dir' => 'browse',
-           )
-          
+          )
 %>
 %>
-<%init>
-
-die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-
-my @category = qsearch('pkg_category', { 'disabled' => '' });
-</%init>
diff --git a/httemplate/edit/process/cust_category.html b/httemplate/edit/process/cust_category.html
new file mode 100644 (file)
index 0000000..c3a8809
--- /dev/null
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+               'table'       => 'cust_category',
+               'viewall_dir' => 'browse',
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cust_class.html b/httemplate/edit/process/cust_class.html
new file mode 100644 (file)
index 0000000..3f63ea4
--- /dev/null
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+               'table'       => 'cust_class',
+               'viewall_dir' => 'browse',
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
index c54ed07..cd54661 100644 (file)
@@ -342,15 +342,20 @@ $config_pkg{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi', 'One or mor
   if    $curuser->access_right('Edit package definitions')
      || $curuser->access_right('Edit global package definitions');
 if ( $curuser->access_right('Configuration') ) {
   if    $curuser->access_right('Edit package definitions')
      || $curuser->access_right('Edit global package definitions');
 if ( $curuser->access_right('Configuration') ) {
-  $config_pkg{'Package categories'} =  [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes, for reporting and convenience purposes.' ];
-  $config_pkg{'Package tax classes'} =  [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ];
-  $config_pkg{'Package report classes'} =  [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting purposes.' ];
-  $config_pkg{'Cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ];
+  $config_pkg{'Package classes'} =  [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for taxation, ordering convenience and reporting.' ];
+  $config_pkg{'Package categories'} =  [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes.' ];
+  $config_pkg{'Package report classes'} =  [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting only.' ];
   $config_pkg{'Cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
   $config_pkg{'Cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
-  $config_pkg{'Suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ];
+  $config_pkg{'Cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons.' ];
   $config_pkg{'Suspend reasons'} = [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ];
   $config_pkg{'Suspend reasons'} = [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ];
+  $config_pkg{'Suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons.' ];
 }
 
 }
 
+tie my %config_cust, 'Tie::IxHash',
+  'Customer classes'    =>  [ $fsurl.'browse/cust_class.html', 'Customer classes define groups of customers for reporting.' ],
+  'Customer categories' =>  [ $fsurl.'browse/cust_category.html', 'Customer categories define groups of customer classes.' ],
+;
+
 tie my %config_agent, 'Tie::IxHash',
   'Agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
   'Agents'      => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ],
 tie my %config_agent, 'Tie::IxHash',
   'Agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
   'Agents'      => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ],
@@ -360,7 +365,7 @@ tie my %config_agent, 'Tie::IxHash',
 tie my %config_billing_rates, 'Tie::IxHash',
   'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
   'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
 tie my %config_billing_rates, 'Tie::IxHash',
   'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
   'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
-  'Usage classes'  => [ $fsurl.'browse/usage_class.html', 'Usage classes define groups of usage for taxation purposes.' ],
+  'Usage classes'  => [ $fsurl.'browse/usage_class.html', 'Usage classes define groups of usage for taxation.' ],
   'Edit rates with Excel' => [ $fsurl.'misc/rate_edit_excel.html', 'Download and edit rates with Excel, then upload changes.' ], #"Edit with Excel" ?
 ;
 
   'Edit rates with Excel' => [ $fsurl.'misc/rate_edit_excel.html', 'Download and edit rates with Excel, then upload changes.' ], #"Edit with Excel" ?
 ;
 
@@ -385,8 +390,8 @@ if ( $curuser->access_right('Configuration') ) {
      if $conf->exists('enable_taxproducts');
   $config_billing{'Tax classes'} = [ $fsurl. 'browse/part_pkg_taxclass.html', 'Tax classes' ];
 
      if $conf->exists('enable_taxproducts');
   $config_billing{'Tax classes'} = [ $fsurl. 'browse/part_pkg_taxclass.html', 'Tax classes' ];
 
-  $config_billing{'Credit reason types'}  = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons, for reporting and convenience purposes.' ];
   $config_billing{'Credit reasons'}  = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
   $config_billing{'Credit reasons'}  = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
+  $config_billing{'Credit reason types'}  = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ];
 }
 
 tie my %config_ticketing, 'Tie::IxHash',
 }
 
 tie my %config_ticketing, 'Tie::IxHash',
@@ -412,7 +417,7 @@ tie my %config_phone, 'Tie::IxHash',
 ;
 
 tie my %config_misc, 'Tie::IxHash';
 ;
 
 tie my %config_misc, 'Tie::IxHash';
-$config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.  Tracked for informational purposes' ]
+$config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.' ]
   if $curuser->access_right('Edit advertising sources')
   || $curuser->access_right('Edit global advertising sources');
 if ( $curuser->access_right('Configuration') ) {
   if $curuser->access_right('Edit advertising sources')
   || $curuser->access_right('Edit global advertising sources');
 if ( $curuser->access_right('Configuration') ) {
@@ -435,9 +440,11 @@ $config_menu{'Packages'} = [ \%config_pkg, '' ]
   if    $curuser->access_right('Configuration' )
      || $curuser->access_right('Edit package definitions')
      || $curuser->access_right('Edit global package definitions');
   if    $curuser->access_right('Configuration' )
      || $curuser->access_right('Edit package definitions')
      || $curuser->access_right('Edit global package definitions');
-$config_menu{'Resellers'} = [ \%config_agent, ''    ]
+$config_menu{'Customers'} = [ \%config_cust, '' ]
+  if $curuser->access_right('Configuration');
+$config_menu{'Resellers'} = [ \%config_agent, '' ]
   if $curuser->access_right('Configuration');
   if $curuser->access_right('Configuration');
-$config_menu{'Billing'} = [ \%config_billing, ''    ]
+$config_menu{'Billing'} = [ \%config_billing, '' ]
   if $curuser->access_right('Edit billing events')
   || $curuser->access_right('Edit global billing events');
 $config_menu{'Ticketing'} = [ \%config_ticketing, '' ]
   if $curuser->access_right('Edit billing events')
   || $curuser->access_right('Edit global billing events');
 $config_menu{'Ticketing'} = [ \%config_ticketing, '' ]
@@ -445,7 +452,7 @@ $config_menu{'Ticketing'} = [ \%config_ticketing, '' ]
   && FS::TicketSystem->access_right(\%session, 'ShowConfigTab');
 $config_menu{'Dialup'}  = [ \%config_dialup, ''    ]
   if ( $curuser->access_right('Dialup configuration') );
   && FS::TicketSystem->access_right(\%session, 'ShowConfigTab');
 $config_menu{'Dialup'}  = [ \%config_dialup, ''    ]
   if ( $curuser->access_right('Dialup configuration') );
-$config_menu{'Fixed (username-less) broadband'} = [ \%config_broadband, ''    ]
+$config_menu{'Broadband'} = [ \%config_broadband, ''    ]
   if ( $curuser->access_right('Broadband configuration') );
 $config_menu{'Phone'}  = [ \%config_phone, ''    ]
   if ( $curuser->access_right('Configuration') );
   if ( $curuser->access_right('Broadband configuration') );
 $config_menu{'Phone'}  = [ \%config_phone, ''    ]
   if ( $curuser->access_right('Configuration') );