mirror of
https://github.com/grocy/grocy.git
synced 2025-10-14 09:11:11 +00:00
Implemented daily/weekly/monthly recurrence patterns for chores (closes #151)
This commit is contained in:
@@ -2,5 +2,8 @@
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'manually' => 'Manually',
|
'manually' => 'Manually',
|
||||||
'dynamic-regular' => 'Dynamic regular'
|
'dynamic-regular' => 'Dynamic regular',
|
||||||
|
'daily' => 'Daily',
|
||||||
|
'weekly' => 'Weekly',
|
||||||
|
'monthly' => 'Monthly'
|
||||||
);
|
);
|
||||||
|
@@ -86,5 +86,8 @@ return array(
|
|||||||
'French' => 'French',
|
'French' => 'French',
|
||||||
'Turkish' => 'Turkish',
|
'Turkish' => 'Turkish',
|
||||||
'Spanish' => 'Spanish',
|
'Spanish' => 'Spanish',
|
||||||
'Russian' => 'Russian'
|
'Russian' => 'Russian',
|
||||||
|
'The thing which happens on the 5th of every month' => 'The thing which happens on the 5th of every month',
|
||||||
|
'The thing which happens daily' => 'The thing which happens daily',
|
||||||
|
'The thing which happens on Mondays and Wednesdays' => 'The thing which happens on Mondays and Wednesdays'
|
||||||
);
|
);
|
||||||
|
@@ -371,5 +371,14 @@ return array(
|
|||||||
'Average shelf life' => 'Average shelf life',
|
'Average shelf life' => 'Average shelf life',
|
||||||
'Spoil rate' => 'Spoil rate',
|
'Spoil rate' => 'Spoil rate',
|
||||||
'Show more' => 'Show more',
|
'Show more' => 'Show more',
|
||||||
'Show less' => 'Show less'
|
'Show less' => 'Show less',
|
||||||
|
'The amount must be between #1 and #2' => 'The amount must be between #1 and #2',
|
||||||
|
'Day of month' => 'Day of month',
|
||||||
|
'Monday' => 'Monday',
|
||||||
|
'Tuesday' => 'Tuesday',
|
||||||
|
'Wednesday' => 'Wednesday',
|
||||||
|
'Thursday' => 'Thursday',
|
||||||
|
'Friday' => 'Friday',
|
||||||
|
'Saturday' => 'Saturday',
|
||||||
|
'Sunday' => 'Sunday'
|
||||||
);
|
);
|
||||||
|
29
migrations/0065.sql
Normal file
29
migrations/0065.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
ALTER TABLE chores
|
||||||
|
ADD period_config TEXT;
|
||||||
|
|
||||||
|
DROP VIEW chores_current;
|
||||||
|
CREATE VIEW chores_current
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
h.id AS chore_id,
|
||||||
|
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), DATETIME('now', 'localtime')), '+1 day')
|
||||||
|
WHEN 'weekly' THEN
|
||||||
|
CASE
|
||||||
|
WHEN period_config LIKE '%sunday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 0')
|
||||||
|
WHEN period_config LIKE '%monday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 1')
|
||||||
|
WHEN period_config LIKE '%tuesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 2')
|
||||||
|
WHEN period_config LIKE '%wednesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 3')
|
||||||
|
WHEN period_config LIKE '%thursday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 4')
|
||||||
|
WHEN period_config LIKE '%friday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 5')
|
||||||
|
WHEN period_config LIKE '%saturday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), 'weekday 6')
|
||||||
|
END
|
||||||
|
WHEN 'monthly' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '+1 month', 'start of month', '+' || CAST(h.period_days - 1 AS TEXT) || ' day')
|
||||||
|
END AS next_estimated_execution_time
|
||||||
|
FROM chores h
|
||||||
|
LEFT JOIN chores_log l
|
||||||
|
ON h.id = l.chore_id
|
||||||
|
GROUP BY h.id, h.period_days;
|
@@ -57,6 +57,15 @@ $('#chore-form input').keydown(function(event)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var checkboxValues = $("#period_config").val().split(",");
|
||||||
|
for (var i = 0; i < checkboxValues.length; i++)
|
||||||
|
{
|
||||||
|
if (!checkboxValues[i].isEmpty())
|
||||||
|
{
|
||||||
|
$("#" + checkboxValues[i]).prop('checked', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('#name').focus();
|
$('#name').focus();
|
||||||
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
||||||
|
|
||||||
@@ -70,13 +79,36 @@ $('.input-group-chore-period-type').on('change', function(e)
|
|||||||
var periodType = $('#period_type').val();
|
var periodType = $('#period_type').val();
|
||||||
var periodDays = $('#period_days').val();
|
var periodDays = $('#period_days').val();
|
||||||
|
|
||||||
if (periodType === 'dynamic-regular')
|
$(".period-type-input").addClass("d-none");
|
||||||
|
$(".period-type-" + periodType).removeClass("d-none");
|
||||||
|
$('#chore-period-type-info').text("");
|
||||||
|
$("#period_config").val("");
|
||||||
|
|
||||||
|
if (periodType === 'manually')
|
||||||
{
|
{
|
||||||
$('#chore-period-type-info').text(L('This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', periodDays.toString()));
|
//
|
||||||
$('#chore-period-type-info').removeClass('d-none');
|
|
||||||
}
|
}
|
||||||
else
|
else if (periodType === 'dynamic-regular')
|
||||||
{
|
{
|
||||||
$('#chore-period-type-info').addClass('d-none');
|
$("label[for='period_days']").text(L("Period days"));
|
||||||
|
$("#period_days").attr("min", "0");
|
||||||
|
$("#period_days").attr("max", "9999");
|
||||||
|
$("#period_days").parent().find(".invalid-feedback").text(L('This cannot be negative'));
|
||||||
|
$('#chore-period-type-info').text(L('This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', periodDays.toString()));
|
||||||
|
}
|
||||||
|
else if (periodType === 'daily')
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
else if (periodType === 'weekly')
|
||||||
|
{
|
||||||
|
$("#period_config").val($(".period-type-weekly input:checkbox:checked").map(function () { return this.value; }).get().join(","));
|
||||||
|
}
|
||||||
|
else if (periodType === 'monthly')
|
||||||
|
{
|
||||||
|
$("label[for='period_days']").text(L("Day of month"));
|
||||||
|
$("#period_days").attr("min", "1");
|
||||||
|
$("#period_days").attr("max", "31");
|
||||||
|
$("#period_days").parent().find(".invalid-feedback").text(L('The amount must be between #1 and #2', "0", "31"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -6,6 +6,9 @@ class ChoresService extends BaseService
|
|||||||
{
|
{
|
||||||
const CHORE_TYPE_MANUALLY = 'manually';
|
const CHORE_TYPE_MANUALLY = 'manually';
|
||||||
const CHORE_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
const CHORE_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
||||||
|
const CHORE_TYPE_DAILY = 'daily';
|
||||||
|
const CHORE_TYPE_weekly = 'weekly';
|
||||||
|
const CHORE_TYPE_monthly = 'monthly';
|
||||||
|
|
||||||
public function GetCurrent()
|
public function GetCurrent()
|
||||||
{
|
{
|
||||||
|
@@ -100,6 +100,9 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Changed towels in the bathroom')}', 'manually', 5); --1
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Changed towels in the bathroom')}', 'manually', 5); --1
|
||||||
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
|
||||||
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
|
||||||
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->LocalizeForSqlString('The thing which happens on the 5th of every month')}', 'monthly', 5); --4
|
||||||
|
INSERT INTO chores (name, period_type) VALUES ('{$localizationService->LocalizeForSqlString('The thing which happens daily')}', 'daily'); --5
|
||||||
|
INSERT INTO chores (name, period_type, period_config) VALUES ('{$localizationService->LocalizeForSqlString('The thing which happens on Mondays and Wednesdays')}', 'weekly', 'monday,wednesday'); --6
|
||||||
|
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->LocalizeForSqlString('Battery')}1', '{$localizationService->LocalizeForSqlString('Warranty ends')} 2023', '{$localizationService->LocalizeForSqlString('TV remote control')}'); --1
|
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->LocalizeForSqlString('Battery')}1', '{$localizationService->LocalizeForSqlString('Warranty ends')} 2023', '{$localizationService->LocalizeForSqlString('TV remote control')}'); --1
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->LocalizeForSqlString('Battery')}2', '{$localizationService->LocalizeForSqlString('Warranty ends')} 2022', '{$localizationService->LocalizeForSqlString('Alarm clock')}'); --2
|
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->LocalizeForSqlString('Battery')}2', '{$localizationService->LocalizeForSqlString('Warranty ends')} 2022', '{$localizationService->LocalizeForSqlString('Alarm clock')}'); --2
|
||||||
@@ -210,6 +213,9 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-10 days')));
|
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-10 days')));
|
||||||
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-20 days')));
|
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-20 days')));
|
||||||
$choresService->TrackChore(3, date('Y-m-d H:i:s', strtotime('-17 days')));
|
$choresService->TrackChore(3, date('Y-m-d H:i:s', strtotime('-17 days')));
|
||||||
|
$choresService->TrackChore(4, date('Y-m-d H:i:s', strtotime('-10 days')));
|
||||||
|
$choresService->TrackChore(5, date('Y-m-d H:i:s', strtotime('+0 days')));
|
||||||
|
$choresService->TrackChore(6, date('Y-m-d H:i:s', strtotime('-10 days')));
|
||||||
|
|
||||||
$batteriesService = new BatteriesService();
|
$batteriesService = new BatteriesService();
|
||||||
$batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));
|
$batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));
|
||||||
|
@@ -50,9 +50,43 @@
|
|||||||
'min' => '0',
|
'min' => '0',
|
||||||
'additionalCssClasses' => 'input-group-chore-period-type',
|
'additionalCssClasses' => 'input-group-chore-period-type',
|
||||||
'invalidFeedback' => $L('This cannot be negative'),
|
'invalidFeedback' => $L('This cannot be negative'),
|
||||||
'hintId' => 'chore-period-type-info'
|
'hintId' => 'chore-period-type-info',
|
||||||
|
'additionalGroupCssClasses' => 'period-type-input period-type-dynamic-regular period-type-monthly'
|
||||||
))
|
))
|
||||||
|
|
||||||
|
<div class="form-group period-type-input period-type-weekly">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="monday" value="monday">
|
||||||
|
<label class="form-check-label" for="monday">{{ $L('Monday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="tuesday" value="tuesday">
|
||||||
|
<label class="form-check-label" for="tuesday">{{ $L('Tuesday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="wednesday" value="wednesday">
|
||||||
|
<label class="form-check-label" for="wednesday">{{ $L('Wednesday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="thursday" value="thursday">
|
||||||
|
<label class="form-check-label" for="thursday">{{ $L('Thursday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="friday" value="friday">
|
||||||
|
<label class="form-check-label" for="friday">{{ $L('Friday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="saturday" value="saturday">
|
||||||
|
<label class="form-check-label" for="saturday">{{ $L('Saturday') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input input-group-chore-period-type" type="checkbox" id="sunday" value="sunday">
|
||||||
|
<label class="form-check-label" for="sunday">{{ $L('Sunday') }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" id="period_config" name="period_config" value="@if($mode == 'edit'){{ $chore->period_config }}@endif">
|
||||||
|
|
||||||
<button id="save-chore-button" class="btn btn-success">{{ $L('Save') }}</button>
|
<button id="save-chore-button" class="btn btn-success">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
@@ -31,7 +31,6 @@
|
|||||||
<th class="border-right"></th>
|
<th class="border-right"></th>
|
||||||
<th>{{ $L('Name') }}</th>
|
<th>{{ $L('Name') }}</th>
|
||||||
<th>{{ $L('Period type') }}</th>
|
<th>{{ $L('Period type') }}</th>
|
||||||
<th>{{ $L('Period days') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
<th>{{ $L('Description') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -52,9 +51,6 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ $L($chore->period_type) }}
|
{{ $L($chore->period_type) }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
{{ $chore->period_days }}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{{ $chore->description }}
|
{{ $chore->description }}
|
||||||
</td>
|
</td>
|
||||||
|
@@ -50,7 +50,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="d-none">
|
<tbody class="d-none">
|
||||||
@foreach($currentChores as $curentChoreEntry)
|
@foreach($currentChores as $curentChoreEntry)
|
||||||
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
||||||
<td class="fit-content border-right">
|
<td class="fit-content border-right">
|
||||||
<a class="btn btn-success btn-sm track-chore-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Track execution of chore #1', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
|
<a class="btn btn-success btn-sm track-chore-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Track execution of chore #1', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
|
||||||
data-chore-id="{{ $curentChoreEntry->chore_id }}"
|
data-chore-id="{{ $curentChoreEntry->chore_id }}"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
|
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR)
|
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY)
|
||||||
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
|
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
|
||||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
|
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
|
||||||
@else
|
@else
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
|
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none">
|
<td class="d-none">
|
||||||
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
|
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
Reference in New Issue
Block a user