diff --git a/changelog/66_UNRELEASED_xxxx-xx-xx.md b/changelog/66_UNRELEASED_xxxx-xx-xx.md index 591b6173..62db668f 100644 --- a/changelog/66_UNRELEASED_xxxx-xx-xx.md +++ b/changelog/66_UNRELEASED_xxxx-xx-xx.md @@ -43,6 +43,9 @@ - Chore schedules can now be skipped - New button on the chores overview and chore tracking page - Skipped schedules will be highlighted accordingly on the chore journal +- Added a new chore option "Start date" which is used as a schedule starting point when the chore was never tracked + - Until now, the schedule starting point was the first tracked execution + - For all existing chores, the start date will be set to the first tracked execution time (or today, for chores which were never tracked) on migration ### Calendar diff --git a/localization/strings.pot b/localization/strings.pot index 8bafba45..54c17894 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -2301,3 +2301,9 @@ msgstr "" msgid "Time" msgstr "" + +msgid "A start date is required" +msgstr "" + +msgid "Start date" +msgstr "" diff --git a/migrations/0164.sql b/migrations/0164.sql new file mode 100644 index 00000000..4d253e38 --- /dev/null +++ b/migrations/0164.sql @@ -0,0 +1,79 @@ +ALTER TABLE chores +ADD start_date DATETIME; + +-- All existing chores get the oldest tracking time as start date +UPDATE chores +SET start_date = (SELECT MIN(tracked_time) FROM chores_log WHERE chore_id = chores.id AND undone = 0 AND skipped = 0); + +-- Any existing but not yet tracked chore get today as start date +UPDATE chores +SET start_date = DATETIME('now', 'localtime') +WHERE start_date IS NULL; + +DROP VIEW chores_current; +CREATE VIEW chores_current +AS +SELECT + x.chore_id AS id, -- Dummy, LessQL needs an id column + x.chore_id, + x.chore_name, + x.last_tracked_time, + CASE WHEN x.rollover = 1 AND DATETIME('now', 'localtime') > x.next_estimated_execution_time THEN + CASE WHEN IFNULL(x.track_date_only, 0) = 1 THEN + DATETIME(STRFTIME('%Y-%m-%d', DATETIME('now', 'localtime')) || ' 23:59:59') + ELSE + DATETIME(STRFTIME('%Y-%m-%d', DATETIME('now', 'localtime')) || ' ' || STRFTIME('%H:%M:%S', x.next_estimated_execution_time)) + END + ELSE + CASE WHEN IFNULL(x.track_date_only, 0) = 1 THEN + DATETIME(STRFTIME('%Y-%m-%d', x.next_estimated_execution_time) || ' 23:59:59') + ELSE + x.next_estimated_execution_time + END + END AS next_estimated_execution_time, + x.track_date_only, + x.next_execution_assigned_to_user_id +FROM ( + +SELECT + h.id AS chore_id, + h.name AS chore_name, + MAX(l.tracked_time) AS last_tracked_time, + CASE h.period_type + WHEN 'manually' THEN '2999-12-31 23:59:59' + WHEN 'dynamic-regular' THEN DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day') + WHEN 'daily' THEN DATETIME(IFNULL(MAX(l.tracked_time), h.start_date), '+' || CAST(h.period_interval AS TEXT) || ' day') + WHEN 'weekly' THEN ( + SELECT next + FROM ( + SELECT 'sunday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 0') AS next + UNION + SELECT 'monday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 1') AS next + UNION + SELECT 'tuesday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 2') AS next + UNION + SELECT 'wednesday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 3') AS next + UNION + SELECT 'thursday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 4') AS next + UNION + SELECT 'friday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 5') AS next + UNION + SELECT 'saturday' AS day, DATETIME(COALESCE((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), h.start_date), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 6') AS next + ) + WHERE INSTR(period_config, day) > 0 + ORDER BY next + LIMIT 1 + ) + WHEN 'monthly' THEN DATETIME(IFNULL(MAX(l.tracked_time), h.start_date), 'start of month', '+' || CAST(h.period_interval AS TEXT) || ' month', '+' || CAST(h.period_days - 1 AS TEXT) || ' day') + WHEN 'yearly' THEN DATETIME(IFNULL(MAX(l.tracked_time), h.start_date), '+' || CAST(h.period_interval AS TEXT) || ' years') + END AS next_estimated_execution_time, + h.track_date_only, + h.rollover, + h.next_execution_assigned_to_user_id +FROM chores h +LEFT JOIN chores_log l + ON h.id = l.chore_id + AND l.undone = 0 +WHERE h.active = 1 +GROUP BY h.id, h.name, h.period_days +) x; diff --git a/public/viewjs/choreform.js b/public/viewjs/choreform.js index 9b500ec9..c340ff25 100644 --- a/public/viewjs/choreform.js +++ b/public/viewjs/choreform.js @@ -8,6 +8,8 @@ } var jsonData = $('#chore-form').serializeJSON(); + jsonData.start_date = Grocy.Components.DateTimePicker.GetValue(); + if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) { jsonData.assignment_config = $("#assignment_config").val().join(","); @@ -107,6 +109,23 @@ Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('chore-form'); +if (Grocy.EditMode == "edit") +{ + Grocy.Api.Get('objects/chores_log?limit=1&query[]=chore_id=' + Grocy.EditObjectId, + function(journalEntries) + { + if (journalEntries.length > 0) + { + $(".datetimepicker-input").attr("disabled", ""); + } + }, + function(xhr) + { + console.error(xhr); + } + ); +} + setTimeout(function() { $(".input-group-chore-period-type").trigger("change"); diff --git a/public/viewjs/components/chorecard.js b/public/viewjs/components/chorecard.js index 85012427..780932bb 100644 --- a/public/viewjs/components/chorecard.js +++ b/public/viewjs/components/chorecard.js @@ -5,6 +5,11 @@ Grocy.Components.ChoreCard.Refresh = function(choreId) Grocy.Api.Get('chores/' + choreId, function(choreDetails) { + if (choreDetails.last_done_by == null) + { + choreDetails.last_done_by = {}; + } + $('#chorecard-chore-name').text(choreDetails.chore.name); $('#chorecard-chore-description').html(nl2br(choreDetails.chore.description)); $('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || __t('never'))); diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js index b4bacf32..f44dcf6c 100644 --- a/public/viewjs/productform.js +++ b/public/viewjs/productform.js @@ -142,7 +142,7 @@ $('.save-product-button').on('click', function(e) if (Grocy.EditMode == "edit") { - Grocy.Api.Get('objects/stock_log?query[]=product_id=' + Grocy.EditObjectId, + Grocy.Api.Get('objects/stock_log?limit=1&query[]=product_id=' + Grocy.EditObjectId, function(productJournalEntries) { if (productJournalEntries.length == 0) diff --git a/views/choreform.blade.php b/views/choreform.blade.php index 2b7db699..dd135f41 100644 --- a/views/choreform.blade.php +++ b/views/choreform.blade.php @@ -169,6 +169,25 @@ 'hintId' => 'chore-period-interval-info' )) + @php + $value = date('Y-m-d H:i:s'); + if ($mode == 'edit') + { + $value = date('Y-m-d', strtotime($chore->start_date)); + } + @endphp + @include('components.datetimepicker', array( + 'id' => 'start', + 'label' => 'Start date', + 'initialValue' => $value, + 'format' => 'YYYY-MM-DD HH:mm:ss', + 'initWithNow' => true, + 'limitEndToNow' => false, + 'limitStartToNow' => false, + 'invalidFeedback' => $__t('A start date is required'), + 'hint' => 'The start date cannot be changed when the chore was once tracked' + )) + @if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)