Skip to content

Commit bf8de4f

Browse files
Add common costs and context, tests for all VEWA types.
1 parent 93789c9 commit bf8de4f

13 files changed

Lines changed: 545 additions & 245 deletions

File tree

django/cohiva/settings_defaults.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
TEST_MAIL_RECIPIENT = ADMINS[0][1]
4545

4646
SERVER_EMAIL = "info@" + cbc.DOMAIN
47+
DEFAULT_FROM_EMAIL = SERVER_EMAIL
4748
EMAIL_SUBJECT_PREFIX = f"[Cohiva {cbc.SITE_NICKNAME}] "
4849

4950
# Hosts/domain names that are valid for this site; required if DEBUG is False

django/geno/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ def decode_from_iso8859(file):
212212
yield line.decode("iso8859")
213213

214214

215-
def nformat(number, precision=2, round_to=False, thousands_separator="'"):
215+
def nformat(
216+
number: float, precision: int = 2, round_to: bool = False, thousands_separator: str = "'"
217+
) -> str:
216218
if round_to:
217219
number = round_to * round(number / round_to)
218220
if thousands_separator:
@@ -221,7 +223,7 @@ def nformat(number, precision=2, round_to=False, thousands_separator="'"):
221223
return format(number, ".%df" % precision)
222224

223225

224-
def unformat(number: str | float, thousands_separator="'"):
226+
def unformat(number: str | float, thousands_separator: str = "'") -> float:
225227
if isinstance(number, str):
226228
number = number.replace(thousands_separator, "").replace("%", "")
227229
return float(number)

django/report/nk/bill.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from geno.models import InvoiceCategory
1414
from geno.utils import fill_template_pod, nformat, odt2pdf
1515
from report.nk.contract import NkContract
16-
from report.nk.cost import NkCost, NkCostValueType
16+
from report.nk.cost import NkCost
1717
from report.nk.graph import NkGraph
1818
from report.nk.rental_unit import NkRentalUnit
1919

@@ -152,7 +152,7 @@ def _get_billing_group_context(
152152
building_cost = 0
153153
for cost in group:
154154
object_cost += cost.get_assigned_cost(self.contract, rental_unit)
155-
building_cost += cost.get_building_amount(NkCostValueType.COST)
155+
building_cost += cost.get_building_cost()
156156
if object_cost == 0 and building_cost == 0:
157157
return None
158158
if building_cost:

django/report/nk/cost/base.py

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from report.nk.contract import NkContract
77
from report.nk.generator import NkReportGenerator
88
from report.nk.rental_unit import NkRentalUnit
9+
from report.nk.section import NkSection
910

1011

1112
class NkCostValueType(Enum):
@@ -50,6 +51,7 @@ def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
5051
self.rental_unit_values: dict[int, dict[NkCostValueType, NkCostValue]] = {}
5152
self.section_weights = cost_config.get("section_weights", "default")
5253
self.add_value_type(NkCostValueType.COST, "Kosten", "CHF")
54+
self.add_value_type(NkCostValueType.WEIGHT, "Gewichtung", "")
5355
self.warnings = []
5456

5557
def add_value_type(self, kind: NkCostValueType, name: str, unit: str):
@@ -95,7 +97,7 @@ def _normalize_monthly_amounts_for_dict(self, container: dict, value_required=Fa
9597
if value.amount:
9698
if total:
9799
# Annual and monthly values are given, check consistency
98-
if total != value.amount:
100+
if abs(total - value.amount) > 0.00001:
99101
raise ValueError(
100102
f"Inkonsistente Angaben für Totalbetrag {value.amount} und "
101103
f"Summe der Monatswerte {total} für {value.name}/{_kind}"
@@ -177,9 +179,9 @@ def _calculate_weights(self):
177179
def _calculate_weights_for_type(
178180
self, value_type: NkCostValueType, rental_unit_weights_function_name: str
179181
):
180-
self.add_value_type(value_type, "Gewichtung", "")
182+
self._zero_values(value_type)
181183
monthly_weights = self.get_monthly_weights()
182-
section_weights = self.get_section_weights()
184+
section_weights = self.get_section_weights(value_type)
183185
total = self.total_values[value_type]
184186
rental_unit_weights_function = getattr(self, rental_unit_weights_function_name)
185187
if not callable(rental_unit_weights_function):
@@ -202,6 +204,20 @@ def _calculate_weights_for_type(
202204
)
203205
total.amount = sum(total.monthly_amounts)
204206

207+
def _zero_values(self, value_type: NkCostValueType):
208+
for ru in self.generator.rental_units:
209+
self.rental_unit_values[ru.id][value_type].amount = 0
210+
self.rental_unit_values[ru.id][value_type].monthly_amounts = (
211+
self.generator.num_months * [0]
212+
)
213+
for section in self.generator.sections:
214+
self.section_values[section.id][value_type].amount = 0
215+
self.section_values[section.id][value_type].monthly_amounts = (
216+
self.generator.num_months * [0]
217+
)
218+
self.total_values[value_type].amount = 0
219+
self.total_values[value_type].monthly_amounts = self.generator.num_months * [0]
220+
205221
def _aggregate_monthly_amounts(self, value_type: NkCostValueType = NkCostValueType.COST):
206222
"""Aggregate pre-calculated monthly per-rental-unit costs up to sections and total."""
207223
for ru in self.generator.rental_units:
@@ -220,13 +236,10 @@ def get_monthly_weights(self):
220236
"""Default with equal weights for all months."""
221237
return self.generator.num_months * [1.0]
222238

223-
def get_section_weights(self):
239+
def get_section_weights(self, value_type: NkCostValueType) -> dict[int, float]:
224240
"""Return weights per section, using the configured section_weights profile if available."""
225-
weight_profile = (
226-
self.generator.section_weights.get(self.section_weights)
227-
if self.section_weights
228-
else None
229-
)
241+
242+
weight_profile = self._get_weight_profile(value_type)
230243
weights = {}
231244
for section in self.generator.sections:
232245
if weight_profile is not None:
@@ -235,6 +248,11 @@ def get_section_weights(self):
235248
weights[section.id] = 1.0
236249
return weights
237250

251+
def _get_weight_profile(self, value_type: NkCostValueType):
252+
if self.section_weights:
253+
return self.generator.section_weights.get(self.section_weights)
254+
return None
255+
238256
def get_rental_unit_weights(self, ru):
239257
"""Default with equal weights for all rental units."""
240258
if ru.is_virtual:
@@ -308,9 +326,24 @@ def _get_assigned_sum(
308326
ret += amount
309327
return ret
310328

311-
def get_building_amount(self, value_type: NkCostValueType):
329+
def get_building_cost(self):
330+
return self._get_building_amount(NkCostValueType.COST)
331+
332+
def _get_building_amount(self, value_type: NkCostValueType):
312333
return self.total_values[value_type].amount
313334

335+
def get_section_cost(self, section):
336+
return self._get_section_amount(section, NkCostValueType.COST)
337+
338+
def _get_section_amount(self, section: "NkSection", value_type: NkCostValueType):
339+
return self.section_values[section.id][value_type].amount
340+
341+
def get_rental_unit_cost(self, rental_unit):
342+
return self._get_rental_unit_amount(rental_unit, NkCostValueType.COST)
343+
344+
def _get_rental_unit_amount(self, rental_unit: "NkRentalUnit", value_type: NkCostValueType):
345+
return self.rental_unit_values[rental_unit.id][value_type].amount
346+
314347
def _get_context(self, ru: "NkRentalUnit", contract: "NkContract") -> dict:
315348
"""Return extra context variables for ODT template rendering. Override in subclasses."""
316349
return {}
@@ -351,7 +384,11 @@ class NkCommonCostMixin:
351384

352385
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
353386
super().__init__(report_generator, cost_config)
387+
self.common_cost_section_weights = cost_config.get(
388+
"common_cost_section_weights", "default"
389+
)
354390
self.add_value_type(NkCostValueType.COMMON_COST, "Allgemeinkosten", "CHF")
391+
self.add_value_type(NkCostValueType.COMMON_WEIGHT, "Gewichtung", "")
355392

356393
def get_assigned_cost(self, contract: "NkContract", rental_unit: "NkRentalUnit | None" = None):
357394
ret = super().get_assigned_cost(contract, rental_unit)
@@ -374,6 +411,7 @@ def set_common_costs(self, cost: float | list[float], usage: float | list[float]
374411
self.total_values[NkCostValueType.COMMON_USAGE].monthly_amounts = usage
375412
else:
376413
self.total_values[NkCostValueType.COMMON_USAGE].amount = usage
414+
self.normalize_monthly_amounts()
377415

378416
def _split_common_costs(self):
379417
self._calculate_common_weights()
@@ -384,3 +422,15 @@ def _calculate_common_weights(self):
384422
self._calculate_weights_for_type(
385423
NkCostValueType.COMMON_WEIGHT, "get_rental_unit_common_weights"
386424
)
425+
426+
def _get_weight_profile(self, value_type: NkCostValueType):
427+
if value_type in (
428+
NkCostValueType.COMMON_COST,
429+
NkCostValueType.COMMON_USAGE,
430+
NkCostValueType.COMMON_WEIGHT,
431+
):
432+
if self.common_cost_section_weights:
433+
return self.generator.section_weights.get(self.common_cost_section_weights)
434+
else:
435+
return None
436+
return super()._get_weight_profile(value_type)

django/report/nk/cost/general.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ def get_total_costs(self):
3535
return self.generator.config.get(f"Kosten:{self.name}")
3636

3737
def load_rental_unit_usage(self):
38+
# We use the weights as usage
39+
self._calculate_weights()
3840
for ru in self.generator.rental_units:
39-
weight = sum(self.get_rental_unit_weights(ru))
41+
weight = self.rental_unit_values[ru.id][NkCostValueType.WEIGHT].amount
4042
self.rental_unit_values[ru.id][NkCostValueType.USAGE].amount = weight
4143
self.section_values[ru.section.id][NkCostValueType.USAGE].amount += weight
4244
self.total_values[NkCostValueType.USAGE].amount += weight

0 commit comments

Comments
 (0)