mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Added the possibility to manually reschedule chores (closes #1830)
This commit is contained in:
parent
033cd306c1
commit
d65734c896
@ -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
|
- 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
|
- 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)
|
- 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 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)
|
- 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)
|
||||||
|
@ -2305,3 +2305,12 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Average execution frequency"
|
msgid "Average execution frequency"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Reschedule next execution"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "This can only be in the future"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Rescheduled"
|
||||||
|
msgstr ""
|
||||||
|
81
migrations/0176.sql
Normal file
81
migrations/0176.sql
Normal 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;
|
@ -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)
|
if (GetUriParam("user") !== undefined)
|
||||||
{
|
{
|
||||||
$("#user-filter").val("xx" + GetUriParam("user") + "xx");
|
$("#user-filter").val("xx" + GetUriParam("user") + "xx");
|
||||||
|
@ -165,6 +165,13 @@
|
|||||||
<i class="fas fa-ellipsis-v"></i>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
|
<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"
|
<a class="dropdown-item chore-name-cell"
|
||||||
data-chore-id="{{ $curentChoreEntry->chore_id }}"
|
data-chore-id="{{ $curentChoreEntry->chore_id }}"
|
||||||
type="button"
|
type="button"
|
||||||
@ -211,6 +218,13 @@
|
|||||||
@else
|
@else
|
||||||
<span>-</span>
|
<span>-</span>
|
||||||
@endif
|
@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>
|
||||||
<td>
|
<td>
|
||||||
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
|
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
|
||||||
@ -269,4 +283,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
@stop
|
||||||
|
Loading…
x
Reference in New Issue
Block a user