rt# 74031 implement svc_realestate
authorMitch Jackson <mitch@freeside.biz>
Fri, 12 Jan 2018 02:05:34 +0000 (20:05 -0600)
committerMitch Jackson <mitch@freeside.biz>
Fri, 12 Jan 2018 02:05:34 +0000 (20:05 -0600)
22 files changed:
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/h_svc_realestate.pm [new file with mode: 0644]
FS/FS/realestate_location.pm [new file with mode: 0644]
FS/FS/realestate_unit.pm [new file with mode: 0644]
FS/FS/svc_Common.pm
FS/FS/svc_realestate.pm [new file with mode: 0644]
FS/t/realestate_location.t [new file with mode: 0644]
FS/t/realestate_unit.t [new file with mode: 0644]
FS/t/svc_realestate.t [new file with mode: 0644]
httemplate/browse/realestate_location.html [new file with mode: 0644]
httemplate/browse/realestate_unit.html [new file with mode: 0644]
httemplate/docs/part_svc-table.html
httemplate/edit/process/realestate_location.html [new file with mode: 0644]
httemplate/edit/process/realestate_unit.html [new file with mode: 0644]
httemplate/edit/realestate_location.html [new file with mode: 0644]
httemplate/edit/realestate_unit.html [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/elements/select-realestate_location.html [new file with mode: 0644]
httemplate/elements/select-realestate_unit.html [new file with mode: 0644]
httemplate/elements/tr-select-realestate_location.html [new file with mode: 0644]
httemplate/elements/tr-select-realestate_unit.html [new file with mode: 0644]

index 7bdb605..7f883de 100644 (file)
@@ -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
index 6d7520b..f7ac973 100644 (file)
@@ -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 (file)
index 0000000..2fdd291
--- /dev/null
@@ -0,0 +1,31 @@
+package FS::h_svc_realestate;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+
+
+@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 (file)
index 0000000..d9cd76a
--- /dev/null
@@ -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 (file)
index 0000000..d1d1f7f
--- /dev/null
@@ -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;
index f2456a5..afd5db6 100644 (file)
@@ -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 (file)
index 0000000..a7512ee
--- /dev/null
@@ -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 (file)
index 0000000..ecb1d8b
--- /dev/null
@@ -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 (file)
index 0000000..bbecc1a
--- /dev/null
@@ -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 (file)
index 0000000..4145d8b
--- /dev/null
@@ -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 (file)
index 0000000..be2cd11
--- /dev/null
@@ -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 (file)
index 0000000..399cd25
--- /dev/null
@@ -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>
index 820d0b9..56a4d0e 100644 (file)
@@ -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">
         <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 (file)
index 0000000..ab5cf23
--- /dev/null
@@ -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 (file)
index 0000000..ba9b5dc
--- /dev/null
@@ -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 (file)
index 0000000..34344e9
--- /dev/null
@@ -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 (file)
index 0000000..a7ca72d
--- /dev/null
@@ -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>
index defcc49..0a73d71 100644 (file)
@@ -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 (file)
index 0000000..001ed3e
--- /dev/null
@@ -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 (file)
index 0000000..e189d5d
--- /dev/null
@@ -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 (file)
index 0000000..1367886
--- /dev/null
@@ -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 (file)
index 0000000..b1a4296
--- /dev/null
@@ -0,0 +1,5 @@
+<& tr-td-label.html, @_ &>
+<td>
+<& select-realestate_unit.html, @_ &>
+</td>
+</tr>