[freeside-commits] branch rt74031_svc_realestate created. efa77ef0e8309528eb3b613a5b28d173f765b6c3

Mitch Jackson mitch at freeside.biz
Tue Dec 19 19:31:47 PST 2017


The branch, rt74031_svc_realestate has been created
        at  efa77ef0e8309528eb3b613a5b28d173f765b6c3 (commit)

- Log -----------------------------------------------------------------
commit efa77ef0e8309528eb3b613a5b28d173f765b6c3
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Tue Dec 19 13:09:13 2017 -0600

    rt# 74031 implementing svc_realestate
    
     - updated schema
     - updated includes
     - begin writing FS::svc_realestate.pm
     - progress adding a "Real estate unit" selection to
       provision a real estate service

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 068f5b6c0..cbfeca020 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -223,8 +223,6 @@ if ( -e $addl_handler_use_file ) {
   use FS::cdr_batch;
   use FS::inventory_class;
   use FS::inventory_item;
-  use FS::realestate_location;
-  use FS::realestate_unit;
   use FS::pkg_category;
   use FS::pkg_class;
   use FS::access_user;
@@ -422,6 +420,9 @@ if ( -e $addl_handler_use_file ) {
   use FS::commission_rate;
   use FS::saved_search;
   use FS::sector_coverage;
+  use FS::realestate_location;
+  use FS::realestate_unit;
+  use FS::svc_realestate;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index e1301b6ca..f2817f5cb 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7652,7 +7652,7 @@ sub tables_hashref {
         ['custnum'],
         ['realestatelocnum'],
         ['disabled'],
-        ['title']
+        ['unit_title']
       ],
       'foreign_keys' => [
         {columns => ['agentnum'], table => 'agent'},
@@ -7681,6 +7681,16 @@ sub tables_hashref {
       ],
     },
 
+    svc_realestate => {
+      columns => [
+        'svcnum',      'serial', '',      '',      '', '',
+        'description', 'varchar', 'NULL', $char_d, '', '',
+        'property',    'varchar', 'NULL', $char_d, '', '',
+      ],
+      primary_key => 'svcnum',
+      index => [],
+    },
+
     # name type nullability length default local
 
     #'new_table' => {
diff --git a/FS/FS/h_svc_realestate.pm b/FS/FS/h_svc_realestate.pm
new file mode 100644
index 000000000..55f6edf60
--- /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 telecom circuit 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/part_svc_column.pm b/FS/FS/part_svc_column.pm
index 75a2dfb1a..7d6e57c85 100644
--- a/FS/FS/part_svc_column.pm
+++ b/FS/FS/part_svc_column.pm
@@ -97,7 +97,7 @@ sub check {
   ;
   return $error if $error;
 
-  $self->columnflag =~ /^([DFSMAHX]?)$/
+  $self->columnflag =~ /^([DFSMAHXR]?)$/
     or return "illegal columnflag ". $self->columnflag;
   $self->columnflag(uc($1));
 
@@ -133,4 +133,3 @@ schema.html 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..27012d047
--- /dev/null
+++ b/FS/FS/svc_realestate.pm
@@ -0,0 +1,99 @@
+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);
+
+$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 {{
+  name => 'Real estate',
+  name_plural => 'Real estate services',
+  longname_plural => 'Real estate services',
+  display_weight => 100,
+  cancel_weight => 100,
+  fields => {
+    svcnum => 'Service',
+    description => 'Descriptive label',
+    property => 'Real estate property',
+  },
+}}
+
+sub table {'svc_realestate'};
+
+=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/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/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
index 816f3428b..ecf8eff9a 100644
--- a/httemplate/edit/elements/part_svc_column.html
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -35,6 +35,9 @@ tie my %flag, 'Tie::IxHash',
   'A' => { 'desc' => 'Automatically fill in from inventory',
            'condition' => $inv_sub,
          },
+  'R' => { 'desc' => 'Manual selection from real estate',
+           'condition' => $inv_sub,
+         },
   'H' => { 'desc' => 'Select from hardware class',
            'condition' => sub { $_[0]->{type} ne 'select-hardware' },
          },
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index fed21256f..3018e3a0a 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -68,7 +68,7 @@ function flag_changed(obj) {
   // for fields that have both 'input' and 'select', 'select' is 'select from
   // inventory class'.
   var select = document.getElementById(layer + '__' + field + '_select');
-  if (newflag == "" || newflag == "X") { // disable
+  if (newflag == "" || newflag == "X" || newflag == 'R' ) { // disable
     if ( input ) {
       input.disabled = true;
       input.className = 'disabled';
@@ -280,6 +280,3 @@ my $widget = new HTML::Widgets::SelectLayers(
   }
 );
 </%init>
-
-
-

commit 8c04f3676f1e83393432a0ba899ec5b7fea4afe0
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Wed Dec 13 18:14:43 2017 -0600

    rt# 74031 Implement forms for editing real estate inventory

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..e71a59819
--- /dev/null
+++ b/httemplate/browse/realestate_unit.html
@@ -0,0 +1,63 @@
+<% 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 cust_main
+        ON realestate_unit.custnum = cust_main.custnum
+    ",
+  },
+
+  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?",  '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/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..254e57f5e
--- /dev/null
+++ b/httemplate/edit/realestate_location.html
@@ -0,0 +1,71 @@
+<% 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',
+    },
+    { 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..b8bf1fa22
--- /dev/null
+++ b/httemplate/edit/realestate_unit.html
@@ -0,0 +1,49 @@
+<% 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 => 'custnum', 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 3532e6074..0a73d71a6 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -829,12 +829,12 @@ $config_misc{'Custom fields'} = [ $fsurl.'browse/part_virtual_field.html', 'Loca
   if $curuser->access_right('Edit custom fields');
 $config_misc{'Translation strings'} = [ $fsurl.'browse/msgcat.html', 'Translations and other customizable labels for each locale' ]
   if $curuser->access_right('Configuration');
-$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/realestate_inventory.html', 'Setup inventory classes and stock inventory' ]
+$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]
   if $curuser->access_right('Edit inventory')
   || $curuser->access_right('Edit global inventory')
   || $curuser->access_right('Configuration');
 
-$config_misc{'Real estate inventory'} = [ $fsurl.'browse/inventory_realestate.html', 'Setup real estate inventory' ]
+$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');
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/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>

commit 1650732b0a3a3d52a2072d72aa75b2c6939bacfe
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Wed Dec 13 18:13:01 2017 -0600

    rt# 74031 reworked realestate schema as locations and units
    
     - Update schemas
     - Original approach discarded and rewritten as locations with units

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 7bdb6059e..068f5b6c0 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -223,6 +223,8 @@ if ( -e $addl_handler_use_file ) {
   use FS::cdr_batch;
   use FS::inventory_class;
   use FS::inventory_item;
+  use FS::realestate_location;
+  use FS::realestate_unit;
   use FS::pkg_category;
   use FS::pkg_class;
   use FS::access_user;
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index b94407bc8..e1301b6ca 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7636,38 +7636,48 @@ sub tables_hashref {
       'foreign_keys'  => [],
     },
 
-    'realestate_property' => {
+    'realestate_unit' => {
       'columns' => [
-        'propnum',  'serial',  '',     '',      '',  '',
-        'agentnum', 'int',     'NULL', '',      '',  '',
-        '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',    '',     1,       '0', '',
+        'realestatenum',    'serial',  '',     '',      '',  '',
+        'realestatelocnum', 'int',     '',     '',      '',   '',
+        'agentnum',         'int',     'NULL', '',      '',  '',
+        'custnum',          'int',     'NULL', '',      '',  '',
+        'unit_title',       'varchar', '',     $char_d, '',  '',
+        'disabled',         'char',    'NULL', 1,       '',  '',
+      ],
+      'primary_key'  => 'realestatenum',
+      'unique'       => [ ['unit_title'] ],
+      'index'        => [
+        ['agentnum'],
+        ['custnum'],
+        ['realestatelocnum'],
+        ['disabled'],
+        ['title']
       ],
-      'primary_key'  => 'propnum',
-      'unique'       => [],
-      'index'        => [ ['agentnum'] ],
       'foreign_keys' => [
-        {columns => ['agentnum'], table => 'agent'}
+        {columns => ['agentnum'], table => 'agent'},
+        {columns => ['custnum'],  table => 'cust_main'},
+        {columns => ['realestatelocnum'] => table => 'realestate_location'},
       ],
     },
 
-    'realestate_subproperty' => {
+    realestate_location => {
       'columns' => [
-        'subpropnum', 'serial',  '', '',      '',  '',
-        'propnum',    'int',     '', '',      '',  '',
-        'subtitle',   'varchar', '', $char_d, '',  '',
-        'disabled',   'char',    '', 1,       '0', '',
+        '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'  => 'subpropnum',
-      'unique'       => [],
-      'index'        => [ ['propnum'],['subtitle'] ],
+      primary_key  => 'realestatelocnum',
+      'unique'     => [ ['location_title'] ],
+      'index'      => [ ['agentnum'], ['disabled'] ],
       'foreign_keys' => [
-        {columns => ['propnum'], table => 'realestate_property'}
+        {columns => ['agentnum'], table => 'agent'},
       ],
     },
 
diff --git a/FS/FS/realestate_location.pm b/FS/FS/realestate_location.pm
new file mode 100644
index 000000000..08d6dd3fa
--- /dev/null
+++ b/FS/FS/realestate_location.pm
@@ -0,0 +1,178 @@
+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,
+    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;
+  @units = $location->active_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_property.pm b/FS/FS/realestate_property.pm
deleted file mode 100644
index 4e564175a..000000000
--- a/FS/FS/realestate_property.pm
+++ /dev/null
@@ -1,176 +0,0 @@
-package FS::realestate_property;
-use strict;
-use warnings;
-use Carp qw(croak);
-
-use base 'FS::Record';
-use FS::Record qw(qsearch qsearchs);
-
-=head1 NAME
-
-FS::realestate_property - Object representing a realestate_property record
-
-=head1 SYNOPSIS
-
-  use FS::realestate_property;
-
-  $record = new FS:realestate_property  \%values;
-  $record = new FS::realestate_property {
-    title    => 'Superdome',
-    address1 => '1500 Sugar Bowl Dr',
-    city     => 'New Orleans',
-    state    => 'LA',
-    zip      => '70112',
-    disabled => 0,
-    agentnum => 1,
-  };
-
-  $error = $record->insert;
-  $error = $new_rec->replace($record)
-  $error = $record->check;
-
-  $sub_record = $record->add_subproperty('Box Seat No. 42');
-
-  @subprops = $record->subproperties;
-  @subprops = $record->enabled_subproperties;
-
-=head1 DESCRIPTION
-
-An FS::realestate_property object represents a real estate property.  This
-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.
-
-FS::realestate_property inherits from FS::Record.
-
-The following fields are currently supported:
-
-=over 4
-
-=item propnum
-
-=item agentnum
-
-=item 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_property';}
-
-=item insert (see L<FS::Record>)
-
-=item delete
-
-  FS::realestate_property 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_property 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 subproperties
-
-Returns all subproperties linked to this property
-
-=cut
-
-sub subproperties {
-  qsearch('realestate_subproperty', {propnum => shift->propnum} );
-}
-
-
-=item enabled_subproperties
-
-Returns all subproperties linked to this property where disabled = 0
-
-=cut
-
-sub enabled_subproperties {
-  qsearch( 'realestate_subproperty', {
-    propnum => shift->propnum,
-    disabled => 0,
-  });
-}
-
-=item add_subproperty SUBPROPERTY_TITLE
-
-Create a new subproperty record for this property
-
-=cut
-
-sub add_subproperty {
-  my ($self, $subtitle) = @_;
-  croak "add_subproperty() requires a \$subtitle parameter" unless $subtitle;
-
-  my $subp = new FS::realestate_subproperty {
-    propnum  => $self->propnum,
-    subtitle => $subtitle,
-    disabled => 0,
-  };
-  my $err = $subp->insert;
-  die "Error inserting subproperty: $err" if $err;
-  $subp;
-}
-
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=cut
-
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::record>, L<FS::realestate_subproperty>, L<FS::svc_realestate>
-
-=cut
-
-1;
diff --git a/FS/FS/realestate_subproperty.pm b/FS/FS/realestate_subproperty.pm
deleted file mode 100644
index 5a93934da..000000000
--- a/FS/FS/realestate_subproperty.pm
+++ /dev/null
@@ -1,125 +0,0 @@
-package FS::realestate_subproperty;
-use strict;
-use warnings;
-use Carp qw(croak);
-
-use base 'FS::Record';
-
-use FS::Record qw(qsearchs);
-
-=head1 NAME
-
-FS::realestate_subproperty - Object representing a realestate_subproperty record
-
-=head1 SYNOPSIS
-
-  use FS::realestate_subproperty;
-
-  $record = new FS::realestate_subproperty \%values;
-  $record = new FS::realestate_subproperty {
-    propnum => 65535,
-    subtitle => 'Box Seat No. 42',
-  };
-
-  $error = $record->insert;
-  $error = new_rec->replace($record);
-  $error = $record->check;
-
-  $parent = $record->property;
-
-=head1 DESCRIPTION
-
-An FS::realestate_subproperty object represents a unit of real estate property.
-Every L<FS::realestate_property> must contain at least one subproperty, or unit,
-which is the actual unit considered for sale, rent, etc as tied to
-L<FS::svc_realestate>.
-
-FS::realestate_subproperty inherits from FS::Record.
-
-The following fields are currently supported:
-
-=over 4
-
-=item subpropnum
-
-=item propnum
-
-=item subtitle
-
-=item disabled
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF (see L<FS::Record>)
-
-=cut
-
-sub table {'realestate_subproperty';}
-
-=item insert (see L<FS::Record>)
-
-=item delete
-
-  FS::realestate_subproperty 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_subproperty 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 the parent L<FS::realestate_property>
-
-=cut
-
-sub agent {
-  shift->property->agent;
-}
-
-=item property
-
-Returns the associated parent L<FS::realestate_property> record
-
-=cut
-
-sub property {
-  my $self = shift;
-  exists $self->{property}
-  ? $self->{property}
-  : $self->{property} = qsearchs('realestate_property',$self->propnum);
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=cut
-
-
-
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::record>, L<FS::realestate_property>, 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..98b333765
--- /dev/null
+++ b/FS/FS/realestate_unit.pm
@@ -0,0 +1,134 @@
+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 custnum
+
+=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
+
+=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/t/realestate_property.t b/FS/t/realestate_location.t
similarity index 77%
rename from FS/t/realestate_property.t
rename to FS/t/realestate_location.t
index af7f2f4de..ecb1d8be9 100644
--- a/FS/t/realestate_property.t
+++ b/FS/t/realestate_location.t
@@ -1,5 +1,5 @@
 BEGIN { $| = 1; print "1..1\n" }
 END {print "not ok 1\n" unless $loaded;}
-use FS::realestate_property;
+use FS::realestate_location;
 $loaded=1;
 print "ok 1\n";
diff --git a/FS/t/realestate_subproperty.t b/FS/t/realestate_unit.t
similarity index 75%
rename from FS/t/realestate_subproperty.t
rename to FS/t/realestate_unit.t
index 0f00eeec5..bbecc1a4c 100644
--- a/FS/t/realestate_subproperty.t
+++ b/FS/t/realestate_unit.t
@@ -1,5 +1,5 @@
 BEGIN { $| = 1; print "1..1\n" }
 END {print "not ok 1\n" unless $loaded;}
-use FS::realestate_subproperty;
+use FS::realestate_unit;
 $loaded=1;
 print "ok 1\n";

commit 4c1510910211827eec5cb6a4bd3726857e6fb94c
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Sun Dec 10 03:40:06 2017 +0000

    rt# 74031 work in progress
    
     - Update schemas
     - First draft for FS::realestate_property
     - and FS::realestate_subproperty
     - worthless test for new modules
     - Add a menuitem for property management

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6d882046f..b94407bc8 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7658,14 +7658,14 @@ sub tables_hashref {
 
     'realestate_subproperty' => {
       'columns' => [
-        'spropnum', 'serial',  '', '',      '',  '',
-        'propnum',  'int',     '', '',      '',  '',
-        'subtitle', 'varchar', '', $char_d, '',  '',
-        'disabled', 'char',    '', 1,       '0', '',
+        'subpropnum', 'serial',  '', '',      '',  '',
+        'propnum',    'int',     '', '',      '',  '',
+        'subtitle',   'varchar', '', $char_d, '',  '',
+        'disabled',   'char',    '', 1,       '0', '',
       ],
-      'primary_key'  => 'spropnum',
+      'primary_key'  => 'subpropnum',
       'unique'       => [],
-      'index'        => [ ['propnum'] ],
+      'index'        => [ ['propnum'],['subtitle'] ],
       'foreign_keys' => [
         {columns => ['propnum'], table => 'realestate_property'}
       ],
diff --git a/FS/FS/realestate_property.pm b/FS/FS/realestate_property.pm
new file mode 100644
index 000000000..4e564175a
--- /dev/null
+++ b/FS/FS/realestate_property.pm
@@ -0,0 +1,176 @@
+package FS::realestate_property;
+use strict;
+use warnings;
+use Carp qw(croak);
+
+use base 'FS::Record';
+use FS::Record qw(qsearch qsearchs);
+
+=head1 NAME
+
+FS::realestate_property - Object representing a realestate_property record
+
+=head1 SYNOPSIS
+
+  use FS::realestate_property;
+
+  $record = new FS:realestate_property  \%values;
+  $record = new FS::realestate_property {
+    title    => 'Superdome',
+    address1 => '1500 Sugar Bowl Dr',
+    city     => 'New Orleans',
+    state    => 'LA',
+    zip      => '70112',
+    disabled => 0,
+    agentnum => 1,
+  };
+
+  $error = $record->insert;
+  $error = $new_rec->replace($record)
+  $error = $record->check;
+
+  $sub_record = $record->add_subproperty('Box Seat No. 42');
+
+  @subprops = $record->subproperties;
+  @subprops = $record->enabled_subproperties;
+
+=head1 DESCRIPTION
+
+An FS::realestate_property object represents a real estate property.  This
+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.
+
+FS::realestate_property inherits from FS::Record.
+
+The following fields are currently supported:
+
+=over 4
+
+=item propnum
+
+=item agentnum
+
+=item 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_property';}
+
+=item insert (see L<FS::Record>)
+
+=item delete
+
+  FS::realestate_property 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_property 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 subproperties
+
+Returns all subproperties linked to this property
+
+=cut
+
+sub subproperties {
+  qsearch('realestate_subproperty', {propnum => shift->propnum} );
+}
+
+
+=item enabled_subproperties
+
+Returns all subproperties linked to this property where disabled = 0
+
+=cut
+
+sub enabled_subproperties {
+  qsearch( 'realestate_subproperty', {
+    propnum => shift->propnum,
+    disabled => 0,
+  });
+}
+
+=item add_subproperty SUBPROPERTY_TITLE
+
+Create a new subproperty record for this property
+
+=cut
+
+sub add_subproperty {
+  my ($self, $subtitle) = @_;
+  croak "add_subproperty() requires a \$subtitle parameter" unless $subtitle;
+
+  my $subp = new FS::realestate_subproperty {
+    propnum  => $self->propnum,
+    subtitle => $subtitle,
+    disabled => 0,
+  };
+  my $err = $subp->insert;
+  die "Error inserting subproperty: $err" if $err;
+  $subp;
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=cut
+
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::record>, L<FS::realestate_subproperty>, L<FS::svc_realestate>
+
+=cut
+
+1;
diff --git a/FS/FS/realestate_subproperty.pm b/FS/FS/realestate_subproperty.pm
new file mode 100644
index 000000000..5a93934da
--- /dev/null
+++ b/FS/FS/realestate_subproperty.pm
@@ -0,0 +1,125 @@
+package FS::realestate_subproperty;
+use strict;
+use warnings;
+use Carp qw(croak);
+
+use base 'FS::Record';
+
+use FS::Record qw(qsearchs);
+
+=head1 NAME
+
+FS::realestate_subproperty - Object representing a realestate_subproperty record
+
+=head1 SYNOPSIS
+
+  use FS::realestate_subproperty;
+
+  $record = new FS::realestate_subproperty \%values;
+  $record = new FS::realestate_subproperty {
+    propnum => 65535,
+    subtitle => 'Box Seat No. 42',
+  };
+
+  $error = $record->insert;
+  $error = new_rec->replace($record);
+  $error = $record->check;
+
+  $parent = $record->property;
+
+=head1 DESCRIPTION
+
+An FS::realestate_subproperty object represents a unit of real estate property.
+Every L<FS::realestate_property> must contain at least one subproperty, or unit,
+which is the actual unit considered for sale, rent, etc as tied to
+L<FS::svc_realestate>.
+
+FS::realestate_subproperty inherits from FS::Record.
+
+The following fields are currently supported:
+
+=over 4
+
+=item subpropnum
+
+=item propnum
+
+=item subtitle
+
+=item disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF (see L<FS::Record>)
+
+=cut
+
+sub table {'realestate_subproperty';}
+
+=item insert (see L<FS::Record>)
+
+=item delete
+
+  FS::realestate_subproperty 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_subproperty 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 the parent L<FS::realestate_property>
+
+=cut
+
+sub agent {
+  shift->property->agent;
+}
+
+=item property
+
+Returns the associated parent L<FS::realestate_property> record
+
+=cut
+
+sub property {
+  my $self = shift;
+  exists $self->{property}
+  ? $self->{property}
+  : $self->{property} = qsearchs('realestate_property',$self->propnum);
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=cut
+
+
+
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::record>, L<FS::realestate_property>, L<FS::svc_realestate>
+
+=cut
+
+1;
diff --git a/FS/t/realestate_property.t b/FS/t/realestate_property.t
new file mode 100644
index 000000000..af7f2f4de
--- /dev/null
+++ b/FS/t/realestate_property.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::realestate_property;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/realestate_subproperty.t b/FS/t/realestate_subproperty.t
new file mode 100644
index 000000000..0f00eeec5
--- /dev/null
+++ b/FS/t/realestate_subproperty.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::realestate_subproperty;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index defcc494f..3532e6074 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -829,11 +829,16 @@ $config_misc{'Custom fields'} = [ $fsurl.'browse/part_virtual_field.html', 'Loca
   if $curuser->access_right('Edit custom fields');
 $config_misc{'Translation strings'} = [ $fsurl.'browse/msgcat.html', 'Translations and other customizable labels for each locale' ]
   if $curuser->access_right('Configuration');
-$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]
+$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/realestate_inventory.html', 'Setup inventory classes and stock inventory' ]
   if $curuser->access_right('Edit inventory')
   || $curuser->access_right('Edit global inventory')
   || $curuser->access_right('Configuration');
 
+$config_misc{'Real estate inventory'} = [ $fsurl.'browse/inventory_realestate.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>
-

commit 0521301227e488e4bef7a65dd814b1489cd06972
Author: Mitch Jackson <mitch at freeside.biz>
Date:   Sat Dec 9 23:00:21 2017 +0000

    rt# 74031 Update database schema

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6d7520bd9..6d882046f 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -495,7 +495,42 @@ sub tables_hashref {
 
   my $username_len = 64; #usernamemax config file
 
-    # name type nullability length default local
+  # Returns 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 is 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,
+  #   ...
+  #
+  # ],
 
   return {
 
@@ -7601,6 +7636,41 @@ sub tables_hashref {
       'foreign_keys'  => [],
     },
 
+    'realestate_property' => {
+      'columns' => [
+        'propnum',  'serial',  '',     '',      '',  '',
+        'agentnum', 'int',     'NULL', '',      '',  '',
+        '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',    '',     1,       '0', '',
+      ],
+      'primary_key'  => 'propnum',
+      'unique'       => [],
+      'index'        => [ ['agentnum'] ],
+      'foreign_keys' => [
+        {columns => ['agentnum'], table => 'agent'}
+      ],
+    },
+
+    'realestate_subproperty' => {
+      'columns' => [
+        'spropnum', 'serial',  '', '',      '',  '',
+        'propnum',  'int',     '', '',      '',  '',
+        'subtitle', 'varchar', '', $char_d, '',  '',
+        'disabled', 'char',    '', 1,       '0', '',
+      ],
+      'primary_key'  => 'spropnum',
+      'unique'       => [],
+      'index'        => [ ['propnum'] ],
+      'foreign_keys' => [
+        {columns => ['propnum'], table => 'realestate_property'}
+      ],
+    },
+
     # name type nullability length default local
 
     #'new_table' => {
@@ -7627,4 +7697,3 @@ L<DBIx::DBSchema>
 =cut
 
 1;
-

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





More information about the freeside-commits mailing list