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
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Companies that use Atlassian Jira for project management and ERPNext for time tr

## Features

- Allows logging of miscellanous time, project time and breaks
- Allows logging of miscellaneous time, project time, breaks and paid breaks
- Allows to set a percentage of working time as billable time in a Working Time Log
- Rounds billable time to 5 minutes
- Fetches issue titles from Jira (used as time log description)
Expand All @@ -17,8 +17,8 @@ Companies that use Atlassian Jira for project management and ERPNext for time tr
- If a draft working time entry is older than 3 days, and
- on the last working day of the month
- **Working Time Policy** enforcement per employee, including:
- Maximum working time per day
- Mandatory break requirements based on working time thresholds
- Maximum productive time per day
- Mandatory break requirements based on productive time thresholds
- Minimum rest time between days
- Blocked weekdays
- Holiday blocking (based on the employee's holiday list)
Expand All @@ -45,6 +45,22 @@ Companies that use Atlassian Jira for project management and ERPNext for time tr
- Add a time log and link it to a _Project_ and Jira issue _Key_
- Submit your **Working Time**

## Time Fields

**Working Time** separates productive time, break time and paid time:

- _Productive Time_ (`productive_time`) is the total duration of logs that are not marked as breaks.
- _Break Time_ (`break_time`) is the total duration of logs marked as breaks, including paid breaks.
- _Paid Break Time_ (`paid_break_time`) is the total duration of break logs where _Paid_ (`is_paid_break`) is enabled.
- _Paid Working Time_ (`working_time`) is the paid total: _Productive Time_ plus _Paid Break Time_. Number cards, stats and reports use this field as the paid working time total.
- _Project Time_ (`project_time`) and _Billable Time_ (`billable_time`) are calculated from non-break project logs.

In a **Working Time Log**, mark _Break_ (`is_break`) for any physical break. Regular working logs are paid by default and should not be marked as breaks. Regular breaks are unpaid by default. Use _Paid_ (`is_paid_break`) only for exceptional break rows that should count toward paid working time, such as mandatory but passive travel time.

**Working Time Policy** restrictions use _Productive Time_ for maximum time and threshold checks, while mandatory break requirements use _Break Time_. This means paid breaks count as paid time, but do not increase productive time for policy restrictions.

German users may refer to [this article](https://www.kanzlei-chevalier.de/blog/dienstreise-als-arbeitszeit) for more information.

## Further Reading

Want to add pretty time logs to your invoice? Check out our [print formats](https://github.com/alyf-de/erpnext_druckformate).
Expand Down
69 changes: 46 additions & 23 deletions working_time/locale/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Working Time VERSION\n"
"Report-Msgid-Bugs-To: hallo@alyf.de\n"
"POT-Creation-Date: 2026-03-27 14:57+0053\n"
"POT-Creation-Date: 2026-05-26 23:08+0053\n"
"PO-Revision-Date: 2026-03-27 14:57+0053\n"
"Last-Translator: hallo@alyf.de\n"
"Language-Team: hallo@alyf.de\n"
Expand Down Expand Up @@ -112,13 +112,16 @@ msgstr "Gesperrte Tage"
msgid "Blocks real holidays from an employee's holiday list. Ignores weekly off."
msgstr "Sperrt echte Feiertage aus der Feiertagsliste des Mitarbeiters. Wöchentliche freie Tage werden ignoriert."

#. Label of a Duration field in DocType 'Working Time'
#. Label of a Check field in DocType 'Working Time Log'
#: working_time/working_time/doctype/working_time/working_time.json
#: working_time/working_time/doctype/working_time_log/working_time_log.json
msgid "Break"
msgstr "Pause"

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Break Time"
msgstr "Pausenzeit"

#. Label of a number card in the Time Tracking Workspace
#: working_time/working_time/workspace/time_tracking/time_tracking.json
msgid "Daily Billable Time (this month)"
Expand Down Expand Up @@ -146,11 +149,11 @@ msgstr "Tägliche Arbeitsstunden"
msgid "Daily Working Time"
msgstr "Tägliche Arbeitszeit"

#: working_time/reminders.py:138
#: working_time/reminders.py:146
msgid ""
"Dear {first_name},\n"
"\n"
"Your have a draft <a href='{url}'>working time entry</a> that is older than {cutoff_days} days. Please submit it as soon as possible.\n"
"You have a draft <a href='{url}'>working time entry</a> that is older than {cutoff_days} days. Please submit it as soon as possible.\n"
"\n"
"Thanks in advance!"
msgstr ""
Comment thread
barredterra marked this conversation as resolved.
Expand All @@ -160,7 +163,7 @@ msgstr ""
"\n"
"Vielen Dank!"

#: working_time/reminders.py:63
#: working_time/reminders.py:66
msgid ""
"Dear {first_name},\n"
"\n"
Expand All @@ -174,6 +177,11 @@ msgstr ""
"\n"
"Vielen Dank!"

#. Description of the 'Paid' (Check) field in DocType 'Working Time Log'
#: working_time/working_time/doctype/working_time_log/working_time_log.json
msgid "Enable to count this break as paid working time (e.g. mandatory travel time)"
msgstr "Aktivieren, um diese Pause als bezahlte Arbeitszeit zu zählen (z.B. Pflichtreisezeit)"

#: working_time/working_time/report/expected_and_actual_working_time/expected_and_actual_working_time.py:58
msgid "Expected Working Time"
msgstr "Erwartete Arbeitszeit"
Expand Down Expand Up @@ -242,8 +250,8 @@ msgstr "Pflichtpausen"

#. Label of a Duration field in DocType 'Working Time Policy'
#: working_time/working_time/doctype/working_time_policy/working_time_policy.json
msgid "Max Working Time Per Day"
msgstr "Maximale Arbeitszeit pro Tag"
msgid "Max Productive Time Per Day"
msgstr "Maximale produktive Arbeitszeit pro Tag"

#. Label of a Duration field in DocType 'Working Time Policy'
#: working_time/working_time/doctype/working_time_policy/working_time_policy.json
Expand All @@ -268,14 +276,37 @@ msgstr "Nicht Plausibel"
msgid "Only submitted working times are considered in this table."
msgstr "Nur gebuchte Arbeitszeiten werden in dieser Tabelle berücksichtigt."

#: working_time/working_time/doctype/working_time/working_time.py:55
#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Paid Break Time"
msgstr "Bezahlte Pausenzeit"

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Paid Working Time"
msgstr "Bezahlte Arbeitszeit"

#: working_time/working_time/doctype/working_time/working_time.py:64
msgid "Please add an issue key or invoice note to the billable row {0}"
msgstr "Bitte einen Issue-Key oder eine Rechnungsnotiz zur abrechenbaren Zeile {0} hinzufügen"

#: working_time/working_time/doctype/working_time/working_time.py:46
#: working_time/working_time/doctype/working_time/working_time.py:55
msgid "Please fix negative duration in row {0}"
msgstr "Bitte negative Dauer in Zeile {0} korrigieren"

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Productive Time"
msgstr "Produktive Arbeitszeit"

#: working_time/working_time/doctype/working_time/working_time.py:116
msgid "Productive time ({0}) exceeds the maximum allowed ({1}) per day"
msgstr "Produktive Arbeitszeit ({0}) überschreitet die maximal erlaubte ({1}) pro Tag"

#: working_time/working_time/doctype/working_time/working_time.py:129
msgid "Productive time of {0} or more requires at least {1} of break time"
msgstr "Produktive Arbeitszeit von {0} oder mehr benötigt mindestens {1} Pausenzeit"

#. Label of a Percent field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Project %"
Expand All @@ -286,7 +317,7 @@ msgstr "Projekt %"
msgid "Project Time"
msgstr "Projektzeit"

#: working_time/reminders.py:62 working_time/reminders.py:137
#: working_time/reminders.py:65 working_time/reminders.py:145
msgid "Remember to submit your working time"
msgstr "Erinnerung: Arbeitszeit einreichen"

Expand All @@ -295,7 +326,7 @@ msgstr "Erinnerung: Arbeitszeit einreichen"
msgid "Required Break Minutes"
msgstr "Erforderliche Pausenminuten"

#: working_time/working_time/doctype/working_time/working_time.py:164
#: working_time/working_time/doctype/working_time/working_time.py:173
msgid "Rest time since previous day ({0}) is less than the required minimum ({1})"
msgstr "Die Ruhezeit seit dem Vortag ({0}) ist kürzer als das erforderliche Minimum ({1})"

Expand Down Expand Up @@ -332,7 +363,6 @@ msgid "Work Threshold"
msgstr "Arbeitsschwelle"

#. Name of a DocType
#. Label of a Duration field in DocType 'Working Time'
#. Label of a Link in the Time Tracking Workspace
#: working_time/config/desktop.py:11
#: working_time/working_time/doctype/working_time/working_time.json
Expand Down Expand Up @@ -370,28 +400,21 @@ msgstr "Arbeitszeitrichtlinie"
msgid "Working Time Summary"
msgstr "Arbeitszeit Zusammenfassung"

#: working_time/working_time/doctype/working_time/working_time.py:107
msgid "Working time ({0}) exceeds the maximum allowed ({1}) per day"
msgstr "Die Arbeitszeit ({0}) überschreitet das erlaubte Maximum ({1}) pro Tag"

#: working_time/working_time/doctype/working_time/working_time.py:120
msgid "Working time of {0} or more requires at least {1} of break time"
msgstr "Eine Arbeitszeit von {0} oder mehr erfordert mindestens {1} Pausenzeit"

#. Description of the 'Site URL' (Data) field in DocType 'Jira Site'
#: working_time/working_time/doctype/jira_site/jira_site.json
msgid "e.g. your-domain.atlassian.net"
msgstr "z.B. your-domain.atlassian.net"

#: working_time/working_time/doctype/working_time/working_time.py:80
#: working_time/working_time/doctype/working_time/working_time.py:89
msgid "{0} is a blocked day according to the Working Time Policy"
msgstr "{0} ist laut Arbeitszeitrichtlinie ein gesperrter Tag"

#: working_time/working_time/doctype/working_time/working_time.py:96
#: working_time/working_time/doctype/working_time/working_time.py:105
msgid "{0} is a holiday according to your holiday list"
msgstr "{0} ist laut Ihrer Feiertagsliste ein Feiertag"

#. Count format of shortcut in the Time Tracking Workspace
#: working_time/working_time/workspace/time_tracking/time_tracking.json
msgid "{} Drafts"
msgstr "{} Entwürfe"

68 changes: 45 additions & 23 deletions working_time/locale/main.pot
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Working Time VERSION\n"
"Report-Msgid-Bugs-To: hallo@alyf.de\n"
"POT-Creation-Date: 2026-03-27 14:57+0053\n"
"PO-Revision-Date: 2026-03-27 14:57+0053\n"
"POT-Creation-Date: 2026-05-26 23:08+0053\n"
"PO-Revision-Date: 2026-05-26 23:08+0053\n"
"Last-Translator: hallo@alyf.de\n"
"Language-Team: hallo@alyf.de\n"
"MIME-Version: 1.0\n"
Expand Down Expand Up @@ -112,13 +112,16 @@ msgstr ""
msgid "Blocks real holidays from an employee's holiday list. Ignores weekly off."
msgstr ""

#. Label of a Duration field in DocType 'Working Time'
#. Label of a Check field in DocType 'Working Time Log'
#: working_time/working_time/doctype/working_time/working_time.json
#: working_time/working_time/doctype/working_time_log/working_time_log.json
msgid "Break"
msgstr ""

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Break Time"
msgstr ""

#. Label of a number card in the Time Tracking Workspace
#: working_time/working_time/workspace/time_tracking/time_tracking.json
msgid "Daily Billable Time (this month)"
Expand Down Expand Up @@ -146,16 +149,16 @@ msgstr ""
msgid "Daily Working Time"
msgstr ""

#: working_time/reminders.py:138
#: working_time/reminders.py:146
msgid ""
"Dear {first_name},\n"
"\n"
"Your have a draft <a href='{url}'>working time entry</a> that is older than {cutoff_days} days. Please submit it as soon as possible.\n"
"You have a draft <a href='{url}'>working time entry</a> that is older than {cutoff_days} days. Please submit it as soon as possible.\n"
"\n"
"Thanks in advance!"
msgstr ""

#: working_time/reminders.py:63
#: working_time/reminders.py:66
msgid ""
"Dear {first_name},\n"
"\n"
Expand All @@ -164,6 +167,11 @@ msgid ""
"Thanks in advance!"
msgstr ""

#. Description of the 'Paid' (Check) field in DocType 'Working Time Log'
#: working_time/working_time/doctype/working_time_log/working_time_log.json
msgid "Enable to count this break as paid working time (e.g. mandatory travel time)"
msgstr ""

#: working_time/working_time/report/expected_and_actual_working_time/expected_and_actual_working_time.py:58
msgid "Expected Working Time"
msgstr ""
Expand Down Expand Up @@ -232,7 +240,7 @@ msgstr ""

#. Label of a Duration field in DocType 'Working Time Policy'
#: working_time/working_time/doctype/working_time_policy/working_time_policy.json
msgid "Max Working Time Per Day"
msgid "Max Productive Time Per Day"
msgstr ""

#. Label of a Duration field in DocType 'Working Time Policy'
Expand All @@ -258,14 +266,37 @@ msgstr ""
msgid "Only submitted working times are considered in this table."
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:55
#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Paid Break Time"
msgstr ""

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Paid Working Time"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:64
msgid "Please add an issue key or invoice note to the billable row {0}"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:46
#: working_time/working_time/doctype/working_time/working_time.py:55
msgid "Please fix negative duration in row {0}"
msgstr ""

#. Label of a Duration field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Productive Time"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:116
msgid "Productive time ({0}) exceeds the maximum allowed ({1}) per day"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:129
msgid "Productive time of {0} or more requires at least {1} of break time"
msgstr ""

#. Label of a Percent field in DocType 'Working Time'
#: working_time/working_time/doctype/working_time/working_time.json
msgid "Project %"
Expand All @@ -276,7 +307,7 @@ msgstr ""
msgid "Project Time"
msgstr ""

#: working_time/reminders.py:62 working_time/reminders.py:137
#: working_time/reminders.py:65 working_time/reminders.py:145
msgid "Remember to submit your working time"
msgstr ""

Expand All @@ -285,7 +316,7 @@ msgstr ""
msgid "Required Break Minutes"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:164
#: working_time/working_time/doctype/working_time/working_time.py:173
msgid "Rest time since previous day ({0}) is less than the required minimum ({1})"
msgstr ""

Expand Down Expand Up @@ -322,7 +353,6 @@ msgid "Work Threshold"
msgstr ""

#. Name of a DocType
#. Label of a Duration field in DocType 'Working Time'
#. Label of a Link in the Time Tracking Workspace
#: working_time/config/desktop.py:11
#: working_time/working_time/doctype/working_time/working_time.json
Expand Down Expand Up @@ -360,24 +390,16 @@ msgstr ""
msgid "Working Time Summary"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:107
msgid "Working time ({0}) exceeds the maximum allowed ({1}) per day"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:120
msgid "Working time of {0} or more requires at least {1} of break time"
msgstr ""

#. Description of the 'Site URL' (Data) field in DocType 'Jira Site'
#: working_time/working_time/doctype/jira_site/jira_site.json
msgid "e.g. your-domain.atlassian.net"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:80
#: working_time/working_time/doctype/working_time/working_time.py:89
msgid "{0} is a blocked day according to the Working Time Policy"
msgstr ""

#: working_time/working_time/doctype/working_time/working_time.py:96
#: working_time/working_time/doctype/working_time/working_time.py:105
msgid "{0} is a holiday according to your holiday list"
msgstr ""

Expand Down
2 changes: 2 additions & 0 deletions working_time/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ working_time.patches.link_timesheet_and_attendance_to_working_time # 2023-08-14
working_time.patches.billable_time_pct # 1
working_time.patches.add_total_to_old_freelancer_time
execute:from working_time.install import make_custom_fields; make_custom_fields() # 2026-03-27
working_time.patches.rename_max_working_time_to_productive_time
working_time.patches.backfill_productive_and_paid_break_time # 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import frappe


def execute():
wt = frappe.qb.DocType("Working Time")
frappe.qb.update(wt).set(wt.productive_time, wt.working_time).set(wt.paid_break_time, 0).run()
Loading
Loading