mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
New cron job for bills.
This commit is contained in:
parent
e5a08d2cf1
commit
f2849c8058
@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use Illuminate\Console\Command;
|
||||
use InvalidArgumentException;
|
||||
use Log;
|
||||
@ -66,7 +67,7 @@ class Cron extends Command
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->error(sprintf('"%s" is not a valid date', $this->option('date')));
|
||||
}
|
||||
$force = (bool)$this->option('force');
|
||||
$force = (bool) $this->option('force');
|
||||
|
||||
/*
|
||||
* Fire recurring transaction cron job.
|
||||
@ -90,6 +91,17 @@ class Cron extends Command
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
/*
|
||||
* Fire bill warning cron job
|
||||
*/
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->info('More feedback on the cron jobs can be found in the log files.');
|
||||
|
||||
return 0;
|
||||
@ -150,4 +162,32 @@ class Cron extends Command
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
*
|
||||
*/
|
||||
private function billWarningCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$autoBudget = new BillWarningCronjob;
|
||||
$autoBudget->setForce($force);
|
||||
// set date in cron job:
|
||||
if (null !== $date) {
|
||||
$autoBudget->setDate($date);
|
||||
}
|
||||
|
||||
$autoBudget->fire();
|
||||
|
||||
if ($autoBudget->jobErrored) {
|
||||
$this->error(sprintf('Error in "bill warnings" cron: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobFired) {
|
||||
$this->error(sprintf('"Send bill warnings" cron fired: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobSucceeded) {
|
||||
$this->error(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
54
app/Events/WarnUserAboutBill.php
Normal file
54
app/Events/WarnUserAboutBill.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DestroyedTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public Bill $bill;
|
||||
public string $field;
|
||||
public int $diff;
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param string $field
|
||||
* @param int $diff
|
||||
*/
|
||||
public function __construct(Bill $bill, string $field, int $diff)
|
||||
{
|
||||
$this->bill = $bill;
|
||||
$this->field = $field;
|
||||
$this->diff = $diff;
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ class BillFactory
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__), $data);
|
||||
$factory = app(TransactionCurrencyFactory::class);
|
||||
$currency = $factory->find((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)) ??
|
||||
$currency = $factory->find((int) ($data['currency_id'] ?? null), (string) ($data['currency_code'] ?? null)) ??
|
||||
app('amount')->getDefaultCurrencyByUser($this->user);
|
||||
|
||||
try {
|
||||
@ -82,7 +82,7 @@ class BillFactory
|
||||
}
|
||||
|
||||
if (array_key_exists('notes', $data)) {
|
||||
$this->updateNote($bill, (string)$data['notes']);
|
||||
$this->updateNote($bill, (string) $data['notes']);
|
||||
}
|
||||
$objectGroupTitle = $data['object_group_title'] ?? '';
|
||||
if ('' !== $objectGroupTitle) {
|
||||
@ -93,7 +93,7 @@ class BillFactory
|
||||
}
|
||||
}
|
||||
// try also with ID:
|
||||
$objectGroupId = (int)($data['object_group_id'] ?? 0);
|
||||
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
|
||||
if (0 !== $objectGroupId) {
|
||||
$objectGroup = $this->findObjectGroupById($objectGroupId);
|
||||
if (null !== $objectGroup) {
|
||||
@ -113,8 +113,8 @@ class BillFactory
|
||||
*/
|
||||
public function find(?int $billId, ?string $billName): ?Bill
|
||||
{
|
||||
$billId = (int)$billId;
|
||||
$billName = (string)$billName;
|
||||
$billId = (int) $billId;
|
||||
$billName = (string) $billName;
|
||||
$bill = null;
|
||||
// first find by ID:
|
||||
if ($billId > 0) {
|
||||
|
41
app/Handlers/Events/BillEventHandler.php
Normal file
41
app/Handlers/Events/BillEventHandler.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Mail\BillWarningMail;
|
||||
use Log;
|
||||
use Mail;
|
||||
|
||||
/**
|
||||
* Class BillEventHandler
|
||||
*/
|
||||
class BillEventHandler
|
||||
{
|
||||
/**
|
||||
* @param WarnUserAboutBill $event
|
||||
* @return void
|
||||
*/
|
||||
public function warnAboutBill(WarnUserAboutBill $event): void
|
||||
{
|
||||
$bill = $event->bill;
|
||||
$field = $event->field;
|
||||
$diff = $event->diff;
|
||||
$user = $bill->user;
|
||||
$address = $user->email;
|
||||
$ipAddress = request()?->ip();
|
||||
|
||||
// see if user has alternative email address:
|
||||
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
|
||||
if (null !== $pref) {
|
||||
$address = $pref->data;
|
||||
}
|
||||
|
||||
// send message:
|
||||
Mail::to($address)->send(new BillWarningMail($bill, $field, $diff, $ipAddress));
|
||||
|
||||
|
||||
Log::debug('warnAboutBill');
|
||||
}
|
||||
|
||||
}
|
@ -102,6 +102,7 @@ class CreateController extends Controller
|
||||
public function store(BillStoreRequest $request): RedirectResponse
|
||||
{
|
||||
$billData = $request->getBillData();
|
||||
|
||||
$billData['active'] = true;
|
||||
try {
|
||||
$bill = $this->repository->store($billData);
|
||||
|
@ -53,7 +53,7 @@ class EditController extends Controller
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', (string)trans('firefly.bills'));
|
||||
app('view')->share('title', (string) trans('firefly.bills'));
|
||||
app('view')->share('mainTitleIcon', 'fa-calendar-o');
|
||||
$this->attachments = app(AttachmentHelperInterface::class);
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
@ -78,10 +78,10 @@ class EditController extends Controller
|
||||
$billPeriods = config('firefly.bill_periods');
|
||||
|
||||
foreach ($billPeriods as $current) {
|
||||
$periods[$current] = (string)trans('firefly.' . $current);
|
||||
$periods[$current] = (string) trans('firefly.' . $current);
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.edit_bill', ['name' => $bill->name]);
|
||||
$subTitle = (string) trans('firefly.edit_bill', ['name' => $bill->name]);
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (true !== session('bills.edit.fromUpdate')) {
|
||||
@ -89,8 +89,8 @@ class EditController extends Controller
|
||||
}
|
||||
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
$bill->amount_min = round((float)$bill->amount_min, $currency->decimal_places);
|
||||
$bill->amount_max = round((float)$bill->amount_max, $currency->decimal_places);
|
||||
$bill->amount_min = round((float) $bill->amount_min, $currency->decimal_places);
|
||||
$bill->amount_max = round((float) $bill->amount_max, $currency->decimal_places);
|
||||
$rules = $this->repository->getRulesForBill($bill);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
|
||||
@ -98,9 +98,11 @@ class EditController extends Controller
|
||||
$hasOldInput = null !== $request->old('_token');
|
||||
|
||||
$preFilled = [
|
||||
'bill_end_date' => $bill->end_date,
|
||||
'extension_date' => $bill->extension_date,
|
||||
'notes' => $this->repository->getNoteText($bill),
|
||||
'transaction_currency_id' => $bill->transaction_currency_id,
|
||||
'active' => $hasOldInput ? (bool)$request->old('active') : $bill->active,
|
||||
'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active,
|
||||
'object_group' => $bill->objectGroups->first() ? $bill->objectGroups->first()->title : '',
|
||||
];
|
||||
|
||||
@ -123,7 +125,7 @@ class EditController extends Controller
|
||||
$billData = $request->getBillData();
|
||||
$bill = $this->repository->update($bill, $billData);
|
||||
|
||||
$request->session()->flash('success', (string)trans('firefly.updated_bill', ['name' => $bill->name]));
|
||||
$request->session()->flash('success', (string) trans('firefly.updated_bill', ['name' => $bill->name]));
|
||||
app('preferences')->mark();
|
||||
|
||||
/** @var array $files */
|
||||
@ -132,7 +134,7 @@ class EditController extends Controller
|
||||
$this->attachments->saveAttachmentsForModel($bill, $files);
|
||||
}
|
||||
if (null !== $files && auth()->user()->hasRole('demo')) {
|
||||
session()->flash('info', (string)trans('firefly.no_att_demo_user'));
|
||||
session()->flash('info', (string) trans('firefly.no_att_demo_user'));
|
||||
}
|
||||
|
||||
// flash messages
|
||||
@ -141,7 +143,7 @@ class EditController extends Controller
|
||||
}
|
||||
$redirect = redirect($this->getPreviousUri('bills.edit.uri'));
|
||||
|
||||
if (1 === (int)$request->get('return_to_edit')) {
|
||||
if (1 === (int) $request->get('return_to_edit')) {
|
||||
|
||||
$request->session()->put('bills.edit.fromUpdate', true);
|
||||
|
||||
|
@ -136,8 +136,9 @@ class IndexController extends Controller
|
||||
// summarise per currency / per group.
|
||||
$sums = $this->getSums($bills);
|
||||
$totals = $this->getTotals($sums);
|
||||
$today = now()->startOfDay();
|
||||
|
||||
return view('bills.index', compact('bills', 'sums', 'total', 'totals'));
|
||||
return view('bills.index', compact('bills', 'sums', 'total', 'totals','today'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,8 @@ class BillStoreRequest extends FormRequest
|
||||
'currency_code' => '',
|
||||
'amount_max' => $this->string('amount_max'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'end_date' => $this->getCarbonDate('bill_end_date'),
|
||||
'extension_date' => $this->getCarbonDate('extension_date'),
|
||||
'repeat_freq' => $this->string('repeat_freq'),
|
||||
'skip' => $this->integer('skip'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
@ -68,6 +70,8 @@ class BillStoreRequest extends FormRequest
|
||||
'amount_max' => 'required|numeric|gt:0|max:1000000000',
|
||||
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'bill_end_date' => 'nullable|date',
|
||||
'extension_date' => 'nullable|date',
|
||||
'repeat_freq' => sprintf('required|in:%s', join(',', config('firefly.bill_periods'))),
|
||||
'skip' => 'required|integer|gte:0|lte:31',
|
||||
'active' => 'boolean',
|
||||
|
@ -48,6 +48,8 @@ class BillUpdateRequest extends FormRequest
|
||||
'currency_code' => '',
|
||||
'amount_max' => $this->string('amount_max'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'end_date' => $this->getCarbonDate('bill_end_date'),
|
||||
'extension_date' => $this->getCarbonDate('extension_date'),
|
||||
'repeat_freq' => $this->string('repeat_freq'),
|
||||
'skip' => $this->integer('skip'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
@ -72,6 +74,8 @@ class BillUpdateRequest extends FormRequest
|
||||
'amount_max' => 'required|numeric|gt:0|max:1000000000',
|
||||
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'bill_end_date' => 'nullable|date',
|
||||
'extension_date' => 'nullable|date',
|
||||
'repeat_freq' => sprintf('required|in:%s', join(',', config('firefly.bill_periods'))),
|
||||
'skip' => 'required|integer|gte:0|lte:31',
|
||||
'active' => 'boolean',
|
||||
|
@ -130,7 +130,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
// find budget limit:
|
||||
$budgetLimit = $this->findBudgetLimit($autoBudget->budget, $start, $end);
|
||||
|
||||
if (null === $budgetLimit && AutoBudget::AUTO_BUDGET_RESET === (int)$autoBudget->auto_budget_type) {
|
||||
if (null === $budgetLimit && AutoBudget::AUTO_BUDGET_RESET === (int) $autoBudget->auto_budget_type) {
|
||||
// that's easy: create one.
|
||||
// do nothing else.
|
||||
$this->createBudgetLimit($autoBudget, $start, $end);
|
||||
@ -139,7 +139,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $budgetLimit && AutoBudget::AUTO_BUDGET_ROLLOVER === (int)$autoBudget->auto_budget_type) {
|
||||
if (null === $budgetLimit && AutoBudget::AUTO_BUDGET_ROLLOVER === (int) $autoBudget->auto_budget_type) {
|
||||
// budget limit exists already,
|
||||
$this->createRollover($autoBudget);
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
@ -277,7 +277,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
$repository->setUser($autoBudget->budget->user);
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency);
|
||||
$currencyId = (int)$autoBudget->transaction_currency_id;
|
||||
$currencyId = (int) $autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
|
||||
|
@ -430,7 +430,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
'type' => strtolower($recurrence->transactionType->type),
|
||||
'date' => $date,
|
||||
'user' => $recurrence->user_id,
|
||||
'currency_id' => (int)$transaction->transaction_currency_id,
|
||||
'currency_id' => (int) $transaction->transaction_currency_id,
|
||||
'currency_code' => null,
|
||||
'description' => $transactions->first()->description,
|
||||
'amount' => $transaction->amount,
|
||||
@ -447,9 +447,9 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
'foreign_amount' => $transaction->foreign_amount,
|
||||
'reconciled' => false,
|
||||
'identifier' => $index,
|
||||
'recurrence_id' => (int)$recurrence->id,
|
||||
'recurrence_id' => (int) $recurrence->id,
|
||||
'order' => $index,
|
||||
'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]),
|
||||
'notes' => (string) trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]),
|
||||
'tags' => $this->repository->getTags($transaction),
|
||||
'piggy_bank_id' => $this->repository->getPiggyBank($transaction),
|
||||
'piggy_bank_name' => null,
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Log;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use FireflyIII\Services\Webhook\WebhookSenderInterface;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -32,6 +31,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class SendWebhookMessage
|
||||
|
153
app/Jobs/WarnAboutBills.php
Normal file
153
app/Jobs/WarnAboutBills.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\RequestedReportOnJournals;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class WarnAboutBills
|
||||
*/
|
||||
class WarnAboutBills implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private Carbon $date;
|
||||
private bool $force;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param Carbon|null $date
|
||||
*/
|
||||
public function __construct(?Carbon $date)
|
||||
{
|
||||
if (null !== $date) {
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
if (null === $date) {
|
||||
$newDate = new Carbon;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
$this->force = false;
|
||||
|
||||
Log::debug(sprintf('Created new WarnAboutBills("%s")', $this->date->format('Y-m-d')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Log::debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
|
||||
$bills = Bill::all();
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
|
||||
if ($this->hasDateFields($bill)) {
|
||||
if ($this->needsWarning($bill, 'end_date')) {
|
||||
$this->sendWarning($bill, 'end_date');
|
||||
}
|
||||
if ($this->needsWarning($bill, 'extension_date')) {
|
||||
$this->sendWarning($bill, 'extension_date');
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug('Done with handle()');
|
||||
|
||||
// clear cache:
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @return bool
|
||||
*/
|
||||
private function hasDateFields(Bill $bill): bool
|
||||
{
|
||||
if (false === $bill->active) {
|
||||
Log::debug('Bill is not active.');
|
||||
return false;
|
||||
}
|
||||
if (null === $bill->end_date && null === $bill->extension_date) {
|
||||
Log::debug('Bill has no date fields.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param string $field
|
||||
* @return bool
|
||||
*/
|
||||
private function needsWarning(Bill $bill, string $field): bool
|
||||
{
|
||||
if (null === $bill->$field) {
|
||||
return false;
|
||||
}
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
$list = config('firefly.bill_reminder_periods');
|
||||
Log::debug(sprintf('Difference in days for field "%s" ("%s") is %d day(s)', $field, $bill->$field->format('Y-m-d'), $diff));
|
||||
if (in_array($diff, $list, true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param string $field
|
||||
* @return int
|
||||
*/
|
||||
private function getDiff(Bill $bill, string $field): int
|
||||
{
|
||||
$today = clone $this->date;
|
||||
$carbon = clone $bill->$field;
|
||||
return $today->diffInDays($carbon, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Bill $bill
|
||||
* @param string $field
|
||||
* @return void
|
||||
*/
|
||||
private function sendWarning(Bill $bill, string $field): void
|
||||
{
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
Log::debug('Will now send warning!');
|
||||
event(new WarnUserAboutBill($bill, $field, $diff));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $date
|
||||
*/
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
*/
|
||||
public function setForce(bool $force): void
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
}
|
@ -37,10 +37,8 @@ class AdminTestMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/** @var string Email address of admin */
|
||||
public $email;
|
||||
/** @var string IP address of admin */
|
||||
public $ipAddress;
|
||||
public string $email;
|
||||
public string $ipAddress;
|
||||
|
||||
/**
|
||||
* ConfirmEmailChangeMail constructor.
|
||||
|
52
app/Mail/BillWarningMail.php
Normal file
52
app/Mail/BillWarningMail.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Mail;
|
||||
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BillWarningMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public Bill $bill;
|
||||
public string $field;
|
||||
public int $diff;
|
||||
public string $ipAddress;
|
||||
|
||||
/**
|
||||
* ConfirmEmailChangeMail constructor.
|
||||
*
|
||||
* @param Bill $bill
|
||||
* @param string $field
|
||||
* @param int $diff
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(Bill $bill, string $field, int $diff, string $ipAddress)
|
||||
{
|
||||
$this->bill = $bill;
|
||||
$this->field = $field;
|
||||
$this->diff = $diff;
|
||||
$this->ipAddress = $ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build(): self
|
||||
{
|
||||
$subject = (string) trans(sprintf('email.bill_warning_subject_%s', $this->field), ['diff' => $this->diff, 'name' => $this->bill->name]);
|
||||
if (0 === $this->diff) {
|
||||
$subject = (string) trans(sprintf('email.bill_warning_subject_now_%s', $this->field), ['diff' => $this->diff, 'name' => $this->bill->name]);
|
||||
}
|
||||
|
||||
return $this
|
||||
->view('emails.bill-warning-html')
|
||||
->text('emails.bill-warning-text')
|
||||
->subject($subject);
|
||||
}
|
||||
}
|
@ -37,14 +37,10 @@ class ConfirmEmailChangeMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/** @var string IP address of user */
|
||||
public $ipAddress;
|
||||
/** @var string New email address */
|
||||
public $newEmail;
|
||||
/** @var string Old email address */
|
||||
public $oldEmail;
|
||||
/** @var string Confirmation link */
|
||||
public $uri;
|
||||
public string $ipAddress;
|
||||
public string $newEmail;
|
||||
public string $oldEmail;
|
||||
public string $uri;
|
||||
|
||||
/**
|
||||
* ConfirmEmailChangeMail constructor.
|
||||
@ -70,6 +66,6 @@ class ConfirmEmailChangeMail extends Mailable
|
||||
public function build(): self
|
||||
{
|
||||
return $this->view('emails.confirm-email-change-html')->text('emails.confirm-email-change-text')
|
||||
->subject((string)trans('email.email_change_subject'));
|
||||
->subject((string) trans('email.email_change_subject'));
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ use FireflyIII\Events\StoredTransactionGroup;
|
||||
use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Mail\OAuthTokenCreatedMail;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
@ -134,6 +135,11 @@ class EventServiceProvider extends ServiceProvider
|
||||
UpdatedAccount::class => [
|
||||
'FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit',
|
||||
],
|
||||
|
||||
// bill related events:
|
||||
WarnUserAboutBill::class => [
|
||||
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -166,6 +166,12 @@ class BillUpdateService
|
||||
if (array_key_exists('active', $data)) {
|
||||
$bill->active = $data['active'];
|
||||
}
|
||||
if(array_key_exists('end_date', $data)) {
|
||||
$bill->end_date = $data['end_date'];
|
||||
}
|
||||
if(array_key_exists('extension_date', $data)) {
|
||||
$bill->extension_date = $data['extension_date'];
|
||||
}
|
||||
|
||||
$bill->match = 'EMPTY';
|
||||
$bill->automatch = true;
|
||||
|
102
app/Support/Cronjobs/BillWarningCronjob.php
Normal file
102
app/Support/Cronjobs/BillWarningCronjob.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* RecurringCronjob.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Cronjobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Jobs\WarnAboutBills;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class BillWarningCronjob
|
||||
*/
|
||||
class BillWarningCronjob extends AbstractCronjob
|
||||
{
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function fire(): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_bw_job', 0);
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = time() - $lastTime;
|
||||
$diffForHumans = Carbon::now()->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
if (0 === $lastTime) {
|
||||
Log::info('The bill warning cron-job has never fired before.');
|
||||
}
|
||||
// less than half a day ago:
|
||||
if ($lastTime > 0 && $diff <= 43200) {
|
||||
Log::info(sprintf('It has been %s since the bill warning cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
Log::info('The cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the bill warning cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// fire job regardless.
|
||||
if (true === $this->force) {
|
||||
Log::info('Execution of the bill warning cron-job has been FORCED.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 43200) {
|
||||
Log::info(sprintf('It has been %s since the bill warning cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireWarnings();
|
||||
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function fireWarnings(): void
|
||||
{
|
||||
Log::info(sprintf('Will now fire bill warning job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
/** @var WarnAboutBills $job */
|
||||
$job = app(WarnAboutBills::class);
|
||||
$job->setDate($this->date);
|
||||
$job->setForce($this->force);
|
||||
$job->handle();
|
||||
|
||||
// get stuff from job:
|
||||
$this->jobFired = true;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Bill warning cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_bw_job', (int)$this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
|
||||
Log::info('Done with bill warning cron job task.');
|
||||
}
|
||||
}
|
@ -68,6 +68,7 @@ class General extends AbstractExtension
|
||||
$this->getMetaField(),
|
||||
$this->hasRole(),
|
||||
$this->getRootSearchOperator(),
|
||||
$this->carbonize()
|
||||
];
|
||||
}
|
||||
|
||||
@ -395,6 +396,19 @@ class General extends AbstractExtension
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigFunction
|
||||
*/
|
||||
protected function carbonize(): TwigFunction
|
||||
{
|
||||
return new TwigFunction(
|
||||
'carbonize',
|
||||
static function (string $date): Carbon {
|
||||
return new Carbon($date);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return true if the user is of role X.
|
||||
*
|
||||
|
@ -221,6 +221,7 @@ return [
|
||||
TransactionJournal::class,
|
||||
Recurrence::class,
|
||||
],
|
||||
'bill_reminder_periods' => [90, 30, 14, 7, 0],
|
||||
'valid_view_ranges' => ['1D', '1W', '1M', '3M', '6M', '1Y',],
|
||||
'allowedMimes' => [
|
||||
/* plain files */
|
||||
@ -485,7 +486,7 @@ return [
|
||||
'convert_transfer',
|
||||
],
|
||||
|
||||
'test-triggers' => [
|
||||
'test-triggers' => [
|
||||
'limit' => 10,
|
||||
'range' => 200,
|
||||
],
|
||||
@ -708,7 +709,7 @@ return [
|
||||
'import_hash', 'import_hash_v2', 'external_id', 'original_source',
|
||||
|
||||
// recurring transactions
|
||||
'recurrence_total', 'recurrence_count'
|
||||
'recurrence_total', 'recurrence_count',
|
||||
],
|
||||
'webhooks' => [
|
||||
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
|
||||
|
@ -24,82 +24,97 @@ declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// common items
|
||||
'greeting' => 'Hi there,',
|
||||
'closing' => 'Beep boop,',
|
||||
'signature' => 'The Firefly III Mail Robot',
|
||||
'footer_ps' => 'PS: This message was sent because a request from IP :ipAddress triggered it.',
|
||||
'greeting' => 'Hi there,',
|
||||
'closing' => 'Beep boop,',
|
||||
'signature' => 'The Firefly III Mail Robot',
|
||||
'footer_ps' => 'PS: This message was sent because a request from IP :ipAddress triggered it.',
|
||||
|
||||
// admin test
|
||||
'admin_test_subject' => 'A test message from your Firefly III installation',
|
||||
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
||||
'admin_test_subject' => 'A test message from your Firefly III installation',
|
||||
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
||||
|
||||
// new IP
|
||||
'login_from_new_ip' => 'New login on Firefly III',
|
||||
'new_ip_body' => 'Firefly III detected a new login on your account from an unknown IP address. If you never logged in from the IP address below, or it has been more than six months ago, Firefly III will warn you.',
|
||||
'new_ip_warning' => 'If you recognize this IP address or the login, you can ignore this message. If you didn\'t login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!',
|
||||
'ip_address' => 'IP address',
|
||||
'host_name' => 'Host',
|
||||
'date_time' => 'Date + time',
|
||||
'login_from_new_ip' => 'New login on Firefly III',
|
||||
'new_ip_body' => 'Firefly III detected a new login on your account from an unknown IP address. If you never logged in from the IP address below, or it has been more than six months ago, Firefly III will warn you.',
|
||||
'new_ip_warning' => 'If you recognize this IP address or the login, you can ignore this message. If you didn\'t login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!',
|
||||
'ip_address' => 'IP address',
|
||||
'host_name' => 'Host',
|
||||
'date_time' => 'Date + time',
|
||||
|
||||
// access token created
|
||||
'access_token_created_subject' => 'A new access token was created',
|
||||
'access_token_created_body' => 'Somebody (hopefully you) just created a new Firefly III API Access Token for your user account.',
|
||||
'access_token_created_explanation' => 'With this token, they can access <strong>all</strong> of your financial records through the Firefly III API.',
|
||||
'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url.',
|
||||
'access_token_created_subject' => 'A new access token was created',
|
||||
'access_token_created_body' => 'Somebody (hopefully you) just created a new Firefly III API Access Token for your user account.',
|
||||
'access_token_created_explanation' => 'With this token, they can access <strong>all</strong> of your financial records through the Firefly III API.',
|
||||
'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url.',
|
||||
|
||||
// registered
|
||||
'registered_subject' => 'Welcome to Firefly III!',
|
||||
'registered_welcome' => 'Welcome to <a style="color:#337ab7" href=":address">Firefly III</a>. Your registration has made it, and this email is here to confirm it. Yay!',
|
||||
'registered_pw' => 'If you have forgotten your password already, please reset it using <a style="color:#337ab7" href=":address/password/reset">the password reset tool</a>.',
|
||||
'registered_help' => 'There is a help-icon in the top right corner of each page. If you need help, click it!',
|
||||
'registered_doc_html' => 'If you haven\'t already, please read the <a style="color:#337ab7" href="https://docs.firefly-iii.org/about-firefly-iii/personal-finances">grand theory</a>.',
|
||||
'registered_doc_text' => 'If you haven\'t already, please read the first use guide and the full description.',
|
||||
'registered_closing' => 'Enjoy!',
|
||||
'registered_firefly_iii_link' => 'Firefly III:',
|
||||
'registered_pw_reset_link' => 'Password reset:',
|
||||
'registered_doc_link' => 'Documentation:',
|
||||
'registered_subject' => 'Welcome to Firefly III!',
|
||||
'registered_welcome' => 'Welcome to <a style="color:#337ab7" href=":address">Firefly III</a>. Your registration has made it, and this email is here to confirm it. Yay!',
|
||||
'registered_pw' => 'If you have forgotten your password already, please reset it using <a style="color:#337ab7" href=":address/password/reset">the password reset tool</a>.',
|
||||
'registered_help' => 'There is a help-icon in the top right corner of each page. If you need help, click it!',
|
||||
'registered_doc_html' => 'If you haven\'t already, please read the <a style="color:#337ab7" href="https://docs.firefly-iii.org/about-firefly-iii/personal-finances">grand theory</a>.',
|
||||
'registered_doc_text' => 'If you haven\'t already, please read the first use guide and the full description.',
|
||||
'registered_closing' => 'Enjoy!',
|
||||
'registered_firefly_iii_link' => 'Firefly III:',
|
||||
'registered_pw_reset_link' => 'Password reset:',
|
||||
'registered_doc_link' => 'Documentation:',
|
||||
|
||||
// email change
|
||||
'email_change_subject' => 'Your Firefly III email address has changed',
|
||||
'email_change_body_to_new' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this message, please ignore and delete it.',
|
||||
'email_change_body_to_old' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this to happen, you <strong>must</strong> follow the "undo"-link below to protect your account!',
|
||||
'email_change_ignore' => 'If you initiated this change, you may safely ignore this message.',
|
||||
'email_change_old' => 'The old email address was: :email',
|
||||
'email_change_old_strong' => 'The old email address was: <strong>:email</strong>',
|
||||
'email_change_new' => 'The new email address is: :email',
|
||||
'email_change_new_strong' => 'The new email address is: <strong>:email</strong>',
|
||||
'email_change_instructions' => 'You cannot use Firefly III until you confirm this change. Please follow the link below to do so.',
|
||||
'email_change_undo_link' => 'To undo the change, follow this link:',
|
||||
'email_change_subject' => 'Your Firefly III email address has changed',
|
||||
'email_change_body_to_new' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this message, please ignore and delete it.',
|
||||
'email_change_body_to_old' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this to happen, you <strong>must</strong> follow the "undo"-link below to protect your account!',
|
||||
'email_change_ignore' => 'If you initiated this change, you may safely ignore this message.',
|
||||
'email_change_old' => 'The old email address was: :email',
|
||||
'email_change_old_strong' => 'The old email address was: <strong>:email</strong>',
|
||||
'email_change_new' => 'The new email address is: :email',
|
||||
'email_change_new_strong' => 'The new email address is: <strong>:email</strong>',
|
||||
'email_change_instructions' => 'You cannot use Firefly III until you confirm this change. Please follow the link below to do so.',
|
||||
'email_change_undo_link' => 'To undo the change, follow this link:',
|
||||
|
||||
// OAuth token created
|
||||
'oauth_created_subject' => 'A new OAuth client has been created',
|
||||
'oauth_created_body' => 'Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It\'s labeled ":name" and has callback URL <span style="font-family: monospace;">:url</span>.',
|
||||
'oauth_created_explanation' => 'With this client, they can access <strong>all</strong> of your financial records through the Firefly III API.',
|
||||
'oauth_created_undo' => 'If this wasn\'t you, please revoke this client as soon as possible at :url.',
|
||||
'oauth_created_subject' => 'A new OAuth client has been created',
|
||||
'oauth_created_body' => 'Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It\'s labeled ":name" and has callback URL <span style="font-family: monospace;">:url</span>.',
|
||||
'oauth_created_explanation' => 'With this client, they can access <strong>all</strong> of your financial records through the Firefly III API.',
|
||||
'oauth_created_undo' => 'If this wasn\'t you, please revoke this client as soon as possible at :url.',
|
||||
|
||||
// reset password
|
||||
'reset_pw_subject' => 'Your password reset request',
|
||||
'reset_pw_instructions' => 'Somebody tried to reset your password. If it was you, please follow the link below to do so.',
|
||||
'reset_pw_warning' => '<strong>PLEASE</strong> verify that the link actually goes to the Firefly III you expect it to go!',
|
||||
'reset_pw_subject' => 'Your password reset request',
|
||||
'reset_pw_instructions' => 'Somebody tried to reset your password. If it was you, please follow the link below to do so.',
|
||||
'reset_pw_warning' => '<strong>PLEASE</strong> verify that the link actually goes to the Firefly III you expect it to go!',
|
||||
|
||||
// error
|
||||
'error_subject' => 'Caught an error in Firefly III',
|
||||
'error_intro' => 'Firefly III v:version ran into an error: <span style="font-family: monospace;">:errorMessage</span>.',
|
||||
'error_type' => 'The error was of type ":class".',
|
||||
'error_timestamp' => 'The error occurred on/at: :time.',
|
||||
'error_location' => 'This error occurred in file "<span style="font-family: monospace;">:file</span>" on line :line with code :code.',
|
||||
'error_user' => 'The error was encountered by user #:id, <a href="mailto::email">:email</a>.',
|
||||
'error_no_user' => 'There was no user logged in for this error or no user was detected.',
|
||||
'error_ip' => 'The IP address related to this error is: :ip',
|
||||
'error_url' => 'URL is: :url',
|
||||
'error_user_agent' => 'User agent: :userAgent',
|
||||
'error_stacktrace' => 'The full stacktrace is below. If you think this is a bug in Firefly III, you can forward this message to <a href="mailto:james@firefly-iii.org?subject=BUG!">james@firefly-iii.org</a>. This can help fix the bug you just encountered.',
|
||||
'error_github_html' => 'If you prefer, you can also open a new issue on <a href="https://github.com/firefly-iii/firefly-iii/issues">GitHub</a>.',
|
||||
'error_github_text' => 'If you prefer, you can also open a new issue on https://github.com/firefly-iii/firefly-iii/issues.',
|
||||
'error_stacktrace_below' => 'The full stacktrace is below:',
|
||||
'error_headers' => 'The following headers may also be relevant:',
|
||||
'error_subject' => 'Caught an error in Firefly III',
|
||||
'error_intro' => 'Firefly III v:version ran into an error: <span style="font-family: monospace;">:errorMessage</span>.',
|
||||
'error_type' => 'The error was of type ":class".',
|
||||
'error_timestamp' => 'The error occurred on/at: :time.',
|
||||
'error_location' => 'This error occurred in file "<span style="font-family: monospace;">:file</span>" on line :line with code :code.',
|
||||
'error_user' => 'The error was encountered by user #:id, <a href="mailto::email">:email</a>.',
|
||||
'error_no_user' => 'There was no user logged in for this error or no user was detected.',
|
||||
'error_ip' => 'The IP address related to this error is: :ip',
|
||||
'error_url' => 'URL is: :url',
|
||||
'error_user_agent' => 'User agent: :userAgent',
|
||||
'error_stacktrace' => 'The full stacktrace is below. If you think this is a bug in Firefly III, you can forward this message to <a href="mailto:james@firefly-iii.org?subject=BUG!">james@firefly-iii.org</a>. This can help fix the bug you just encountered.',
|
||||
'error_github_html' => 'If you prefer, you can also open a new issue on <a href="https://github.com/firefly-iii/firefly-iii/issues">GitHub</a>.',
|
||||
'error_github_text' => 'If you prefer, you can also open a new issue on https://github.com/firefly-iii/firefly-iii/issues.',
|
||||
'error_stacktrace_below' => 'The full stacktrace is below:',
|
||||
'error_headers' => 'The following headers may also be relevant:',
|
||||
|
||||
// report new journals
|
||||
'new_journals_subject' => 'Firefly III has created a new transaction|Firefly III has created :count new transactions',
|
||||
'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:',
|
||||
'new_journals_subject' => 'Firefly III has created a new transaction|Firefly III has created :count new transactions',
|
||||
'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:',
|
||||
|
||||
// bill warning
|
||||
'bill_warning_subject_end_date' => 'Your bill ":name" is due to end in :diff days',
|
||||
'bill_warning_subject_now_end_date' => 'Your bill ":name" is due to end TODAY',
|
||||
'bill_warning_subject_extension_date' => 'Your bill ":name" is due to be extended or cancelled in :diff days',
|
||||
'bill_warning_subject_now_extension_date' => 'Your bill ":name" is due to be extended or cancelled TODAY',
|
||||
'bill_warning_end_date_text' => 'Your bill ":name" is due to end on :date. This moment will pass in about :diff days.',
|
||||
'bill_warning_extension_date_text' => 'Your bill ":name" is due to be extended or cancelled on :date. This moment will pass in about :diff days.',
|
||||
'bill_warning_end_date_text_zero' => 'Your bill ":name" is due to end on :date. This moment will pass TODAY!',
|
||||
'bill_warning_extension_date_text_zero' => 'Your bill ":name" is due to be extended or cancelled on :date. This moment will pass TODAY!',
|
||||
'bill_warning_please_action' => 'Please take the appropriate action.',
|
||||
'bill_warning_end_date_html' => 'Your bill <strong>":name"</strong> is due to end on :date. This moment will pass in about <strong>:diff days</strong>.',
|
||||
'bill_warning_extension_date_html' => 'Your bill <strong>":name"</strong> is due to be extended or cancelled on :date. This moment will pass in about <strong>:diff days</strong>.',
|
||||
'bill_warning_end_date_html_zero' => 'Your bill <strong>":name"</strong> is due to end on :date. This moment will pass <strong>TODAY!</strong>',
|
||||
'bill_warning_extension_date_html_zero' => 'Your bill <strong>":name"</strong> is due to be extended or cancelled on :date. This moment will pass <strong>TODAY!</strong>',
|
||||
];
|
||||
|
@ -1309,6 +1309,10 @@ return [
|
||||
'running_again_loss' => 'Previously linked transactions to this bill may lose their connection, if they (no longer) match the rule(s).',
|
||||
'bill_related_rules' => 'Rules related to this bill',
|
||||
'repeats' => 'Repeats',
|
||||
'bill_end_date_help' => 'Optional field. The bill is expected to end on this date.',
|
||||
'bill_extension_date_help' => 'Optional field. The bill must be extended (or cancelled) on or before this date.',
|
||||
'bill_end_index_line' => 'This bill ends on :date',
|
||||
'bill_extension_index_line' => 'This bill must be extended or cancelled on :date',
|
||||
'connected_journals' => 'Connected transactions',
|
||||
'auto_match_on' => 'Automatically matched by Firefly III',
|
||||
'auto_match_off' => 'Not automatically matched by Firefly III',
|
||||
|
@ -181,6 +181,7 @@ return [
|
||||
'login_name' => 'Login',
|
||||
'is_owner' => 'Is admin?',
|
||||
'url' => 'URL',
|
||||
'bill_end_date' => 'End date',
|
||||
|
||||
// import
|
||||
'apply_rules' => 'Apply rules',
|
||||
|
@ -22,6 +22,7 @@
|
||||
{{ ExpandedForm.amountNoCurrency('amount_max') }}
|
||||
{{ ExpandedForm.date('date',phpdate('Y-m-d')) }}
|
||||
{{ ExpandedForm.select('repeat_freq',periods,'monthly') }}
|
||||
{{ ExpandedForm.integer('skip',0) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,9 +33,12 @@
|
||||
<h3 class="box-title">{{ 'optionalFields'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.date('bill_end_date',null, {'helpText': trans('firefly.bill_end_date_help')}) }}
|
||||
{{ ExpandedForm.date('extension_date',null,{'helpText': trans('firefly.bill_extension_date_help')} ) }}
|
||||
|
||||
|
||||
{{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }}
|
||||
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
|
||||
{{ ExpandedForm.integer('skip',0) }}
|
||||
{{ ExpandedForm.objectGroup() }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,6 +27,8 @@
|
||||
{{ ExpandedForm.amountNoCurrency('amount_max') }}
|
||||
{{ ExpandedForm.date('date',bill.date.format('Y-m-d')) }}
|
||||
{{ ExpandedForm.select('repeat_freq',periods) }}
|
||||
{{ ExpandedForm.integer('skip') }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -38,9 +40,11 @@
|
||||
<h3 class="box-title">{{ 'optionalFields'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.date('bill_end_date',null, {'helpText': trans('firefly.bill_end_date_help')}) }}
|
||||
{{ ExpandedForm.date('extension_date',null,{'helpText': trans('firefly.bill_extension_date_help')} ) }}
|
||||
|
||||
{{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }}
|
||||
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
|
||||
{{ ExpandedForm.integer('skip') }}
|
||||
{{ ExpandedForm.objectGroup() }}
|
||||
{# only correct way to do active checkbox #}
|
||||
{{ ExpandedForm.checkbox('active', 1) }}
|
||||
|
25
resources/views/emails/bill-warning-html.twig
Normal file
25
resources/views/emails/bill-warning-html.twig
Normal file
@ -0,0 +1,25 @@
|
||||
{% include 'emails.header-html' %}
|
||||
|
||||
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;">
|
||||
|
||||
{% if field == 'end_date' and diff != 0 %}
|
||||
{{ trans('email.bill_warning_end_date_html', {name: bill.name|escape, date: bill.end_date.isoFormat(trans('config.month_and_day_js')), diff: diff})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'extension_date' and diff != 0 %}
|
||||
{{ trans('email.bill_warning_extension_date_html', {name: bill.name|escape, date: bill.extension_date.isoFormat(trans('config.month_and_day_js')), diff: diff})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'end_date' and diff == 0 %}
|
||||
{{ trans('email.bill_warning_end_date_html_zero', {name: bill.name|escape, date: bill.end_date.isoFormat(trans('config.month_and_day_js'))})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'extension_date' and diff == 0 %}
|
||||
{{ trans('email.bill_warning_extension_date_html_zero', {name: bill.name|escape, date: bill.end_date.isoFormat(trans('config.month_and_day_js'))})|raw }}
|
||||
{% endif %}
|
||||
|
||||
</p>
|
||||
|
||||
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;">
|
||||
Please take the appropriate action.
|
||||
</p>
|
||||
|
||||
|
||||
{% include 'emails.footer-html' %}
|
17
resources/views/emails/bill-warning-text.twig
Normal file
17
resources/views/emails/bill-warning-text.twig
Normal file
@ -0,0 +1,17 @@
|
||||
{% include 'emails.header-text' %}
|
||||
{% if field == 'end_date' and diff != 0 %}
|
||||
{{ trans('email.bill_warning_end_date_text', {name: bill.name, date: bill.end_date.isoFormat(trans('config.month_and_day_js')), diff: diff})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'extension_date' and diff != 0 %}
|
||||
{{ trans('email.bill_warning_extension_date_text', {name: bill.name|escape, date: bill.extension_date.isoFormat(trans('config.month_and_day_js')), diff: diff})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'end_date' and diff == 0 %}
|
||||
{{ trans('email.bill_warning_end_date_text_zero', {name: bill.name|escape, date: bill.end_date.isoFormat(trans('config.month_and_day_js'))})|raw }}
|
||||
{% endif %}
|
||||
{% if field == 'extension_date' and diff == 0 %}
|
||||
{{ trans('email.bill_warning_extension_date_text_zero', {name: bill.name|escape, date: bill.end_date.isoFormat(trans('config.month_and_day_js'))})|raw }}
|
||||
{% endif %}
|
||||
|
||||
{{ trans('email.bill_warning_please_action') }}
|
||||
|
||||
{% include 'emails.footer-text' %}
|
@ -16,20 +16,25 @@
|
||||
</thead>
|
||||
{% for objectGroupOrder, objectGroup in bills %}
|
||||
{% if objectGroup.bills|length > 0 %}
|
||||
<tbody class="bill-connected-list" {% if objectGroupOrder != 0 %}data-title="{{ objectGroup.object_group_title }}" {% else %}data-title=""{% endif %}>
|
||||
<tbody class="bill-connected-list"
|
||||
{% if objectGroupOrder != 0 %}data-title="{{ objectGroup.object_group_title }}"
|
||||
{% else %}data-title=""{% endif %}>
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td>
|
||||
<td class="hidden-sm hidden-xs"> </td>
|
||||
<td colspan="6"><small>{{ objectGroup.object_group_title }}</small></td>
|
||||
</tr>
|
||||
{% for entry in objectGroup.bills %}
|
||||
<tr class="bill-sortable" data-id="{{ entry.id }}" data-name="{{ entry.name }}" data-order="{{ entry.order }}" data-position="{{ loop.index0 }}">
|
||||
<tr class="bill-sortable" data-id="{{ entry.id }}" data-name="{{ entry.name }}"
|
||||
data-order="{{ entry.order }}" data-position="{{ loop.index0 }}">
|
||||
<td class="hidden-sm hidden-xs">
|
||||
<span class="fa fa-fw fa-bars bill-handle"></span>
|
||||
</td>
|
||||
<td class="hidden-sm hidden-xs">
|
||||
<div class="btn-group btn-group-xs edit_tr_buttons"><a href="{{ route('bills.edit',entry.id) }}" class="btn btn-default btn-xs"><span
|
||||
class="fa fa-fw fa-pencil"></span></a><a href="{{ route('bills.delete',entry.id) }}" class="btn btn-danger btn-xs"><span
|
||||
<div class="btn-group btn-group-xs edit_tr_buttons"><a href="{{ route('bills.edit',entry.id) }}"
|
||||
class="btn btn-default btn-xs"><span
|
||||
class="fa fa-fw fa-pencil"></span></a><a
|
||||
href="{{ route('bills.delete',entry.id) }}" class="btn btn-danger btn-xs"><span
|
||||
class="fa fa-fw fa-trash-o"></span></a></div>
|
||||
</td>
|
||||
<td>
|
||||
@ -126,6 +131,26 @@
|
||||
{% if entry.skip > 0 %}
|
||||
{{ 'skips_over'|_ }} {{ entry.skip }}
|
||||
{% endif %}
|
||||
{% if entry.end_date %}
|
||||
<br>
|
||||
{% if carbonize(entry.end_date).lte(today) %}
|
||||
<span class="text-danger">
|
||||
{{ trans('firefly.bill_end_index_line', {date: carbonize(entry.end_date).isoFormat(monthAndDayFormat) }) }}
|
||||
</span>
|
||||
{% else %}
|
||||
{{ trans('firefly.bill_end_index_line', {date: carbonize(entry.end_date).isoFormat(monthAndDayFormat) }) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if entry.extension_date %}
|
||||
<br>
|
||||
{% if carbonize(entry.extension_date).lte(today) %}
|
||||
<span class="text-danger">
|
||||
{{ trans('firefly.bill_extension_index_line', {date: carbonize(entry.extension_date).isoFormat(monthAndDayFormat) }) }}
|
||||
</span>
|
||||
{% else %}
|
||||
{{ trans('firefly.bill_extension_index_line', {date: carbonize(entry.extension_date).isoFormat(monthAndDayFormat) }) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -150,7 +175,8 @@
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ ('per_period_sum_'~sum.period)|_ }} ({{ sum.currency_name }}) ({{ 'active_bills_only'|_ }})</small>
|
||||
<small>{{ ('per_period_sum_'~sum.period)|_ }} ({{ sum.currency_name }})
|
||||
({{ 'active_bills_only'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.per_period, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
@ -165,43 +191,44 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if totals|length > 0 %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="8" style="border-top:1px #777 solid;"></td>
|
||||
</tr>
|
||||
{% for sum in totals %}
|
||||
{% if '0' != sum.avg %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ 'sum'|_ }} ({{ sum.currency_name }}) ({{ 'active_exp_bills_only_total'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.avg, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
</td>
|
||||
<td> </td> <!-- paid in period -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- next expected match -->
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if '0' != sum.per_period %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ ('per_period_sum_'~sum.period)|_ }} ({{ sum.currency_name }}) ({{ 'active_bills_only_total'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.per_period, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
</td>
|
||||
<td> </td> <!-- paid in period -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- next expected match -->
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tfoot>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="8" style="border-top:1px #777 solid;"></td>
|
||||
</tr>
|
||||
{% for sum in totals %}
|
||||
{% if '0' != sum.avg %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ 'sum'|_ }} ({{ sum.currency_name }}) ({{ 'active_exp_bills_only_total'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.avg, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
</td>
|
||||
<td> </td> <!-- paid in period -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- next expected match -->
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if '0' != sum.per_period %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ ('per_period_sum_'~sum.period)|_ }} ({{ sum.currency_name }})
|
||||
({{ 'active_bills_only_total'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.per_period, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
</td>
|
||||
<td> </td> <!-- paid in period -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- next expected match -->
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
Loading…
Reference in New Issue
Block a user