diff --git a/compensation/migrations/0011_ecoaccount_deductable_rest.py b/compensation/migrations/0011_ecoaccount_deductable_rest.py new file mode 100644 index 00000000..96ab2d3b --- /dev/null +++ b/compensation/migrations/0011_ecoaccount_deductable_rest.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.3 on 2022-10-11 11:39 + +from django.db import migrations, models +from django.db.models import Sum + + +def fill_deductable_rest(apps, schema_editor): + EcoAccount = apps.get_model("compensation", "EcoAccount") + accs = EcoAccount.objects.all() + for acc in accs: + + deductions = acc.deductions.filter( + intervention__deleted=None, + ) + deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0 + available_surfaces = acc.deductable_surface or deductions_surfaces + rest = available_surfaces - deductions_surfaces + + acc.deductable_rest = rest + acc.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('compensation', '0010_auto_20220815_1030'), + ] + + operations = [ + migrations.AddField( + model_name='ecoaccount', + name='deductable_rest', + field=models.FloatField(blank=True, default=0, help_text='Amount of deductable rest', null=True), + ), + migrations.RunPython(fill_deductable_rest) + ] diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py index dc9e336f..48414cca 100644 --- a/compensation/models/eco_account.py +++ b/compensation/models/eco_account.py @@ -35,6 +35,12 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix help_text="Amount of deductable surface - can be lower than the total surface due to deduction limitations", default=0, ) + deductable_rest = models.FloatField( + blank=True, + null=True, + help_text="Amount of deductable rest", + default=0, + ) legal = models.OneToOneField( "intervention.Legal", @@ -100,28 +106,22 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix """ return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0 - def get_available_rest(self) -> (float, float): + def __calculate_deductable_rest(self): """ Calculates available rest surface of the eco account Args: Returns: ret_val_total (float): Total amount - ret_val_relative (float): Amount as percentage (0-100) """ deductions = self.deductions.filter( intervention__deleted=None, ) deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0 available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero - ret_val_total = available_surfaces - deductions_surfaces + ret_val = available_surfaces - deductions_surfaces - if available_surfaces > 0: - ret_val_relative = int((ret_val_total / available_surfaces) * 100) - else: - ret_val_relative = 0 - - return ret_val_total, ret_val_relative + return ret_val def quality_check(self) -> EcoAccountQualityChecker: """ Quality check @@ -181,6 +181,29 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix for team_id in shared_teams: celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change) + def update_deductable_rest(self): + """ + Updates deductable_rest, which holds the amount of rest surface for this account. + + Returns: + + """ + self.deductable_rest = self.__calculate_deductable_rest() + self.save() + + def get_deductable_rest_relative(self): + """ + Returns deductable_rest relative to deductable_surface mapped to [0,100] + + Returns: + + """ + try: + ret_val = int((self.deductable_rest / (self.deductable_surface or 0)) * 100) + except ZeroDivisionError: + ret_val = 0 + return ret_val + class EcoAccountDocument(AbstractDocument): """ @@ -272,3 +295,8 @@ class EcoAccountDeduction(BaseResource): self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED) self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED) super().delete(*args, **kwargs) + self.account.update_deductable_rest() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.account.update_deductable_rest() diff --git a/compensation/tables/eco_account.py b/compensation/tables/eco_account.py index 17b8ecaa..7cd121f1 100644 --- a/compensation/tables/eco_account.py +++ b/compensation/tables/eco_account.py @@ -37,7 +37,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin): av = tables.Column( verbose_name=_("Available"), orderable=True, - empty_values=[], + accessor="deductable_rest", attrs={ "th": { "class": "w-20", @@ -100,13 +100,16 @@ class EcoAccountTable(BaseTable, TableRenderMixin): """ Renders the available column for an eco account Args: - value (str): The identifier value + value (float): The deductable_rest record (EcoAccount): The eco account record Returns: """ - value_total, value_relative = record.get_available_rest() + try: + value_relative = record.get_deductable_rest_relative() + except ZeroDivisionError: + value_relative = 0 html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative}) return format_html(html) diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index d1a3cf09..c7efb05a 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -230,7 +230,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): self.assertTrue(self.intervention.log.first().action == UserAction.EDITED) def test_edit_deduction(self): - test_surface = self.eco_account.get_available_rest()[0] + test_surface = self.eco_account.deductable_rest self.eco_account.set_recorded(self.superuser) self.intervention.share_with_user(self.superuser) self.eco_account.refresh_from_db() diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py index f75fcc18..1d259b95 100644 --- a/compensation/views/eco_account/eco_account.py +++ b/compensation/views/eco_account/eco_account.py @@ -200,7 +200,8 @@ def detail_view(request: HttpRequest, id: str): sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0 diff_states = abs(sum_before_states - sum_after_states) # Calculate rest of available surface for deductions - available_total, available_relative = acc.get_available_rest() + available_total = acc.deductable_rest + available_relative = acc.get_deductable_rest_relative() # Prefetch related data to decrease the amount of db connections deductions = acc.deductions.filter( diff --git a/konova/models/geometry.py b/konova/models/geometry.py index 6e7b0b6f..f69fcc7e 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -221,8 +221,17 @@ class Geometry(BaseResource): polygons.append(p) geojson = { "type": "FeatureCollection", + "crs": { + "type": "name", + "properties": { + "name": f"urn:ogc:def:crs:EPSG::{geom.srid}" + } + }, "features": [ - json.loads(x.geojson) for x in polygons + { + "type": "Feature", + "geometry": json.loads(x.geojson) + } for x in polygons ] } return geojson diff --git a/templates/map/client/libs/netgis/MapOpenLayers.js b/templates/map/client/libs/netgis/MapOpenLayers.js index 640b39ec..5321cce5 100644 --- a/templates/map/client/libs/netgis/MapOpenLayers.js +++ b/templates/map/client/libs/netgis/MapOpenLayers.js @@ -394,16 +394,12 @@ netgis.MapOpenLayers.prototype.clearAll = function() { for ( var i = 0; i < this.layers.length; i++ ) { - if(this.layers[i] === this.editLayer){ - continue; - } this.map.removeLayer( this.layers[ i ] ); } - this.layers = [this.editLayer]; + this.layers = []; this.snapFeatures.clear(); - this.snapFeatures.push(this.editLayer); }; netgis.MapOpenLayers.prototype.onUpdateStyle = function( e ) @@ -1144,15 +1140,9 @@ netgis.MapOpenLayers.prototype.updateEditLayerItem = function() netgis.MapOpenLayers.prototype.onEditFeaturesLoaded = function( e ) { - var json = e; - var format = new ol.format.GeoJSON(); - var features = format.readFeatures( json ); - - this.editLayer.getSource().addFeatures( features ); - //this.snapFeatures.push( e.feature ); - - if ( features.length > 0 ) - this.view.fit( this.editLayer.getSource().getExtent(), { padding: [ 40, 40, 40, 40 ] } ); + var json = e; + var self = this; + window.setTimeout( function() { self.createLayerGeoJSON( "Import", json ); }, 10 ); }; netgis.MapOpenLayers.prototype.onDragEnter = function( e ) @@ -1267,6 +1257,7 @@ netgis.MapOpenLayers.prototype.createLayerGeoJSON = function( title, data ) //NOTE: netgis.util.foreach( proj4.defs, function( k,v ) { console.info( "DEF:", k, v ); } ) var projcode = projection.getCode(); + switch ( projcode ) { case "EPSG:3857": @@ -1277,14 +1268,14 @@ netgis.MapOpenLayers.prototype.createLayerGeoJSON = function( title, data ) //console.info( "Import Projection:", projcode ); break; } - + default: { // Projection Not Supported console.warn( "Unsupported Import Projection:", projcode ); break; } - } + } this.addImportedFeatures( features ); }; @@ -1304,57 +1295,57 @@ netgis.MapOpenLayers.prototype.createLayerGML = function( title, data ) //var format = new ol.format.WFS( { featureNS: "ogr", featureType: "RLP_OG_utf8_epsg4326" } ); //var projection = format.readProjection( data ); //var features = format.readFeatures( data, { dataProjection: "EPSG:4326", featureProjection: "EPSG:3857" } ); - + //var features = format.readFeatures( data, { dataProjection: this.client.config.map.projection, featureProjection: this.client.config.map.projection } ); //console.info( "GML:", projection, features, features[ 0 ].getGeometry() ); var features = []; - + var parser = new DOMParser(); var xml = parser.parseFromString( data, "text/xml" ); - + // Features var featureMembers = xml.getElementsByTagName( "gml:featureMember" ); - + for ( var f = 0; f < featureMembers.length; f++ ) { var props = {}; - + var node = featureMembers[ f ]; var child = node.children[ 0 ]; - + // Attributes for ( var a = 0; a < child.attributes.length; a++ ) { var attribute = child.attributes[ a ]; props[ attribute.nodeName ] = attribute.nodeValue; } - + for ( var c = 0; c < child.children.length; c++ ) { var childNode = child.children[ c ]; - + if ( childNode.nodeName === "ogr:geometryProperty" ) continue; - + var parts = childNode.nodeName.split( ":" ); var k = parts[ parts.length - 1 ]; var v = childNode.innerHTML; - + props[ k ] = v; } - + // Geometry var geomprop = child.getElementsByTagName( "ogr:geometryProperty" )[ 0 ]; - + //for ( var g = 0; g < geomprop.children.length; g++ ) { var geom = geomprop.children[ 0 ]; var proj = geom.getAttribute( "srsName" ); - + if ( proj && proj !== "EPSG:4326" && proj !== this.client.config.map.projection ) console.warn( "Unsupported Import Projection:", proj ); - + switch ( geom.nodeName ) { case "gml:Polygon": @@ -1362,7 +1353,7 @@ netgis.MapOpenLayers.prototype.createLayerGML = function( title, data ) props[ "geometry" ] = this.gmlParsePolygon( geom, proj ); break; } - + case "gml:MultiPolygon": { props[ "geometry" ] = this.gmlParseMultiPolygon( geom, proj ); @@ -1370,7 +1361,7 @@ netgis.MapOpenLayers.prototype.createLayerGML = function( title, data ) } } } - + var feature = new ol.Feature( props ); features.push( feature ); } @@ -1452,23 +1443,22 @@ netgis.MapOpenLayers.prototype.createLayerShapefile = function( title, shapeData netgis.MapOpenLayers.prototype.addImportedFeatures = function( features ) { - // ToDO: Changes in here problematic on initial data loading // Add To Edit Layer this.editEventsSilent = true; this.editLayer.getSource().addFeatures( features ); this.editEventsSilent = false; this.updateEditOutput(); - + // Zoom Imported Features if ( features.length > 0 ) { var extent = features[ 0 ].getGeometry().getExtent(); - + for ( var f = 1; f < features.length; f++ ) { ol.extent.extend( extent, features[ f ].getGeometry().getExtent() ); } - + var padding = 40; this.view.fit( extent, { duration: 300, padding: [ padding, padding, padding, padding ] } ); }