diff --git a/landa/hooks.py b/landa/hooks.py
index 861e7dcd..9ae31271 100644
--- a/landa/hooks.py
+++ b/landa/hooks.py
@@ -340,6 +340,9 @@
"0 0 1 10 *": [ # every 1st october at 00:00
"landa.water_body_management.doctype.stocking_target.stocking_target.copy_to_next_year",
],
+ "0 0 1 1 *": [ # every 1st january at 00:00
+ "landa.organization_management.doctype.work_ledger_entry.work_ledger_entry.create_yearly_negative_entries",
+ ],
},
# "all": ["landa.tasks.all"],
# , "hourly": [
diff --git a/landa/locale/de.po b/landa/locale/de.po
index c487bc61..935dcd5a 100644
--- a/landa/locale/de.po
+++ b/landa/locale/de.po
@@ -94,6 +94,12 @@ msgstr "Kürzel \"{}\" existiert bereits in Attribut {}."
msgid "Access Level"
msgstr "Zugangs Level"
+#. Label of a Data field in DocType 'Work Assignment'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.py:49
+msgid "Activity Title"
+msgstr "Bezeichnung der Tätigkeit"
+
#. Label of a Data field in DocType 'Member Data Import'
#: landa/organization_management/doctype/member_data_import/member_data_import.json
#: landa/organization_management/report/current_member_data/current_member_data.py:174
@@ -156,10 +162,25 @@ msgstr "Art der Auszeichnung"
msgid "Awards"
msgstr "Auszeichnungen"
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:26
+msgid "Balance End Previous Year"
+msgstr "Saldo am Ende des Vorjahres"
+
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:44
+msgid "Balance End of Year"
+msgstr "Saldo am Ende des Jahres"
+
#: landa/water_body_management/doctype/stocking_measure/stocking_measure_list.js:30
msgid "Based on your selection, Stocking Targets are created accordingly for the specified year."
msgstr "Basierend auf Ihrer Auswahl werden für das angegebene Jahr entsprechende Besatzplanungen erstellt."
+#. Label of a Section Break field in DocType 'Work Assignment'
+#. Label of a Section Break field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Basic Information"
+msgstr "Allgemeine Informationen"
+
#. Label of a Card Break in the Water Body Management Workspace
#: landa/water_body_management/workspace/water_body_management/water_body_management.json
msgid "Berichte"
@@ -225,9 +246,9 @@ msgstr "Kann untergeordnete Vereine enthalten"
msgid "Cannot be a member of organization {} because it is a group."
msgstr "Kann kein Mitglied von Verein/Verband {} sein, da nur Vereine bzw. Ortsgruppen Mitglieder haben dürfen, keine Regional- und Landesverbände."
-#: landa/organization_management/doctype/organization/organization.py:70
+#: landa/organization_management/doctype/organization/organization.py:74
msgid "Cannot set Parent Organization to a local group."
-msgstr ""
+msgstr "Ortsgruppe kann nicht als Elternverein festgelegt werden."
#. Name of a report
#. Label of a Link in the Water Body Management Workspace
@@ -345,7 +366,7 @@ msgstr "Aktuelle Informationen werden an diesem Tag automatisch entfernt"
#. Name of a DocType
#: landa/water_body_management/doctype/custom_icon/custom_icon.json
msgid "Custom Icon"
-msgstr ""
+msgstr "Benutzerdefiniertes Symbol"
#. Label of a Section Break field in DocType 'Water Body'
#: landa/water_body_management/doctype/water_body/water_body.json
@@ -392,11 +413,30 @@ msgstr "Sperrbereich zeichnen"
msgid "Dummy"
msgstr ""
+#. Label of a Float field in DocType 'Work Assignment Member'
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/report/work_hours/work_hours.py:56
+msgid "Duration (Hours)"
+msgstr "Dauer (Stunden)"
+
#. Label of a Check field in DocType 'Firebase Settings'
#: landa/water_body_management/doctype/firebase_settings/firebase_settings.json
msgid "Enable Firebase Notifications"
msgstr "Firebase-Benachrichtigungen aktivieren"
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:32
+msgid "Expected This Year"
+msgstr "Erwartet dieses Jahr"
+
+#. Label of a Float field in DocType 'Organization'
+#: landa/organization_management/doctype/organization/organization.json
+msgid "Expected Work Hours per Year"
+msgstr "Erwartete Arbeitsstunden pro Jahr"
+
+#: landa/organization_management/report/work_hours/work_hours.py:119
+msgid "Expected work hours adjustment"
+msgstr "Anpassung der erwarteten Arbeitszeit"
+
#. Name of a DocType
#. Label of a Heading field in DocType 'External Contact'
#. Label of a Link in the Organization Management Workspace
@@ -488,6 +528,9 @@ msgid ""
"
\n"
"Eg. footballfans"
msgstr ""
+"Firebase Thema für Push-Benachrichtigungen\n"
+"
\n"
+"z.B. footballfans"
#: landa/water_body_management/doctype/firebase_settings/firebase_settings.js:19
msgid "Firebase credentials uploaded successfully"
@@ -633,7 +676,7 @@ msgstr "Vollständiger Name des Lieferanten"
#: landa/organization_management/doctype/organization/organization_tree.js:30
msgid "Full name of the organization. For example, \"Landesverband Sächsischer Angler e.V.\""
-msgstr ""
+msgstr "Vollständiger Name des Vereins, z.B. \"Landesverband Sächsischer Angler e.V.\""
#. Label of a Table MultiSelect field in DocType 'External Contact'
#: landa/organization_management/doctype/external_contact/external_contact.json
@@ -694,6 +737,10 @@ msgstr "Zugriff auf bestimmte Daten von Vereinen / Verbänden unterhalb dieses L
msgid "Group By Fish Species"
msgstr "Nach Fischart gruppieren"
+#: landa/organization_management/report/work_hours/work_hours.js:36
+msgid "Group by Member"
+msgstr "Nach Mitglied gruppieren"
+
#. Label of a Check field in DocType 'Water Body'
#: landa/water_body_management/doctype/water_body/water_body.json
#: landa/water_body_management/report/water_body_export/water_body_export.py:55
@@ -780,6 +827,15 @@ msgstr ""
msgid "History"
msgstr "Verlauf"
+#: landa/organization_management/report/work_hours/work_hours.py:32
+msgid "Hours Balance"
+msgstr "Stundensaldo"
+
+#. Label of a Float field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Hours Change"
+msgstr "Stundenänderung"
+
#. Label of a Link field in DocType 'Member Data Import'
#: landa/organization_management/doctype/member_data_import/member_data_import.json
#: landa/organization_management/report/current_member_data/current_member_data.py:221
@@ -789,12 +845,18 @@ msgstr "Eindeutige Erlaubnisschein Identifikation"
#. Label of a Attach Image field in DocType 'Water Body'
#: landa/water_body_management/doctype/water_body/water_body.json
msgid "Icon Path"
-msgstr ""
+msgstr "Symbolpfad"
#. Label of a Image field in DocType 'Water Body'
#: landa/water_body_management/doctype/water_body/water_body.json
msgid "Icon Preview"
-msgstr ""
+msgstr "Symbolvorschau"
+
+#. Description of the 'Work Assignment' (Link) field in DocType 'Work Ledger
+#. Entry'
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "If not set, this entry will be treated as an adjustment of expected hours."
+msgstr "Ohne Einsatz gilt dieser Eintrag als Anpassung der erwarteten Arbeitsstunden."
#. Label of a Card Break in the Water Body Management Workspace
#: landa/water_body_management/workspace/water_body_management/water_body_management.json
@@ -818,7 +880,7 @@ msgstr "Eingehende Zahlung"
#: landa/organization_management/doctype/landa_member/landa_member_list.js:39
msgid "Incorrect password"
-msgstr ""
+msgstr "Falsches Passwort"
#. Label of a Section Break field in DocType 'Award'
#. Label of a Section Break field in DocType 'External Contact'
@@ -978,12 +1040,15 @@ msgstr "LANDA Mitgliederverwaltung Ortsgruppe"
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
msgid "LANDA Local Organization Management"
msgstr "LANDA Mitgliederverwaltung Verein"
#. Name of a role
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/stocking_measure_import/stocking_measure_import.json
#: landa/water_body_management/doctype/stocking_target/stocking_target.json
@@ -1034,12 +1099,12 @@ msgstr "Mitglied {0} wurde erstellt bzw. aktualisiert."
#. Name of a DocType
#: landa/landa_sales/doctype/landa_payment_row/landa_payment_row.json
msgid "LANDA Payment Row"
-msgstr ""
+msgstr "LANDA Zahlungszeile"
#. Name of a report
#: landa/organization_management/report/landa_power_users/landa_power_users.json
msgid "LANDA Power Users"
-msgstr ""
+msgstr "LANDA Power-Nutzer"
#. Label of a Section Break field in DocType 'Organization'
#: landa/organization_management/doctype/organization/organization.json
@@ -1057,6 +1122,8 @@ msgstr "LANDA Einstellungen"
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/doctype/yearly_fishing_permit_type/yearly_fishing_permit_type.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
@@ -1093,6 +1160,8 @@ msgstr ""
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/doctype/yearly_fishing_permit_type/yearly_fishing_permit_type.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
@@ -1266,11 +1335,15 @@ msgstr ""
#. Label of a Heading field in DocType 'LANDA Member'
#. Label of a Link field in DocType 'Member Function'
+#. Label of a Link field in DocType 'Work Assignment Member'
+#. Label of a Link field in DocType 'Work Ledger Entry'
#. Label of a Link field in DocType 'Yearly Fishing Permit'
#. Label of a Link in the Organization Management Workspace
#: landa/landa_sales/customer/customer.py:26
#: landa/organization_management/doctype/landa_member/landa_member.json
#: landa/organization_management/doctype/member_function/member_function.json
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/report/magazine_address_list/magazine_address_list.py:214
#: landa/organization_management/report/member_address_list/member_address_list.js:10
@@ -1279,6 +1352,11 @@ msgstr ""
#: landa/organization_management/report/member_contact_list/member_contact_list.js:10
#: landa/organization_management/report/member_contact_list/member_contact_list.py:53
#: landa/organization_management/report/members_with_member_functions/members_with_member_functions.py:21
+#: landa/organization_management/report/work_hours/work_hours.js:24
+#: landa/organization_management/report/work_hours/work_hours.py:19
+#: landa/organization_management/report/work_hours/work_hours.py:42
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.js:24
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:18
#: landa/organization_management/workspace/organization_management/organization_management.json
#: landa/water_body_management/report/members_in_water_body_management/members_in_water_body_management.py:17
msgid "Member"
@@ -1378,6 +1456,16 @@ msgstr "Mitgliedsnummer"
msgid "Member Last Name"
msgstr "Nachname des Mitglieds"
+#. Label of a Data field in DocType 'Work Assignment Member'
+#. Label of a Data field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+#: landa/organization_management/report/work_hours/work_hours.py:26
+#: landa/organization_management/report/work_hours/work_hours.py:48
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:24
+msgid "Member Name"
+msgstr "Mitgliedsname"
+
#. Label of a Date field in DocType 'LANDA Member'
#: landa/organization_management/doctype/landa_member/landa_member.json
msgid "Member Since"
@@ -1469,7 +1557,7 @@ msgstr "Neue Mitgliedsfunktion anlegen"
msgid "New Stocking Measure Import"
msgstr "Neuer Besatzmaßnahmenimport"
-#: landa/organization_management/doctype/organization/organization.py:320
+#: landa/organization_management/doctype/organization/organization.py:335
msgid "No Company found for Organization {0}."
msgstr "Kein Bestellwesenkonto RV für Verein {0} gefunden."
@@ -1557,6 +1645,11 @@ msgstr "Lebenslange Gültigkeit des Fischereischeins"
msgid "Permit Issue Date"
msgstr "Ausgabedatum Fischereischein"
+#. Label of a Float field in DocType 'Work Assignment'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.py:51
+msgid "Planned Duration (Hours)"
+msgstr "Geplante Dauer (Stunden)"
#: landa/organization_management/doctype/landa_member/landa_member_list.js:115
msgid "Permits have been created for {0} members and skipped for {1} members."
msgstr ""
@@ -1594,7 +1687,7 @@ msgstr "Bitte tragen Sie vor dem Buchen der Rechnung eine Rechnungsadresse ein."
msgid "Please set a Billing Contact before submitting the Sales Invoice."
msgstr "Bitte tragen Sie vor dem Buchen der Rechnung einen Rechnungskontakt ein."
-#: landa/organization_management/doctype/organization/organization.py:62
+#: landa/organization_management/doctype/organization/organization.py:66
msgid "Please set a Parent Organization."
msgstr "Bitte tragen Sie einen übergeordneten Verein ein."
@@ -1811,7 +1904,7 @@ msgstr "Abkürzung"
#: landa/organization_management/doctype/organization/organization_tree.js:40
msgid "Short code for regional organizations. For example, \"LVSA\", \"AVL\", etc."
-msgstr ""
+msgstr "Kürzel für Regionalverbände. z.B. \"LVSA\", \"AVL\", etc."
#: landa/organization_management/report/magazine_address_list/magazine_address_list.js:33
msgid "Show Only Current Recipients"
@@ -1950,7 +2043,7 @@ msgstr "Kostenübersicht Zusammenfassung"
#. Name of a DocType
#: landa/organization_management/doctype/tag_organization/tag_organization.json
msgid "Tag Organization"
-msgstr ""
+msgstr "Tag-Verein"
#. Label of a Link in the Organization Management Workspace
#: landa/organization_management/workspace/organization_management/organization_management.json
@@ -1981,15 +2074,15 @@ msgstr "Die hochgeladene Datei enthält keine Projekt-ID."
msgid "The weight of {0} in row {1} diverges from the typical weight by more than 40 %"
msgstr "Das Gewicht von {0} in Zeile {1} weicht um mehr als 40 % vom typischen Gewicht ab"
-#: landa/organization_management/doctype/organization/organization.py:316
+#: landa/organization_management/doctype/organization/organization.py:331
msgid "There is no Customer linked to {0}."
msgstr "Es ist kein Bestellwesenkonto mit {0} verknüpft."
-#: landa/organization_management/doctype/organization/organization.py:239
+#: landa/organization_management/doctype/organization/organization.py:254
msgid "There is no single address linked to {0}."
msgstr "Es ist keine einzelne Adresse mit {0} verknüpft."
-#: landa/organization_management/doctype/organization/organization.py:231
+#: landa/organization_management/doctype/organization/organization.py:246
msgid "There is no single contact linked to {0}."
msgstr "Es ist kein einzelner Kontakt mit {0} verknüpft."
@@ -2113,6 +2206,7 @@ msgstr ""
msgid "Vollzahler"
msgstr ""
+#. Label of a Link field in DocType 'Work Assignment'
#. Label of a Link field in DocType 'Catch Log Entry'
#. Linked DocType in Fishing Area's connections
#. Label of a Link field in DocType 'Lease Contract'
@@ -2126,6 +2220,9 @@ msgstr ""
#. Name of a DocType
#. Label of a Link field in DocType 'Water Body Management Local Organization'
#. Label of a Link in the Water Body Management Workspace
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.js:30
+#: landa/organization_management/report/work_hours/work_hours.py:58
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/fishing_area/fishing_area.json
#: landa/water_body_management/doctype/lease_contract/lease_contract.json
@@ -2256,11 +2353,13 @@ msgstr "Gewässer Sonderbestimmungen"
msgid "Water Body Status"
msgstr "Gewässerstatus"
+#. Label of a Data field in DocType 'Work Assignment'
#. Label of a Data field in DocType 'Catch Log Entry'
#. Label of a Data field in DocType 'Lease Contract'
#. Label of a Data field in DocType 'Stocking Measure'
#. Label of a Data field in DocType 'Stocking Target'
#. Label of a Data field in DocType 'Water Body Management Local Organization'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/lease_contract/lease_contract.json
#: landa/water_body_management/doctype/stocking_measure/stocking_measure.json
@@ -2301,6 +2400,56 @@ msgstr "Gewicht pro Gewässer Fläche/Länge"
msgid "Wikipedia Link"
msgstr ""
+#. Name of a DocType
+#. Label of a Link field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/landa_member/landa_member.js:77
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Work Assignment"
+msgstr "Arbeitseinsatz"
+
+#. Name of a DocType
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+msgid "Work Assignment Member"
+msgstr "Arbeitseinsatz Teilnehmer"
+
+#. Name of a report
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/report/work_hours/work_hours.json
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Hours"
+msgstr "Arbeitsstunden"
+
+#. Label of a Section Break field in DocType 'Organization'
+#: landa/organization_management/doctype/organization/organization.json
+msgid "Work Hours Account"
+msgstr "Arbeitsstundenkonto"
+
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Hours Per Member and Year"
+msgstr "Arbeitsstunden pro Mitglied und Jahr"
+
+#. Name of a report
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.json
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Ledger Balance"
+msgstr "Arbeitszeit je Verein und Jahr"
+
+#. Linked DocType in Organization's connections
+#. Linked DocType in Work Assignment's connections
+#. Name of a DocType
+#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Work Ledger Entry"
+msgstr "Arbeitsbucheintrag"
+
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:38
+msgid "Worked This Year"
+msgstr "Geleistet dieses Jahr"
+
#: landa/water_body_management/stocking_controller.py:23
msgid "Year must be between {0} and {1}."
msgstr "Jahr muss zwischen {0} und {1} liegen."
diff --git a/landa/locale/main.pot b/landa/locale/main.pot
index 0b8c2abe..6b85d0c0 100644
--- a/landa/locale/main.pot
+++ b/landa/locale/main.pot
@@ -94,6 +94,12 @@ msgstr ""
msgid "Access Level"
msgstr ""
+#. Label of a Data field in DocType 'Work Assignment'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.py:49
+msgid "Activity Title"
+msgstr ""
+
#. Label of a Data field in DocType 'Member Data Import'
#: landa/organization_management/doctype/member_data_import/member_data_import.json
#: landa/organization_management/report/current_member_data/current_member_data.py:174
@@ -156,10 +162,25 @@ msgstr ""
msgid "Awards"
msgstr ""
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:26
+msgid "Balance End Previous Year"
+msgstr ""
+
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:44
+msgid "Balance End of Year"
+msgstr ""
+
#: landa/water_body_management/doctype/stocking_measure/stocking_measure_list.js:30
msgid "Based on your selection, Stocking Targets are created accordingly for the specified year."
msgstr ""
+#. Label of a Section Break field in DocType 'Work Assignment'
+#. Label of a Section Break field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Basic Information"
+msgstr ""
+
#. Label of a Card Break in the Water Body Management Workspace
#: landa/water_body_management/workspace/water_body_management/water_body_management.json
msgid "Berichte"
@@ -225,7 +246,7 @@ msgstr ""
msgid "Cannot be a member of organization {} because it is a group."
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:70
+#: landa/organization_management/doctype/organization/organization.py:74
msgid "Cannot set Parent Organization to a local group."
msgstr ""
@@ -392,11 +413,30 @@ msgstr ""
msgid "Dummy"
msgstr ""
+#. Label of a Float field in DocType 'Work Assignment Member'
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/report/work_hours/work_hours.py:56
+msgid "Duration (Hours)"
+msgstr ""
+
#. Label of a Check field in DocType 'Firebase Settings'
#: landa/water_body_management/doctype/firebase_settings/firebase_settings.json
msgid "Enable Firebase Notifications"
msgstr ""
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:32
+msgid "Expected This Year"
+msgstr ""
+
+#. Label of a Float field in DocType 'Organization'
+#: landa/organization_management/doctype/organization/organization.json
+msgid "Expected Work Hours per Year"
+msgstr ""
+
+#: landa/organization_management/report/work_hours/work_hours.py:119
+msgid "Expected work hours adjustment"
+msgstr ""
+
#. Name of a DocType
#. Label of a Heading field in DocType 'External Contact'
#. Label of a Link in the Organization Management Workspace
@@ -694,6 +734,10 @@ msgstr ""
msgid "Group By Fish Species"
msgstr ""
+#: landa/organization_management/report/work_hours/work_hours.js:36
+msgid "Group by Member"
+msgstr ""
+
#. Label of a Check field in DocType 'Water Body'
#: landa/water_body_management/doctype/water_body/water_body.json
#: landa/water_body_management/report/water_body_export/water_body_export.py:55
@@ -780,6 +824,15 @@ msgstr ""
msgid "History"
msgstr ""
+#: landa/organization_management/report/work_hours/work_hours.py:32
+msgid "Hours Balance"
+msgstr ""
+
+#. Label of a Float field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Hours Change"
+msgstr ""
+
#. Label of a Link field in DocType 'Member Data Import'
#: landa/organization_management/doctype/member_data_import/member_data_import.json
#: landa/organization_management/report/current_member_data/current_member_data.py:221
@@ -796,6 +849,12 @@ msgstr ""
msgid "Icon Preview"
msgstr ""
+#. Description of the 'Work Assignment' (Link) field in DocType 'Work Ledger
+#. Entry'
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "If not set, this entry will be treated as an adjustment of expected hours."
+msgstr ""
+
#. Label of a Card Break in the Water Body Management Workspace
#: landa/water_body_management/workspace/water_body_management/water_body_management.json
msgid "Importe"
@@ -978,12 +1037,15 @@ msgstr ""
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
msgid "LANDA Local Organization Management"
msgstr ""
#. Name of a role
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/stocking_measure_import/stocking_measure_import.json
#: landa/water_body_management/doctype/stocking_target/stocking_target.json
@@ -1057,6 +1119,8 @@ msgstr ""
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/doctype/yearly_fishing_permit_type/yearly_fishing_permit_type.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
@@ -1093,6 +1157,8 @@ msgstr ""
#: landa/organization_management/doctype/member_function/member_function.json
#: landa/organization_management/doctype/member_function_category/member_function_category.json
#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/doctype/yearly_fishing_permit_type/yearly_fishing_permit_type.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
@@ -1266,11 +1332,15 @@ msgstr ""
#. Label of a Heading field in DocType 'LANDA Member'
#. Label of a Link field in DocType 'Member Function'
+#. Label of a Link field in DocType 'Work Assignment Member'
+#. Label of a Link field in DocType 'Work Ledger Entry'
#. Label of a Link field in DocType 'Yearly Fishing Permit'
#. Label of a Link in the Organization Management Workspace
#: landa/landa_sales/customer/customer.py:26
#: landa/organization_management/doctype/landa_member/landa_member.json
#: landa/organization_management/doctype/member_function/member_function.json
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
#: landa/organization_management/doctype/yearly_fishing_permit/yearly_fishing_permit.json
#: landa/organization_management/report/magazine_address_list/magazine_address_list.py:214
#: landa/organization_management/report/member_address_list/member_address_list.js:10
@@ -1279,6 +1349,11 @@ msgstr ""
#: landa/organization_management/report/member_contact_list/member_contact_list.js:10
#: landa/organization_management/report/member_contact_list/member_contact_list.py:53
#: landa/organization_management/report/members_with_member_functions/members_with_member_functions.py:21
+#: landa/organization_management/report/work_hours/work_hours.js:24
+#: landa/organization_management/report/work_hours/work_hours.py:19
+#: landa/organization_management/report/work_hours/work_hours.py:42
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.js:24
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:18
#: landa/organization_management/workspace/organization_management/organization_management.json
#: landa/water_body_management/report/members_in_water_body_management/members_in_water_body_management.py:17
msgid "Member"
@@ -1378,6 +1453,16 @@ msgstr ""
msgid "Member Last Name"
msgstr ""
+#. Label of a Data field in DocType 'Work Assignment Member'
+#. Label of a Data field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+#: landa/organization_management/report/work_hours/work_hours.py:26
+#: landa/organization_management/report/work_hours/work_hours.py:48
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:24
+msgid "Member Name"
+msgstr ""
+
#. Label of a Date field in DocType 'LANDA Member'
#: landa/organization_management/doctype/landa_member/landa_member.json
msgid "Member Since"
@@ -1469,7 +1554,7 @@ msgstr ""
msgid "New Stocking Measure Import"
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:320
+#: landa/organization_management/doctype/organization/organization.py:335
msgid "No Company found for Organization {0}."
msgstr ""
@@ -1557,6 +1642,11 @@ msgstr ""
msgid "Permit Issue Date"
msgstr ""
+#. Label of a Float field in DocType 'Work Assignment'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.py:51
+msgid "Planned Duration (Hours)"
+msgstr ""
#: landa/organization_management/doctype/landa_member/landa_member_list.js:115
msgid "Permits have been created for {0} members and skipped for {1} members."
msgstr ""
@@ -1594,7 +1684,7 @@ msgstr ""
msgid "Please set a Billing Contact before submitting the Sales Invoice."
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:62
+#: landa/organization_management/doctype/organization/organization.py:66
msgid "Please set a Parent Organization."
msgstr ""
@@ -1981,15 +2071,15 @@ msgstr ""
msgid "The weight of {0} in row {1} diverges from the typical weight by more than 40 %"
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:316
+#: landa/organization_management/doctype/organization/organization.py:331
msgid "There is no Customer linked to {0}."
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:239
+#: landa/organization_management/doctype/organization/organization.py:254
msgid "There is no single address linked to {0}."
msgstr ""
-#: landa/organization_management/doctype/organization/organization.py:231
+#: landa/organization_management/doctype/organization/organization.py:246
msgid "There is no single contact linked to {0}."
msgstr ""
@@ -2113,6 +2203,7 @@ msgstr ""
msgid "Vollzahler"
msgstr ""
+#. Label of a Link field in DocType 'Work Assignment'
#. Label of a Link field in DocType 'Catch Log Entry'
#. Linked DocType in Fishing Area's connections
#. Label of a Link field in DocType 'Lease Contract'
@@ -2126,6 +2217,9 @@ msgstr ""
#. Name of a DocType
#. Label of a Link field in DocType 'Water Body Management Local Organization'
#. Label of a Link in the Water Body Management Workspace
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/report/work_hours/work_hours.js:30
+#: landa/organization_management/report/work_hours/work_hours.py:58
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/fishing_area/fishing_area.json
#: landa/water_body_management/doctype/lease_contract/lease_contract.json
@@ -2256,11 +2350,13 @@ msgstr ""
msgid "Water Body Status"
msgstr ""
+#. Label of a Data field in DocType 'Work Assignment'
#. Label of a Data field in DocType 'Catch Log Entry'
#. Label of a Data field in DocType 'Lease Contract'
#. Label of a Data field in DocType 'Stocking Measure'
#. Label of a Data field in DocType 'Stocking Target'
#. Label of a Data field in DocType 'Water Body Management Local Organization'
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
#: landa/water_body_management/doctype/catch_log_entry/catch_log_entry.json
#: landa/water_body_management/doctype/lease_contract/lease_contract.json
#: landa/water_body_management/doctype/stocking_measure/stocking_measure.json
@@ -2301,6 +2397,56 @@ msgstr ""
msgid "Wikipedia Link"
msgstr ""
+#. Name of a DocType
+#. Label of a Link field in DocType 'Work Ledger Entry'
+#: landa/organization_management/doctype/landa_member/landa_member.js:77
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Work Assignment"
+msgstr ""
+
+#. Name of a DocType
+#: landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
+msgid "Work Assignment Member"
+msgstr ""
+
+#. Name of a report
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/report/work_hours/work_hours.json
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Hours"
+msgstr ""
+
+#. Label of a Section Break field in DocType 'Organization'
+#: landa/organization_management/doctype/organization/organization.json
+msgid "Work Hours Account"
+msgstr ""
+
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Hours Per Member and Year"
+msgstr ""
+
+#. Name of a report
+#. Label of a Link in the Organization Management Workspace
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.json
+#: landa/organization_management/workspace/organization_management/organization_management.json
+msgid "Work Ledger Balance"
+msgstr ""
+
+#. Linked DocType in Organization's connections
+#. Linked DocType in Work Assignment's connections
+#. Name of a DocType
+#: landa/organization_management/doctype/organization/organization.json
+#: landa/organization_management/doctype/work_assignment/work_assignment.json
+#: landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
+msgid "Work Ledger Entry"
+msgstr ""
+
+#: landa/organization_management/report/work_ledger_balance/work_ledger_balance.py:38
+msgid "Worked This Year"
+msgstr ""
+
#: landa/water_body_management/stocking_controller.py:23
msgid "Year must be between {0} and {1}."
msgstr ""
diff --git a/landa/organization_management/doctype/landa_member/landa_member.js b/landa/organization_management/doctype/landa_member/landa_member.js
index e20fb5f4..702515a4 100644
--- a/landa/organization_management/doctype/landa_member/landa_member.js
+++ b/landa/organization_management/doctype/landa_member/landa_member.js
@@ -21,6 +21,21 @@ frappe.ui.form.on("LANDA Member", {
organization: frm.doc.organization,
});
},
+ "Work Assignment": () => {
+ frappe.model.with_doctype("Work Assignment", () => {
+ let new_doc = frappe.model.get_new_doc("Work Assignment");
+ new_doc.organization = frm.doc.organization;
+
+ let child = frappe.model.add_child(
+ new_doc,
+ "Work Assignment Member",
+ "members",
+ );
+ child.member = frm.doc.name;
+
+ frappe.set_route("Form", "Work Assignment", new_doc.name);
+ });
+ },
};
},
refresh: function (frm) {
@@ -57,6 +72,14 @@ frappe.ui.form.on("LANDA Member", {
});
});
});
+
+ frm.add_custom_button(
+ __("Work Assignment"),
+ function () {
+ frm.make_methods["Work Assignment"]();
+ },
+ __("Create"),
+ );
}
},
});
diff --git a/landa/organization_management/doctype/organization/organization.json b/landa/organization_management/doctype/organization/organization.json
index 28f92ece..cbc48ba6 100644
--- a/landa/organization_management/doctype/organization/organization.json
+++ b/landa/organization_management/doctype/organization/organization.json
@@ -28,6 +28,8 @@
"section_break_15",
"is_group",
"disabled",
+ "section_break_work_hours",
+ "expected_work_hours_per_year",
"lft",
"rgt",
"old_parent",
@@ -198,6 +200,17 @@
"fieldtype": "Check",
"label": "Disabled",
"permlevel": 1
+ },
+ {
+ "fieldname": "section_break_work_hours",
+ "fieldtype": "Section Break",
+ "label": "Work Hours Account"
+ },
+ {
+ "fieldname": "expected_work_hours_per_year",
+ "fieldtype": "Float",
+ "label": "Expected Work Hours per Year",
+ "non_negative": 1
}
],
"grid_page_length": 50,
@@ -247,6 +260,11 @@
"group": "Gew\u00e4sserverwaltung",
"link_doctype": "Water Body Management Local Organization",
"link_fieldname": "organization"
+ },
+ {
+ "group": "Vereinsverwaltung",
+ "link_doctype": "Work Assignment",
+ "link_fieldname": "organization"
}
],
"modified": "2026-05-30 19:17:07.406605",
diff --git a/landa/organization_management/doctype/organization/organization.py b/landa/organization_management/doctype/organization/organization.py
index 77273705..578d0302 100644
--- a/landa/organization_management/doctype/organization/organization.py
+++ b/landa/organization_management/doctype/organization/organization.py
@@ -12,10 +12,13 @@
from frappe.desk.treeview import make_tree_args
from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.permissions import has_permission
-from frappe.utils.data import cint, get_link_to_form
+from frappe.utils.data import cint, flt, get_link_to_form
from frappe.utils.nestedset import NestedSet
from landa.organization_management.doctype.landa_member.landa_member import get_address_or_contact
+from landa.organization_management.doctype.work_ledger_entry.work_ledger_entry import (
+ create_expected_hours_adjustment_entries,
+)
class Organization(NestedSet):
@@ -29,6 +32,7 @@ class Organization(NestedSet):
charitable_until: DF.Date | None
disabled: DF.Check
+ expected_work_hours_per_year: DF.Float
fishing_area: DF.Link | None
is_charitable: DF.Check
is_group: DF.Check
@@ -84,10 +88,21 @@ def after_insert(self):
# of it yet.
frappe.cache().delete_key("user_permissions")
+ def before_save(self):
+ old = 0.0
+ if frappe.db.exists("Organization", self.name):
+ old = flt(frappe.db.get_value("Organization", self.name, "expected_work_hours_per_year"))
+ self._expected_work_hours_before_save = old
+
def onload(self):
load_address_and_contact(self)
def on_update(self):
+ if getattr(self, "_expected_work_hours_before_save", None) is not None:
+ old = flt(self._expected_work_hours_before_save)
+ new = flt(self.expected_work_hours_per_year)
+ if old != new:
+ create_expected_hours_adjustment_entries(self.name, old - new)
super().on_update()
def on_trash(self):
diff --git a/landa/organization_management/doctype/work_assignment/__init__.py b/landa/organization_management/doctype/work_assignment/__init__.py
new file mode 100644
index 00000000..6addbb82
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
diff --git a/landa/organization_management/doctype/work_assignment/test_work_assignment.py b/landa/organization_management/doctype/work_assignment/test_work_assignment.py
new file mode 100644
index 00000000..8a23a0b9
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment/test_work_assignment.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2026, ALYF GmbH and Contributors
+# See license.txt
+
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestWorkAssignment(FrappeTestCase):
+ pass
diff --git a/landa/organization_management/doctype/work_assignment/work_assignment.js b/landa/organization_management/doctype/work_assignment/work_assignment.js
new file mode 100644
index 00000000..ba606429
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment/work_assignment.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2026, ALYF GmbH and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Work Assignment", {
+ setup(frm) {
+ const can_select_any =
+ frappe.user.has_role("LANDA State Organization Employee") ||
+ frappe.user.has_role("LANDA Regional Organization Management");
+ const local_organization = frappe.boot.landa?.local_organization;
+
+ if (!can_select_any && local_organization && frm.is_new()) {
+ frm.set_value("organization", local_organization);
+ }
+
+ frm.set_query("organization", function () {
+ let filters = { is_group: 0 };
+ if (!can_select_any && local_organization) {
+ filters.name = local_organization;
+ }
+ return { filters };
+ });
+
+ frm.set_query("member", "members", function (doc) {
+ return {
+ filters: {
+ organization: doc.organization,
+ },
+ };
+ });
+ },
+});
+
+frappe.ui.form.on("Work Assignment Member", {
+ member(frm, cdt, cdn) {
+ let row = frappe.get_doc(cdt, cdn);
+ if (!row.duration && frm.doc.planned_duration) {
+ frappe.model.set_value(cdt, cdn, "duration", frm.doc.planned_duration);
+ }
+ },
+});
diff --git a/landa/organization_management/doctype/work_assignment/work_assignment.json b/landa/organization_management/doctype/work_assignment/work_assignment.json
new file mode 100644
index 00000000..47bfca2c
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment/work_assignment.json
@@ -0,0 +1,228 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "hash",
+ "creation": "2026-01-08 10:00:00",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_basic",
+ "organization",
+ "organization_name",
+ "date",
+ "column_break_basic",
+ "title",
+ "planned_duration",
+ "section_location",
+ "water_body",
+ "water_body_title",
+ "column_break_location",
+ "location",
+ "description",
+ "section_members",
+ "members",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "section_basic",
+ "fieldtype": "Section Break",
+ "label": "Basic Information"
+ },
+ {
+ "fieldname": "organization",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Organization",
+ "options": "Organization",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fetch_from": "organization.organization_name",
+ "fieldname": "organization_name",
+ "fieldtype": "Data",
+ "label": "Organization Name",
+ "read_only": 1
+ },
+ {
+ "default": "Today",
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_basic",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Activity Title",
+ "reqd": 1
+ },
+ {
+ "fieldname": "planned_duration",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Planned Duration (Hours)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_location",
+ "fieldtype": "Section Break",
+ "label": "Location"
+ },
+ {
+ "fieldname": "water_body",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Water Body",
+ "options": "Water Body"
+ },
+ {
+ "fetch_from": "water_body.title",
+ "fieldname": "water_body_title",
+ "fieldtype": "Data",
+ "label": "Water Body Title",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_location",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "location",
+ "fieldtype": "Data",
+ "label": "Location"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description"
+ },
+ {
+ "fieldname": "section_members",
+ "fieldtype": "Section Break",
+ "label": "Participants"
+ },
+ {
+ "fieldname": "members",
+ "fieldtype": "Table",
+ "label": "Members",
+ "options": "Work Assignment Member"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Work Assignment",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "is_submittable": 1,
+ "links": [
+ {
+ "link_doctype": "Work Ledger Entry",
+ "link_fieldname": "work_assignment"
+ }
+ ],
+ "modified": "2026-05-06 09:45:36.342442",
+ "modified_by": "Administrator",
+ "module": "Organization Management",
+ "name": "Work Assignment",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA State Organization Employee",
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA Regional Organization Management",
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA Local Organization Management",
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA Local Water Body Management",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "search_fields": "title,organization",
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/landa/organization_management/doctype/work_assignment/work_assignment.py b/landa/organization_management/doctype/work_assignment/work_assignment.py
new file mode 100644
index 00000000..a9af8da1
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment/work_assignment.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+
+
+class WorkAssignment(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ from landa.organization_management.doctype.work_assignment_member.work_assignment_member import (
+ WorkAssignmentMember,
+ )
+
+ amended_from: DF.Link | None
+ date: DF.Date
+ description: DF.SmallText | None
+ location: DF.Data | None
+ members: DF.Table[WorkAssignmentMember]
+ organization: DF.Link
+ organization_name: DF.Data | None
+ planned_duration: DF.Float
+ title: DF.Data
+ water_body: DF.Link | None
+ water_body_title: DF.Data | None
+ # end: auto-generated types
+
+ def on_submit(self):
+ self.create_work_ledger_entry()
+
+ def on_cancel(self):
+ self.delete_work_ledger_entry()
+
+ def create_work_ledger_entry(self):
+ if not self.members:
+ return
+ for member in self.members:
+ work_ledger_entry = frappe.new_doc("Work Ledger Entry")
+ work_ledger_entry.member = member.member
+ work_ledger_entry.organization = self.organization
+ work_ledger_entry.date = self.date
+ work_ledger_entry.work_assignment = self.name
+ work_ledger_entry.hours_change = member.duration
+ work_ledger_entry.insert()
+
+ def delete_work_ledger_entry(self):
+ for name in frappe.get_all(
+ "Work Ledger Entry",
+ filters={"work_assignment": self.name},
+ pluck="name",
+ ):
+ frappe.delete_doc("Work Ledger Entry", name)
diff --git a/landa/organization_management/doctype/work_assignment_member/__init__.py b/landa/organization_management/doctype/work_assignment_member/__init__.py
new file mode 100644
index 00000000..6addbb82
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment_member/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
diff --git a/landa/organization_management/doctype/work_assignment_member/work_assignment_member.json b/landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
new file mode 100644
index 00000000..f43c0a83
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment_member/work_assignment_member.json
@@ -0,0 +1,51 @@
+{
+ "actions": [],
+ "creation": "2026-01-08 10:00:00.000000",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "member",
+ "member_name",
+ "duration"
+ ],
+ "fields": [
+ {
+ "fieldname": "member",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Member",
+ "options": "LANDA Member",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "member.full_name",
+ "fieldname": "member_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Member Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "duration",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Duration (Hours)",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-01-08 10:00:00.000000",
+ "modified_by": "Administrator",
+ "module": "Organization Management",
+ "name": "Work Assignment Member",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
+
diff --git a/landa/organization_management/doctype/work_assignment_member/work_assignment_member.py b/landa/organization_management/doctype/work_assignment_member/work_assignment_member.py
new file mode 100644
index 00000000..dd5b85f2
--- /dev/null
+++ b/landa/organization_management/doctype/work_assignment_member/work_assignment_member.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
+from frappe.model.document import Document
+
+
+class WorkAssignmentMember(Document):
+ pass
diff --git a/landa/organization_management/doctype/work_ledger_entry/__init__.py b/landa/organization_management/doctype/work_ledger_entry/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/landa/organization_management/doctype/work_ledger_entry/test_work_ledger_entry.py b/landa/organization_management/doctype/work_ledger_entry/test_work_ledger_entry.py
new file mode 100644
index 00000000..c5ef9427
--- /dev/null
+++ b/landa/organization_management/doctype/work_ledger_entry/test_work_ledger_entry.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2026, ALYF GmbH and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestWorkLedgerEntry(FrappeTestCase):
+ pass
diff --git a/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.js b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.js
new file mode 100644
index 00000000..2997a13c
--- /dev/null
+++ b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2026, ALYF GmbH and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Work Ledger Entry", {
+ setup(frm) {
+ frm.set_query("member", function () {
+ return {
+ filters: { organization: frm.doc.organization },
+ };
+ });
+ },
+
+ refresh(frm) {
+ if (!frm.is_new() && frm.doc.work_assignment) {
+ frm.disable_form();
+ }
+ },
+});
diff --git a/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
new file mode 100644
index 00000000..35b3726c
--- /dev/null
+++ b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.json
@@ -0,0 +1,154 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "hash",
+ "creation": "2026-01-28 12:55:36.000975",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_basic",
+ "organization",
+ "organization_name",
+ "member",
+ "member_name",
+ "column_break_basic",
+ "date",
+ "work_assignment",
+ "hours_change"
+ ],
+ "fields": [
+ {
+ "fieldname": "section_basic",
+ "fieldtype": "Section Break",
+ "label": "Basic Information"
+ },
+ {
+ "depends_on": "eval:doc.organization",
+ "fieldname": "member",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Member",
+ "options": "LANDA Member",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fetch_from": "member.full_name",
+ "fieldname": "member_name",
+ "fieldtype": "Data",
+ "label": "Member Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "organization",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Organization",
+ "options": "Organization",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fetch_from": "organization.organization_name",
+ "fieldname": "organization_name",
+ "fieldtype": "Data",
+ "label": "Organization Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_basic",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Today",
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "description": "If not set, this entry will be treated as an adjustment of expected hours.",
+ "fieldname": "work_assignment",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Work Assignment",
+ "options": "Work Assignment"
+ },
+ {
+ "fieldname": "hours_change",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Hours Change",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2026-03-09 21:22:56.616937",
+ "modified_by": "Administrator",
+ "module": "Organization Management",
+ "name": "Work Ledger Entry",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA State Organization Employee",
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA Regional Organization Management",
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "LANDA Local Organization Management",
+ "write": 1
+ }
+ ],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "search_fields": "member,organization",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "member_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.py b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.py
new file mode 100644
index 00000000..256a5727
--- /dev/null
+++ b/landa/organization_management/doctype/work_ledger_entry/work_ledger_entry.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+from frappe.utils import getdate
+from frappe.utils.data import now_datetime
+
+
+class WorkLedgerEntry(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ date: DF.Date
+ hours_change: DF.Float
+ member: DF.Link
+ member_name: DF.Data | None
+ organization: DF.Link
+ organization_name: DF.Data | None
+ work_assignment: DF.Link | None
+ # end: auto-generated types
+
+
+def create_yearly_negative_entries():
+ """Create negative Work Ledger Entries for all members based on expected work hours per year."""
+
+ organizations = frappe.get_list(
+ "Organization",
+ filters={"expected_work_hours_per_year": [">", 0]},
+ fields=["name", "expected_work_hours_per_year"],
+ )
+
+ if not organizations:
+ return
+
+ # Get all active members for each organization
+ for org in organizations:
+ members = frappe.get_list(
+ "LANDA Member",
+ filters={"organization": org.name},
+ fields=["name"],
+ )
+
+ for member in members:
+ ledger_entry = frappe.new_doc("Work Ledger Entry")
+ ledger_entry.member = member.name
+ ledger_entry.organization = org.name
+ ledger_entry.date = f"{now_datetime().year}-01-01"
+ ledger_entry.hours_change = -org.expected_work_hours_per_year
+ ledger_entry.insert()
+
+
+def create_expected_hours_adjustment_entries(organization: str, hours_change: float):
+ """
+ Create one Work Ledger Entry per member of the organization for the given hours change.
+ Its called when Organization.expected_work_hours_per_year is updated.
+ """
+
+ if hours_change == 0:
+ return
+
+ members = frappe.get_list(
+ "LANDA Member",
+ filters={"organization": organization},
+ fields=["name"],
+ )
+ for m in members:
+ entry = frappe.new_doc("Work Ledger Entry")
+ entry.organization = organization
+ entry.member = m.name
+ entry.date = getdate()
+ entry.hours_change = hours_change
+ entry.insert()
diff --git a/landa/organization_management/report/work_hours/__init__.py b/landa/organization_management/report/work_hours/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/landa/organization_management/report/work_hours/work_hours.js b/landa/organization_management/report/work_hours/work_hours.js
new file mode 100644
index 00000000..46db7496
--- /dev/null
+++ b/landa/organization_management/report/work_hours/work_hours.js
@@ -0,0 +1,50 @@
+// Copyright (c) 2026, ALYF GmbH and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Work Hours"] = {
+ filters: [
+ {
+ fieldname: "year",
+ label: __("Year"),
+ fieldtype: "Select",
+ options: get_year_options(),
+ default: new Date().getFullYear().toString(),
+ },
+ {
+ fieldname: "organization",
+ label: __("Organization"),
+ fieldtype: "Link",
+ options: "Organization",
+ get_query: function () {
+ return { filters: { is_group: 0 } };
+ },
+ },
+ {
+ fieldname: "member",
+ label: __("Member"),
+ fieldtype: "Link",
+ options: "LANDA Member",
+ },
+ {
+ fieldname: "water_body",
+ label: __("Water Body"),
+ fieldtype: "Link",
+ options: "Water Body",
+ },
+ {
+ fieldname: "group_by_member",
+ label: __("Group by Member"),
+ fieldtype: "Check",
+ default: 0,
+ },
+ ],
+};
+
+function get_year_options() {
+ const current_year = new Date().getFullYear();
+ const years = [];
+ for (let i = current_year; i >= current_year - 5; i--) {
+ years.push(i.toString());
+ }
+ return years.join("\n");
+}
diff --git a/landa/organization_management/report/work_hours/work_hours.json b/landa/organization_management/report/work_hours/work_hours.json
new file mode 100644
index 00000000..dbfd30f4
--- /dev/null
+++ b/landa/organization_management/report/work_hours/work_hours.json
@@ -0,0 +1,43 @@
+{
+ "add_total_row": 1,
+ "add_translate_data": 0,
+ "columns": [],
+ "creation": "2026-01-08 17:06:21.498262",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": null,
+ "modified": "2026-05-06 10:14:44.943018",
+ "modified_by": "Administrator",
+ "module": "Organization Management",
+ "name": "Work Hours",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Ledger Entry",
+ "report_name": "Work Hours",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "LANDA Member"
+ },
+ {
+ "role": "LANDA State Organization Employee"
+ },
+ {
+ "role": "LANDA Regional Organization Management"
+ },
+ {
+ "role": "LANDA Local Organization Management"
+ },
+ {
+ "role": "LANDA Local Group Management"
+ }
+ ],
+ "timeout": 0
+}
\ No newline at end of file
diff --git a/landa/organization_management/report/work_hours/work_hours.py b/landa/organization_management/report/work_hours/work_hours.py
new file mode 100644
index 00000000..b3c0d956
--- /dev/null
+++ b/landa/organization_management/report/work_hours/work_hours.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.utils import getdate
+
+
+def execute(filters=None):
+ columns = get_columns(filters)
+ data = get_data(filters)
+ return columns, data
+
+
+def get_columns(filters):
+ if filters.get("group_by_member"):
+ return [
+ {
+ "label": _("Member"),
+ "fieldname": "member",
+ "fieldtype": "Link",
+ "options": "LANDA Member",
+ "width": 150,
+ },
+ {
+ "label": _("Member Name"),
+ "fieldname": "member_name",
+ "fieldtype": "Data",
+ "width": 200,
+ },
+ {
+ "label": _("Hours Balance"),
+ "fieldname": "total_hours",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ ]
+
+ return [
+ {"label": _("Date"), "fieldname": "date", "fieldtype": "Date", "width": 100},
+ {
+ "label": _("Member"),
+ "fieldname": "member",
+ "fieldtype": "Link",
+ "options": "LANDA Member",
+ "width": 120,
+ },
+ {"label": _("Member Name"), "fieldname": "member_name", "fieldtype": "Data", "width": 150},
+ {"label": _("Activity Title"), "fieldname": "title", "fieldtype": "Data", "width": 200},
+ {
+ "label": _("Planned Duration (Hours)"),
+ "fieldname": "planned_duration",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ {"label": _("Duration (Hours)"), "fieldname": "duration", "fieldtype": "Float", "width": 120},
+ {
+ "label": _("Water Body"),
+ "fieldname": "water_body",
+ "fieldtype": "Link",
+ "options": "Water Body",
+ "width": 150,
+ },
+ {"label": _("Location"), "fieldname": "location", "fieldtype": "Data", "width": 150},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200},
+ ]
+
+
+def get_data(filters):
+ filters = filters or {}
+ ledger_filters = _get_ledger_filters(filters)
+
+ if filters.get("group_by_member"):
+ return frappe.get_list(
+ "Work Ledger Entry",
+ filters=ledger_filters,
+ fields=[
+ "member",
+ "member_name",
+ "sum(hours_change) as total_hours",
+ ],
+ group_by="member, member_name",
+ order_by="total_hours desc",
+ )
+
+ entries = frappe.get_list(
+ "Work Ledger Entry",
+ filters=ledger_filters,
+ fields=["date", "member", "member_name", "work_assignment", "hours_change"],
+ order_by="date desc",
+ )
+
+ if not entries:
+ return []
+
+ assignment_names = list({e.get("work_assignment") for e in entries if e.get("work_assignment")})
+ assignments_by_name = {}
+ if assignment_names:
+ for a in frappe.get_list(
+ "Work Assignment",
+ filters={"name": ("in", assignment_names)},
+ fields=["name", "title", "planned_duration", "water_body", "location", "description"],
+ ):
+ assignments_by_name[a["name"]] = a
+
+ data = []
+ for row in entries:
+ assignment = assignments_by_name.get(row.get("work_assignment")) or {}
+ data.append(
+ {
+ "date": row.get("date"),
+ "member": row.get("member"),
+ "member_name": row.get("member_name"),
+ "title": assignment.get("title"),
+ "planned_duration": assignment.get("planned_duration"),
+ "duration": row.get("hours_change"),
+ "water_body": assignment.get("water_body"),
+ "location": assignment.get("location"),
+ "description": _("Expected work hours adjustment")
+ if not row.get("work_assignment")
+ else assignment.get("description"),
+ }
+ )
+
+ default_date = getdate("1900-01-01")
+ data.sort(key=lambda r: (r.get("member") or ""))
+ data.sort(key=lambda r: r.get("date") or default_date, reverse=True)
+ return data
+
+
+def _get_ledger_filters(filters):
+ ledger_filters = {}
+
+ if filters.get("year"):
+ year = int(filters["year"])
+ ledger_filters["date"] = ["between", [f"{year}-01-01", f"{year}-12-31"]]
+
+ if filters.get("organization"):
+ ledger_filters["organization"] = filters["organization"]
+
+ if filters.get("member"):
+ ledger_filters["member"] = filters["member"]
+
+ if filters.get("water_body"):
+ assignment_names = frappe.get_list(
+ "Work Assignment",
+ filters={"water_body": filters["water_body"]},
+ pluck="name",
+ )
+ if not assignment_names:
+ return {"name": "__no_match__"}
+ ledger_filters["work_assignment"] = ("in", assignment_names)
+
+ return ledger_filters
diff --git a/landa/organization_management/report/work_ledger_balance/__init__.py b/landa/organization_management/report/work_ledger_balance/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/landa/organization_management/report/work_ledger_balance/work_ledger_balance.js b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.js
new file mode 100644
index 00000000..c5776fd0
--- /dev/null
+++ b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.js
@@ -0,0 +1,38 @@
+// Copyright (c) 2026, ALYF GmbH and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Work Ledger Balance"] = {
+ filters: [
+ {
+ fieldname: "year",
+ label: __("Year"),
+ fieldtype: "Select",
+ options: get_year_options(),
+ default: new Date().getFullYear().toString(),
+ },
+ {
+ fieldname: "organization",
+ label: __("Organization"),
+ fieldtype: "Link",
+ options: "Organization",
+ get_query: function () {
+ return { filters: { is_group: 0 } };
+ },
+ },
+ {
+ fieldname: "member",
+ label: __("Member"),
+ fieldtype: "Link",
+ options: "LANDA Member",
+ },
+ ],
+};
+
+function get_year_options() {
+ const current_year = new Date().getFullYear();
+ const years = [];
+ for (let i = current_year; i >= current_year - 10; i--) {
+ years.push(i.toString());
+ }
+ return years.join("\n");
+}
diff --git a/landa/organization_management/report/work_ledger_balance/work_ledger_balance.json b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.json
new file mode 100644
index 00000000..b5554912
--- /dev/null
+++ b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "add_translate_data": 0,
+ "columns": [],
+ "creation": "2026-02-18 12:00:00.000000",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": null,
+ "modified": "2026-02-18 12:00:00.000000",
+ "modified_by": "Administrator",
+ "module": "Organization Management",
+ "name": "Work Ledger Balance",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Ledger Entry",
+ "report_name": "Work Ledger Balance",
+ "report_type": "Script Report",
+ "roles": [
+ {"role": "System Manager"},
+ {"role": "LANDA Member"},
+ {"role": "LANDA State Organization Employee"},
+ {"role": "LANDA Regional Organization Management"},
+ {"role": "LANDA Local Organization Management"},
+ {"role": "LANDA Local Group Management"}
+ ],
+ "timeout": 0
+}
diff --git a/landa/organization_management/report/work_ledger_balance/work_ledger_balance.py b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.py
new file mode 100644
index 00000000..3016d899
--- /dev/null
+++ b/landa/organization_management/report/work_ledger_balance/work_ledger_balance.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2026, ALYF GmbH and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.utils import getdate
+
+
+def execute(filters=None):
+ columns = get_columns()
+ data = get_data(filters or {})
+ return columns, data
+
+
+def get_columns():
+ return [
+ {
+ "label": _("Member"),
+ "fieldname": "member",
+ "fieldtype": "Link",
+ "options": "LANDA Member",
+ "width": 150,
+ },
+ {"label": _("Member Name"), "fieldname": "member_name", "fieldtype": "Data", "width": 180},
+ {
+ "label": _("Balance End Previous Year"),
+ "fieldname": "balance_previous_year",
+ "fieldtype": "Float",
+ "width": 200,
+ },
+ {
+ "label": _("Expected This Year"),
+ "fieldname": "expected_this_year",
+ "fieldtype": "Float",
+ "width": 200,
+ },
+ {
+ "label": _("Worked This Year"),
+ "fieldname": "worked_this_year",
+ "fieldtype": "Float",
+ "width": 200,
+ },
+ {
+ "label": _("Balance End of Year"),
+ "fieldname": "balance_end_of_year",
+ "fieldtype": "Float",
+ "width": 200,
+ },
+ ]
+
+
+def get_data(filters):
+ year = int(filters.get("year") or frappe.utils.getdate().year)
+ year_start = getdate(f"{year}-01-01")
+ year_end = getdate(f"{year}-12-31")
+
+ org_filter = filters.get("organization")
+ member_filter = filters.get("member")
+
+ base_filters = [["date", "<=", year_end]]
+ if org_filter:
+ base_filters.append(["organization", "=", org_filter])
+ if member_filter:
+ base_filters.append(["member", "=", member_filter])
+
+ entries = frappe.get_list(
+ "Work Ledger Entry",
+ filters=base_filters,
+ fields=["member", "member_name", "date", "hours_change", "work_assignment"],
+ )
+ if not entries:
+ return []
+
+ by_member = {}
+ for row in entries:
+ m = row["member"]
+ if m not in by_member:
+ by_member[m] = {"member": m, "member_name": row.get("member_name"), "rows": []}
+ by_member[m]["rows"].append(
+ {
+ "date": row["date"],
+ "hours_change": float(row.get("hours_change") or 0),
+ "work_assignment": row.get("work_assignment"),
+ }
+ )
+
+ result = []
+ for member, data in by_member.items():
+ balance_previous = sum(
+ r["hours_change"] for r in data["rows"] if r["date"] is not None and r["date"] < year_start
+ )
+ in_year = [
+ row for row in data["rows"] if row["date"] is not None and year_start <= row["date"] <= year_end
+ ]
+ expected_this_year = -1 * sum(
+ row.get("hours_change") for row in in_year if row.get("work_assignment") is None
+ )
+ worked_this_year = sum(
+ row.get("hours_change") for row in in_year if row.get("work_assignment") is not None
+ )
+ balance_end = balance_previous - expected_this_year + worked_this_year
+
+ result.append(
+ {
+ "member": member,
+ "member_name": data["member_name"],
+ "balance_previous_year": round(balance_previous, 2),
+ "expected_this_year": round(expected_this_year, 2),
+ "worked_this_year": round(worked_this_year, 2),
+ "balance_end_of_year": round(balance_end, 2),
+ }
+ )
+
+ result.sort(key=lambda r: (r["member_name"] or "", r["member"]))
+ return result
diff --git a/landa/organization_management/workspace/organization_management/organization_management.json b/landa/organization_management/workspace/organization_management/organization_management.json
index 51b9911d..db1b0b73 100644
--- a/landa/organization_management/workspace/organization_management/organization_management.json
+++ b/landa/organization_management/workspace/organization_management/organization_management.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"id\":\"1uTyrJlebP\",\"type\":\"header\",\"data\":{\"text\":\"Schnellzugriff\",\"col\":12}},{\"id\":\"vSObTmx5FJ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Neues Mitglied anlegen\",\"col\":4}},{\"id\":\"kIubCGyGa6\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Address List\",\"col\":4}},{\"id\":\"hxillFj4xC\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Contact List\",\"col\":4}},{\"id\":\"TdmgLajXcz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Birthday List\",\"col\":4}},{\"id\":\"pyW2rRoxN5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Award Entry\",\"col\":4}},{\"id\":\"LhkZgoe7J6\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Member Function Entry\",\"col\":4}},{\"id\":\"M6BGPcbTdL\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Show Organization List\",\"col\":4}},{\"id\":\"4XErd7BVw_\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"RgNOYMq0fd\",\"type\":\"header\",\"data\":{\"text\":\"Berichte & Stammdaten\",\"col\":12}},{\"id\":\"Vs8PWZl7sT\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"T-qvfB98nf\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"ce1489-Jj0\",\"type\":\"card\",\"data\":{\"card_name\":\"Imports\",\"col\":4}},{\"id\":\"KN8t-FsoLs\",\"type\":\"card\",\"data\":{\"card_name\":\"Setup\",\"col\":4}}]",
+ "content": "[{\"id\":\"1uTyrJlebP\",\"type\":\"header\",\"data\":{\"text\":\"Schnellzugriff\",\"col\":12}},{\"id\":\"vSObTmx5FJ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Neues Mitglied anlegen\",\"col\":4}},{\"id\":\"kIubCGyGa6\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Address List\",\"col\":4}},{\"id\":\"hxillFj4xC\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Contact List\",\"col\":4}},{\"id\":\"TdmgLajXcz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member Birthday List\",\"col\":4}},{\"id\":\"pyW2rRoxN5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Award Entry\",\"col\":4}},{\"id\":\"LhkZgoe7J6\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Member Function Entry\",\"col\":4}},{\"id\":\"M6BGPcbTdL\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Show Organization List\",\"col\":4}},{\"id\":\"js1RQK6MeL\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Work Assignment\",\"col\":4}},{\"id\":\"rCLi2ifrHP\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Show Work Assignment List\",\"col\":4}},{\"id\":\"4XErd7BVw_\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"RgNOYMq0fd\",\"type\":\"header\",\"data\":{\"text\":\"Berichte & Stammdaten\",\"col\":12}},{\"id\":\"Vs8PWZl7sT\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"T-qvfB98nf\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"ce1489-Jj0\",\"type\":\"card\",\"data\":{\"card_name\":\"Imports\",\"col\":4}},{\"id\":\"KN8t-FsoLs\",\"type\":\"card\",\"data\":{\"card_name\":\"Setup\",\"col\":4}}]",
"creation": "2021-04-19 18:01:23.947644",
"custom_blocks": [],
"docstatus": 0,
@@ -213,6 +213,7 @@
"link_to": "Yearly Fishing Permits Issued",
"link_type": "Report",
"onboard": 0,
+ "report_ref_doctype": "Yearly Fishing Permit",
"type": "Link"
},
{
@@ -225,6 +226,28 @@
"onboard": 0,
"type": "Link"
},
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Work Hours",
+ "link_count": 0,
+ "link_to": "Work Hours",
+ "link_type": "Report",
+ "onboard": 0,
+ "report_ref_doctype": "Work Assignment",
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Work Ledger Balance",
+ "link_count": 0,
+ "link_to": "Work Ledger Balance",
+ "link_type": "Report",
+ "onboard": 0,
+ "report_ref_doctype": "Work Ledger Entry",
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -285,7 +308,7 @@
"type": "Link"
}
],
- "modified": "2024-07-11 14:59:31.302312",
+ "modified": "2026-05-05 16:36:15.445843",
"modified_by": "Administrator",
"module": "Organization Management",
"name": "Organization Management",
@@ -304,22 +327,41 @@
"stats_filter": "[]",
"type": "DocType"
},
+ {
+ "color": "Grey",
+ "doc_view": "New",
+ "label": "New Work Assignment",
+ "link_to": "Work Assignment",
+ "stats_filter": "[]",
+ "type": "DocType"
+ },
{
"doc_view": "",
"label": "Member Address List",
"link_to": "Member Address List",
+ "report_ref_doctype": "LANDA Member",
"type": "Report"
},
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Show Work Assignment List",
+ "link_to": "Work Assignment",
+ "stats_filter": "[]",
+ "type": "DocType"
+ },
{
"doc_view": "",
"label": "Member Contact List",
"link_to": "Member Contact List",
+ "report_ref_doctype": "LANDA Member",
"type": "Report"
},
{
"doc_view": "",
"label": "Member Birthday List",
"link_to": "Member Birthday List",
+ "report_ref_doctype": "LANDA Member",
"type": "Report"
},
{