Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modal to update meter connections #4863

Open
wants to merge 4 commits into
base: inventory-groups-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion seed/api/v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from seed.views.v3.historical_notes import HistoricalNoteViewSet
from seed.views.v3.import_files import ImportFileViewSet
from seed.views.v3.inventory_group_mappings import InventoryGroupMappingViewSet
from seed.views.v3.inventory_groups import InventoryGroupViewSet
from seed.views.v3.inventory_groups import InventoryGroupMetersViewSet, InventoryGroupViewSet
from seed.views.v3.label_inventories import LabelInventoryViewSet
from seed.views.v3.labels import LabelViewSet
from seed.views.v3.measures import MeasureViewSet
Expand Down Expand Up @@ -149,6 +149,7 @@

inventory_group_router = nested_routers.NestedSimpleRouter(api_v3_router, r"inventory_groups", lookup="inventory_group")
inventory_group_router.register(r"systems", SystemViewSet, basename="inventory_group-systems")
inventory_group_router.register(r"meters", InventoryGroupMetersViewSet, basename="inventory_group-meters")

system_router = nested_routers.NestedSimpleRouter(inventory_group_router, r"systems", lookup="system")
system_router.register(r"services", ServiceViewSet, basename="system-services")
Expand Down
37 changes: 23 additions & 14 deletions seed/models/meters.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,6 @@ def presave_meter(sender, instance, **kwargs):
if property is not None and system is not None:
raise IntegrityError(f"Meter {instance.id} has both a property and a system. It must only have one.")

# Only systems have connection type "total"
system_connections = connection_type in [Meter.TOTAL_FROM_PATRON, Meter.TOTAL_TO_PATRON]
if system_connections and instance.system is None:
raise IntegrityError(f"Meter {instance.id} has connection_type {connection_type}, but is not connected to a system")

outside_connection = connection_type in [Meter.FROM_OUTSIDE, Meter.TO_OUTSIDE]
if outside_connection:
# outside connections don't have services
Expand All @@ -232,15 +227,29 @@ def presave_meter(sender, instance, **kwargs):
if service is None:
raise IntegrityError(f"Meter {instance.id} has connection_type {connection_type}, but is not connected to a service")

# inside connections must be within the group
if property is not None:
meter_groups = InventoryGroupMapping.objects.filter(property=property).values_list("group_id", flat=True)
else:
meter_groups = [system.group]
if service is not None and service.system.group.id not in meter_groups:
raise IntegrityError(
f"Meter {instance.id} on property {property.id} and has service {service.id}, but meter is not in the services group"
)
total_connections = connection_type in [Meter.TOTAL_FROM_PATRON, Meter.TOTAL_TO_PATRON]
if total_connections:
# Only systems have connection type "total"
if system is None:
raise IntegrityError(f"Meter {instance.id} has connection_type {connection_type}, but is not connected to a system")

# Total connections must have a service owned by system
if system.id != service.system_id:
raise IntegrityError(
f"Meter {instance.id} on system {system.id} has connection_type {connection_type}, but is also connected to service {service.id}, which is on a different system, {service.system_id}. Meters with connection_type {connection_type} must have a service on the system the meter is connected to"
)

# Service should only have one meter of each "total" connection type
if Meter.objects.filter(service=service, connection_type=connection_type).exclude(pk=instance.pk).exists():
raise IntegrityError(f"Service {service.id} already has a meter with connection type {connection_type}")

else: # Meter.FROM_PATRON_TO_SERVICE and Meter.FROM_SERVICE_TO_PATRON
# service must be within the meter's property's group
property_groups = InventoryGroupMapping.objects.filter(property=property).values_list("group_id", flat=True)
if service is not None and service.system.group.id not in property_groups:
raise IntegrityError(
f"Meter {instance.id} on property {property.id} and has service {service.id}, but meter and property are not in the service's group"
)


class MeterReading(models.Model):
Expand Down
15 changes: 14 additions & 1 deletion seed/serializers/inventory_groups.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# !/usr/bin/env python
import logging

from django.core.exceptions import ValidationError
from rest_framework import serializers

from seed.models import VIEW_LIST_INVENTORY_TYPE, InventoryGroup, InventoryGroupMapping, PropertyView, TaxLotView
from seed.serializers.access_level_instances import AccessLevelInstanceSerializer
from seed.serializers.base import ChoiceField
from seed.serializers.systems import SystemSerializer

logger = logging.getLogger()


class InventoryGroupMappingSerializer(serializers.ModelSerializer):
Expand All @@ -21,8 +26,16 @@ def get_group_name(self, obj):
class InventoryGroupSerializer(serializers.ModelSerializer):
inventory_type = ChoiceField(choices=VIEW_LIST_INVENTORY_TYPE)
access_level_instance_data = AccessLevelInstanceSerializer(source="access_level_instance", many=False, read_only=True)
systems = serializers.SerializerMethodField()

def get_systems(self, obj):
return SystemSerializer(obj.systems.all().select_subclasses(), many=True).data

def __init__(self, *args, **kwargs):
include_systems = kwargs.pop("include_systems", False)
if not include_systems:
self.fields.pop("systems")

if "inventory" not in kwargs:
super().__init__(*args, **kwargs)
return
Expand All @@ -33,7 +46,7 @@ def __init__(self, *args, **kwargs):

class Meta:
model = InventoryGroup
fields = ("id", "name", "inventory_type", "access_level_instance", "access_level_instance_data", "organization")
fields = ("id", "name", "inventory_type", "access_level_instance", "access_level_instance_data", "organization", "systems")

def to_representation(self, obj):
result = super().to_representation(obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,17 @@ angular.module('SEED.controller.inventory_detail_meters', []).controller('invent
};

$scope.open_meter_connection_edit_modal = (meter) => {
// TODO
console.log(meter);
$uibModal.open({
templateUrl: `${urls.static_url}seed/partials/meter_edit_modal.html`,
controller: 'meter_edit_modal_controller',
resolve: {
organization_id: () => $scope.organization.id,
meter: () => meter,
property_id: () => inventory_payload.property.id,
view_id: () => $scope.inventory.view_id,
refresh_meters_and_readings: () => $scope.refresh_meters_and_readings
}
});
};

$scope.apply_column_settings = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@
'$scope',
'$state',
'$stateParams',
'cycles',
'inventory_group_service',
// eslint-disable-next-line func-names
function (
$scope,
$state,
$stateParams
$stateParams,
cycles,
inventory_group_service,

Check failure on line 18 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Unexpected trailing comma
) {
$scope.inventory_type = $stateParams.inventory_type;
$scope.group_id = $stateParams.group_id;
$scope.cycles = cycles.cycles;
$scope.selectedCycle = $scope.cycles[0]?? undefined;

Check failure on line 23 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Operator '??' must be spaced
$scope.data = {}

Check failure on line 24 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Missing semicolon
inventory_group_service.get_dashboard_info($scope.group_id, $scope.selectedCycle.id).then(data => $scope.data=data)

Check failure on line 25 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Expected parentheses around arrow function argument

Check failure on line 25 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Arrow function should not return assignment

Check failure on line 25 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Operator '=' must be spaced

Check failure on line 25 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Missing semicolon

$scope.changeCycle = () => {
inventory_group_service.get_dashboard_info($scope.group_id, $scope.selectedCycle.id).then(data => $scope.data=data)

Check failure on line 28 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Expected parentheses around arrow function argument

Check failure on line 28 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Arrow function should not return assignment

Check failure on line 28 in seed/static/seed/js/controllers/inventory_group_detail_dashboard_controller.js

View workflow job for this annotation

GitHub Actions / formatting (lint)

Operator '=' must be spaced
}
}]);
58 changes: 58 additions & 0 deletions seed/static/seed/js/controllers/meter_edit_modal_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* SEED Platform (TM), Copyright (c) Alliance for Sustainable Energy, LLC, and other contributors.
* See also https://github.com/SEED-platform/seed/blob/main/LICENSE.md
*/
angular.module('SEED.controller.meter_edit_modal', []).controller('meter_edit_modal_controller', [
'$scope',
'$state',
'$uibModalInstance',
'inventory_group_service',
'meter_service',
'spinner_utility',
'organization_id',
'meter',
'property_id',
'view_id',
'refresh_meters_and_readings',
// eslint-disable-next-line func-names
function (
$scope,
$state,
$uibModalInstance,
inventory_group_service,
meter_service,
spinner_utility,
organization_id,
meter,
property_id,
view_id,
refresh_meters_and_readings
) {
$scope.selectedGroup = null;
$scope.selectedSystem = null;
$scope.selectedService = null;

$scope.potentialGroups = null;
$scope.potentialSystems = null;
$scope.potentialServices = null;

inventory_group_service.get_groups_for_inventory("properties", [property_id], include_systems=true).then((groups) => {
$scope.potentialGroups = groups;
});

$scope.selectGroup = () => {$scope.potentialSystems = $scope.selectedGroup.systems};
$scope.selectSystem = () => {$scope.potentialServices = $scope.selectedSystem.services};

$scope.update_meter = () => {
meter_service.update_meter_connection(organization_id, view_id, meter.id, $scope.selectedService?.id).then(() => {
refresh_meters_and_readings();
spinner_utility.show();
$uibModalInstance.dismiss('cancel');
});
};

$scope.cancel = () => {
$uibModalInstance.dismiss('cancel');
};
}
]);
5 changes: 4 additions & 1 deletion seed/static/seed/js/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
'SEED.controller.menu',
'SEED.controller.merge_modal',
'SEED.controller.meter_deletion_modal',
'SEED.controller.meter_edit_modal',
'SEED.controller.modified_modal',
'SEED.controller.move_inventory_modal',
'SEED.controller.new_member_modal',
Expand Down Expand Up @@ -2300,7 +2301,9 @@
url: '/{inventory_type:properties|taxlots}/groups/{group_id:int}',
templateUrl: `${static_url}seed/partials/inventory_group_detail_dashboard.html`,
controller: 'inventory_group_detail_dashboard_controller',
resolve: {}
resolve: {
cycles: ['cycle_service', (cycle_service) => cycle_service.get_cycles()],
}
})
.state({
name: 'inventory_group_detail_meters',
Expand Down
21 changes: 17 additions & 4 deletions seed/static/seed/js/services/inventory_group_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ angular.module('SEED.service.inventory_group', []).factory('inventory_group_serv

/* Passing an inventory type will return all groups & corresponding inv type they're applied to
Passing inventory type & filter_ids will return all groups, limited to only selected props/taxlots */
group_factory.get_groups_for_inventory = (inventory_type, filter_ids) => {
group_factory.get_groups_for_inventory = (inventory_type, filter_ids, include_systems=false) => {
const params = {
organization_id: user_service.get_organization().id
organization_id: user_service.get_organization().id,
include_systems,
};
let body = null;
if (inventory_type === 'properties') {
params.inventory_type = 'property';
body = { selected: filter_ids };
} else if (inventory_type === 'taxlots') {
params.inventory_type = 'tax_lot';
body = { selected: filter_ids };

}
body = { selected: filter_ids };

return $http.post('/api/v3/inventory_groups/filter/', body, {
params
}).then(map_groups);
Expand Down Expand Up @@ -81,6 +83,17 @@ angular.module('SEED.service.inventory_group', []).factory('inventory_group_serv
}).then((response) => response.data.data);
};

group_factory.get_dashboard_info = (id, cycle_id) => {
return $http.get(`/api/v3/inventory_groups/${id}/dashboard/`,
{
params: {
organization_id: user_service.get_organization().id,
cycle_id: cycle_id
}
}
).then((response) => response.data.data);
};

group_factory.remove_group = (id) => {
if (id === null) {
Notification.error('This group is protected from modifications');
Expand Down
8 changes: 8 additions & 0 deletions seed/static/seed/js/services/meter_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ angular.module('SEED.service.meter', []).factory('meter_service', [
.then((response) => response)
.catch((response) => response);

meter_factory.update_meter_connection = (organization_id, property_view_id, meter_id, service_id) => $http
.put(
`/api/v3/properties/${property_view_id}/meters/${meter_id}/update_connection/?organization_id=${organization_id}`,
{ service_id },
)
.then((response) => response)
.catch((response) => response);

meter_factory.property_meter_usage = (property_view_id, organization_id, interval, excluded_meter_ids) => {
if (_.isUndefined(excluded_meter_ids)) excluded_meter_ids = [];
return $http
Expand Down
14 changes: 13 additions & 1 deletion seed/static/seed/partials/inventory_group_detail_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,19 @@ <h1>{$:: 'Group Detail' | translate $}</h1>
<div class="section_nav_container">
<div class="section_nav" ng-include="::urls.static_url + 'seed/partials/inventory_group_detail_nav.html'"></div>
</div>
<div class="ellipsis">
<span style="display: flex; align-items: baseline; gap: 1rem">
<strong>{$:: 'Cycle' | translate $}:</strong>
<select class="form-control" style="width: auto" ng-model="selectedCycle" ng-change="changeCycle()">
<option ng-repeat="cycle in cycles" ng-value="cycle">{$ cycle.name $}</option>
</select>
</span>
</div>

<div>
<div style="display: flex; justify-content: center; margin: 100px; font-style: italics">DASHBOARD IN DEVELOPMENT...</div>
<div style="display: flex; flex-direction:column">
<div ng-repeat="(key, item) in data">
<div>{$ key $}: {$ item $}</div>
</div>
</div>
</div>
31 changes: 31 additions & 0 deletions seed/static/seed/partials/meter_edit_modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div class="modal-header">
</div>

<div class="modal-body row" style="padding-bottom: 0">
<div class="form-group">
<label class="control-label col-sm-3" for="group" translate>group:</label>
<div class="col-sm-8">
<select class="form-control" id="selectedGroup" ng-model="selectedGroup" ng-change="selectGroup()"
ng-options="group as group.name for group in potentialGroups"></select>
</div>
<label class="control-label col-sm-3" for="group" translate>system:</label>
<div class="col-sm-8">
<select class="form-control" id="selectedSystem" ng-model="selectedSystem" ng-change="selectSystem()"
ng-options="system as system.name for system in potentialSystems"></select>
</div>
<label class="control-label col-sm-3" for="group" translate>service:</label>
<div class="col-sm-8">
<select class="form-control" id="selectedService" ng-model="selectedService"
ng-options="service as service.name for service in potentialServices"></select>
</div>
</div>
</div>

<div class="modal-footer">
<div class="row text-center">
<button type="button" class="btn btn-primary col-sm-6 center-block" ng-click="update_meter()" translate>Confirm</button>
</div>
<div class="row text-center">
<button type="button" class="btn btn-default col-sm-6 center-block" ng-click="cancel()" translate>Dismiss</button>
</div>
</div>
1 change: 1 addition & 0 deletions seed/templates/seed/_scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
<script src="{{ STATIC_URL }}seed/js/controllers/menu_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/merge_modal_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/meter_deletion_modal_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/meter_edit_modal_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/modified_modal_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/move_inventory_modal_controller.js"></script>
<script src="{{ STATIC_URL }}seed/js/controllers/new_member_modal_controller.js"></script>
Expand Down
2 changes: 1 addition & 1 deletion seed/tests/test_inventory_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def create_meter_entry(property_view_id, source_id):
self.meter3_id = create_meter_entry(view3.id, 301)

def test_group_meters(self):
url = reverse_lazy("api:v3:inventory_groups-meters", args=[self.property_group.id]) + f"?organization_id={self.org.id}"
url = reverse_lazy("api:v3:inventory_group-meters-list", args=[self.property_group.id]) + f"?organization_id={self.org.id}"
response = self.client.get(url, content_type="application/json")

data = response.json()["data"]
Expand Down
Loading
Loading