Implemented that chores can be assigned to users (closes #253)

This commit is contained in:
Bernd Bestel 2019-09-17 13:13:26 +02:00
parent 3dcd513094
commit 74f9470769
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
65 changed files with 987 additions and 75 deletions

View File

@ -1,9 +1,15 @@
[main]
host = https://www.transifex.com
[grocy.chore_types]
file_filter = localization/<lang>/chore_types.po
source_file = localization/chore_types.pot
[grocy.chore_period_types]
file_filter = localization/<lang>/chore_period_types.po
source_file = localization/chore_period_types.pot
source_lang = en
type = PO
[grocy.chore_assignment_types]
file_filter = localization/<lang>/chore_assignment_types.po
source_file = localization/chore_assignment_types.pot
source_lang = en
type = PO

View File

@ -12,6 +12,9 @@
- Recipe improvements
- Based on the new linked quantity units, recipe ingredients can now use any product related unit, the amount is calculated according to the cnoversion factor of the unit relation
- Chores improvements
- Chores can now be assigned to users
- Option per chore, different "assignment types" like "Random", "Who least did first", etc.
- On the chores overview page the list can be filterd to only show chores assigned to the currently logged in user
- New option "Due date rollover" per chore which means the chore can never be overdue, the due date will shift forward each day when due
- Equipment improvements/fixes
- Fixed that the delete button not always deleted the currently selected equipment item

View File

@ -70,4 +70,37 @@ class ChoresApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function CalculateNextExecutionAssignments(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$requestBody = $request->getParsedBody();
$choreId = null;
if (array_key_exists('chore_id', $requestBody) && !empty($requestBody['chore_id']) && is_numeric($requestBody['chore_id']))
{
$choreId = intval($requestBody['chore_id']);
}
if ($choreId === null)
{
$chores = $this->Database->chores();
foreach ($chores as $chore)
{
$this->ChoresService->CalculateNextExecutionAssignment($chore->id);
}
}
else
{
$this->ChoresService->CalculateNextExecutionAssignment($choreId);
}
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@ -28,7 +28,8 @@ class ChoresController extends BaseController
'currentChores' => $this->ChoresService->GetCurrent(),
'nextXDays' => $nextXDays,
'userfields' => $this->UserfieldsService->GetFields('chores'),
'userfieldValues' => $this->UserfieldsService->GetAllValues('chores')
'userfieldValues' => $this->UserfieldsService->GetAllValues('chores'),
'users' => $usersService->GetUsersAsDto()
]);
}
@ -60,21 +61,28 @@ class ChoresController extends BaseController
public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
if ($args['choreId'] == 'new')
{
return $this->AppContainer->view->render($response, 'choreform', [
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'),
'mode' => 'create',
'userfields' => $this->UserfieldsService->GetFields('chores')
'userfields' => $this->UserfieldsService->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users
]);
}
else
{
return $this->AppContainer->view->render($response, 'choreform', [
'chore' => $this->Database->chores($args['choreId']),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'),
'mode' => 'edit',
'userfields' => $this->UserfieldsService->GetFields('chores')
'userfields' => $this->UserfieldsService->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users
]);
}
}

View File

@ -2100,6 +2100,48 @@
}
}
},
"/chores/executions/calculate-next-assignments": {
"post": {
"summary": "(Re)calculates all next user assignments of all chores",
"tags": [
"Chores"
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"chore_id": {
"type": "integer",
"description": "The chore id of the chore which next user assignment should be (re)calculated, when omitted, the next user assignments of all chores will (re)caluclated"
}
},
"example": {
"chore_id": 1
}
}
}
}
},
"responses": {
"204": {
"description": "The operation was successful"
},
"400": {
"description": "The operation was not successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponse"
}
}
}
}
}
}
},
"/batteries": {
"get": {
"summary": "Returns all batteries incl. the next estimated charge time per battery",
@ -2853,6 +2895,9 @@
"next_estimated_execution_time": {
"type": "string",
"format": "date-time"
},
"next_execution_assigned_user": {
"$ref": "#/components/schemas/UserDto"
}
},
"example": {
@ -3091,15 +3136,39 @@
"type": "string",
"enum": [
"manually",
"dynamic-regular"
"dynamic-regular",
"daily",
"weekly",
"monthly"
]
},
"period_config": {
"type": "string"
},
"period_days": {
"type": "integer"
},
"track_date_only": {
"type": "boolean"
},
"rollover": {
"type": "boolean"
},
"assignment_type": {
"type": "string",
"enum": [
"no-assignment",
"who-least-did-first",
"random",
"in-alphabetical-order"
]
},
"assignment_config": {
"type": "string"
},
"next_execution_assigned_to_user_id": {
"type": "integer"
},
"row_created_timestamp": {
"type": "string",
"format": "date-time"

View File

@ -88,10 +88,20 @@ function SumArrayValue($array, $propertyName)
return $sum;
}
function GetClassConstants($className)
function GetClassConstants($className, $prefix = null)
{
$r = new ReflectionClass($className);
return $r->getConstants();
$constants = $r->getConstants();
if ($prefix === null)
{
return $constants;
}
else
{
$matchingKeys = preg_grep('!^' . $prefix . '!', array_keys($constants));
return array_intersect_key($constants, array_flip($matchingKeys));
}
}
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')

View File

@ -0,0 +1,25 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Translation migration from old PHP array files\n"
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Language: en\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -29,3 +29,6 @@ msgstr ""
msgid "fullcalendar_locale"
msgstr ""
msgid "bootstrap-select_locale"
msgstr ""

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -1,5 +1,5 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Troels Siggaard <troels@siggaard.com>, 2019
#
msgid ""
msgstr ""
@ -7,7 +7,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Troels Siggaard <troels@siggaard.com>, 2019\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,16 +17,16 @@ msgstr ""
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr "Manuelt"
msgstr "manuelt"
msgid "dynamic-regular"
msgstr ""
msgstr "gentagende-dynamisk"
msgid "daily"
msgstr ""
msgstr "daglig"
msgid "weekly"
msgstr ""
msgstr "ugentlig"
msgid "monthly"
msgstr ""
msgstr "månedlig"

View File

@ -1,3 +1,6 @@
# Translators:
# Troels Siggaard <troels@siggaard.com>, 2019
# Bernd Bestel <bernd@berrnd.de>, 2019
#
msgid ""
msgstr ""
@ -5,6 +8,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Language-Team: Danish (https://www.transifex.com/grocy/teams/93189/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -14,19 +18,22 @@ msgstr ""
"X-Domain: grocy/component_translations\n"
msgid "timeago_locale"
msgstr ""
msgstr "timdsiden_lokal"
msgid "timeago_nan"
msgstr ""
msgstr "tidsiden_ien"
msgid "moment_locale"
msgstr ""
msgstr "tidspunkt_lokal"
msgid "datatables_localization"
msgstr ""
msgstr "datatabeller_lokalisering"
msgid "summernote_locale"
msgstr ""
msgstr "summernote_lokal"
msgid "fullcalendar_locale"
msgstr ""
msgstr "fuldkalender_lokal"
msgid "bootstrap-select_locale"
msgstr "da_DK"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: German (https://www.transifex.com/grocy/teams/93189/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -52,3 +52,6 @@ msgstr "de-DE"
msgid "fullcalendar_locale"
msgstr "de"
msgid "bootstrap-select_locale"
msgstr "de_DE"

View File

@ -0,0 +1,25 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Translation migration from old PHP array files\n"
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Language: en\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr "No assignment"
msgid "who-least-did-first"
msgstr "Who least did first"
msgid "random"
msgstr "Random"
msgid "in-alphabetical-order"
msgstr "In alphabetical order"

View File

@ -1,14 +1,15 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Last-Translator: Translation migration from old PHP array files\n"
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
"Language: en\n"
"X-Domain: grocy/chore_types\n"

View File

@ -29,3 +29,6 @@ msgstr "x"
msgid "fullcalendar_locale"
msgstr "x"
msgid "bootstrap-select_locale"
msgstr "x"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -17,19 +17,32 @@ msgstr ""
"X-Domain: grocy/component_translations\n"
msgid "timeago_locale"
msgstr ""
msgstr "en"
msgid "timeago_nan"
msgstr ""
msgstr "NaN years ago"
msgid "moment_locale"
msgstr "en-gb"
msgid "datatables_localization"
msgstr ""
"{\"sEmptyTable\":\"No data available in table\",\"sInfo\":\"Showing _START_ "
"to _END_ of _TOTAL_ entries\",\"sInfoEmpty\":\"Showing 0 to 0 of 0 "
"entries\",\"sInfoFiltered\":\"(filtered from _MAX_ total "
"entries)\",\"sInfoPostFix\":\"\",\"sInfoThousands\":\",\",\"sLengthMenu\":\"Show"
" _MENU_ "
"entries\",\"sLoadingRecords\":\"Loading...\",\"sProcessing\":\"Processing...\",\"sSearch\":\"Search:\",\"sZeroRecords\":\"No"
" matching records "
"found\",\"oPaginate\":{\"sFirst\":\"First\",\"sLast\":\"Last\",\"sNext\":\"Next\",\"sPrevious\":\"Previous\"},\"oAria\":{\"sSortAscending\":\":"
" activate to sort column ascending\",\"sSortDescending\":\": activate to "
"sort column descending\"}}"
msgid "summernote_locale"
msgstr "en-gb"
msgid "fullcalendar_locale"
msgstr "en-gb"
msgid "bootstrap-select_locale"
msgstr "x"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Spanish (https://www.transifex.com/grocy/teams/93189/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -43,3 +43,6 @@ msgstr "es-ES"
msgid "fullcalendar_locale"
msgstr "es"
msgid "bootstrap-select_locale"
msgstr "es_ES"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: French (https://www.transifex.com/grocy/teams/93189/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -47,3 +47,6 @@ msgstr "fr-FR"
msgid "fullcalendar_locale"
msgstr "fr"
msgid "bootstrap-select_locale"
msgstr "fr_FR"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Italian (https://www.transifex.com/grocy/teams/93189/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -1,4 +1,5 @@
# Translators:
# Antonino Ursino <ninus_@libero.it>, 2019
# Bernd Bestel <bernd@berrnd.de>, 2019
#
msgid ""
@ -17,28 +18,22 @@ msgstr ""
"X-Domain: grocy/component_translations\n"
msgid "timeago_locale"
msgstr "it"
msgstr "tempofa_locale"
msgid "timeago_nan"
msgstr "NaN anni fa"
msgstr "tempofa_nan"
msgid "moment_locale"
msgstr "it"
msgstr "istante_locale"
msgid "datatables_localization"
msgstr ""
"{\"sEmptyTable\":\"Nessun dato disponibile\",\"sInfo\":\"Mostrando da "
"_START_ a _END_ di _TOTAL_ voci\",\"sInfoEmpty\":\"Mostrando da 0 a 0 di 0 "
"voci\",\"sInfoFiltered\":\"(Filtrato da _MAX_ voci "
"totali)\",\"sInfoPostFix\":\"\",\"sInfoThousands\":\",\",\"sLengthMenu\":\"Mostra"
" _MENU_ "
"voci\",\"sLoadingRecords\":\"Caricando...\",\"sProcessing\":\"Calcolando...\",\"sSearch\":\"Cerca:\",\"sZeroRecords\":\"Nessun"
" risultato "
"trovato\",\"oPaginate\":{\"sFirst\":\"Prima\",\"sLast\":\"Ultima\",\"sNext\":\"Prossima\",\"sPrevious\":\"Precedente\"},\"oAria\":{\"sSortAscending\":\":"
" ordine crescente\",\"sSortDescending\":\": ordine decrescente\"}}"
msgstr "datitabelle_localizzazione"
msgid "summernote_locale"
msgstr "it-IT"
msgstr "summernote_locale"
msgid "fullcalendar_locale"
msgstr "fr"
msgstr "calendariocompleto_locale"
msgid "bootstrap-select_locale"
msgstr "it_IT"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Dutch (https://www.transifex.com/grocy/teams/93189/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -45,3 +45,6 @@ msgstr "nl-NL"
msgid "fullcalendar_locale"
msgstr "nl"
msgid "bootstrap-select_locale"
msgstr "nl_NL"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: no\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -1,5 +1,6 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Andreas Henden <chairman2s.ah@gmail.com>, 2019
#
msgid ""
msgstr ""
@ -7,7 +8,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Andreas Henden <chairman2s.ah@gmail.com>, 2019\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,7 +18,7 @@ msgstr ""
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr "Manuel"
msgstr "manuelt"
msgid "dynamic-regular"
msgstr "Automatisk"

View File

@ -48,3 +48,6 @@ msgstr "nb-NO"
msgid "fullcalendar_locale"
msgstr "nb"
msgid "bootstrap-select_locale"
msgstr "nb_NO"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Polish (https://www.transifex.com/grocy/teams/93189/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -43,3 +43,6 @@ msgstr "pl-PL"
msgid "fullcalendar_locale"
msgstr "pl"
msgid "bootstrap-select_locale"
msgstr "pl_PL"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Russian (https://www.transifex.com/grocy/teams/93189/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -33,3 +33,6 @@ msgstr "ru-RU"
msgid "fullcalendar_locale"
msgstr "ru"
msgid "bootstrap-select_locale"
msgstr "ru_RU"

View File

@ -349,7 +349,7 @@ msgstr ""
msgid "This means %s will be removed from stock"
msgstr ""
msgid "This means it is estimated that a new execution of this chore is tracked %s days after the last was tracked"
msgid "This means the next execution of this chore is scheduled %s days after the last execution"
msgstr ""
msgid "Removed %1$s of %2$s from stock"
@ -1367,3 +1367,47 @@ msgstr ""
msgid "Edit QU conversion"
msgstr ""
msgid "An assignment type is required"
msgstr ""
msgid "Assignment type"
msgstr ""
msgid "This means the next execution of this chore is scheduled 1 day after the last execution"
msgstr ""
msgid "This means the next execution of this chore is scheduled 1 day after the last execution, but only for the weekdays selected below"
msgstr ""
msgid "This means the next execution of this chore is not scheduled"
msgstr ""
msgid "This means the next execution of this chore is scheduled on the below selected day of each month"
msgstr ""
msgid "This means the next execution of this chore will not be assigned to anyone"
msgstr ""
msgid "This means the next execution of this chore will be assigned to the one who executed it least"
msgstr ""
msgid "This means the next execution of this chore will be assigned randomly"
msgstr ""
msgid "This means the next execution of this chore will be assigned to the next one in alphabetical order"
msgstr ""
msgid "Assign to"
msgstr ""
msgid "This assignment type requires that at least one is assigned"
msgstr ""
msgid "%s chore is assigned to me"
msgid_plural "%s chores are assigned to me"
msgstr[0] ""
msgstr[1] ""
msgid "Assigned to me"
msgstr ""

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Swedish (Sweden) (https://www.transifex.com/grocy/teams/93189/sv_SE/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sv_SE\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -43,3 +43,6 @@ msgstr "sv-SE"
msgid "fullcalendar_locale"
msgstr "sv"
msgid "bootstrap-select_locale"
msgstr "sv_SE"

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Tamil (https://www.transifex.com/grocy/teams/93189/ta/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ta\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -30,3 +30,6 @@ msgstr ""
msgid "fullcalendar_locale"
msgstr ""
msgid "bootstrap-select_locale"
msgstr ""

View File

@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-09-17 10:45+0000\n"
"Language-Team: Turkish (https://www.transifex.com/grocy/teams/93189/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Domain: grocy/chore_assignment_types\n"
msgid "no-assignment"
msgstr ""
msgid "who-least-did-first"
msgstr ""
msgid "random"
msgstr ""
msgid "in-alphabetical-order"
msgstr ""

View File

@ -44,3 +44,6 @@ msgstr "tr-TR"
msgid "fullcalendar_locale"
msgstr "tr"
msgid "bootstrap-select_locale"
msgstr "tr_TR"

View File

@ -67,7 +67,7 @@ UNION
-- Second: Product specific overrides
SELECT
p.id AS id, -- Dummy, LessQL needs an id column
p.id AS id, -- Dummy, LessQL needs an id column
p.id AS product_id,
quc.from_qu_id AS from_qu_id,
qu_from.name AS from_qu_name,
@ -87,7 +87,7 @@ UNION
-- Third: Default quantity unit conversion factors
SELECT
p.id AS id, -- Dummy, LessQL needs an id column
p.id AS id, -- Dummy, LessQL needs an id column
p.id AS product_id,
p.qu_id_stock AS from_qu_id,
qu_from.name AS from_qu_name,

74
migrations/0083.sql Normal file
View File

@ -0,0 +1,74 @@
ALTER TABLE chores
ADD assignment_type TEXT;
ALTER TABLE chores
ADD assignment_config TEXT;
ALTER TABLE chores
ADD next_execution_assigned_to_user_id INT;
DROP VIEW chores_current;
CREATE VIEW chores_current
AS
SELECT
x.chore_id,
x.last_tracked_time,
CASE WHEN x.rollover = 1 AND DATETIME('now', 'localtime') > x.next_estimated_execution_time THEN
DATETIME(STRFTIME('%Y-%m-%d', DATETIME('now', 'localtime')) || ' ' || STRFTIME('%H:%M:%S', x.next_estimated_execution_time))
ELSE
x.next_estimated_execution_time
END AS next_estimated_execution_time,
x.track_date_only,
x.next_execution_assigned_to_user_id
FROM (
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')), '1 days', 'weekday 0')
WHEN period_config LIKE '%monday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 1')
WHEN period_config LIKE '%tuesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 2')
WHEN period_config LIKE '%wednesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 3')
WHEN period_config LIKE '%thursday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 4')
WHEN period_config LIKE '%friday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 5')
WHEN period_config LIKE '%saturday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', '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,
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
GROUP BY h.id, h.period_days
) x;
CREATE VIEW chores_assigned_users_resolved
AS
SELECT
c.id AS chore_id,
u.id AS user_id
FROM chores c
JOIN users u
ON ',' || c.assignment_config || ',' LIKE '%,' || CAST(u.id AS TEXT) || ',%';
CREATE VIEW chores_execution_users_statistics
AS
SELECT
c.id AS id, -- Dummy, LessQL needs an id column
c.id AS chore_id,
caur.user_id AS user_id,
(SELECT COUNT(1) FROM chores_log WHERE chore_id = c.id AND done_by_user_id = caur.user_id AND undone = 0) AS execution_count
FROM chores c
JOIN chores_assigned_users_resolved caur
ON c.id = caur.chore_id
GROUP BY c.id, caur.user_id;

View File

@ -30,6 +30,7 @@
"swagger-ui-dist": "^3.22.1",
"tempusdominus-bootstrap-4": "https://github.com/berrnd/tempusdominus-bootstrap-4.git#master",
"timeago": "^1.6.7",
"toastr": "^2.1.4"
"toastr": "^2.1.4",
"bootstrap-select": "^1.13.10"
}
}

View File

@ -3,6 +3,7 @@
e.preventDefault();
var jsonData = $('#chore-form').serializeJSON({ checkboxUncheckedValue: "0" });
jsonData.assignment_config = $("#assignment_config").val().join(",");
Grocy.FrontendHelpers.BeginUiBusy("chore-form");
if (Grocy.EditMode === 'create')
@ -13,7 +14,17 @@
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save(function()
{
window.location.href = U('/chores');
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function (result)
{
window.location.href = U('/chores');
},
function (xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
},
function(xhr)
@ -30,7 +41,17 @@
{
Grocy.Components.UserfieldsForm.Save(function()
{
window.location.href = U('/chores');
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function (result)
{
window.location.href = U('/chores');
},
function (xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
},
function(xhr)
@ -80,6 +101,7 @@ Grocy.FrontendHelpers.ValidateForm('chore-form');
setTimeout(function()
{
$(".input-group-chore-period-type").trigger("change");
$(".input-group-chore-assignment-type").trigger("change");
}, 100);
$('.input-group-chore-period-type').on('change', function(e)
@ -94,7 +116,7 @@ $('.input-group-chore-period-type').on('change', function(e)
if (periodType === 'manually')
{
//
$('#chore-period-type-info').text(__t('This means the next execution of this chore is not scheduled'));
}
else if (periodType === 'dynamic-regular')
{
@ -102,21 +124,59 @@ $('.input-group-chore-period-type').on('change', function(e)
$("#period_days").attr("min", "0");
$("#period_days").attr("max", "9999");
$("#period_days").parent().find(".invalid-feedback").text(__t('This cannot be negative'));
$('#chore-period-type-info').text(__t('This means it is estimated that a new execution of this chore is tracked %s days after the last was tracked', periodDays.toString()));
$('#chore-period-type-info').text(__t('This means the next execution of this chore is scheduled %s days after the last execution', periodDays.toString()));
}
else if (periodType === 'daily')
{
//
$('#chore-period-type-info').text(__t('This means the next execution of this chore is scheduled 1 day after the last execution'));
}
else if (periodType === 'weekly')
{
$('#chore-period-type-info').text(__t('This means the next execution of this chore is scheduled 1 day after the last execution, but only for the weekdays selected below'));
$("#period_config").val($(".period-type-weekly input:checkbox:checked").map(function () { return this.value; }).get().join(","));
}
else if (periodType === 'monthly')
{
$('#chore-period-type-info').text(__t('This means the next execution of this chore is scheduled on the below selected day of each month'));
$("label[for='period_days']").text(__t("Day of month"));
$("#period_days").attr("min", "1");
$("#period_days").attr("max", "31");
$("#period_days").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', "1", "31"));
}
Grocy.FrontendHelpers.ValidateForm('chore-form');
});
$('.input-group-chore-assignment-type').on('change', function(e)
{
var assignmentType = $('#assignment_type').val();
$('#chore-period-assignment-info').text("");
$("#assignment_config").removeAttr("required");
$("#assignment_config").attr("disabled", "");
if (assignmentType === 'no-assignment')
{
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will not be assigned to anyone'));
}
else if (assignmentType === 'who-least-did-first')
{
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned to the one who executed it least'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
}
else if (assignmentType === 'random')
{
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned randomly'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
}
else if (assignmentType === 'in-alphabetical-order')
{
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned to the next one in alphabetical order'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
}
Grocy.FrontendHelpers.ValidateForm('chore-form');
});

View File

@ -43,7 +43,7 @@ $("#status-filter").on("change", function()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
choresOverviewTable.column(4).search(value).draw();
choresOverviewTable.column(5).search(value).draw();
});
$(".status-filter-button").on("click", function()
@ -114,6 +114,15 @@ $(document).on('click', '.track-chore-button', function(e)
$('#chore-' + choreId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time);
}
if (result.chore.next_execution_assigned_to_user_id != null)
{
$('#chore-' + choreId + '-next-execution-assigned-user').parent().effect('highlight', {}, 500);
$('#chore-' + choreId + '-next-execution-assigned-user').fadeOut(500, function ()
{
$(this).text(result.next_execution_assigned_user.display_name).fadeIn(500);
});
}
Grocy.FrontendHelpers.EndUiBusy();
toastr.success(__t('Tracked execution of chore %1$s on %2$s', choreName, trackedTime));
RefreshStatistics();
@ -160,6 +169,7 @@ function RefreshStatistics()
{
var dueCount = 0;
var overdueCount = 0;
var assignedToMeCount = 0;
var now = moment();
var nextXDaysThreshold = moment().add(nextXDays, "days");
result.forEach(element => {
@ -172,10 +182,16 @@ function RefreshStatistics()
{
dueCount++;
}
if (parseInt(element.next_execution_assigned_to_user_id) == Grocy.UserId)
{
assignedToMeCount++;
}
});
$("#info-due-chores").text(__n(dueCount, '%s chore is due to be done', '%s chores are due to be done') + ' ' + __n(nextXDays, 'within the next day', 'within the next %s days'));
$("#info-overdue-chores").text(__n(overdueCount, '%s chore is overdue to be done', '%s chores are overdue to be done'));
$("#info-assigned-to-me-chores").text(__n(assignedToMeCount, '%s chore is assigned to me', '%s chores are assigned to me'));
},
function(xhr)
{

View File

@ -190,6 +190,7 @@ $app->group('/api', function()
$this->get('/chores/{choreId}', '\Grocy\Controllers\ChoresApiController:ChoreDetails');
$this->post('/chores/{choreId}/execute', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution');
$this->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
$this->post('/chores/executions/calculate-next-assignments', '\Grocy\Controllers\ChoresApiController:CalculateNextExecutionAssignments');
}
// Batteries

View File

@ -4,11 +4,16 @@ namespace Grocy\Services;
class ChoresService extends BaseService
{
const CHORE_TYPE_MANUALLY = 'manually';
const CHORE_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
const CHORE_TYPE_DAILY = 'daily';
const CHORE_TYPE_weekly = 'weekly';
const CHORE_TYPE_monthly = 'monthly';
const CHORE_PERIOD_TYPE_MANUALLY = 'manually';
const CHORE_PERIOD_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
const CHORE_PERIOD_TYPE_DAILY = 'daily';
const CHORE_PERIOD_TYPE_WEEKLY = 'weekly';
const CHORE_PERIOD_TYPE_MONTHLY = 'monthly';
const CHORE_ASSIGNMENT_TYPE_NO_ASSIGNMENT = 'no-assignment';
const CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST = 'who-least-did-first';
const CHORE_ASSIGNMENT_TYPE_RANDOM = 'random';
const CHORE_ASSIGNMENT_TYPE_IN_ALPHABETICAL_ORDER = 'in-alphabetical-order';
public function GetCurrent()
{
@ -23,26 +28,34 @@ class ChoresService extends BaseService
throw new \Exception('Chore does not exist');
}
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$chore = $this->Database->chores($choreId);
$choreTrackedCount = $this->Database->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->count();
$choreLastTrackedTime = $this->Database->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->max('tracked_time');
$nextExeuctionTime = $this->Database->chores_current()->where('chore_id', $choreId)->min('next_estimated_execution_time');
$nextExecutionTime = $this->Database->chores_current()->where('chore_id', $choreId)->min('next_estimated_execution_time');
$lastChoreLogRow = $this->Database->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->fetch();
$lastDoneByUser = null;
if ($lastChoreLogRow !== null && !empty($lastChoreLogRow))
{
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $lastChoreLogRow->done_by_user_id);
}
$nextExecutionAssignedUser = null;
if (!empty($chore->next_execution_assigned_to_user_id))
{
$nextExecutionAssignedUser = FindObjectInArrayByPropertyValue($users, 'id', $chore->next_execution_assigned_to_user_id);
}
return array(
'chore' => $chore,
'last_tracked' => $choreLastTrackedTime,
'tracked_count' => $choreTrackedCount,
'last_done_by' => $lastDoneByUser,
'next_estimated_execution_time' => $nextExeuctionTime
'next_estimated_execution_time' => $nextExecutionTime,
'next_execution_assigned_user' => $nextExecutionAssignedUser
);
}
@ -72,7 +85,9 @@ class ChoresService extends BaseService
));
$logRow->save();
return $this->Database->lastInsertId();
$lastInsertId = $this->Database->lastInsertId();
$this->CalculateNextExecutionAssignment($choreId);
return $lastInsertId;
}
private function ChoreExists($choreId)
@ -95,4 +110,86 @@ class ChoresService extends BaseService
'undone_timestamp' => date('Y-m-d H:i:s')
));
}
public function CalculateNextExecutionAssignment($choreId)
{
if (!$this->ChoreExists($choreId))
{
throw new \Exception('Chore does not exist');
}
$chore = $this->Database->chores($choreId);
$choreLastTrackedTime = $this->Database->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->max('tracked_time');
$lastChoreLogRow = $this->Database->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->fetch();
$lastDoneByUserId = $lastChoreLogRow->done_by_user_id;
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$assignedUsers = array();
foreach ($users as $user)
{
if (in_array($user->id, explode(',', $chore->assignment_config)))
{
$assignedUsers[] = $user;
}
}
$nextExecutionUserId = null;
if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_RANDOM)
{
// Random assignment and only 1 user in the group? Well, ok - will be hard to guess the next one...
if (count($assignedUsers) == 1)
{
$nextExecutionUserId = array_shift($assignedUsers)->id;
}
else
{
// Randomness in small groups will likely often result in the same user, so try it as long as this is the case
while ($nextExecutionUserId == null || $nextExecutionUserId == $lastDoneByUserId)
{
$nextExecutionUserId = $assignedUsers[array_rand($assignedUsers)]->id;
}
}
}
else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_IN_ALPHABETICAL_ORDER)
{
usort($assignedUsers, function($a, $b)
{
return strcmp($a->display_name, $b->display_name);
});
$nextRoundMatches = false;
foreach ($assignedUsers as $user)
{
if ($nextRoundMatches)
{
$nextExecutionUserId = $user->id;
break;
}
if ($user->id == $lastDoneByUserId)
{
$nextRoundMatches = true;
}
}
// If nothing has matched, probably it was the last user in the sorted list -> the first one is the next one
if ($nextExecutionUserId == null)
{
$nextExecutionUserId = array_shift($assignedUsers)->id;
}
}
else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST)
{
$row = $this->Database->chores_execution_users_statistics()->where('chore_id = :1', $choreId)->orderBy('execution_count')->limit(1)->fetch();
if ($row != null)
{
$nextExecutionUserId = $row->user_id;
}
}
$chore->update(array(
'next_execution_assigned_to_user_id' => $nextExecutionUserId
));
}
}

View File

@ -125,8 +125,8 @@ class DemoDataGeneratorService extends BaseService
INSERt INTO meal_plan(day, recipe_id) VALUES ('{$sundayThisWeek}', 4);
INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Changed towels in the bathroom')}', 'manually', 5); --1
INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
INSERT INTO chores (name, period_type, period_days, assignment_type, assignment_config, next_execution_assigned_to_user_id) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7, 'random', '1,2,3,4', 1); --2
INSERT INTO chores (name, period_type, period_days, assignment_type, assignment_config, next_execution_assigned_to_user_id) VALUES ('{$this->__t_sql('Lawn mowed in the garden')}', 'dynamic-regular', 21, 'random', '1,2,3,4', 1); --3
INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('The thing which happens on the 5th of every month')}', 'monthly', 5); --4
INSERT INTO chores (name, period_type) VALUES ('{$this->__t_sql('The thing which happens daily')}', 'daily'); --5
INSERT INTO chores (name, period_type, period_config) VALUES ('{$this->__t_sql('The thing which happens on Mondays and Wednesdays')}', 'weekly', 'monday,wednesday'); --6

View File

@ -34,7 +34,8 @@ class LocalizationService
{
$this->PotMain = Translations::fromPoFile(__DIR__ . '/../localization/strings.pot');
$this->Pot = Translations::fromPoFile(__DIR__ . '/../localization/chore_types.pot');
$this->Pot = Translations::fromPoFile(__DIR__ . '/../localization/chore_period_types.pot');
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/chore_assignment_types.pot'));
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/component_translations.pot'));
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/demo_data.pot'));
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/stock_transaction_types.pot'));
@ -45,7 +46,8 @@ class LocalizationService
$this->PoUserStrings = new Translations();
$this->PoUserStrings->setDomain('grocy/userstrings');
$this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_types.po");
$this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_period_types.po");
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_assignment_types.po"));
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/component_translations.po"));
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po"));
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/stock_transaction_types.po"));

View File

@ -8,6 +8,15 @@
@section('viewJsName', 'choreform')
@push('pageScripts')
<script src="{{ $U('/node_modules/bootstrap-select/dist/js/bootstrap-select.min.js?v=', true) }}{{ $version }}"></script>
@if(!empty($__t('bootstrap-select_locale') && $__t('bootstrap-select_locale') != 'x'))<script src="{{ $U('/node_modules', true) }}/bootstrap-select/dist/js/i18n/defaults-{{ $__t('bootstrap-select_locale') }}.js?v={{ $version }}"></script>@endif
@endpush
@push('pageStyles')
<link href="{{ $U('/node_modules/bootstrap-select/dist/css/bootstrap-select.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col-lg-6 col-xs-12">
@ -33,7 +42,7 @@
</div>
<div class="form-group">
<label for="period_type">{{ $__t('Period type') }}</label>
<label for="period_type">{{ $__t('Period type') }} <span id="chore-period-type-info" class="small text-muted"></span></label>
<select required class="form-control input-group-chore-period-type" id="period_type" name="period_type">
@foreach($periodTypes as $periodType)
<option @if($mode == 'edit' && $periodType == $chore->period_type) selected="selected" @endif value="{{ $periodType }}">{{ $__t($periodType) }}</option>
@ -50,7 +59,6 @@
'min' => '0',
'additionalCssClasses' => 'input-group-chore-period-type',
'invalidFeedback' => $__t('This cannot be negative'),
'additionalHtmlElements' => '<span id="chore-period-type-info" class="small text-muted"></span>',
'additionalGroupCssClasses' => 'period-type-input period-type-dynamic-regular period-type-monthly'
))
@ -87,6 +95,26 @@
<input type="hidden" id="period_config" name="period_config" value="@if($mode == 'edit'){{ $chore->period_config }}@endif">
<div class="form-group">
<label for="assignment_type">{{ $__t('Assignment type') }} <span id="chore-assignment-type-info" class="small text-muted"></span></label>
<select required class="form-control input-group-chore-assignment-type" id="assignment_type" name="assignment_type">
@foreach($assignmentTypes as $assignmentType)
<option @if($mode == 'edit' && $assignmentType == $chore->assignment_type) selected="selected" @endif value="{{ $assignmentType }}">{{ $__t($assignmentType) }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('An assignment type is required') }}</div>
</div>
<div class="form-group">
<label for="assignment_config">{{ $__t('Assign to') }}</label>
<select required multiple class="form-control input-group-chore-assignment-type selectpicker" id="assignment_config" name="assignment_config" data-actions-Box="true" data-live-search="true">
@foreach($users as $user)
<option @if($mode == 'edit' && in_array($user->id, explode(',', $chore->assignment_config))) selected="selected" @endif value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('This assignment type requires that at least one is assigned') }}</div>
</div>
<div class="form-group">
<div class="form-check">
<input type="hidden" name="track_date_only" value="0">

View File

@ -17,7 +17,8 @@
</a>
</h1>
<p id="info-due-chores" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-overdue-chores" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button"></p>
<p id="info-overdue-chores" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button mr-2"></p>
<p id="info-assigned-to-me-chores" data-status-filter="assigned-to-me" class="btn btn-lg btn-secondary status-filter-button responsive-button"></p>
</div>
</div>
@ -32,6 +33,7 @@
<option class="bg-white" value="all">{{ $__t('All') }}</option>
<option class="bg-warning" value="duesoon">{{ $__t('Due soon') }}</option>
<option class="bg-danger" value="overdue">{{ $__t('Overdue') }}</option>
<option class="bg-secondary text-white" value="assigned-to-me">{{ $__t('Assigned to me') }}</option>
</select>
</div>
</div>
@ -45,6 +47,7 @@
<th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Next estimated tracking') }}</th>
<th>{{ $__t('Last tracked') }}</th>
<th>{{ $__t('Assigned to') }}</th>
<th class="d-none">Hidden status</th>
@include('components.userfields_thead', array(
@ -55,7 +58,7 @@
</thead>
<tbody class="d-none">
@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_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">
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_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_PERIOD_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">
<a class="btn btn-success btn-sm track-chore-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track execution of chore %s', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
@ -83,7 +86,7 @@
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
</td>
<td>
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY)
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY)
<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 @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
@else
@ -94,8 +97,18 @@
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
</td>
<td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
{{ FindObjectInArrayByPropertyValue($users, 'id', $curentChoreEntry->next_execution_assigned_to_user_id)->display_name }}
@else
...
@endif
</span>
</td>
<td class="d-none">
@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
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_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_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
@if($curentChoreEntry->next_execution_assigned_to_user_id == GROCY_USER_ID) assigned-to-me @endif
</td>
@include('components.userfields_tbody', array(

View File

@ -57,6 +57,7 @@
Grocy.GettextPo = {!! $GettextPo !!};
Grocy.UserSettings = {!! json_encode($userSettings) !!};
Grocy.FeatureFlags = {!! json_encode($featureFlags) !!};
Grocy.UserId = {{ GROCY_USER_ID }};
</script>
</head>

View File

@ -25,6 +25,11 @@ bootbox@^5.1.3:
bootstrap ">=3.0.0"
jquery ">=1.9.1"
bootstrap-select@^1.13.10:
version "1.13.10"
resolved "https://registry.yarnpkg.com/bootstrap-select/-/bootstrap-select-1.13.10.tgz#b0cf57f8d448bc0488faba04ecaa2f4c5216e144"
integrity sha512-mwfyd+VWHY8wDN4je4Y8PPEaOAzDPUz95z5F5zxzlIqebqxtbrujclFWLrhkVOyETpsgZCL1rsfyEnHpAugtGg==
bootstrap@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"