Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: Bug report
about: Report a bug encountered while using this app
title: ''
labels: bug
assignees: ''

---

<!--

Hättest du diesen Fehler nicht gemacht oder kannst ihn selbst beheben? Dann komm zu uns!
https://www.alyf.de/karriere

-->

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Installed apps and versions**
Go to "Help" > "About", or run `bench version` to get the exact version number of all installed apps.

**Additional context**
Add any other context about the problem here: error logs, stack traces, etc.
27 changes: 27 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea to improve this app
title: ''
labels: enhancement
assignees: ''

---

<!--

Könntest du dieses Feature auch selbst entwickeln? Dann komm zu uns!
https://www.alyf.de/karriere

-->

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ frappe.ui.form.on("Business Trip", {
frm.set_query("employee", erpnext.queries.employee);
frm.set_query("region", (doc) => {
return {
filters: {
valid_from: ["<=", doc.from_date],
},
filters: [["Business Trip Region Allowance", "valid_from", "<=", doc.from_date]],
};
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
DEFAULT_EXPENSE_CLAIM_TYPE = "Additional meal expenses"

if TYPE_CHECKING:
from erpnext_germany.erpnext_germany.doctype.business_trip_region.business_trip_region import (
BusinessTripRegion,
)
from erpnext_germany.erpnext_germany.doctype.business_trip_settings.business_trip_settings import (
BusinessTripSettings,
)
Expand Down Expand Up @@ -75,12 +72,19 @@ def set_regional_amount(self):
if not self.region:
return

region: BusinessTripRegion = frappe.get_doc("Business Trip Region", self.region)
whole_day = region.whole_day or 0.0
arrival_or_departure = region.arrival_or_departure or 0.0
accomodation = region.accommodation or 0.0

for allowance in self.allowances:
whole_day = 0.0
arrival_or_departure = 0.0
accommodation = 0.0

allowance_rates = _get_allowance_rates(self.region, allowance.date)
days_rates = allowance_rates[0] if allowance_rates else None

if days_rates:
whole_day = days_rates.whole_day
arrival_or_departure = days_rates.arrival_or_departure
accommodation = days_rates.accommodation

amount = whole_day if allowance.whole_day else arrival_or_departure
if allowance.breakfast_was_provided:
amount -= whole_day * 0.2
Expand All @@ -92,7 +96,7 @@ def set_regional_amount(self):
amount -= whole_day * 0.4

if not allowance.accommodation_was_provided:
amount += accomodation
amount += accommodation

allowance.amount = max(amount, 0.0)

Expand Down Expand Up @@ -192,9 +196,13 @@ def get_meal_expenses(business_trip: BusinessTrip, expense_claim_type: str) -> l
expenses = []
for allowance in business_trip.allowances:
description = "Ganztägig" if allowance.whole_day else "An-/Abreise"
if not allowance.accommodation_was_provided and frappe.db.get_value(
"Business Trip Region", business_trip.region, "accommodation"
):

accommodation = 0.0
allowance_rates = _get_allowance_rates(business_trip.region, allowance.date)
if allowance_rates:
accommodation = allowance_rates[0].accommodation

if not allowance.accommodation_was_provided and accommodation:
description += ", zzgl. Hotel"

if allowance.breakfast_was_provided:
Expand Down Expand Up @@ -258,3 +266,19 @@ def get_processing_details(business_trip: str):
combined_records.append(invoice)

return combined_records


def _get_allowance_rates(region: str, date: str):
"""Return allowance rates for a region valid on or before a date.

Results are ordered by `valid_from` in descending order so the most recent applicable rate is first.
"""
return frappe.get_all(
"Business Trip Region Allowance",
filters={
"parent": region,
"valid_from": ["<=", date],
},
order_by="valid_from DESC",
fields=["valid_from", "whole_day", "arrival_or_departure", "accommodation"],
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,18 @@
"engine": "InnoDB",
"field_order": [
"title",
"whole_day",
"arrival_or_departure",
"accommodation",
"column_break_ajul",
"valid_from",
"disabled"
"disabled",
"section_break_xukx",
"allowances"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title"
},
{
"description": "For an absence of at least 24 hours per calendar day",
"fieldname": "whole_day",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Whole Day"
},
{
"description": "For the day of arrival and departure and for an absence of more than 8 hours per calendar day",
"fieldname": "arrival_or_departure",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Arrival or Departure"
},
{
"description": "Lump sum for accommodation expenses",
"fieldname": "accommodation",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Accommodation"
},
{
"fieldname": "valid_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Valid From"
"label": "Title",
"reqd": 1
},
{
"fieldname": "column_break_ajul",
Expand All @@ -56,16 +29,28 @@
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "section_break_xukx",
"fieldtype": "Section Break"
},
{
"fieldname": "allowances",
"fieldtype": "Table",
"label": "Allowances",
"options": "Business Trip Region Allowance",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [
{
"link_doctype": "Business Trip",
"link_fieldname": "region"
}
],
"modified": "2024-10-24 22:42:37.127349",
"modified": "2026-01-19 16:42:23.130520",
"modified_by": "Administrator",
"module": "ERPNext Germany",
"name": "Business Trip Region",
Expand Down Expand Up @@ -95,7 +80,7 @@
"write": 1
}
],
"search_fields": "valid_from",
"row_format": "Dynamic",
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright (c) 2024, ALYF GmbH and contributors
# For license information, please see license.txt

# import frappe
import frappe
from frappe import _
from frappe.model.document import Document


Expand All @@ -14,11 +15,15 @@ class BusinessTripRegion(Document):
if TYPE_CHECKING:
from frappe.types import DF

accommodation: DF.Currency
arrival_or_departure: DF.Currency
from erpnext_germany.erpnext_germany.doctype.business_trip_region_allowance.business_trip_region_allowance import ( # noqa: E501
BusinessTripRegionAllowance,
)

allowances: DF.Table[BusinessTripRegionAllowance]
disabled: DF.Check
title: DF.Data | None
valid_from: DF.Date | None
whole_day: DF.Currency
title: DF.Data
# end: auto-generated types
pass

def validate(self):
if len(set(allowance.valid_from for allowance in self.allowances)) != len(self.allowances):
frappe.throw(_("There are multiple allowance rows with the same Valid From date."))
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-12-22 16:33:46.478777",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"valid_from",
"whole_day",
"arrival_or_departure",
"accommodation"
],
"fields": [
{
"fieldname": "valid_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Valid From",
"reqd": 1
},
{
"fieldname": "whole_day",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Whole Day",
"reqd": 1
},
{
"fieldname": "arrival_or_departure",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Arrival or Departure",
"reqd": 1
},
{
"fieldname": "accommodation",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Accommodation",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-12-22 16:36:19.657408",
"modified_by": "Administrator",
"module": "ERPNext Germany",
"name": "Business Trip Region Allowance",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) 2025, ALYF GmbH and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class BusinessTripRegionAllowance(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

accommodation: DF.Currency
arrival_or_departure: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
valid_from: DF.Date
whole_day: DF.Currency
# end: auto-generated types
pass
1 change: 1 addition & 0 deletions erpnext_germany/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ execute:frappe.db.set_single_value("ERPNext Germany Settings", "prevent_gaps_in_
erpnext_germany.patches.set_business_trip_settings
erpnext_germany.patches.remove_some_employee_property_setters
execute:from erpnext_germany.install import make_property_setters; make_property_setters()
erpnext_germany.patches.move_business_trip_region_data_to_child_table #2
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import frappe


def execute():
"""
Move the data from the old fields (on doc level) to the new child table.
"""
regions = frappe.get_all("Business Trip Region", pluck="name")
for business_trip_region_id in regions:
doc = frappe.get_doc("Business Trip Region", business_trip_region_id)

# Step 1: Move the data to the child table.
doc.append(
"allowances",
{
"valid_from": doc.get("valid_from") or "2000-01-01",
"whole_day": doc.get("whole_day") or 0.0,
"arrival_or_departure": doc.get("arrival_or_departure") or 0.0,
"accommodation": doc.get("accommodation") or 0.0,
},
)
doc.save()
Loading