Merge pull request 'master' (#219) from master into Docker
Reviewed-on: SGD-Nord/konova#219
This commit is contained in:
		
						commit
						65a665d924
					
				
							
								
								
									
										36
									
								
								compensation/migrations/0011_ecoaccount_deductable_rest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								compensation/migrations/0011_ecoaccount_deductable_rest.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
    ]
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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 ] } );
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user