diff --git a/seed/static/seed/js/controllers/inventory_create_controller.js b/seed/static/seed/js/controllers/inventory_create_controller.js index c0055160ad..967a6c231c 100644 --- a/seed/static/seed/js/controllers/inventory_create_controller.js +++ b/seed/static/seed/js/controllers/inventory_create_controller.js @@ -3,146 +3,176 @@ * See also https://github.com/SEED-platform/seed/blob/main/LICENSE.md */ angular.module('SEED.controller.inventory_create', []).controller('inventory_create_controller', [ - '$scope', - '$window', - '$stateParams', - 'ah_service', - 'inventory_service', - 'access_level_tree', - 'all_columns', - 'cycles', - 'profiles', - // eslint-disable-next-line func-names - function ( - $scope, - $window, - $stateParams, - ah_service, - inventory_service, - access_level_tree, - all_columns, - cycles, - profiles, - ) { - $scope.data = { state: { extra_data: {} } }; - $scope.inventory_type = $stateParams.inventory_type; - $scope.inventory_types = ['Property', 'TaxLot']; - const table_name = $scope.inventory_type === 'taxlots' ? 'TaxLotState' : 'PropertyState'; - $scope.cycles = cycles.cycles; - $scope.profiles = profiles; - $scope.profile = []; - $scope.columns = all_columns; - - $scope.matching_columns = []; - $scope.extra_columns = []; - $scope.canonical_columns = []; - $scope.columns.forEach((c) => { - if (c.table_name == table_name) { - if (c.is_matching_criteria) $scope.matching_columns.push(c); - if (c.is_extra_data) $scope.extra_columns.push(c); - if (!c.is_extra_data && !c.derived_column) $scope.canonical_columns.push(c); - } + '$scope', + '$state', + '$stateParams', + 'ah_service', + 'inventory_service', + 'Notification', + 'simple_modal_service', + 'spinner_utility', + 'access_level_tree', + 'all_columns', + 'cycles', + 'profiles', + // eslint-disable-next-line func-names + function ( + $scope, + $state, + $stateParams, + ah_service, + inventory_service, + Notification, + simple_modal_service, + spinner_utility, + access_level_tree, + all_columns, + cycles, + profiles + ) { + $scope.data = { state: { extra_data: {} } }; + $scope.inventory_type = $stateParams.inventory_type; + $scope.inventory_types = ['Property', 'TaxLot']; + const table_name = $scope.inventory_type === 'taxlots' ? 'TaxLotState' : 'PropertyState'; + $scope.cycles = cycles.cycles; + $scope.profiles = profiles; + $scope.profile = []; + $scope.columns = all_columns; + + $scope.matching_columns = []; + $scope.extra_columns = []; + $scope.canonical_columns = []; + $scope.columns.forEach((c) => { + if (c.table_name === table_name) { + if (c.is_matching_criteria) $scope.matching_columns.push(c); + if (c.is_extra_data) $scope.extra_columns.push(c); + if (!c.is_extra_data && !c.derived_column) $scope.canonical_columns.push(c); + } + }); + // create a copy, not a reference. from_column contents is a list of columns dicts + $scope.form_columns = [...$scope.matching_columns]; + // form_values allows value persistance + $scope.form_values = []; + + $scope.$watch('data', () => { + $scope.valid = $scope.data.cycle && $scope.data.access_level_instance && !_.isEqual($scope.data.state, { extra_data: {} }); + }, true); + + // ACCESS LEVEL TREE + $scope.access_level_tree = access_level_tree.access_level_tree; + $scope.level_names = access_level_tree.access_level_names.map((level, i) => ({ + index: i, + name: level + })); + const access_level_instances_by_depth = ah_service.calculate_access_level_instances_by_depth($scope.access_level_tree); + $scope.change_selected_level_index = () => { + const new_level_instance_depth = parseInt($scope.data.level_name_index, 10) + 1; + $scope.potential_level_instances = access_level_instances_by_depth[new_level_instance_depth]; + }; + $scope.data.level_name_index = $scope.level_names.at(-1).index; + $scope.change_selected_level_index(); + $scope.data.access_level_instance = $scope.potential_level_instances.at(0).id; + + // COLUMN LIST PROFILES + $scope.set_columns = (type) => { + remove_empty_last_column(); + switch (type) { + case 'canonical': + $scope.form_columns = Array.from(new Set([...$scope.form_columns, ...$scope.canonical_columns])); + break; + case 'extra': + $scope.form_columns = Array.from(new Set([...$scope.form_columns, ...$scope.extra_columns])); + break; + default: + $scope.form_columns = [...$scope.matching_columns]; + $scope.form_columns = $scope.form_columns.map((c) => ({ ...c, value: null })); + $scope.form_values = []; + } + }; + + // FORM LOGIC + $scope.remove_column = (column, index) => { + $scope.form_columns.splice(index, 1); + $scope.form_values[index] = null; + set_column_value(column, null); + }; + + $scope.add_column = () => $scope.form_columns.push({ displayName: '', table_name }); + + const remove_empty_last_column = () => { + if (!_.isEmpty($scope.form_columns) && $scope.form_columns.at(-1).displayName === '') { + $scope.form_columns.pop(); + } + }; + + $scope.change_profile = () => { + const profile_column_names = $scope.profile.columns.map((p) => p.column_name); + $scope.form_columns = $scope.columns.filter((c) => profile_column_names.includes(c.column_name)); + }; + + $scope.select_column = (column, index) => { + column.value = $scope.form_values.at(index); + $scope.form_columns[index] = column; + }; + + $scope.change_column = (displayName, index) => { + const defaults = { + table_name, + is_extra_data: true, + is_matching_criteria: false, + data_type: 'string' + }; + let column = $scope.columns.find((c) => c.displayName === displayName) || { displayName }; + column = { ...defaults, ...column }; + + column.value = $scope.form_values.at(index); + $scope.form_columns[index] = column; + }; + + $scope.change_value = (column, index) => { + $scope.form_values[index] = column.value; + set_column_value(column, column.value); + }; + + const set_column_value = (column, value) => { + const column_name = column.column_name || column.displayName; + if (!column_name) return; + const target = column.is_extra_data ? $scope.data.state.extra_data : $scope.data.state; + target[column_name] = value; + }; + + $scope.save_inventory = () => { + const type_name = $scope.inventory_type === 'taxlots' ? 'Tax Lot' : 'Property'; + const cycle_name = $scope.cycles.find((c) => c.id === $scope.data.cycle).name; + const ali_name = $scope.access_level_tree.find((ali) => ali.id === $scope.data.access_level_instance).name; + const modalOptions = { + type: 'default', + okButtonText: 'Confirm', + headerText: `Create new ${type_name}`, + bodyText: `Create ${ali_name} ${type_name} in Cycle ${cycle_name}?` + }; + const successOptions = { + type: 'default', + okButtonText: `View ${type_name}`, + headerText: 'Success', + bodyText: `Successfully created ${type_name}` + }; + simple_modal_service.showModal(modalOptions).then(() => { + spinner_utility.show(); + inventory_service.create_inventory($scope.data, $scope.inventory_type).then((response) => { + Notification.success(`Successfully created ${type_name}`); + spinner_utility.hide(); + return response.data.view_id; + }).then((view_id) => { + simple_modal_service.showModal(successOptions).then(() => { + window.location.href = `/app/#/${$scope.inventory_type}/${view_id}`; + }).catch(() => { + $state.reload(); + }); + }).catch(() => { + Notification.error(`Failed to create ${type_name}`); }); - // create a copy, not a reference. from_column contents is a list of columns dicts - $scope.form_columns =[...$scope.matching_columns]; - // form_values allows value persistance - $scope.form_values = []; - - $scope.$watch('data', () => { - console.log('watch') - $scope.valid = $scope.data.cycle && $scope.data.access_level_instance && !_.isEqual($scope.data.state, { extra_data: {} }); - }, true) - - - // ACCESS LEVEL TREE - $scope.access_level_tree = access_level_tree.access_level_tree; - $scope.level_names = access_level_tree.access_level_names.map((level, i) => ({ - index: i, - name: level - })); - const access_level_instances_by_depth = ah_service.calculate_access_level_instances_by_depth($scope.access_level_tree); - $scope.change_selected_level_index = () => { - const new_level_instance_depth = parseInt($scope.data.level_name_index, 10) + 1; - $scope.potential_level_instances = access_level_instances_by_depth[new_level_instance_depth]; - }; - $scope.data.level_name_index = $scope.level_names.at(-1).index; - $scope.change_selected_level_index(); - $scope.data.access_level_instance = $scope.potential_level_instances.at(0).id; - - // COLUMN LIST PROFILES - $scope.set_columns = (type) => { - remove_empty_last_column(); - switch (type) { - case 'canonical': - $scope.form_columns = Array.from(new Set([...$scope.form_columns, ...$scope.canonical_columns])); - break; - case 'extra': - $scope.form_columns = Array.from(new Set([...$scope.form_columns, ...$scope.extra_columns])); - break; - default: - $scope.form_columns = [...$scope.matching_columns]; - $scope.form_columns = $scope.form_columns.map(c => ({...c, value: null})); - $scope.form_values = []; - } - } - - // FORM LOGIC - $scope.remove_column = (column, index) => { - $scope.form_columns.splice(index, 1) - $scope.form_values[index] = null; - set_column_value(column, null); - }; - - $scope.add_column = () => $scope.form_columns.push({ displayName: '', table_name: table_name }); - - const remove_empty_last_column = () => { - if (!_.isEmpty($scope.form_columns) && $scope.form_columns.at(-1).displayName === '') { - $scope.form_columns.pop(); - } - } - - $scope.change_profile = () => { - const profile_column_names = $scope.profile.columns.map(p => p.column_name); - $scope.form_columns = $scope.columns.filter(c => profile_column_names.includes(c.column_name)) - } - - $scope.select_column = (column, index) => { - column.value = $scope.form_values.at(index); - $scope.form_columns[index] = column; - } - - $scope.change_column = (displayName, index) => { - const defaults = { - table_name: table_name, - is_extra_data: true, - is_matching_criteria: false, - data_type: 'string', - } - let column = $scope.columns.find(c => c.displayName === displayName) || {displayName: displayName}; - column = {...defaults, ...column}; - - column.value = $scope.form_values.at(index); - $scope.form_columns[index] = column; - }; - - $scope.change_value = (column, index) => { - $scope.form_values[index] = column.value; - set_column_value(column, column.value) - } - - const set_column_value = (column, value) => { - const column_name = column.column_name || column.displayName; - if (!column_name) return - const target = column.is_extra_data ? $scope.data.state.extra_data : $scope.data.state; - target[column_name] = value; - } - - $scope.save_inventory = () => { - console.log($scope.data.state) - inventory_service.create_inventory($scope.data, $scope.inventory_type).then((data) => { - console.log('>>>', data) - }) - } - - } + }); + }; + } ]); diff --git a/seed/static/seed/js/seed.js b/seed/static/seed/js/seed.js index 6535761b12..ab0c49e16a 100644 --- a/seed/static/seed/js/seed.js +++ b/seed/static/seed/js/seed.js @@ -2275,7 +2275,7 @@ const inventory_type = $stateParams.inventory_type === 'properties' ? 'Property' : 'Tax Lot'; return inventory_service.get_column_list_profiles('List View Profile', inventory_type); } - ], + ] } }) .state({ diff --git a/seed/static/seed/js/services/inventory_service.js b/seed/static/seed/js/services/inventory_service.js index a4fadbc95f..f683f461a5 100644 --- a/seed/static/seed/js/services/inventory_service.js +++ b/seed/static/seed/js/services/inventory_service.js @@ -1294,13 +1294,11 @@ angular.module('SEED.service.inventory', []).factory('inventory_service', [ taxlot_view_ids }).then((response) => response.data); - inventory_service.create_inventory = (data, inventory_type) => { - return $http.post(`/api/v3/${inventory_type}/form_create/`, data, { - params: { - organization_id: user_service.get_organization().id - } - }); - } + inventory_service.create_inventory = (data, inventory_type) => $http.post(`/api/v3/${inventory_type}/form_create/`, data, { + params: { + organization_id: user_service.get_organization().id + } + }); return inventory_service; } diff --git a/seed/static/seed/partials/inventory_create.html b/seed/static/seed/partials/inventory_create.html index 9783c2b8f4..d82f180398 100644 --- a/seed/static/seed/partials/inventory_create.html +++ b/seed/static/seed/partials/inventory_create.html @@ -5,121 +5,126 @@
-
-
+
-
-
-

Create a {$ inventory_type === 'properties' ? 'Property' : 'Tax Lot'$}

-
-
- -
+
+
+

Create a {$ inventory_type === 'properties' ? 'Property' : 'Tax Lot'$}

+
+ +
+
-
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
Inventory TypeColumnValueMatching CriteriaExtra DataData Type
{$ column.table_name.slice(0, -5) $} - - - - {$ column.is_matching_criteria $}{$ column.is_extra_data $}{$ column.data_type $} - -
- -
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
-
\ No newline at end of file +
+
+ + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Inventory TypeColumnValueMatching CriteriaExtra DataData Type
{$ column.table_name.slice(0, -5) $} + + + + {$ column.is_matching_criteria $}{$ column.is_extra_data $}{$ column.data_type $} + +
+ +
+
+
diff --git a/seed/static/seed/scss/style.scss b/seed/static/seed/scss/style.scss index 328e68e9a8..bf83e93021 100755 --- a/seed/static/seed/scss/style.scss +++ b/seed/static/seed/scss/style.scss @@ -1668,6 +1668,7 @@ a:not([href]) { border-collapse: separate; border-spacing: 5px; } + .content-row { display: flex; margin: 10px 0; diff --git a/seed/tests/test_property_views.py b/seed/tests/test_property_views.py index d7d514f59d..4302595cb2 100644 --- a/seed/tests/test_property_views.py +++ b/seed/tests/test_property_views.py @@ -47,7 +47,6 @@ TaxLotProperty, TaxLotView, ) -from seed.serializers.columns import ColumnSerializer from seed.serializers.properties import PropertyStatePromoteWritableSerializer, PropertyStateSerializer from seed.test_helpers.fake import ( FakeColumnFactory, @@ -672,11 +671,7 @@ def test_property_form_create(self): "extra_data": {"Extra Data Column": "456"}, } - data = { - "access_level_instance": self.org.root.id, - "cycle": self.cycle.id, - "state": state_data - } + data = {"access_level_instance": self.org.root.id, "cycle": self.cycle.id, "state": state_data} self.client.post(url, json.dumps(data), content_type="application/json") # For a new property, counts should only increase by 1 @@ -684,8 +679,7 @@ def test_property_form_create(self): assert PropertyView.objects.count() == original_view_count + 1 assert Property.objects.count() == original_property_count + 1 - - # verify with existing matches + # verify with existing matches cycle2 = self.cycle_factory.get_cycle(start=datetime(2000, 10, 10, tzinfo=get_current_timezone())) self.property_view_factory.get_property_view(cycle=cycle2, pm_property_id="456", custom_id_1="DEF") @@ -694,11 +688,7 @@ def test_property_form_create(self): "custom_id_1": "DEF", "extra_data": {"Extra Data Column": "GHI"}, } - data = { - "access_level_instance": self.org.root.id, - "cycle": self.cycle.id, - "state": state_data - } + data = {"access_level_instance": self.org.root.id, "cycle": self.cycle.id, "state": state_data} self.client.post(url, json.dumps(data), content_type="application/json") # For property merges, counts should increase by 2 (new property, and merged property) @@ -2756,4 +2746,3 @@ def test_update_property_view_with_espm(self): # verify that the property has meters too, which came from the XLSX file self.assertEqual(pv.property.meters.count(), 2) - diff --git a/seed/views/v3/properties.py b/seed/views/v3/properties.py index 53149241ac..3bd2a9c323 100644 --- a/seed/views/v3/properties.py +++ b/seed/views/v3/properties.py @@ -90,7 +90,7 @@ from seed.utils.api_schema import AutoSchemaHelper, swagger_auto_schema_org_query_param from seed.utils.inventory_filter import get_filtered_results from seed.utils.labels import get_labels -from seed.utils.match import MergeLinkPairError, match_merge_link, get_matching_criteria_column_names +from seed.utils.match import MergeLinkPairError, get_matching_criteria_column_names, match_merge_link from seed.utils.merge import merge_properties from seed.utils.meters import PropertyMeterReadingsExporter from seed.utils.properties import get_changed_fields, pair_unpair_property_taxlot, properties_across_cycles, update_result_with_master @@ -1970,8 +1970,8 @@ def form_create(self, request): organization_id=org_id, table_name="PropertyState", ) - - # Create stub stateĀ  + + # Create stub state state = PropertyState.objects.create(organization_id=org_id) # get_or_create existing property and view matching_columns = get_matching_criteria_column_names(org_id, "PropertyState") @@ -1988,9 +1988,8 @@ def form_create(self, request): # Use existing view endpoint to ensure matching, merging, and linking. return self.update(request, pk=view.id) -def _row_from_views(views): - +def _row_from_views(views): data = pd.Series() def mode(field, extra_data=False):