Coverage for backend/django/Economics/costing/costable_items/deleted_objects.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-06-23 21:51 +0000

1from __future__ import annotations 

2 

3from collections.abc import Iterable 

4 

5from django.db.models import Q 

6 

7from Economics.costing.models import CapitalCostLine, OperatingCostLine 

8 

9from Economics.studies.models import EconomicsStudy 

10from Economics.formulas.native_properties.sync import sync_economics_native_properties_for_study 

11from Economics.results.services.lifecycle.runs import mark_result_runs_stale_for_study 

12 

13 

14def delete_economics_lines_for_simulation_objects(simulation_object_ids: Iterable[int]) -> int: 

15 """Delete study cost lines backed by deleted streams or unit operations. 

16 

17 Simulation objects are soft-deleted, so ordinary FK cascade behaviour does not 

18 remove economics lines whose source property or costable item belongs to a 

19 deleted object. This cleanup keeps studies from retaining orphaned cost rows. 

20 """ 

21 

22 object_ids = {object_id for object_id in simulation_object_ids if object_id is not None} 

23 if not object_ids: 

24 return 0 

25 

26 operating_lines = OperatingCostLine.objects.filter( 

27 Q(source_property_info__set__simulationObject_id__in=object_ids) 

28 | Q(costable_item__simulation_object_id__in=object_ids) 

29 ) 

30 capital_lines = CapitalCostLine.objects.filter(costable_item__simulation_object_id__in=object_ids) 

31 affected_study_ids = set(operating_lines.values_list("study_id", flat=True)) 

32 affected_study_ids.update(capital_lines.values_list("study_id", flat=True)) 

33 deleted_operating_count, _ = operating_lines.delete() 

34 deleted_capital_count, _ = capital_lines.delete() 

35 deleted_count = deleted_operating_count + deleted_capital_count 

36 if deleted_count: 

37 for study in EconomicsStudy.objects.filter(pk__in=affected_study_ids): 

38 sync_economics_native_properties_for_study(study) 

39 mark_result_runs_stale_for_study( 

40 study=study, 

41 reason="flowsheet_simulation_object_deleted", 

42 requires_solve=True, 

43 ) 

44 return deleted_count 

45 

46 

47def delete_economics_lines_for_deleted_simulation_objects(study: EconomicsStudy) -> int: 

48 """Delete study cost lines that still point at already soft-deleted objects.""" 

49 

50 deleted_object_ids = set( 

51 OperatingCostLine.objects.filter( 

52 study=study, 

53 source_property_info__set__simulationObject__is_deleted=True, 

54 ).values_list("source_property_info__set__simulationObject_id", flat=True) 

55 ) 

56 deleted_object_ids.update( 

57 OperatingCostLine.objects.filter( 

58 study=study, 

59 costable_item__simulation_object__is_deleted=True, 

60 ).values_list("costable_item__simulation_object_id", flat=True) 

61 ) 

62 deleted_object_ids.update( 

63 CapitalCostLine.objects.filter( 

64 study=study, 

65 costable_item__simulation_object__is_deleted=True, 

66 ).values_list("costable_item__simulation_object_id", flat=True) 

67 ) 

68 return delete_economics_lines_for_simulation_objects(deleted_object_ids)