Added the possibility to manually reschedule chores (closes #1830)

This commit is contained in:
Bernd Bestel 2022-03-26 18:30:26 +01:00
parent 033cd306c1
commit d65734c896
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
5 changed files with 218 additions and 0 deletions

View File

@ -33,6 +33,10 @@
- The `Daily` period type has been changed to schedule the chore at the _same time_ (based on the start date) each `n` days
- This period type scheduled chores `n` days _after the last execution_ before, which is also possible by using the `Hourly` period type and a corresponding period interval; all existing `Daily` schedules will be converted to that on migration
- It's now possible to manually reschedule chores
- New entry "Reschedule next execution" in the context/more menu on the chores overview page
- If you have rescheduled a chore and want to continue the normal schedule instead, use the "Clear" button in the dialog
- Rescheduled chores will be highlighted with an corresponding icon next to the "next estiamted tracking date"
- Optimized that when skipping chores via the chore tracking page, the given time is used as the "skipped time", not the scheduled next estimated tracking time of the corresponding chore (essentially making it possible to skip more then one schedule at once)
- Fixed that when consuming a parent product on chore execution (chore option "Consume product on chore execution"), no child products were used if the parent product itself is not in-stock
- Fixed that the upgrade to v3.2.0 failed when having any former "Dynamic Regular" chore with a "Period interval" of `0` (which makes absolutely no sense in reality)

View File

@ -2305,3 +2305,12 @@ msgstr ""
msgid "Average execution frequency"
msgstr ""
msgid "Reschedule next execution"
msgstr ""
msgid "This can only be in the future"
msgstr ""
msgid "Rescheduled"
msgstr ""

81
migrations/0176.sql Normal file
View File

@ -0,0 +1,81 @@
ALTER TABLE chores
ADD rescheduled_date DATETIME;
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,
CASE WHEN IFNULL(x.rescheduled_date, '') != '' THEN 1 ELSE 0 END AS is_rescheduled
FROM (
SELECT
h.id AS chore_id,
h.name AS chore_name,
MAX(l.tracked_time) AS last_tracked_time,
CASE WHEN IFNULL(h.rescheduled_date, '') != '' THEN
h.rescheduled_date
ELSE
CASE WHEN MAX(l.tracked_time) IS NULL THEN
h.start_date
ELSE
CASE h.period_type
WHEN 'manually' THEN '2999-12-31 23:59:59'
WHEN 'hourly' THEN DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_interval AS TEXT) || ' hour')
WHEN 'daily' THEN DATETIME(SUBSTR(CAST(DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_interval AS TEXT) || ' days') AS TEXT), 1, 11) || SUBSTR(CAST(h.start_date AS TEXT), -8))
WHEN 'weekly' THEN (
SELECT next
FROM (
SELECT 'sunday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 0') AS next
UNION
SELECT 'monday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 1') AS next
UNION
SELECT 'tuesday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 2') AS next
UNION
SELECT 'wednesday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 3') AS next
UNION
SELECT 'thursday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 4') AS next
UNION
SELECT 'friday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '1 days', '+' || CAST((h.period_interval - 1) * 7 AS TEXT) || ' days', 'weekday 5') AS next
UNION
SELECT 'saturday' AS day, DATETIME((SELECT tracked_time FROM chores_log WHERE chore_id = h.id ORDER BY tracked_time DESC LIMIT 1), '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(MAX(l.tracked_time), 'start of month', '+' || CAST(h.period_interval AS TEXT) || ' month', '+' || CAST(h.period_days - 1 AS TEXT) || ' day')
WHEN 'yearly' THEN DATETIME(SUBSTR(CAST(DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_interval AS TEXT) || ' years') AS TEXT), 1, 4) || SUBSTR(CAST(h.start_date AS TEXT), 5, 6) || SUBSTR(CAST(DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_interval AS TEXT) || ' years') AS TEXT), -9))
WHEN 'adaptive' THEN DATETIME(MAX(l.tracked_time), '+' || CAST(IFNULL((SELECT average_frequency_hours FROM chores_execution_average_frequency WHERE chore_id = h.id), 0) AS TEXT) || ' hour')
END
END
END AS next_estimated_execution_time,
h.track_date_only,
h.rollover,
h.next_execution_assigned_to_user_id,
h.rescheduled_date
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;

View File

@ -263,6 +263,74 @@ function RefreshStatistics()
);
}
$(document).on("click", ".reschedule-chore-button", function(e)
{
e.preventDefault();
var choreId = $(e.currentTarget).attr("data-chore-id");
Grocy.EditObjectId = choreId;
Grocy.Api.Get("chores/" + choreId, function(choreDetails)
{
var prefillDate = choreDetails.next_estimated_execution_time;
if (choreDetails.chore.rescheduled_date != null && !choreDetails.chore.rescheduled_date.isEmpty())
{
prefillDate = choreDetails.chore.rescheduled_date;
}
if (choreDetails.chore.track_date_only == 1)
{
Grocy.Components.DateTimePicker.ChangeFormat("YYYY-MM-DD");
Grocy.Components.DateTimePicker.SetValue(moment(prefillDate).format("YYYY-MM-DD"));
}
else
{
Grocy.Components.DateTimePicker.ChangeFormat("YYYY-MM-DD HH:mm:ss");
Grocy.Components.DateTimePicker.SetValue(moment(prefillDate).format("YYYY-MM-DD HH:mm:ss"));
}
$("#reschedule-chore-modal-title").text(choreDetails.chore.name);
$("#reschedule-chore-modal").modal("show");
});
});
$("#reschedule-chore-save-button").on("click", function(e)
{
e.preventDefault();
if (!Grocy.FrontendHelpers.ValidateForm("reschedule-chore-form", true))
{
return;
}
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, { "rescheduled_date": Grocy.Components.DateTimePicker.GetValue() },
function(result)
{
window.location.reload();
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
});
$("#reschedule-chore-clear-button").on("click", function(e)
{
e.preventDefault();
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, { "rescheduled_date": null },
function(result)
{
window.location.reload();
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
});
if (GetUriParam("user") !== undefined)
{
$("#user-filter").val("xx" + GetUriParam("user") + "xx");

View File

@ -165,6 +165,13 @@
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item reschedule-chore-button @if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type == \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY) disabled @endif"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
type="button"
href="#">
<span>{{ $__t('Reschedule next execution') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item chore-name-cell"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
type="button"
@ -211,6 +218,13 @@
@else
<span>-</span>
@endif
@if($curentChoreEntry->is_rescheduled == 1)
<span class="text-muted"
data-toggle="tooltip"
title="{{ $__t('Rescheduled') }}">
<i class="far fa-clock"></i>
</span>
@endif
</td>
<td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
@ -269,4 +283,46 @@
</div>
</div>
</div>
<div class="modal fade"
id="reschedule-chore-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 class="modal-title w-100">{{ $__t('Reschedule next execution') }}<br>
<span id="reschedule-chore-modal-title"
class="small text-muted"></span>
</h4>
</div>
<div class="modal-body">
<form id="reschedule-chore-form"
novalidate>
@include('components.datetimepicker', array(
'id' => 'reschedule_time',
'label' => 'Next estimated tracking',
'format' => 'YYYY-MM-DD HH:mm:ss',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => true,
'invalidFeedback' => $__t('This can only be in the future')
))
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="reschedule-chore-clear-button"
type="button"
class="btn btn-success">{{ $__t('Clear') }}</button>
<button id="reschedule-chore-save-button"
type="button"
class="btn btn-primary">{{ $__t('OK') }}</button>
</div>
</div>
</div>
</div>
@stop