mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Add journal and undo UI for stock bookings, chore executions and battery charge cycles (closes #63, closes #97)
This commit is contained in:
parent
fe83e2fa6f
commit
364f6b2051
@ -24,8 +24,8 @@ class BatteriesApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
$chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
||||
return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
|
@ -53,4 +53,12 @@ class BatteriesController extends BaseController
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'batteriesjournal', [
|
||||
'chargeCycles' => $this->Database->battery_charge_cycles()->orderBy('tracked_time', 'DESC'),
|
||||
'batteries' => $this->Database->batteries()->orderBy('name')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ class ChoresApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
$choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||
return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
|
@ -38,10 +38,10 @@ class ChoresController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'choresanalysis', [
|
||||
'choresLog' => $this->Database->chores_log()->where('undone', 0)->orderBy('tracked_time', 'DESC'),
|
||||
return $this->AppContainer->view->render($response, 'choresjournal', [
|
||||
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
|
||||
'chores' => $this->Database->chores()->orderBy('name'),
|
||||
'users' => $this->Database->users()->orderBy('username')
|
||||
]);
|
||||
|
@ -60,8 +60,8 @@ class StockApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
$bookingId = $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
|
||||
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
@ -85,8 +85,8 @@ class StockApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
$bookingId = $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
||||
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
@ -104,8 +104,8 @@ class StockApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
$bookingId = $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
||||
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
|
@ -192,4 +192,13 @@ class StockController extends BaseController
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'stockjournal', [
|
||||
'stockLog' => $this->Database->stock_log()->orderBy('row_created_timestamp', 'DESC'),
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ return array(
|
||||
'Last done by' => 'Zuletzt ausgeführt von',
|
||||
'Unknown' => 'Unbekannt',
|
||||
'Filter by chore' => 'Nach Hausarbeit filtern',
|
||||
'Chores analysis' => 'Hausarbeiten Analyse',
|
||||
'Chores journal' => 'Hausarbeitenjournal',
|
||||
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
|
||||
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
|
||||
'Last price' => 'Letzter Preis',
|
||||
@ -286,11 +286,31 @@ return array(
|
||||
'Edit included recipe' => 'Enthaltenes Rezept bearbeiten',
|
||||
'Group' => 'Gruppe',
|
||||
'This will be used as a headline to group ingredients together' => 'Dies wird als Überschrift verwendet, um Zutaten zusammenzufassen',
|
||||
'Journal' => 'Journal',
|
||||
'Stock journal' => 'Bestandsjournal',
|
||||
'Filter by product' => 'Nach Produkt filtern',
|
||||
'Booking time' => 'Buchungszeit',
|
||||
'Booking type' => 'Buchungsart',
|
||||
'Undo booking' => 'Buchung rückgängig machen',
|
||||
'Undone on' => 'Rückgängig gemacht am',
|
||||
'Batteries journal' => 'Batteriejournal',
|
||||
'Filter by battery' => 'Nach Batterie filtern',
|
||||
'Undo charge cycle' => 'Ladezyklus rückgängig machen',
|
||||
'Undo chore execution' => 'Ausführung rückgängig machen',
|
||||
'Chore execution successfully undone' => 'Ausführung erfolgreich rückgängig gemacht',
|
||||
'Undo' => 'Rückgängig machen',
|
||||
'Booking successfully undone' => 'Buchung erfolgreich rückgängig gemacht',
|
||||
'Charge cycle successfully undone' => 'Ladezyklus erfolgreich rückgängig gemacht',
|
||||
|
||||
//Constants
|
||||
//Constants - Chore types
|
||||
'manually' => 'Manuell',
|
||||
'dynamic-regular' => 'Dynamisch regelmäßig',
|
||||
|
||||
//Constants - Stock transaction types
|
||||
'purchase' => 'Einkauf',
|
||||
'consume' => 'Verbrauch',
|
||||
'inventory-correction' => 'Inventur-Korrektur',
|
||||
|
||||
//Technical component translations
|
||||
'timeago_locale' => 'de',
|
||||
'timeago_nan' => 'vor NaN Jahren',
|
||||
|
@ -1,10 +1,15 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
//Constants
|
||||
//Constants - Chore types
|
||||
'manually' => 'Manually',
|
||||
'dynamic-regular' => 'Dynamic regular',
|
||||
|
||||
//Constants - Stock transaction types
|
||||
'purchase' => 'Purchase',
|
||||
'consume' => 'Consume',
|
||||
'inventory-correction' => 'Inventory correction',
|
||||
|
||||
//Technical component translations
|
||||
'timeago_locale' => 'en',
|
||||
'timeago_nan' => 'NaN years ago',
|
||||
|
@ -184,7 +184,7 @@ return array(
|
||||
'Last done by' => 'Sist utført av',
|
||||
'Unknown' => 'Ukjent',
|
||||
'Filter by chore' => 'Filtrér husarbeid',
|
||||
'Chores analysis' => 'Statistikk husarbeid',
|
||||
'Chores journal' => 'Statistikk husarbeid',
|
||||
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
|
||||
'Charge cycle interval (days)' => 'Ladesyklysintervall (dager)',
|
||||
'Last price' => 'Siste pris',
|
||||
|
70
public/viewjs/batteriesjournal.js
Normal file
70
public/viewjs/batteriesjournal.js
Normal file
@ -0,0 +1,70 @@
|
||||
var batteriesJournalTable = $('#batteries-journal-table').DataTable({
|
||||
'paginate': true,
|
||||
'order': [[1, 'desc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 }
|
||||
],
|
||||
'language': JSON.parse(L('datatables_localization')),
|
||||
'scrollY': false,
|
||||
'colReorder': true,
|
||||
'stateSave': true,
|
||||
'stateSaveParams': function(settings, data)
|
||||
{
|
||||
data.search.search = "";
|
||||
|
||||
data.columns.forEach(column =>
|
||||
{
|
||||
column.search.search = "";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#battery-filter").on("change", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
var text = $("#battery-filter option:selected").text();
|
||||
if (value === "all")
|
||||
{
|
||||
text = "";
|
||||
}
|
||||
|
||||
batteriesJournalTable.column(1).search(text).draw();
|
||||
});
|
||||
|
||||
$("#search").on("keyup", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
if (value === "all")
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
batteriesJournalTable.search(value).draw();
|
||||
});
|
||||
|
||||
if (typeof GetUriParam("battery") !== "undefined")
|
||||
{
|
||||
$("#battery-filter").val(GetUriParam("battery"));
|
||||
$("#battery-filter").trigger("change");
|
||||
}
|
||||
|
||||
$(document).on('click', '.undo-battery-execution-button', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var element = $(e.currentTarget);
|
||||
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
|
||||
|
||||
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
|
||||
function(result)
|
||||
{
|
||||
element.closest("tr").addClass("text-muted");
|
||||
element.closest(".undo-battery-execution-button").addClass("disabled");
|
||||
toastr.success(L("Charge cycle successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
@ -10,7 +10,7 @@
|
||||
Grocy.Api.Get('batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').find('input').val(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()));
|
||||
toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChargeCycle(' + result.charge_cycle_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||
|
||||
$('#battery_id').val('');
|
||||
$('#battery_id_text_input').focus();
|
||||
@ -86,3 +86,16 @@ $('#tracked_time').find('input').on('keypress', function (e)
|
||||
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||
});
|
||||
|
||||
function UndoChargeCycle(chargeCycleId)
|
||||
{
|
||||
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L("Charge cycle successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,46 +0,0 @@
|
||||
var choresAnalysisTable = $('#chores-analysis-table').DataTable({
|
||||
'paginate': false,
|
||||
'order': [[1, 'desc']],
|
||||
'language': JSON.parse(L('datatables_localization')),
|
||||
'scrollY': false,
|
||||
'colReorder': true,
|
||||
'stateSave': true,
|
||||
'stateSaveParams': function(settings, data)
|
||||
{
|
||||
data.search.search = "";
|
||||
|
||||
data.columns.forEach(column =>
|
||||
{
|
||||
column.search.search = "";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#chore-filter").on("change", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
var text = $("#chore-filter option:selected").text();
|
||||
if (value === "all")
|
||||
{
|
||||
text = "";
|
||||
}
|
||||
|
||||
choresAnalysisTable.column(0).search(text).draw();
|
||||
});
|
||||
|
||||
$("#search").on("keyup", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
if (value === "all")
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
choresAnalysisTable.search(value).draw();
|
||||
});
|
||||
|
||||
if (typeof GetUriParam("chore") !== "undefined")
|
||||
{
|
||||
$("#chore-filter").val(GetUriParam("chore"));
|
||||
$("#chore-filter").trigger("change");
|
||||
}
|
70
public/viewjs/choresjournal.js
Normal file
70
public/viewjs/choresjournal.js
Normal file
@ -0,0 +1,70 @@
|
||||
var choresJournalTable = $('#chores-journal-table').DataTable({
|
||||
'paginate': true,
|
||||
'order': [[1, 'desc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 }
|
||||
],
|
||||
'language': JSON.parse(L('datatables_localization')),
|
||||
'scrollY': false,
|
||||
'colReorder': true,
|
||||
'stateSave': true,
|
||||
'stateSaveParams': function(settings, data)
|
||||
{
|
||||
data.search.search = "";
|
||||
|
||||
data.columns.forEach(column =>
|
||||
{
|
||||
column.search.search = "";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#chore-filter").on("change", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
var text = $("#chore-filter option:selected").text();
|
||||
if (value === "all")
|
||||
{
|
||||
text = "";
|
||||
}
|
||||
|
||||
choresJournalTable.column(1).search(text).draw();
|
||||
});
|
||||
|
||||
$("#search").on("keyup", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
if (value === "all")
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
choresJournalTable.search(value).draw();
|
||||
});
|
||||
|
||||
if (typeof GetUriParam("chore") !== "undefined")
|
||||
{
|
||||
$("#chore-filter").val(GetUriParam("chore"));
|
||||
$("#chore-filter").trigger("change");
|
||||
}
|
||||
|
||||
$(document).on('click', '.undo-chore-execution-button', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var element = $(e.currentTarget);
|
||||
var executionId = $(e.currentTarget).attr('data-execution-id');
|
||||
|
||||
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
|
||||
function(result)
|
||||
{
|
||||
element.closest("tr").addClass("text-muted");
|
||||
element.closest(".undo-chore-execution-button").addClass("disabled");
|
||||
toastr.success(L("Chore execution successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
@ -10,7 +10,7 @@
|
||||
Grocy.Api.Get('chores/track-chore-execution/' + jsonForm.chore_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()));
|
||||
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChoreExecution(' + result.chore_execution_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||
|
||||
$('#chore_id').val('');
|
||||
$('#chore_id_text_input').focus();
|
||||
@ -82,3 +82,17 @@ Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||
});
|
||||
|
||||
function UndoChoreExecution(executionId)
|
||||
{
|
||||
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L("Chore execution successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -16,7 +16,7 @@
|
||||
Grocy.Api.Get('stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled,
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name));
|
||||
toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||
|
||||
$('#amount').val(1);
|
||||
Grocy.Components.ProductPicker.SetValue('');
|
||||
@ -102,3 +102,17 @@ $('#consume-form input').keydown(function(event)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function UndoStockBooking(bookingId)
|
||||
{
|
||||
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L("Booking successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -32,7 +32,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, Pluralize(jsonForm.new_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)));
|
||||
toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, Pluralize(jsonForm.new_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||
|
||||
if (addBarcode !== undefined)
|
||||
{
|
||||
@ -186,3 +186,17 @@ $('#new_amount').on('keyup', function(e)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function UndoStockBooking(bookingId)
|
||||
{
|
||||
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L("Booking successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -40,7 +40,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
toastr.success(L('Added #1 #2 of #3 to stock', amount, Pluralize(amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name));
|
||||
toastr.success(L('Added #1 #2 of #3 to stock', amount, Pluralize(amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||
|
||||
if (addBarcode !== undefined)
|
||||
{
|
||||
@ -171,3 +171,17 @@ $('#amount').on('change', function (e)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('purchase-form');
|
||||
});
|
||||
|
||||
function UndoStockBooking(bookingId)
|
||||
{
|
||||
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success(L("Booking successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
70
public/viewjs/stockjournal.js
Normal file
70
public/viewjs/stockjournal.js
Normal file
@ -0,0 +1,70 @@
|
||||
var stockJournalTable = $('#stock-journal-table').DataTable({
|
||||
'paginate': true,
|
||||
'order': [[3, 'desc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 }
|
||||
],
|
||||
'language': JSON.parse(L('datatables_localization')),
|
||||
'scrollY': false,
|
||||
'colReorder': true,
|
||||
'stateSave': true,
|
||||
'stateSaveParams': function(settings, data)
|
||||
{
|
||||
data.search.search = "";
|
||||
|
||||
data.columns.forEach(column =>
|
||||
{
|
||||
column.search.search = "";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#product-filter").on("change", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
var text = $("#product-filter option:selected").text();
|
||||
if (value === "all")
|
||||
{
|
||||
text = "";
|
||||
}
|
||||
|
||||
stockJournalTable.column(1).search(text).draw();
|
||||
});
|
||||
|
||||
$("#search").on("keyup", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
if (value === "all")
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
stockJournalTable.search(value).draw();
|
||||
});
|
||||
|
||||
if (typeof GetUriParam("product") !== "undefined")
|
||||
{
|
||||
$("#product-filter").val(GetUriParam("product"));
|
||||
$("#product-filter").trigger("change");
|
||||
}
|
||||
|
||||
$(document).on('click', '.undo-stock-booking-button', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var element = $(e.currentTarget);
|
||||
var bookingId = $(e.currentTarget).attr('data-booking-id');
|
||||
|
||||
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
|
||||
function(result)
|
||||
{
|
||||
element.closest("tr").addClass("text-muted");
|
||||
element.closest(".undo-stock-booking-button").addClass("disabled");
|
||||
toastr.success(L("Booking successfully undone"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
@ -35,6 +35,7 @@ $app->group('', function()
|
||||
$this->get('/productgroup/{productGroupId}', '\Grocy\Controllers\StockController:ProductGroupEditForm');
|
||||
$this->get('/shoppinglist', '\Grocy\Controllers\StockController:ShoppingList');
|
||||
$this->get('/shoppinglistitem/{itemId}', '\Grocy\Controllers\StockController:ShoppingListItemEditForm');
|
||||
$this->get('/stockjournal', '\Grocy\Controllers\StockController:Journal');
|
||||
|
||||
// Recipe routes
|
||||
$this->get('/recipes', '\Grocy\Controllers\RecipesController:Overview');
|
||||
@ -44,7 +45,7 @@ $app->group('', function()
|
||||
// Chore routes
|
||||
$this->get('/choresoverview', '\Grocy\Controllers\ChoresController:Overview');
|
||||
$this->get('/choretracking', '\Grocy\Controllers\ChoresController:TrackChoreExecution');
|
||||
$this->get('/choresanalysis', '\Grocy\Controllers\ChoresController:Analysis');
|
||||
$this->get('/choresjournal', '\Grocy\Controllers\ChoresController:Journal');
|
||||
|
||||
$this->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList');
|
||||
$this->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm');
|
||||
@ -52,6 +53,7 @@ $app->group('', function()
|
||||
// Battery routes
|
||||
$this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
|
||||
$this->get('/batterytracking', '\Grocy\Controllers\BatteriesController:TrackChargeCycle');
|
||||
$this->get('/batteriesjournal', '\Grocy\Controllers\BatteriesController:Journal');
|
||||
|
||||
$this->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList');
|
||||
$this->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm');
|
||||
|
@ -18,14 +18,14 @@ class BatteriesService extends BaseService
|
||||
}
|
||||
|
||||
$battery = $this->Database->batteries($batteryId);
|
||||
$batteryChargeCylcesCount = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->count();
|
||||
$batteryChargeCyclesCount = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->count();
|
||||
$batteryLastChargedTime = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->max('tracked_time');
|
||||
$nextChargeTime = $this->Database->batteries_current()->where('battery_id', $batteryId)->min('next_estimated_charge_time');
|
||||
|
||||
return array(
|
||||
'battery' => $battery,
|
||||
'last_charged' => $batteryLastChargedTime,
|
||||
'charge_cycles_count' => $batteryChargeCylcesCount,
|
||||
'charge_cycles_count' => $batteryChargeCyclesCount,
|
||||
'next_estimated_charge_time' => $nextChargeTime
|
||||
);
|
||||
}
|
||||
@ -43,7 +43,7 @@ class BatteriesService extends BaseService
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
return true;
|
||||
return $this->Database->lastInsertId();
|
||||
}
|
||||
|
||||
private function BatteryExists($batteryId)
|
||||
|
@ -63,7 +63,7 @@ class ChoresService extends BaseService
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
return true;
|
||||
return $this->Database->lastInsertId();
|
||||
}
|
||||
|
||||
private function ChoreExists($choreId)
|
||||
|
@ -47,8 +47,8 @@ class StockService extends BaseService
|
||||
|
||||
$product = $this->Database->products($productId);
|
||||
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
||||
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date')->where('undone', 0);
|
||||
$productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date')->where('undone', 0);
|
||||
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->where('undone', 0)->max('purchased_date');
|
||||
$productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone', 0)->max('used_date');
|
||||
$nextBestBeforeDate = $this->Database->stock()->where('product_id', $productId)->min('best_before_date');
|
||||
$quPurchase = $this->Database->quantity_units($product->qu_id_purchase);
|
||||
$quStock = $this->Database->quantity_units($product->qu_id_stock);
|
||||
@ -113,6 +113,8 @@ class StockService extends BaseService
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
$returnValue = $this->Database->lastInsertId();
|
||||
|
||||
$stockRow = $this->Database->stock()->createRow(array(
|
||||
'product_id' => $productId,
|
||||
'amount' => $amount,
|
||||
@ -123,7 +125,7 @@ class StockService extends BaseService
|
||||
));
|
||||
$stockRow->save();
|
||||
|
||||
return true;
|
||||
return $returnValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -197,7 +199,7 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->Database->lastInsertId();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -226,7 +228,7 @@ class StockService extends BaseService
|
||||
$this->ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->Database->lastInsertId();
|
||||
}
|
||||
|
||||
public function AddMissingProductsToShoppingList()
|
||||
|
66
views/batteriesjournal.blade.php
Normal file
66
views/batteriesjournal.blade.php
Normal file
@ -0,0 +1,66 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('title', $L('Batteries journal'))
|
||||
@section('activeNav', 'batteriesjournal')
|
||||
@section('viewJsName', 'batteriesjournal')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@yield('title')</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-3">
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="battery-filter">{{ $L('Filter by battery') }}</label> <i class="fas fa-filter"></i>
|
||||
<select class="form-control" id="battery-filter">
|
||||
<option value="all">{{ $L('All') }}</option>
|
||||
@foreach($batteries as $battery)
|
||||
<option value="{{ $battery->id }}">{{ $battery->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
|
||||
<input type="text" class="form-control" id="search">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table id="batteries-journal-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ $L('Battery') }}</th>
|
||||
<th>{{ $L('Tracked time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($chargeCycles as $chargeCycleEntry)
|
||||
<tr class="@if($chargeCycleEntry->undone == 1) text-muted @endif">
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo charge cycle') }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="@if($chargeCycleEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($batteries, 'id', $chargeCycleEntry->battery_id)->name }}</span>
|
||||
@if($chargeCycleEntry->undone == 1)
|
||||
<br>
|
||||
{{ $L('Undone on') . ' ' . $chargeCycleEntry->undone_timestamp }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $chargeCycleEntry->undone_timestamp }}"></time>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
{{ $chargeCycleEntry->tracked_time }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $chargeCycleEntry->tracked_time }}"></time>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
@ -11,7 +11,11 @@
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@yield('title')</h1>
|
||||
<h1>@yield('title')
|
||||
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/batteriesjournal') }}">
|
||||
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
|
||||
</a>
|
||||
</h1>
|
||||
<p id="info-due-batteries" 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-batteries" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button"></p>
|
||||
</div>
|
||||
@ -53,6 +57,9 @@
|
||||
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}">
|
||||
<i class="fas fa-fire"></i>
|
||||
</a>
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/batteriesjournal?battery=') }}{{ $curentBatteryEntry->battery_id }}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}
|
||||
|
@ -1,8 +1,8 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('title', $L('Chores analysis'))
|
||||
@section('activeNav', 'choresanalysis')
|
||||
@section('viewJsName', 'choresanalysis')
|
||||
@section('title', $L('Chores journal'))
|
||||
@section('activeNav', 'choresjournal')
|
||||
@section('viewJsName', 'choresjournal')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="row my-3">
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="chore-filter">{{ $L('Filter by chore') }}</label> <i class="fas fa-filter"></i>
|
||||
<select class="form-control" id="chore-filter">
|
||||
@ -29,9 +29,10 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table id="chores-analysis-table" class="table table-sm table-striped dt-responsive">
|
||||
<table id="chores-journal-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ $L('Chore') }}</th>
|
||||
<th>{{ $L('Tracked time') }}</th>
|
||||
<th>{{ $L('Done by') }}</th>
|
||||
@ -39,9 +40,19 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($choresLog as $choreLogEntry)
|
||||
<tr>
|
||||
<tr class="@if($choreLogEntry->undone == 1) text-muted @endif">
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-secondary btn-sm undo-chore-execution-button @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo chore execution') }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}
|
||||
<span class="@if($choreLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}</span>
|
||||
@if($choreLogEntry->undone == 1)
|
||||
<br>
|
||||
{{ $L('Undone on') . ' ' . $choreLogEntry->undone_timestamp }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $choreLogEntry->undone_timestamp }}"></time>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
{{ $choreLogEntry->tracked_time }}
|
@ -11,7 +11,11 @@
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@yield('title')</h1>
|
||||
<h1>@yield('title')
|
||||
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/choresjournal') }}">
|
||||
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
|
||||
</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>
|
||||
</div>
|
||||
@ -53,8 +57,8 @@
|
||||
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
|
||||
<i class="fas fa-play"></i>
|
||||
</a>
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/choresanalysis?chore=') }}{{ $curentChoreEntry->chore_id }}">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/choresjournal?chore=') }}{{ $curentChoreEntry->chore_id }}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
74
views/stockjournal.blade.php
Normal file
74
views/stockjournal.blade.php
Normal file
@ -0,0 +1,74 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('title', $L('Stock journal'))
|
||||
@section('activeNav', 'stockjournal')
|
||||
@section('viewJsName', 'stockjournal')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@yield('title')</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-3">
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="product-filter">{{ $L('Filter by product') }}</label> <i class="fas fa-filter"></i>
|
||||
<select class="form-control" id="product-filter">
|
||||
<option value="all">{{ $L('All') }}</option>
|
||||
@foreach($products as $product)
|
||||
<option value="{{ $product->id }}">{{ $product->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
|
||||
<input type="text" class="form-control" id="search">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table id="stock-journal-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ $L('Product') }}</th>
|
||||
<th>{{ $L('Amount') }}</th>
|
||||
<th>{{ $L('Booking time') }}</th>
|
||||
<th>{{ $L('Booking type') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($stockLog as $stockLogEntry)
|
||||
<tr class="@if($stockLogEntry->undone == 1) text-muted @endif">
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-secondary btn-sm undo-stock-booking-button @if($stockLogEntry->undone == 1) disabled @endif" href="#" data-booking-id="{{ $stockLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo booking') }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="@if($stockLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->name }}</span>
|
||||
@if($stockLogEntry->undone == 1)
|
||||
<br>
|
||||
{{ $L('Undone on') . ' ' . $stockLogEntry->undone_timestamp }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $stockLogEntry->undone_timestamp }}"></time>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
{{ $stockLogEntry->amount }} {{ Pluralize($stockLogEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name_plural) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $stockLogEntry->row_created_timestamp }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $stockLogEntry->row_created_timestamp }}"></time>
|
||||
</td>
|
||||
<td>
|
||||
{{ $L($stockLogEntry->transaction_type) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
@ -19,7 +19,12 @@
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@yield('title') <small id="info-current-stock" class="text-muted"></small></h1>
|
||||
<h1>@yield('title')
|
||||
<small id="info-current-stock" class="text-muted"></small>
|
||||
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/stockjournal') }}">
|
||||
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
|
||||
</a>
|
||||
</h1>
|
||||
<p id="info-expiring-products" data-next-x-days="{{ $nextXDays }}" data-status-filter="expiring" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
|
||||
<p id="info-expired-products" data-status-filter="expired" class="btn btn-lg btn-danger status-filter-button responsive-button mr-2"></p>
|
||||
<p id="info-missing-products" data-status-filter="belowminstockamount" class="btn btn-lg btn-info status-filter-button responsive-button"></p>
|
||||
@ -92,6 +97,9 @@
|
||||
data-consume-amount="{{ $currentStockEntry->amount }}">
|
||||
<i class="fas fa-utensils"></i> {{ $L('All') }}
|
||||
</a>
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/stockjournal?product=') }}{{ $currentStockEntry->product_id }}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="product-name-cell" data-product-id="{{ $currentStockEntry->product_id }}">
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }} <i class="fas fa-info text-muted"></i>
|
||||
|
Loading…
x
Reference in New Issue
Block a user