konova/konova/management/commands/kspMigrater/eco_account_migrater.py
mpeltriaux c3cce6eb28 #132 WIP Deduction enhances
* enhances comment messages
* adds fallback calculation for deduction-account ratio in case of missing or unlogical values
* adds creation timestamp for migrated deductions, derived from old kom-deduction log/protokoll table
* fixes bug in eco account migration where legal changes would not be saved
* changes bug in compensation where direct data changes would not be saved
* improves update_all_parcels.py by catching all types of possible exceptions during the update procedure and adding the causing geometry to the try_again_ids list
2022-03-31 14:45:34 +02:00

349 lines
15 KiB
Python

import datetime
from django.contrib.auth.models import Group
from django.contrib.gis.geos import MultiPolygon, Polygon
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.utils import timezone
from codelist.models import KonovaCode
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_COMPENSATION_HANDLER_ID
from compensation.models import EcoAccount, EcoAccountDocument, EcoAccountDeduction
from intervention.models import Responsibility, Handler, Intervention, Legal
from konova.management.commands.kspMigrater.compensation_migrater import CompensationMigrater
from konova.models import Geometry
from konova.settings import DEFAULT_GROUP
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, DEFAULT_SRID
from user.models import User, UserActionLogEntry
class EcoAccountMigrater(CompensationMigrater):
def migrate(self):
self.connect_db()
cursor = self.db_connection.cursor()
cursor.execute(
'select '
'om."KENNUNG", '
'linf."OBJBEZ", '
'ST_AsEWKT(ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_Transform(geomf.the_geom,4326)), 3))) as geomf, '
'ST_AsEWKT(ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_Transform(geoml.the_geom,4326)), 2))) as geoml, '
'ST_AsEWKT(ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_Transform(geomp.the_geom,4326)), 1))) as geomp, '
'linf."Bemerkung" '
'from "OBJ_MASTER" om '
'left join "LINFOS" linf on om."GISPADID"=linf."GISPADID" '
'left join kom on om."GISPADID"=kom.gispadid '
'left join geometry_f geomf on om."GISPADID"=geomf.gispadid '
'left join geometry_l geoml on om."GISPADID"=geoml.gispadid '
'left join geometry_p geomp on om."GISPADID"=geomp.gispadid '
'where '
'om."OKL"=7730081 and '
'om.archiv=false and '
'om.nicht_vollstaendig=0 '
)
all_oeks = cursor.fetchall()
len_all_oeks = len(all_oeks)
num_processed = 0
print(f"Migrate OEKs to ecoaccounts...")
print(f"--Found {len_all_oeks} entries. Process now...")
for oek in all_oeks:
if num_processed % 500 == 0:
print(f"----{num_processed}/{len_all_oeks} processed")
with transaction.atomic():
oek_identifier = oek[0]
oek_title = oek[1]
oek_comment = oek[5] or ""
eco_account = EcoAccount.objects.get_or_create(
identifier=oek_identifier
)[0]
eco_account.title = oek_title
eco_account.comment = oek_comment
eco_account = self._migrate_legal(eco_account, oek)
eco_account = self._migrate_geometry(eco_account, oek)
eco_account = self._migrate_responsibility(eco_account, oek)
eco_account = self._migrate_states(eco_account, oek)
eco_account = self._migrate_deadlines(eco_account, oek)
eco_account = self._migrate_action_control_deadlines(eco_account, oek)
eco_account = self._migrate_actions(eco_account, oek)
eco_account = self._migrate_log(eco_account, oek)
eco_account = self._migrate_deductions(eco_account, oek)
eco_account = self._migrate_documents(eco_account, EcoAccountDocument, oek)
eco_account.save()
num_processed += 1
cursor.close()
def _migrate_geometry(self, instance, db_result: tuple):
identifier = f"'{db_result[0]}'"
empty_str = f"''"
tmp_cursor = self.db_connection.cursor()
tmp_cursor.execute(
'select '
'ST_AsEWKT(ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_Union(ST_Transform(geom.the_geom,4326)))))) '
'from "OBJ_MASTER" om '
'left join "Aufwertung" auf on om."GISPADID"=auf."GISPADID" '
'left join "OBJ_MASTER" om_kom on auf."Infos"=om_kom."KENNUNG" '
'left join geometry_f geom on geom.gispadid=om_kom."GISPADID" '
'where '
'om."OKL"=7730081 and '
'om.archiv=false and '
'om_kom.archiv=false and '
f'(auf."Infos" is not null and auf."Infos"!={empty_str}) and '
f'om."KENNUNG"={identifier}'
)
deduction_db_results = tmp_cursor.fetchall()
if len(deduction_db_results) != 1:
raise AssertionError(f"Unexpected number of matches: {deduction_db_results}")
db_result_geom = deduction_db_results[0][0]
if db_result_geom is not None:
deductions_db_result_geom = MultiPolygon.from_ewkt(deduction_db_results[0][0])
else:
deductions_db_result_geom = MultiPolygon(srid=DEFAULT_SRID)
tmp_cursor.execute(
'select '
'ST_AsEWKT(ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_Transform(geomf.the_geom,4326)), 3))) as geomf '
'from "OBJ_MASTER" om '
'left join geometry_f geomf on om."GISPADID"=geomf.gispadid '
'where '
f'om."KENNUNG"={identifier}'
)
original_geom_db_results = tmp_cursor.fetchall()
if len(original_geom_db_results) != 1:
raise AssertionError(f"Unexpected number of matches: {original_geom_db_results}")
account_db_result_geom = MultiPolygon.from_ewkt(original_geom_db_results[0][0])
db_result_geom = account_db_result_geom.union(deductions_db_result_geom)
if isinstance(db_result_geom, Polygon):
db_result_geom = MultiPolygon(db_result_geom, srid=DEFAULT_SRID)
instance.geometry = instance.geometry or Geometry()
try:
# Calculate area by transforming
rlp_geom = db_result_geom.transform(ct=DEFAULT_SRID_RLP, clone=True)
area = round(rlp_geom.area)
instance.deductable_surface = area
instance.geometry.geom = db_result_geom if not db_result_geom.empty else None
instance.geometry.save()
except TypeError:
raise TypeError(f"{identifier}, {db_result_geom}")
tmp_cursor.close()
return instance
def _migrate_responsibility(self, eco_account, oek):
fallback = "Kein Eintrag"
acc_identifier = f"'{oek[0]}'"
tmp_cursor = self.db_connection.cursor()
tmp_cursor.execute(
'select '
'adr."adr_pruef" as ets, '
'linf."AZ", '
'oek.traeger, '
'oek.bemerkungtraeger '
'from "OBJ_MASTER" om '
'left join "LINFOS" linf on om."GISPADID"=linf."GISPADID" '
'left join adressrolle adr on adr."GISPADID"=om."GISPADID" '
'left join oek on om."GISPADID"=oek.gispadid '
'where '
f'om."KENNUNG"={acc_identifier}'
)
db_results = tmp_cursor.fetchall()
if len(db_results) != 1:
raise AssertionError(f"{acc_identifier} has invalid responsibilities: {db_results}")
db_results = db_results[0]
cons_office_code = db_results[0]
cons_file_number = db_results[1]
handler_type = db_results[2]
handler_detail = db_results[3]
if cons_file_number is None or len(cons_file_number) == 0:
cons_file_number = fallback
eco_account.responsible = eco_account.responsible or Responsibility.objects.create()
try:
conservation_office = KonovaCode.objects.get(
atom_id=cons_office_code,
is_selectable=True,
is_archived=False,
code_lists__in=[CODELIST_CONSERVATION_OFFICE_ID]
)
self._migrate_responsible_code_to_team(eco_account, conservation_office, "ETS")
except ObjectDoesNotExist:
raise ObjectDoesNotExist(f"{acc_identifier}, {db_results}")
try:
handler_type = KonovaCode.objects.get(
atom_id=handler_type,
is_selectable=True,
is_archived=False,
code_lists__in=[CODELIST_COMPENSATION_HANDLER_ID]
)
except ObjectDoesNotExist:
handler_type = None
eco_account.responsible.conservation_office = conservation_office
eco_account.responsible.conservation_file_number = cons_file_number
handler = eco_account.responsible.handler or Handler.objects.create()
handler.type = handler_type
handler.detail = handler_detail
eco_account.responsible.handler = handler
eco_account.responsible.handler.save()
eco_account.responsible.save()
tmp_cursor.close()
return eco_account
def _migrate_deductions(self, eco_account, oek):
identifier = f"'{oek[0]}'"
empty_str = "''"
tmp_cursor = self.db_connection.cursor()
tmp_cursor.execute(
'select '
'om_kom."KENNUNG", '
'auf."Anteil", '
'ref."REFERENZ" '
'from "OBJ_MASTER" om '
'left join "Aufwertung" auf on om."GISPADID"=auf."GISPADID" '
'left join "OBJ_MASTER" om_kom on auf."Infos"=om_kom."KENNUNG" '
'left join "REFERENZ" ref on om_kom."GISPADID"=ref."GISPADID" '
'where '
f'(auf."Infos" is not null and auf."Infos"!={empty_str}) and '
f'(ref."REFERENZ" is not null and ref."REFERENZ"!={empty_str}) and '
f'om."KENNUNG"={identifier}'
)
fetched_results = tmp_cursor.fetchall()
eco_account.deductions.all().delete()
for result in fetched_results:
old_deduction_kom_identifier = result[0]
deduction_amount_percentage = result[1]
target_intervention_identifier = result[2]
if target_intervention_identifier is None:
# old garbage data - skip
continue
if deduction_amount_percentage is None or float(deduction_amount_percentage) > 100.0:
deduction_amount_percentage = self.__calculate_deduction_amount_percentage(eco_account, old_deduction_kom_identifier)
try:
intervention = Intervention.objects.get(
identifier=target_intervention_identifier
)
except ObjectDoesNotExist:
# old garbage data
print(f"{identifier} has deduction for {target_intervention_identifier} which does not exist")
continue
deduction_amount_sqm = round(eco_account.deductable_surface * (float(deduction_amount_percentage)/100))
rest_available_surface = eco_account.deductable_surface - eco_account.get_deductions_surface()
rest_after_deduction = rest_available_surface - deduction_amount_sqm
if rest_after_deduction < 0:
print(f"{identifier} has {rest_available_surface} sqm left but old deduction {old_deduction_kom_identifier} requires {deduction_amount_sqm} sqm.")
print(f"Increase deductable surface by {rest_after_deduction} sqm")
eco_account.deductable_surface += abs(rest_after_deduction)
eco_account.save()
deduction = EcoAccountDeduction.objects.get_or_create(
account=eco_account,
surface=deduction_amount_sqm,
intervention=intervention
)[0]
created_on = self.__fetch_deduction_create_date(old_deduction_kom_identifier)
deduction.created = created_on
deduction.save()
tmp_cursor.close()
return eco_account
def _migrate_legal(self, eco_account, oek):
# Just add default dummy values, since there has never been data for this previously
eco_account.legal = eco_account.legal or Legal()
eco_account.legal.registration_date = datetime.date.fromisoformat("1970-01-01")
eco_account.legal.save()
eco_account.comment += "\nKein Vereinbarungsdatum eingetragen. Platzhalter 01.01.1970 gesetzt."
return eco_account
def __calculate_deduction_amount_percentage(self, eco_account, kom_deduction_identifier):
""" Calculates the amount of a deduction from an eco account in percentage.
Depends on the geometry of the old KOM-deduction
"""
kom_deduction_identifier = f"'{kom_deduction_identifier}'"
result = 0.0
tmp_cursor = self.db_connection.cursor()
tmp_cursor.execute(
'select '
f'st_area(st_transform(geom.the_geom,{DEFAULT_SRID_RLP})) '
'from "OBJ_MASTER" om '
'join geometry_f geom on om."GISPADID"=geom.gispadid '
'where '
f'om."KENNUNG"={kom_deduction_identifier}'
)
fetch_result = tmp_cursor.fetchall()
area_surface = fetch_result[0][0]
tmp_cursor.close()
result = float(area_surface / eco_account.deductable_surface) * 100
return result
def __fetch_deduction_create_date(self, deduction_kom_identifier):
""" Fetches the creation timestamp for the old KOM-deduction to be used as create timestamp for
the migrated deduction entry
"""
deduction_kom_identifier = f"'{deduction_kom_identifier}'"
tmp_cursor = self.db_connection.cursor()
tmp_cursor.execute(
'select '
'log.erstelltam, '
'log.erstelltvon '
'from "OBJ_MASTER" om '
'join log on om."GISPADID"=log.gispadid::Integer '
'where '
f'om."KENNUNG"={deduction_kom_identifier} '
'order by log.erstelltam '
'limit 1'
)
fetch_results = tmp_cursor.fetchall()
if len(fetch_results) == 0:
tmp_cursor.execute(
'select '
'p.geaendertam, '
'p.geaendertvon '
'from "OBJ_MASTER" om '
'join protokoll p on om."GISPADID"=p."FKEY" '
'where '
f'om."KENNUNG"={deduction_kom_identifier} '
'order by p.geaendertam '
'limit 1'
)
fetch_results = tmp_cursor.fetchall()
if len(fetch_results) == 0:
return None
create_ts = fetch_results[0][0]
create_user = fetch_results[0][1]
user = User.objects.get_or_create(
username=create_user,
)
is_new = user[1]
user = user[0]
if is_new:
user.is_active = False
user.first_name = "MIGRIERT"
user.last_name = "MIGRIERT"
user.save()
# Make sure user has at least the default group set
default_group = Group.objects.get(name=DEFAULT_GROUP)
user.groups.add(default_group)
tmp_cursor.close()
create_action = UserActionLogEntry.get_created_action(user, comment="[Migriert] Abbuchung angelegt")
create_action.timestamp = timezone.make_aware(create_ts)
create_action.save()
return create_action