[freeside-commits] branch master updated. 69e481a4a9191b9912d6bb8202627a5dc75f74ce

Mitch Jackson mitch at freeside.biz
Thu Jan 11 18:09:50 PST 2018


The branch, master has been updated
       via  69e481a4a9191b9912d6bb8202627a5dc75f74ce (commit)
      from  77daf007ef522ae71041d9b094643cf868d8ecce (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 69e481a4a9191b9912d6bb8202627a5dc75f74ce
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Thu Jan 11 20:05:34 2018 -0600

    rt# 74031 implement svc_realestate

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 7bdb6059e..7f883dec1 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -418,6 +418,9 @@ if ( -e $addl_handler_use_file ) {
   use FS::part_svc_msgcat;
   use FS::commission_schedule;
   use FS::commission_rate;
+  use FS::realestate_location;
+  use FS::realestate_unit;
+  use FS::svc_realestate;
   use FS::saved_search;
   use FS::sector_coverage;
   # Sammath Naur
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6d7520bd9..f7ac973be 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -495,8 +495,44 @@ sub tables_hashref {
 
   my $username_len = 64; #usernamemax config file
 
-    # name type nullability length default local
+  # Return a hashref defining the entire application database schema
+  # Each key of the hashref contains a structure describing a database table
+  #
+  # table_name => {
+  #   columns      => [...],
+  #   primary_key  => 'column',
+  #   unique       => [column,column,...],
+  #   index        => [[column],[column,column],...],
+  #   foreign_keys => [{...},{...},...],
+  # }
+  #
+  #
+  # columns => [
+  #
+  #   'column_name',
+  #
+  #   'column_type',
+  #
+  #   'NULL' or '',   # 'NULL' : Allow null values
+  #                   # ''     : Disallow null values
+  #
+  #   'length',       # Column size value.  eg:
+  #                   # 40     : VARCHAR(40)
+  #                   # '10,2' : FLOAT(10,2)
+  #
+  #   'default',      # Default column value for a new record
+  #                   # (Unclear if setting this to '' results in a default
+  #                   #  value of NULL or empty string?)
+  #
+  #   '',             # local ?
+  #
+  #   name, type, nullability, length, default, local,
+  #   name, type, nullability, length, default, local,
+  #   ...
+  #
+  # ],
 
+  # name type nullability length default local
   return {
 
     'agent' => {
@@ -7601,6 +7637,57 @@ sub tables_hashref {
       'foreign_keys'  => [],
     },
 
+    'realestate_unit' => {
+      'columns' => [
+        'realestatenum',    'serial',  '',     '',      '',  '',
+        'realestatelocnum', 'int',     '',     '',      '',  '',
+        'agentnum',         'int',     'NULL', '',      '',  '',
+        'unit_title',       'varchar', '',     $char_d, '',  '',
+        'disabled',         'char',    'NULL', 1,       '',  '',
+      ],
+      'primary_key'  => 'realestatenum',
+      'unique'       => [ ['unit_title'] ],
+      'index'        => [
+        ['agentnum'],
+        ['realestatelocnum'],
+        ['disabled'],
+        ['unit_title'],
+      ],
+      'foreign_keys' => [
+        {columns => ['agentnum'], table => 'agent'},
+        {columns => ['realestatelocnum'] => table => 'realestate_location'},
+      ],
+    },
+
+    realestate_location => {
+      'columns' => [
+        'realestatelocnum', 'serial',  '',     '',      '', '',
+        'agentnum',         'int',     'NULL', '',      '', '',
+       'location_title',   'varchar', '',     $char_d, '', '',
+        'address1',         'varchar', 'NULL', $char_d, '',  '',
+        'address2',         'varchar', 'NULL', $char_d, '',  '',
+        'city',             'varchar', 'NULL', $char_d, '',  '',
+        'state',            'varchar', 'NULL', $char_d, '',  '',
+        'zip',              'char',    'NULL', 5,       '',  '',
+        'disabled',         'char',    'NULL', 1,       '',  '',
+      ],
+      primary_key  => 'realestatelocnum',
+      'unique'     => [ ['location_title'] ],
+      'index'      => [ ['agentnum'], ['disabled'] ],
+      'foreign_keys' => [
+        {columns => ['agentnum'], table => 'agent'},
+      ],
+    },
+
+    svc_realestate => {
+      columns => [
+        'svcnum',        'serial',  '',     '',      '', '',
+        'realestatenum', 'int',     'NULL', '',      '', '',
+      ],
+      primary_key => 'svcnum',
+      index => [],
+    },
+
     # name type nullability length default local
 
     #'new_table' => {
@@ -7627,4 +7714,3 @@ L<DBIx::DBSchema>
 =cut
 
 1;
-
diff --git a/FS/FS/h_svc_realestate.pm b/FS/FS/h_svc_realestate.pm
new file mode 100644
index 000000000..2fdd291d1
--- /dev/null
+++ b/FS/FS/h_svc_realestate.pm
@@ -0,0 +1,31 @@
+package FS::h_svc_realestate;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+
+
+ at ISA = qw( FS::h_Common );
+
+sub table { 'h_svc_realestate' };
+
+=head1 NAME
+
+FS::h_svc_circuit - Historical realestate service objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_realestate object
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_realestate>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
diff --git a/FS/FS/realestate_location.pm b/FS/FS/realestate_location.pm
new file mode 100644
index 000000000..d9cd76a58
--- /dev/null
+++ b/FS/FS/realestate_location.pm
@@ -0,0 +1,177 @@
+package FS::realestate_location;
+use strict;
+use warnings;
+use Carp qw(croak);
+
+use base 'FS::Record';
+
+use FS::Record qw(qsearchs qsearch);
+
+=head1 NAME
+
+FS::realestate_location - Object representing a realestate_location record
+
+=head1 SYNOPSIS
+
+  use FS::realestate_location;
+
+  $location = new FS::realestate_location \%values;
+  $location = new FS::realestate_location {
+    agentnum          => 1,
+    location_title    => 'Superdome',
+    address1          => '1500 Sugar Bowl Dr',
+    city              => 'New Orleans',
+    state             => 'LA',
+    zip               => '70112',
+  };
+
+  $error = $location->insert;
+  $error = $new_loc->replace($location);
+  $error = $record->check;
+
+  $error = $location->add_unit('Box Seat No. 42');
+  @units = $location->units;
+
+=head1 DESCRIPTION
+
+An FS::realestate_location object represents a location for one or more
+FS::realestate_unit objects.  Expected to contain at least one unit, as only
+realestate_unit objects are assignable to packages via
+L<FS::svc_realestate>.
+
+FS::realestate_location inherits from FS::Record.
+
+The following fields are currently supported:
+
+=over 4
+
+=item realestatelocnum
+
+=item agentnum
+
+=item location_title
+
+=item address1 (optional)
+
+=item address2 (optional)
+
+=item city (optional)
+
+=item state (optional)
+
+=item zip (optional)
+
+=item disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF (see L<FS::Record>)
+
+=cut
+
+sub table {'realestate_location';}
+
+=item insert (see L<FS::Record>)
+
+=item delete
+
+  FS::realestate_location records should never be deleted, only disabled
+
+=cut
+
+sub delete {
+  # Once this record has been associated with a customer in any way, it
+  # should not be deleted.  todo perhaps, add a is_deletable function that
+  # checks if the record has ever actually been used, and allows deletion
+  # if it hasn't.  (entered in error, etc).
+  croak "FS::realestate_location records should never be deleted";
+}
+
+=item replace OLD_RECORD (see L<FS::Record>)
+
+=item check (see L<FS::Record>)
+
+=item agent
+
+Returns the associated agent
+
+=cut
+
+sub agent {
+  my $self = shift;
+  return undef unless $self->agentnum;
+  return exists $self->{agent}
+  ? $self->{agent}
+  : $self->{agent} = qsearchs('agent', {agentnum => $self->agentnum} );
+}
+
+
+=item add_unit UNIT_TITLE
+
+Create an associated L<FS::realestate_unit> record
+
+=cut
+
+sub add_unit {
+  my ($self, $unit_title) = @_;
+  croak "add_unit() requires a \$unit_title parameter" unless $unit_title;
+
+  if (
+    qsearchs('realestate_unit',{
+      realestatelocnum => $self->realestatelocnum,
+      unit_title => $unit_title,
+    })
+  ) {
+    return "Unit Title ($unit_title) has already been used for location (".
+      $self->location_title.")";
+  }
+
+  my $unit = FS::realestate_unit->new({
+    realestatelocnum => $self->realestatelocnum,
+    agentnum         => $self->agentnum,
+    unit_title       => $unit_title,
+  });
+  my $err = $unit->insert;
+  die "Error creating FS::realestate_new record: $err" if $err;
+
+  return;
+}
+
+
+=item units
+
+Returns all units associated with this location
+
+=cut
+
+sub units {
+  my $self = shift;
+  return qsearch(
+    'realestate_unit',
+    {realestatelocnum => $self->realestatelocnum}
+  );
+}
+
+
+=head1 SUBROUTINES
+
+=over 4
+
+=cut
+
+
+
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::record>, L<FS::realestate_unit>, L<FS::svc_realestate>
+
+=cut
+
+1;
diff --git a/FS/FS/realestate_unit.pm b/FS/FS/realestate_unit.pm
new file mode 100644
index 000000000..d1d1f7fda
--- /dev/null
+++ b/FS/FS/realestate_unit.pm
@@ -0,0 +1,163 @@
+package FS::realestate_unit;
+use strict;
+use warnings;
+use Carp qw(croak);
+
+use base 'FS::Record';
+use FS::Record qw(qsearch qsearchs);
+
+=head1 NAME
+
+FS::realestate_unit - Object representing a realestate_unit record
+
+=head1 SYNOPSIS
+
+  use FS::realestate_unit;
+
+  $record = new FS:realestate_unit  \%values;
+  $record = new FS::realestate_unit {
+    realestatelocnum => 42,
+    agentnum         => 1,
+    unit_title       => 'Ste 404',
+  };
+
+  $error = $record->insert;
+  $error = $new_rec->replace($record)
+  $error = $record->check;
+
+  $location = $record->location;
+
+=head1 DESCRIPTION
+
+An FS::realestate_unit object represents an invoicable unit of real estate.
+Object may represent a single property, such as a rental house.  It may also
+represent a group of properties sharing a common address or identifier, such
+as a shopping mall, apartment complex, or office building, concert hall.
+
+A FS::realestate_unit object must be associated with a FS::realestate_location
+
+FS::realestate_unit inherits from FS::Record.
+
+The following fields are currently supported:
+
+=over 4
+
+=item realestatenum
+
+=item realestatelocnum
+
+=item agentnum
+
+=item unit_title
+
+=item disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF (see L<FS::Record>)
+
+=cut
+
+sub table {'realestate_unit';}
+
+=item insert (see L<FS::Record>)
+
+=item delete
+
+  FS::realestate_unit records should never be deleted, only disabled
+
+=cut
+
+sub delete {
+  # Once this record has been associated with a customer in any way, it
+  # should not be deleted.  todo perhaps, add a is_deletable function that
+  # checks if the record has ever actually been used, and allows deletion
+  # if it hasn't.  (entered in error, etc).
+  croak "FS::realestate_unit records should never be deleted";
+}
+
+
+=item replace OLD_RECORD (see L<FS::Record>)
+
+=item check (see L<FS::Record>)
+
+=item agent
+
+Returns the associated agent, if any, for this object
+
+=cut
+
+sub agent {
+  my $self = shift;
+  return undef unless $self->agentnum;
+  return qsearchs('agent', {agentnum => $self->agentnum} );
+}
+
+=item location
+
+  Return the associated FS::realestate_location object
+
+=cut
+
+sub location {
+  my $self = shift;
+  return $self->{location} if exists $self->{location};
+  return $self->{location} = qsearchs(
+    'realestate_location',
+    {realestatelocnum => $self->realestatelocnum}
+  );
+}
+
+=back
+
+=item custnum
+
+Pull the assigned custnum for this unit, if provisioned
+
+=cut
+
+sub custnum {
+  my $self = shift;
+  return $self->{custnum}
+    if $self->{custnum};
+
+  # select cust_pkg.custnum
+  # from svc_realestate
+  # LEFT JOIN cust_svc ON svc_realestate.svcnum = cust_svc.svcnum
+  # LEFT JOIN cust_pkg ON cust_svc.pkgnum = cust_pkg.pkgnum
+  # WHERE svc_realestate.realestatenum = $realestatenum
+
+  my $row = qsearchs({
+    select    => 'cust_pkg.custnum',
+    table     => 'svc_realestate',
+    addl_from => 'LEFT JOIN cust_svc ON svc_realestate.svcnum = cust_svc.svcnum '
+               . 'LEFT JOIN cust_pkg ON cust_svc.pkgnum = cust_pkg.pkgnum ',
+    extra_sql => 'WHERE svc_realestate.realestatenum = '.$self->realestatenum,
+  });
+
+  return
+    unless $row && $row->custnum;
+
+  return $self->{custnum} = $row->custnum;
+}
+
+=head1 SUBROUTINES
+
+=over 4
+
+=cut
+
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::record>, L<FS::realestate_location>, L<FS::svc_realestate>
+
+=cut
+
+1;
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
index f2456a56f..afd5db64f 100644
--- a/FS/FS/svc_Common.pm
+++ b/FS/FS/svc_Common.pm
@@ -122,6 +122,15 @@ sub virtual_fields {
 
 =item label
 
+Returns a label to identify a record of this service.
+Label may be displayed on freeside screens, and within customer bills.
+
+For example, $obj->label may return:
+
+ - A provisioned phone number for svc_phone
+ - The mailing list name and e-mail address for svc_mailinglist
+ - The address of a rental property svc_realestate
+
 svc_Common provides a fallback label subroutine that just returns the svcnum.
 
 =cut
@@ -1586,4 +1595,3 @@ from the base documentation.
 =cut
 
 1;
-
diff --git a/FS/FS/svc_realestate.pm b/FS/FS/svc_realestate.pm
new file mode 100644
index 000000000..a7512eef8
--- /dev/null
+++ b/FS/FS/svc_realestate.pm
@@ -0,0 +1,172 @@
+package FS::svc_realestate;
+use base qw(FS::svc_Common);
+
+use strict;
+use warnings;
+use vars qw($conf);
+
+use FS::Record qw(qsearchs qsearch dbh);
+use Tie::IxHash;
+
+$FS::UID::callback{'FS::svc_realestate'} = sub {
+  $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::svc_realestate - Object methods for svc_realestate records
+
+=head1 SYNOPSIS
+
+  {...} TODO
+
+=head1 DESCRIPTION
+
+A FS::svc_realestate object represents a billable real estate trasnaction,
+such as renting a home or office.
+
+FS::svc_realestate inherits from FS::svc_Common.  The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Instantiates a new svc_realestate object.
+
+=cut
+
+sub table_info {
+  tie my %fields, 'Tie::IxHash',
+    svcnum      => 'Service',
+    realestatenum => {
+      type => 'select-realestate_unit',
+      label => 'Real estate unit',
+    };
+
+  {
+    name            => 'Real estate',
+    name_plural     => 'Real estate services',
+    longname_plural => 'Real estate services',
+    display_weight  => 100,
+    cancel_weight   => 100,
+    fields          => \%fields,
+  };
+}
+
+sub table {'svc_realestate'}
+
+=item label
+
+Returns a label formatted as:
+  <location_title> <unit_title>
+
+=cut
+
+sub label {
+  my $self = shift;
+  my $unit = $self->realestate_unit;
+  my $location = $self->realestate_location;
+
+  return $location->location_title.' '.$unit->unit_title
+    if $unit && $location;
+
+  return $self->svcnum; # shouldn't happen
+}
+
+
+=item realestate_unit
+
+Returns associated L<FS::realestate_unit>
+
+=cut
+
+sub realestate_unit {
+  my $self = shift;
+
+  return $self->get('_realestate_unit')
+    if $self->get('_realestate_unit');
+
+  return unless $self->realestatenum;
+
+  my $realestate_unit = qsearchs(
+    'realestate_unit',
+    {realestatenum => $self->realestatenum}
+  );
+
+  $self->set('_realestate_unit', $realestate_unit);
+  $realestate_unit;
+}
+
+=item realestate_location
+
+Returns associated L<FS::realestate_location>
+
+=cut
+
+sub realestate_location {
+  my $self = shift;
+
+  my $realestate_unit = $self->realestate_unit;
+  return unless $realestate_unit;
+
+  $realestate_unit->location;
+}
+
+=item cust_svc
+
+Returns associated L<FS::cust_svc>
+
+=cut
+
+sub cust_svc {
+  qsearchs('cust_svc', { 'svcnum' => $_[0]->svcnum } );
+}
+
+=item search_sql
+
+I have an unfounded suspicion this method serves no purpose in this context
+
+=cut
+
+# sub search_sql {die "search_sql called on FS::svc_realestate"}
+
+=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 record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=back 4
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
diff --git a/FS/t/realestate_location.t b/FS/t/realestate_location.t
new file mode 100644
index 000000000..ecb1d8be9
--- /dev/null
+++ b/FS/t/realestate_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::realestate_location;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/realestate_unit.t b/FS/t/realestate_unit.t
new file mode 100644
index 000000000..bbecc1a4c
--- /dev/null
+++ b/FS/t/realestate_unit.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::realestate_unit;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_realestate.t b/FS/t/svc_realestate.t
new file mode 100644
index 000000000..4145d8b52
--- /dev/null
+++ b/FS/t/svc_realestate.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_realestate;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/realestate_location.html b/httemplate/browse/realestate_location.html
new file mode 100644
index 000000000..be2cd11f8
--- /dev/null
+++ b/httemplate/browse/realestate_location.html
@@ -0,0 +1,43 @@
+<% include( 'elements/browse.html',
+  title       => emt('Real Estate Locations'),
+  name        => 'real estate locations',
+
+  menubar => [
+    'Edit units'         => "${p}browse/realestate_unit.html",
+    'Add a new location' => "${p}edit/realestate_location.html",
+    'Add a new unit'     => "${p}edit/realestate_unit.html",
+  ],
+
+  query => { table => 'realestate_location' },
+  count_query => 'SELECT COUNT(*) FROM realestate_location',
+
+  header => [ 'Location', 'Address', 'Address 2', 'City', 'State', 'Zip' ],
+  fields => [
+    'location_title',
+    'address1',
+    'address2',
+    'city',
+    'state',
+    'zip'
+  ],
+  links => [
+    ["${p}edit/realestate_location.html?",  'realestatelocnum' ],
+  ],
+
+  agent_virt  => 1,
+  agent_pos   => 0,
+  disableable => 1,
+)
+%>
+<%init>
+
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die("access denied")
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+
+
+</%init>
diff --git a/httemplate/browse/realestate_unit.html b/httemplate/browse/realestate_unit.html
new file mode 100644
index 000000000..399cd2583
--- /dev/null
+++ b/httemplate/browse/realestate_unit.html
@@ -0,0 +1,70 @@
+<% include( 'elements/browse.html',
+  title       => emt('Real Estate Inventory'),
+  name        => 'real estate inventory',
+
+  menubar => [
+    'Edit locations'     => "${p}browse/realestate_location.html",
+    'Add a new location' => "${p}edit/realestate_location.html",
+    'Add a new unit'     => "${p}edit/realestate_unit.html",
+  ],
+
+  query => {
+    table => 'realestate_unit',
+    select => join(', ',qw(
+      realestate_unit.*
+      realestate_location.location_title
+      cust_main.first
+      cust_main.last
+      cust_main.company
+    )),
+    addl_from => "
+      LEFT JOIN realestate_location
+        ON realestate_location.realestatelocnum
+           = realestate_unit.realestatelocnum
+      LEFT JOIN svc_realestate
+        ON realestate_unit.realestatenum = svc_realestate.realestatenum
+      LEFT JOIN cust_svc
+        ON svc_realestate.svcnum = cust_svc.svcnum
+      LEFT JOIN cust_pkg
+        ON cust_svc.pkgnum = cust_pkg.pkgnum
+      LEFT JOIN cust_main
+        ON cust_pkg.custnum = cust_main.custnum
+    ",
+    order_by => "ORDER BY location_title, unit_title"
+  },
+
+  count_query => 'SELECT COUNT(*) FROM realestate_unit',
+
+  header => [ 'Location', 'Unit', 'Customer' ],
+  fields => [
+    'location_title',
+    'unit_title',
+    sub {
+      return '' unless $_[0]->custnum;
+      return $_[0]->company if $_[0]->company;
+      return $_[0]->first.' '.$_[0]->last;
+    },
+  ],
+  links => [
+    ["${p}edit/realestate_location.html?", 'realestatelocnum' ],
+    ["${p}edit/realestate_unit.html?",     'realestatenum' ],
+    ["${p}view/cust_main.cgi?",            'custnum' ]
+  ],
+
+  agent_virt  => 1,
+  agent_pos   => 0,
+  disableable => 1,
+)
+%>
+<%init>
+
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die("access denied")
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+
+
+</%init>
diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html
index 820d0b9cc..56a4d0e8c 100644
--- a/httemplate/docs/part_svc-table.html
+++ b/httemplate/docs/part_svc-table.html
@@ -39,6 +39,7 @@
   <TR>
     <TH ALIGN="left">Hosting</TH>
     <TH ALIGN="left">Colocation</TH>
+    <TH ALIGN="left">Real Estate</TH>
   </TR>
     <TD VALIGN="top">
       <UL STYLE="margin:0">
@@ -54,6 +55,11 @@
         <LI><B>svc_port</B>: Customer router/switch port
       </UL>
     </TD>
+    <TD VALIGN="top">
+      <UL STYLE="margin:0">
+        <LI><B>svc_realestate</B>: Real estate properties
+      </UL>
+    </TD>
   </TR>
 <TABLE>
 <!--   <LI>svc_charge - One-time charges (Partially unimplemented)
@@ -62,4 +68,3 @@
 
 </BODY>
 </HTML>
-
diff --git a/httemplate/edit/process/realestate_location.html b/httemplate/edit/process/realestate_location.html
new file mode 100644
index 000000000..ab5cf230f
--- /dev/null
+++ b/httemplate/edit/process/realestate_location.html
@@ -0,0 +1,14 @@
+<% include( 'elements/process.html',
+    table => 'realestate_location',
+    viewall_dir => 'browse',
+  )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/realestate_unit.html b/httemplate/edit/process/realestate_unit.html
new file mode 100644
index 000000000..ba9b5dc92
--- /dev/null
+++ b/httemplate/edit/process/realestate_unit.html
@@ -0,0 +1,13 @@
+<& elements/process.html,
+  'table'         => 'realestate_unit',
+  'viewall_dir'   => 'browse',
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die("access denied")
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/realestate_location.html b/httemplate/edit/realestate_location.html
new file mode 100644
index 000000000..34344e98f
--- /dev/null
+++ b/httemplate/edit/realestate_location.html
@@ -0,0 +1,72 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'Real Estate Location',
+ 'table'  => 'realestate_location',
+
+ 'labels' => {
+    realestatelocnum => 'Location',
+    location_title => "Location",
+    address1       => "Address",
+    address2       => "Address",
+    city           => "City",
+    state          => "State",
+    zip            => "Zip-Code",
+    disabled       => "Disabled",
+  },
+  'fields' => [
+    { field => 'realestatelocnum', type => 'hidden' },
+
+    { field => 'location_title',
+      type=>'text',
+      size => 40,
+      maxlength => 80,
+    },
+    { field => 'address1',
+      type=>'text',
+      size => 40,
+      maxlength => 80,
+    },
+    { field => 'address2',
+      type=>'text',
+      size => 40,
+      maxlength => 80,
+    },
+    { field => 'city',
+      type=>'text',
+      size => 40,
+      maxlength => 80,
+    },
+    { field => 'state',
+      type=>'text',
+      size => 40,
+      maxlength => 80,
+    },
+    { field => 'zip',
+      type=>'text',
+      size => 5,
+      maxlength => 5,
+    },
+
+    { field => 'agentnum',
+      type => 'select-agent',
+      value => 1,
+    },
+    { field => 'disabled',
+      type=>'checkbox',
+      value=>'Y'
+    },
+  ],
+
+ 'viewall_dir' => 'browse',
+ 'agent_virt' => 1,
+)
+%>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die("access denied")
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/realestate_unit.html b/httemplate/edit/realestate_unit.html
new file mode 100644
index 000000000..a7ca72dd9
--- /dev/null
+++ b/httemplate/edit/realestate_unit.html
@@ -0,0 +1,48 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'Real Estate Unit',
+ 'table'  => 'realestate_unit',
+
+ 'labels' => {
+    realestatenum  => 'Ref No',
+    unit_title => 'Unit Title',
+    agentnum => 'Agent',
+    realestatelocnum => 'Location',
+  },
+  'fields' => [
+    { field => 'realestatenum', type => 'hidden' },
+
+    { field => 'unit_title',
+      type=>'text',
+      size => 40,
+    },
+    { field => 'realestatelocnum',
+      type => 'select-realestate_location',
+
+      # possible todo:
+      # I'd like to have this field disabled for editing on existing records,
+      # and only show the full selectbox for new records.
+
+    },
+    { field => 'agentnum',
+      type => 'select-agent',
+    },
+    { field => 'disabled',
+      type=>'checkbox',
+      value=>'Y'
+    },
+  ],
+
+ 'viewall_dir' => 'browse',
+ 'agent_virt' => 1,
+)
+%>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die("access denied")
+  unless $curuser->access_right('Edit inventory')
+      || $curuser->access_right('Edit global inventory')
+      || $curuser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index defcc494f..0a73d71a6 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -834,6 +834,11 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla
   || $curuser->access_right('Edit global inventory')
   || $curuser->access_right('Configuration');
 
+$config_misc{'Real estate inventory'} = [ $fsurl.'browse/realestate_unit.html', 'Setup real estate inventory' ]
+  if $curuser->access_right('Edit realestate inventory')
+  || $curuser->access_right('Edit global inventory')
+  || $curuser->access_right('Configuration');
+
 $config_misc{'Upload targets'} = [ $fsurl.'browse/upload_target.html', 'Billing and payment upload destinations' ]
   if $curuser->access_right('Configuration');
 
@@ -1038,4 +1043,3 @@ sub submenu {
 }
 
 </%init>
-
diff --git a/httemplate/elements/select-realestate_location.html b/httemplate/elements/select-realestate_location.html
new file mode 100644
index 000000000..001ed3e89
--- /dev/null
+++ b/httemplate/elements/select-realestate_location.html
@@ -0,0 +1,32 @@
+<%doc>
+
+  Displays a selectbox populated with values from realestate_location.
+  key:   realestate_location.realestatenum
+  value: realestate_location.location_title
+
+</%doc>
+
+<% include( '/elements/select-table.html',
+    %opt,
+    table         => 'realestate_location',
+    name_col      => 'location_title',
+    hashref       => { 'disabled' => '' },
+    value         => $select_value,
+    disable_empty => 1,
+  )
+%>
+
+<%init>
+
+#
+# possible todo:
+# I'd like to change the behavior of this select based on if
+# a new item is being created, or an existing item being edited
+
+my %opt = @_;
+my $select_value = $opt{'curr_value'} || $opt{'value'};
+
+# use Data::Dumper qw(Dumper);
+# print Dumper(\%opt);
+
+</%init>
diff --git a/httemplate/elements/select-realestate_unit.html b/httemplate/elements/select-realestate_unit.html
new file mode 100644
index 000000000..e189d5d99
--- /dev/null
+++ b/httemplate/elements/select-realestate_unit.html
@@ -0,0 +1,59 @@
+<%doc>
+
+Display a pair of select boxes for provisioning a realestate_unit
+- Real Estate Location
+- Real Estate Unit
+
+NOTE:
+  Records are always suppresed if
+  - realestate_location.disabled is set
+  - realestate_unit is provisioned to a customer [not working]
+
+  If it becomes necessary, an option may be added to the template
+  to show disabled/provisioned records, but is not yet implemented
+
+</%doc>
+<& select-tiered.html,
+  'tiers' => [
+    {
+
+      field         => 'realestate_location',
+      table         => 'realestate_location',
+      extra_sql     => "WHERE realestate_location.disabled IS NULL "
+                     . "   OR realestate_location.disabled = '' ",
+      name_col      => 'location_title',
+      empty_label   => '(all)',
+    },
+    {
+      field         => 'realestatenum',
+      table         => 'realestate_unit',
+      name_col      => 'unit_title',
+      value_col     => 'realestatenum',
+      link_col      => 'realestatelocnum',
+
+      # TODO: Filter units assigned to customers
+      # SQL below breaks the selectbox... why?
+
+      # Also, can we assume if realestatenum doesn't appear in svc_realestate
+      # that the realestate_unit is unprovisioned to a customer?  What indicator
+      # should be used to determine when a realestae_unit is not provisioned?
+
+      # addl_from     => "
+      #   LEFT JOIN svc_realestate
+      #     ON svc_realestate.realestatenum = realestate_unit.realestatenum
+      # ",
+
+      #extra_sql     => "WHERE svc_realestate.svcnum IS NULL ",
+
+      disable_empty => 1,
+      debug => 1,
+    },
+  ],
+  %opt,
+  'prefix' => $opt{'prefix'}. $opt{'field'}. '_', #after %opt so it overrides
+&>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/tr-select-realestate_location.html b/httemplate/elements/tr-select-realestate_location.html
new file mode 100644
index 000000000..1367886ed
--- /dev/null
+++ b/httemplate/elements/tr-select-realestate_location.html
@@ -0,0 +1,17 @@
+<TR>
+  <TH ALIGN="right"><% $opt{'label'} || 'Real Estate Location' %></TD>
+  <TD>
+    <% include( '/elements/select-realestate_location.html',
+        'curr_value' => $curr_value,
+        %opt
+      )
+    %>
+  </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-realestate_unit.html b/httemplate/elements/tr-select-realestate_unit.html
new file mode 100644
index 000000000..b1a4296d4
--- /dev/null
+++ b/httemplate/elements/tr-select-realestate_unit.html
@@ -0,0 +1,5 @@
+<& tr-td-label.html, @_ &>
+<td>
+<& select-realestate_unit.html, @_ &>
+</td>
+</tr>

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

Summary of changes:
 FS/FS/Mason.pm                                     |   3 +
 FS/FS/Schema.pm                                    |  90 ++++++++++-
 FS/FS/h_svc_realestate.pm                          |  31 ++++
 FS/FS/realestate_location.pm                       | 177 +++++++++++++++++++++
 FS/FS/realestate_unit.pm                           | 163 +++++++++++++++++++
 FS/FS/svc_Common.pm                                |  10 +-
 FS/FS/svc_realestate.pm                            | 172 ++++++++++++++++++++
 FS/t/{AccessRight.t => realestate_location.t}      |   2 +-
 FS/t/{AccessRight.t => realestate_unit.t}          |   2 +-
 FS/t/{AccessRight.t => svc_realestate.t}           |   2 +-
 httemplate/browse/realestate_location.html         |  43 +++++
 httemplate/browse/realestate_unit.html             |  70 ++++++++
 httemplate/docs/part_svc-table.html                |   7 +-
 httemplate/edit/process/realestate_location.html   |  14 ++
 httemplate/edit/process/realestate_unit.html       |  13 ++
 httemplate/edit/realestate_location.html           |  72 +++++++++
 httemplate/edit/realestate_unit.html               |  48 ++++++
 httemplate/elements/menu.html                      |   6 +-
 .../elements/select-realestate_location.html       |  32 ++++
 httemplate/elements/select-realestate_unit.html    |  59 +++++++
 .../elements/tr-select-realestate_location.html    |  17 ++
 httemplate/elements/tr-select-realestate_unit.html |   5 +
 22 files changed, 1030 insertions(+), 8 deletions(-)
 create mode 100644 FS/FS/h_svc_realestate.pm
 create mode 100644 FS/FS/realestate_location.pm
 create mode 100644 FS/FS/realestate_unit.pm
 create mode 100644 FS/FS/svc_realestate.pm
 copy FS/t/{AccessRight.t => realestate_location.t} (77%)
 copy FS/t/{AccessRight.t => realestate_unit.t} (80%)
 copy FS/t/{AccessRight.t => svc_realestate.t} (80%)
 create mode 100644 httemplate/browse/realestate_location.html
 create mode 100644 httemplate/browse/realestate_unit.html
 create mode 100644 httemplate/edit/process/realestate_location.html
 create mode 100644 httemplate/edit/process/realestate_unit.html
 create mode 100644 httemplate/edit/realestate_location.html
 create mode 100644 httemplate/edit/realestate_unit.html
 create mode 100644 httemplate/elements/select-realestate_location.html
 create mode 100644 httemplate/elements/select-realestate_unit.html
 create mode 100644 httemplate/elements/tr-select-realestate_location.html
 create mode 100644 httemplate/elements/tr-select-realestate_unit.html




More information about the freeside-commits mailing list