Merge branch 'master' of github.com:akaunting/akaunting

This commit is contained in:
Cüneyt Şentürk 2023-04-07 14:31:13 +03:00
commit 7ec0746de6
59 changed files with 750 additions and 282 deletions

View File

@ -5,6 +5,7 @@ on:
pull_request:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
tests:

View File

@ -0,0 +1,15 @@
<?php
namespace App\Events\Email;
use App\Abstracts\Event;
class TooManyEmailsSent extends Event
{
public $user_id;
public function __construct(int $user_id)
{
$this->user_id = $user_id;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Settings;
use App\Abstracts\Http\SettingController;
use App\Models\Banking\Account;
use App\Models\Setting\Category;
use App\Models\Setting\Tax;
@ -10,6 +11,8 @@ class Defaults extends SettingController
{
public function edit()
{
$accounts = Account::enabled()->orderBy('name')->get()->pluck('title', 'id');
$sales_categories = Category::income()->enabled()->orderBy('name')->take(setting('default.select_limit'))->get();
$sale_category_id = setting('default.income_category');
@ -37,6 +40,7 @@ class Defaults extends SettingController
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
return view('settings.default.edit', compact(
'accounts',
'sales_categories',
'purchases_categories',
'taxes',

View File

@ -32,7 +32,7 @@ class Tax extends FormRequest
return [
'name' => 'required|string',
'rate' => 'required|min:0|max:100',
'rate' => 'required|numeric|min:0|max:100',
'type' => $type,
'enabled' => $enabled,
];

View File

@ -4,12 +4,15 @@ namespace App\Jobs\Banking;
use App\Abstracts\Job;
use App\Events\Banking\TransactionSent;
use App\Http\Requests\Common\CustomMail as Request;
use App\Models\Banking\Transaction;
use App\Notifications\Banking\Transaction as Notification;
class SendTransactionAsCustomMail extends Job
{
public function __construct($request, $template_alias)
public string $template_alias;
public function __construct(Request $request, string $template_alias)
{
$this->request = $request;
$this->template_alias = $template_alias;

View File

@ -5,11 +5,14 @@ namespace App\Jobs\Document;
use App\Abstracts\Job;
use App\Events\Document\DocumentSending;
use App\Events\Document\DocumentSent;
use App\Http\Requests\Common\CustomMail as Request;
use App\Models\Document\Document;
class SendDocumentAsCustomMail extends Job
{
public function __construct($request, $template_alias)
public string $template_alias;
public function __construct(Request $request, string $template_alias)
{
$this->request = $request;
$this->template_alias = $template_alias;

View File

@ -31,6 +31,13 @@ class DeleteCategory extends Job implements ShouldDelete
*/
public function authorize(): void
{
// Can not delete transfer category
if ($this->model->isTransferCategory()) {
$message = trans('messages.error.transfer_category', ['type' => $this->model->name]);
throw new \Exception($message);
}
// Can not delete the last category by type
if (Category::where('type', $this->model->type)->count() == 1) {
$message = trans('messages.error.last_category', ['type' => strtolower(trans_choice('general.' . $this->model->type . 's', 1))]);

View File

@ -3,6 +3,7 @@
namespace App\Listeners\Document;
use App\Events\Document\DocumentRecurring as Event;
use App\Events\Document\DocumentSent;
use App\Traits\Documents;
class SendDocumentRecurringNotification
@ -35,6 +36,8 @@ class SendDocumentRecurringNotification
$document->contact->notify(new $notification($document, "{$document->type}_recur_customer"));
}
event(new DocumentSent($document));
// Check if should notify users
if (! $config['notify_user']) {
return;

View File

@ -0,0 +1,14 @@
<?php
namespace App\Listeners\Email;
use App\Exceptions\Common\TooManyEmailsSent;
use App\Events\Email\TooManyEmailsSent as Event;
class ReportTooManyEmailsSent
{
public function handle(Event $event): void
{
report(new TooManyEmailsSent('Too many emails sent!'));
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Listeners\Email;
use Akaunting\Firewall\Events\AttackDetected;
use Akaunting\Firewall\Traits\Helper;
use App\Events\Email\TooManyEmailsSent as Event;
use Illuminate\Support\Facades\Config;
class TellFirewallTooManyEmailsSent
{
use Helper;
public function handle(Event $event): void
{
$this->request = request();
$this->middleware = 'too_many_emails_sent';
$this->user_id = $event->user_id;
$this->loadConfig();
if ($this->skip($event)) {
return;
}
$log = $this->log();
event(new AttackDetected($log));
}
public function loadConfig(): void
{
$config = array_merge_recursive(
Config::get('firewall'),
[
'middleware' => [
$this->middleware => [
'enabled' => env('FIREWALL_MIDDLEWARE_' . strtoupper($this->middleware) . '_ENABLED', env('FIREWALL_ENABLED', true)),
'methods' => ['post'],
'routes' => [
'only' => [], // i.e. 'contact'
'except' => [], // i.e. 'admin/*'
],
'auto_block' => [
'attempts' => env('FIREWALL_MIDDLEWARE_' . strtoupper($this->middleware) . '_AUTO_BLOCK_ATTEMPTS', 20),
'frequency' => 1 * 60, // 1 minute
'period' => 30 * 60, // 30 minutes
],
],
],
]
);
Config::set('firewall', $config);
}
public function skip($event): bool
{
if ($this->isDisabled()) {
return true;
}
if ($this->isWhitelist()) {
return true;
}
return false;
}
}

View File

@ -111,12 +111,18 @@ class Transaction extends Notification
public function getTagsReplacement(): array
{
$route_params = [
'company_id' => $this->transaction->company_id,
'transaction' => $this->transaction->id,
'payment' => $this->transaction->id,
];
return [
money($this->transaction->amount, $this->transaction->currency_code, true),
company_date($this->transaction->paid_at),
URL::signedRoute('signed.payments.show', [$this->transaction->id]),
route('transactions.show', $this->transaction->id),
route('portal.payments.show', $this->transaction->id),
URL::signedRoute('signed.payments.show', $route_params),
route('transactions.show', $route_params),
route('portal.payments.show', $route_params),
$this->transaction->contact->name,
$this->transaction->company->name,
$this->transaction->company->email,

View File

@ -122,14 +122,19 @@ class PaymentReceived extends Notification
public function getTagsReplacement(): array
{
$route_params = [
'company_id' => $this->invoice->company_id,
'invoice' => $this->invoice->id,
];
return [
$this->invoice->document_number,
money($this->invoice->amount, $this->invoice->currency_code, true),
company_date($this->invoice->due_at),
trans('documents.statuses.' . $this->invoice->status),
URL::signedRoute('signed.invoices.show', [$this->invoice->id]),
route('invoices.show', $this->invoice->id),
route('portal.invoices.show', $this->invoice->id),
URL::signedRoute('signed.invoices.show', $route_params),
route('invoices.show', $route_params),
route('portal.invoices.show', $route_params),
money($this->transaction->amount, $this->transaction->currency_code, true),
company_date($this->transaction->paid_at),
$this->transaction->payment_method,

View File

@ -90,13 +90,18 @@ class Bill extends Notification
public function getTagsReplacement(): array
{
$route_params = [
'company_id' => $this->bill->company_id,
'bill' => $this->bill->id,
];
return [
$this->bill->document_number,
money($this->bill->amount, $this->bill->currency_code, true),
money($this->bill->amount_due, $this->bill->currency_code, true),
company_date($this->bill->issued_at),
company_date($this->bill->due_at),
route('bills.show', $this->bill->id),
route('bills.show', $route_params),
$this->bill->contact_name,
$this->bill->company->name,
$this->bill->company->email,

View File

@ -116,15 +116,20 @@ class Invoice extends Notification
public function getTagsReplacement(): array
{
$route_params = [
'company_id' => $this->invoice->company_id,
'invoice' => $this->invoice->id,
];
return [
$this->invoice->document_number,
money($this->invoice->amount, $this->invoice->currency_code, true),
money($this->invoice->amount_due, $this->invoice->currency_code, true),
company_date($this->invoice->issued_at),
company_date($this->invoice->due_at),
URL::signedRoute('signed.invoices.show', [$this->invoice->id]),
route('invoices.show', $this->invoice->id),
route('portal.invoices.show', $this->invoice->id),
URL::signedRoute('signed.invoices.show', $route_params),
route('invoices.show', $route_params),
route('portal.invoices.show', $route_params),
$this->invoice->contact_name,
$this->invoice->company->name,
$this->invoice->company->email,

View File

@ -104,6 +104,10 @@ class Event extends Provider
'App\Events\Setting\CategoryDeleted' => [
'App\Listeners\Setting\DeleteCategoryDeletedSubCategories',
],
'App\Events\Email\TooManyEmailsSent' => [
'App\Listeners\Email\ReportTooManyEmailsSent',
'App\Listeners\Email\TellFirewallTooManyEmailsSent',
],
];
/**

View File

@ -3,7 +3,7 @@
namespace App\Traits;
use App\Abstracts\Job;
use App\Exceptions\Common\TooManyEmailsSent;
use App\Events\Email\TooManyEmailsSent;
use App\Traits\Jobs;
use Illuminate\Support\Facades\RateLimiter;
@ -14,42 +14,38 @@ trait Emails
public function sendEmail(Job $job): array
{
// Check if the user has reached the limit of emails per month
$key_per_month = 'email-month:' . user()->id;
$key_per_month = 'email-month:' . user_id();
$limit_per_month = config('app.throttles.email.month');
$decay_per_month = 60 * 60 * 24 * 30;
$can_send = RateLimiter::attempt($key_per_month, $limit_per_month, fn() => '', $decay_per_month);
$can_send = RateLimiter::attempt($key_per_month, $limit_per_month, fn() => null, $decay_per_month);
if ($can_send) {
// Check if the user has reached the limit of emails per minute
$key_per_minute = 'email-minute:' . user()->id;
$key_per_minute = 'email-minute:' . user_id();
$limit_per_minute = config('app.throttles.email.minute');
$can_send = RateLimiter::attempt($key_per_minute, $limit_per_minute, fn() => '');
$can_send = RateLimiter::attempt($key_per_minute, $limit_per_minute, fn() => null);
}
if ($can_send) {
$this->dispatch($job);
$response = [
return [
'success' => true,
'error' => false,
'data' => '',
'message' => '',
];
return $response;
}
$response = [
event(new TooManyEmailsSent(user_id()));
return [
'success' => false,
'error' => true,
'data' => null,
'message' => 'Too many emails sent!',
];
report(new TooManyEmailsSent('Too many emails sent!'));
return $response;
}
}

View File

@ -22,11 +22,12 @@ use App\Models\Setting\Category;
use App\Models\Setting\Currency;
use App\Models\Setting\Tax;
use App\Traits\Jobs;
use App\Traits\Sources;
use Illuminate\Support\Facades\Validator;
trait Import
{
use Jobs;
use Jobs, Sources;
public function getAccountId($row)
{
@ -98,8 +99,8 @@ trait Import
'symbol' => isset($row['currency_symbol']) ? $row['currency_symbol'] : config('money.' . $row['currency_code'] . '.symbol'),
'precision' => isset($row['currency_precision']) ? $row['currency_precision'] : config('money.' . $row['currency_code'] . '.precision'),
'decimal_mark' => isset($row['currency_decimal_mark']) ? $row['currency_decimal_mark'] : config('money.' . $row['currency_code'] . '.decimal_mark'),
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new CurrencyRequest)->rules());
@ -178,8 +179,8 @@ trait Import
'number' => !empty($row['account_number']) ? $row['account_number'] : (string) rand(1, 10000),
'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0,
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new AccountRequest)->rules());
@ -205,8 +206,8 @@ trait Import
'currency_code' => !empty($row['currency_code']) ? $row['currency_code'] : default_currency(),
'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0,
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new AccountRequest)->rules());
@ -232,8 +233,8 @@ trait Import
'currency_code' => !empty($row['currency_code']) ? $row['currency_code'] : default_currency(),
'opening_balance' => !empty($row['opening_balance']) ? $row['opening_balance'] : 0,
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new AccountRequest)->rules());
@ -257,8 +258,8 @@ trait Import
'type' => $type,
'color' => !empty($row['category_color']) ? $row['category_color'] : '#' . dechex(rand(0x000000, 0xFFFFFF)),
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new CategoryRequest)->rules());
@ -283,8 +284,8 @@ trait Import
'name' => !empty($row['contact_name']) ? $row['contact_name'] : $row['contact_email'],
'currency_code' => !empty($row['contact_currency']) ? $row['contact_currency'] : default_currency(),
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new ContactRequest)->rules());
@ -309,8 +310,8 @@ trait Import
'email' => !empty($row['contact_email']) ? $row['contact_email'] : null,
'currency_code' => !empty($row['contact_currency']) ? $row['contact_currency'] : default_currency(),
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new ContactRequest)->rules());
@ -330,14 +331,14 @@ trait Import
$data = [
'company_id' => company_id(),
'type' => $row['item_type'],
'type' => !empty($row['item_type']) ? $row['item_type'] : (!empty($row['type']) ? $row['type'] : 'product'),
'name' => $row['item_name'],
'description' => !empty($row['item_description']) ? $row['item_description'] : null,
'sale_price' => !empty($row['sale_price']) ? $row['sale_price'] : (!empty($row['price']) ? $row['price'] : 0),
'purchase_price' => !empty($row['purchase_price']) ? $row['purchase_price'] : (!empty($row['price']) ? $row['price'] : 0),
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new ItemRequest())->rules());
@ -361,8 +362,8 @@ trait Import
'type' => $type,
'name' => !empty($row['tax_name']) ? $row['tax_name'] : (string) $row['tax_rate'],
'enabled' => 1,
'created_from' => $row['created_from'],
'created_by' => $row['created_by'],
'created_from' => !empty($row['created_from']) ? $row['created_from'] : $this->getSourcePrefix() . 'import',
'created_by' => !empty($row['created_by']) ? $row['created_by'] : user()->id,
];
Validator::validate($data, (new TaxRequest())->rules());

View File

@ -23,6 +23,7 @@ trait Recurring
$limit_by = !empty($request['recurring_limit']) ? $request['recurring_limit'] : 'count';
$limit_count = isset($request['recurring_limit_count']) ? (int) $request['recurring_limit_count'] : 0;
$limit_date = !empty($request['recurring_limit_date']) ? $request['recurring_limit_date'] : null;
$auto_send = !empty($request['recurring_send_email']) ? $request['recurring_send_email'] : 0;
$source = !empty($request['created_from']) ? $request['created_from'] : source_name();
$owner = !empty($request['created_by']) ? $request['created_by'] : user_id();
@ -35,6 +36,7 @@ trait Recurring
'limit_by' => $limit_by,
'limit_count' => $limit_count,
'limit_date' => $limit_date,
'auto_send' => $auto_send,
'created_from' => $source,
'created_by' => $owner,
]);
@ -54,6 +56,7 @@ trait Recurring
$limit_by = !empty($request['recurring_limit']) ? $request['recurring_limit'] : 'count';
$limit_count = isset($request['recurring_limit_count']) ? (int) $request['recurring_limit_count'] : 0;
$limit_date = !empty($request['recurring_limit_date']) ? $request['recurring_limit_date'] : null;
$auto_send = !empty($request['recurring_send_email']) ? $request['recurring_send_email'] : 0;
$recurring = $this->recurring();
$model_exists = $recurring->count();
@ -66,6 +69,7 @@ trait Recurring
'limit_by' => $limit_by,
'limit_count' => $limit_count,
'limit_date' => $limit_date,
'auto_send' => $auto_send,
];
if (! empty($request['recurring_status'])) {

View File

@ -7,6 +7,7 @@ use App\Abstracts\View\Component;
class Link extends Component
{
public $href;
public $target;
/**
* Create a new component instance.
@ -14,9 +15,10 @@ class Link extends Component
* @return void
*/
public function __construct(
$href = '',
string $href = '', string $target = '_self'
) {
$this->href = $href;
$this->target = $target;
}
/**

View File

@ -22,6 +22,9 @@ class Recurring extends Component
public $limitCount = '';
public $limitDateValue = '';
public $sendEmailShow;
public $sendEmail;
/**
* Create a new component instance.
*
@ -41,6 +44,9 @@ class Recurring extends Component
$startedValue = '',
$limitCount = '',
$limitDateValue = '',
$sendEmailShow = true,
$sendEmail = false
) {
$this->type = $this->getType($type);
$this->frequency = $this->getFrequency($frequency);
@ -55,6 +61,9 @@ class Recurring extends Component
$this->startedValue = $this->getStartedValue($startedValue);
$this->limitCount = $this->getLimitCount($limitCount);
$this->limitDateValue = $this->getLimitDateValue($limitDateValue);
$this->sendEmailShow = $this->getSendEmailShow($sendEmailShow);
$this->sendEmail = $this->getSendEmail($sendEmail);
}
/**
@ -171,4 +180,22 @@ class Recurring extends Component
return Date::now()->toDateString();
}
protected function getSendEmailShow($sendEmailShow)
{
if (! empty($sendEmailShow)) {
return $sendEmailShow;
}
return false;
}
protected function getSendEmail($sendEmail)
{
if (! empty($sendEmail)) {
return $sendEmail;
}
return false;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\View\Components;
use App\Abstracts\View\Component;
use App\Utilities\Modules;
use Illuminate\Support\Str;
class PaymentMethod extends Component
{
public $code, $method;
public $payment_methods;
public $payment_method;
public $type;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct(
$code = null, $method = null, string $payment_method = null, array $payment_methods = [], $type = null
) {
$this->code = $code;
$this->method = $method;
$this->type = $type;
$this->payment_methods = $this->getPaymentMethods($payment_methods, $type);
$this->payment_method = $this->getPaymentMethod($payment_method, $code);
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|string
*/
public function render()
{
return view('components.payment_method');
}
protected function getPaymentMethods($payment_methods, $type)
{
if (! empty($payment_methods)) {
return $payment_methods;
}
// check here protal or admin panel..
if (empty($type)) {
$type = Str::contains(request()->route()->getName(), 'portal') ? 'customer' : 'all';
}
$payment_methods = Modules::getPaymentMethods($type);
return $payment_methods;
}
protected function getPaymentMethod($payment_method, $code)
{
if (! empty($payment_methods)) {
return $payment_methods;
}
if (! empty($this->payment_methods[$code])) {
return $this->payment_methods[$code];
}
if (! empty($this->payment_methods[$this->method])) {
return $this->payment_methods[$this->method];
}
if (! empty($this->payment_methods[$payment_method])) {
return $this->payment_methods[$payment_method];
}
return false;
}
}

View File

@ -122,7 +122,7 @@ class SearchString extends Component
}
if (! empty($options['translation']) && ! isset($options['boolean'])) {
return $options['translation'];
return $this->findTranslation($options['translation']);
}
if (!empty($options['key'])) {

View File

@ -130,7 +130,7 @@ class Menu extends Presenter
{
$id = Str::slug($item->title);
return '<details class="relative">
return '<details class="relative" ' . $this->getActiveStateOnChild($item) . '>
<summary class="' . $this->getClass($item). '" href="#navbar-' . $id . '" aria-controls="navbar-' . $id . '">
<div class="pb-2.5 flex items-center cursor-pointer text-purple text-sm '. $this->getActiveState($item) .'">
' . $this->getIcon($item) . '

237
composer.lock generated
View File

@ -142,16 +142,16 @@
},
{
"name": "akaunting/laravel-firewall",
"version": "2.1.0",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/akaunting/laravel-firewall.git",
"reference": "6a44c0bf31530f3ae94fbee849395ee91c7ffb54"
"reference": "ef48b2e63a7746e0513ce47d8e811ac58ad3bfc7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/akaunting/laravel-firewall/zipball/6a44c0bf31530f3ae94fbee849395ee91c7ffb54",
"reference": "6a44c0bf31530f3ae94fbee849395ee91c7ffb54",
"url": "https://api.github.com/repos/akaunting/laravel-firewall/zipball/ef48b2e63a7746e0513ce47d8e811ac58ad3bfc7",
"reference": "ef48b2e63a7746e0513ce47d8e811ac58ad3bfc7",
"shasum": ""
},
"require": {
@ -203,9 +203,9 @@
],
"support": {
"issues": "https://github.com/akaunting/laravel-firewall/issues",
"source": "https://github.com/akaunting/laravel-firewall/tree/2.1.0"
"source": "https://github.com/akaunting/laravel-firewall/tree/2.1.3"
},
"time": "2023-03-07T12:53:34+00:00"
"time": "2023-03-25T11:05:54+00:00"
},
{
"name": "akaunting/laravel-language",
@ -798,23 +798,27 @@
},
{
"name": "aws/aws-crt-php",
"version": "v1.0.4",
"version": "v1.2.1",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
"reference": "f5c64ee7c5fce196e2519b3d9b7138649efe032d"
"reference": "1926277fc71d253dfa820271ac5987bdb193ccf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/f5c64ee7c5fce196e2519b3d9b7138649efe032d",
"reference": "f5c64ee7c5fce196e2519b3d9b7138649efe032d",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/1926277fc71d253dfa820271ac5987bdb193ccf5",
"reference": "1926277fc71d253dfa820271ac5987bdb193ccf5",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35|^5.6.3"
"phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
},
"type": "library",
"autoload": {
@ -833,7 +837,7 @@
}
],
"description": "AWS Common Runtime for PHP",
"homepage": "http://aws.amazon.com/sdkforphp",
"homepage": "https://github.com/awslabs/aws-crt-php",
"keywords": [
"amazon",
"aws",
@ -842,22 +846,22 @@
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.4"
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.1"
},
"time": "2023-01-31T23:08:25+00:00"
"time": "2023-03-24T20:22:19+00:00"
},
{
"name": "aws/aws-sdk-php",
"version": "3.261.14",
"version": "3.262.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "121638bb7e62ee2f71838c52e79884f4301a9400"
"reference": "42ca7ade60a775fc5eb103d4631df3d366b48a29"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/121638bb7e62ee2f71838c52e79884f4301a9400",
"reference": "121638bb7e62ee2f71838c52e79884f4301a9400",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/42ca7ade60a775fc5eb103d4631df3d366b48a29",
"reference": "42ca7ade60a775fc5eb103d4631df3d366b48a29",
"shasum": ""
},
"require": {
@ -936,9 +940,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.261.14"
"source": "https://github.com/aws/aws-sdk-php/tree/3.262.1"
},
"time": "2023-03-17T18:21:00+00:00"
"time": "2023-03-24T18:20:43+00:00"
},
{
"name": "balping/json-raw-encoder",
@ -2934,16 +2938,16 @@
},
{
"name": "genealabs/laravel-model-caching",
"version": "0.13.2",
"version": "0.13.4",
"source": {
"type": "git",
"url": "https://github.com/GeneaLabs/laravel-model-caching.git",
"reference": "1fe37744efa9d5ed3d8c245c68271022b0e452ab"
"reference": "631bb7f1d84c5863d82cff90e48152f65616597e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/1fe37744efa9d5ed3d8c245c68271022b0e452ab",
"reference": "1fe37744efa9d5ed3d8c245c68271022b0e452ab",
"url": "https://api.github.com/repos/GeneaLabs/laravel-model-caching/zipball/631bb7f1d84c5863d82cff90e48152f65616597e",
"reference": "631bb7f1d84c5863d82cff90e48152f65616597e",
"shasum": ""
},
"require": {
@ -2997,22 +3001,22 @@
"description": "Automatic caching for Eloquent models.",
"support": {
"issues": "https://github.com/GeneaLabs/laravel-model-caching/issues",
"source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.13.2"
"source": "https://github.com/GeneaLabs/laravel-model-caching/tree/0.13.4"
},
"time": "2023-03-09T14:37:04+00:00"
"time": "2023-03-27T13:53:10+00:00"
},
{
"name": "genealabs/laravel-pivot-events",
"version": "10.0.0",
"version": "10.0.1",
"source": {
"type": "git",
"url": "https://github.com/GeneaLabs/laravel-pivot-events.git",
"reference": "48dc3cc7c26d6343741dd23f75763e79b7a2706b"
"reference": "862371f6f89be296cc026c9cf5b372dca4d7958b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GeneaLabs/laravel-pivot-events/zipball/48dc3cc7c26d6343741dd23f75763e79b7a2706b",
"reference": "48dc3cc7c26d6343741dd23f75763e79b7a2706b",
"url": "https://api.github.com/repos/GeneaLabs/laravel-pivot-events/zipball/862371f6f89be296cc026c9cf5b372dca4d7958b",
"reference": "862371f6f89be296cc026c9cf5b372dca4d7958b",
"shasum": ""
},
"require": {
@ -3054,7 +3058,7 @@
"issues": "https://github.com/GeneaLabs/laravel-pivot/issues",
"source": "https://github.com/GeneaLabs/laravel-pivot"
},
"time": "2023-02-17T14:30:37+00:00"
"time": "2023-03-22T14:46:23+00:00"
},
{
"name": "graham-campbell/markdown",
@ -4655,16 +4659,16 @@
},
{
"name": "jaybizzle/crawler-detect",
"version": "v1.2.113",
"version": "v1.2.114",
"source": {
"type": "git",
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
"reference": "6710b75871da2b718550c2bc33388315a3b20151"
"reference": "62d0e6b38f6715c673e156ffb0fc894791de3452"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/6710b75871da2b718550c2bc33388315a3b20151",
"reference": "6710b75871da2b718550c2bc33388315a3b20151",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/62d0e6b38f6715c673e156ffb0fc894791de3452",
"reference": "62d0e6b38f6715c673e156ffb0fc894791de3452",
"shasum": ""
},
"require": {
@ -4701,9 +4705,9 @@
],
"support": {
"issues": "https://github.com/JayBizzle/Crawler-Detect/issues",
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.113"
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.114"
},
"time": "2023-02-02T21:01:40+00:00"
"time": "2023-03-21T21:54:27+00:00"
},
{
"name": "jean85/pretty-package-versions",
@ -5544,16 +5548,16 @@
},
{
"name": "league/commonmark",
"version": "2.3.9",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "c1e114f74e518daca2729ea8c4bf1167038fa4b5"
"reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c1e114f74e518daca2729ea8c4bf1167038fa4b5",
"reference": "c1e114f74e518daca2729ea8c4bf1167038fa4b5",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048",
"reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048",
"shasum": ""
},
"require": {
@ -5589,7 +5593,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
}
},
"autoload": {
@ -5646,7 +5650,7 @@
"type": "tidelift"
}
],
"time": "2023-02-15T14:07:24+00:00"
"time": "2023-03-24T15:16:10+00:00"
},
{
"name": "league/config",
@ -7883,38 +7887,44 @@
},
{
"name": "php-http/discovery",
"version": "1.14.3",
"version": "1.15.2",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "31d8ee46d0215108df16a8527c7438e96a4d7735"
"reference": "5cc428320191ac1d0b6520034c2dc0698628ced5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/31d8ee46d0215108df16a8527c7438e96a4d7735",
"reference": "31d8ee46d0215108df16a8527c7438e96a4d7735",
"url": "https://api.github.com/repos/php-http/discovery/zipball/5cc428320191ac1d0b6520034c2dc0698628ced5",
"reference": "5cc428320191ac1d0b6520034c2dc0698628ced5",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1"
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"symfony/phpunit-bridge": "^6.2"
},
"suggest": {
"php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories"
},
"type": "library",
"type": "composer-plugin",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
@ -7931,7 +7941,7 @@
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds installed HTTPlug implementations and PSR-7 message factories",
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
@ -7940,13 +7950,14 @@
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.14.3"
"source": "https://github.com/php-http/discovery/tree/1.15.2"
},
"time": "2022-07-11T14:04:40+00:00"
"time": "2023-02-11T08:28:41+00:00"
},
{
"name": "php-http/guzzle7-adapter",
@ -9087,16 +9098,16 @@
},
{
"name": "psy/psysh",
"version": "v0.11.12",
"version": "v0.11.13",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "52cb7c47d403c31c0adc9bf7710fc355f93c20f7"
"reference": "722317c9f5627e588788e340f29b923e58f92f54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/52cb7c47d403c31c0adc9bf7710fc355f93c20f7",
"reference": "52cb7c47d403c31c0adc9bf7710fc355f93c20f7",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/722317c9f5627e588788e340f29b923e58f92f54",
"reference": "722317c9f5627e588788e340f29b923e58f92f54",
"shasum": ""
},
"require": {
@ -9157,9 +9168,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.11.12"
"source": "https://github.com/bobthecow/psysh/tree/v0.11.13"
},
"time": "2023-01-29T21:24:40+00:00"
"time": "2023-03-21T14:22:44+00:00"
},
{
"name": "ralouphie/getallheaders",
@ -9703,32 +9714,31 @@
},
{
"name": "sentry/sentry",
"version": "3.16.0",
"version": "3.17.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
"reference": "5326a8786b8c7c3a51ea0c6d06e6cb6a9dfa6779"
"reference": "95d2e932383cf684f77acff0d2a5aef5ad2f1933"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5326a8786b8c7c3a51ea0c6d06e6cb6a9dfa6779",
"reference": "5326a8786b8c7c3a51ea0c6d06e6cb6a9dfa6779",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/95d2e932383cf684f77acff0d2a5aef5ad2f1933",
"reference": "95d2e932383cf684f77acff0d2a5aef5ad2f1933",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/promises": "^1.4",
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
"jean85/pretty-package-versions": "^1.5|^2.0.4",
"php": "^7.2|^8.0",
"php-http/async-client-implementation": "^1.0",
"php-http/client-common": "^1.5|^2.0",
"php-http/discovery": "^1.11, <1.15",
"php-http/discovery": "^1.15",
"php-http/httplug": "^1.1|^2.0",
"php-http/message": "^1.5",
"psr/http-factory": "^1.0",
"psr/http-message-implementation": "^1.0",
"psr/http-factory-implementation": "^1.0",
"psr/log": "^1.0|^2.0|^3.0",
"symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0",
"symfony/polyfill-php80": "^1.17"
@ -9739,6 +9749,7 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19|3.4.*",
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
"http-interop/http-factory-guzzle": "^1.0",
"monolog/monolog": "^1.6|^2.0|^3.0",
"nikic/php-parser": "^4.10.3",
@ -9791,7 +9802,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
"source": "https://github.com/getsentry/sentry-php/tree/3.16.0"
"source": "https://github.com/getsentry/sentry-php/tree/3.17.0"
},
"funding": [
{
@ -9803,20 +9814,20 @@
"type": "custom"
}
],
"time": "2023-03-16T10:37:16+00:00"
"time": "2023-03-26T21:54:06+00:00"
},
{
"name": "sentry/sentry-laravel",
"version": "3.3.0",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
"reference": "e9c87d6580fc56147f580e1d714d8eb4e06d2752"
"reference": "c502e8b9005990d03f5ec5cc852e98a27c26056d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/e9c87d6580fc56147f580e1d714d8eb4e06d2752",
"reference": "e9c87d6580fc56147f580e1d714d8eb4e06d2752",
"url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/c502e8b9005990d03f5ec5cc852e98a27c26056d",
"reference": "c502e8b9005990d03f5ec5cc852e98a27c26056d",
"shasum": ""
},
"require": {
@ -9884,7 +9895,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
"source": "https://github.com/getsentry/sentry-laravel/tree/3.3.0"
"source": "https://github.com/getsentry/sentry-laravel/tree/3.3.2"
},
"funding": [
{
@ -9896,7 +9907,7 @@
"type": "custom"
}
],
"time": "2023-03-16T12:25:43+00:00"
"time": "2023-03-22T10:51:03+00:00"
},
{
"name": "simple-icons/simple-icons",
@ -13198,16 +13209,16 @@
},
{
"name": "brianium/paratest",
"version": "v7.1.1",
"version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
"reference": "abc123183e90f33ce1312b5bfaa49d80d8c646b2"
"reference": "10e66ccdad397200f8129a034f0d3bf8cbe4c524"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/abc123183e90f33ce1312b5bfaa49d80d8c646b2",
"reference": "abc123183e90f33ce1312b5bfaa49d80d8c646b2",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/10e66ccdad397200f8129a034f0d3bf8cbe4c524",
"reference": "10e66ccdad397200f8129a034f0d3bf8cbe4c524",
"shasum": ""
},
"require": {
@ -13221,7 +13232,7 @@
"phpunit/php-code-coverage": "^10.0.2",
"phpunit/php-file-iterator": "^4.0.1",
"phpunit/php-timer": "^6.0",
"phpunit/phpunit": "^10.0.16",
"phpunit/phpunit": "^10.0.17",
"sebastian/environment": "^6.0",
"symfony/console": "^6.2.7",
"symfony/process": "^6.2.7"
@ -13231,8 +13242,8 @@
"ext-pcov": "*",
"ext-posix": "*",
"infection/infection": "^0.26.19",
"phpstan/phpstan": "^1.10.6",
"phpstan/phpstan-deprecation-rules": "^1.1.2",
"phpstan/phpstan": "^1.10.7",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10",
"phpstan/phpstan-strict-rules": "^1.5",
"squizlabs/php_codesniffer": "^3.7.2",
@ -13277,7 +13288,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v7.1.1"
"source": "https://github.com/paratestphp/paratest/tree/v7.1.2"
},
"funding": [
{
@ -13289,7 +13300,7 @@
"type": "paypal"
}
],
"time": "2023-03-13T10:11:07+00:00"
"time": "2023-03-20T15:15:41+00:00"
},
{
"name": "fakerphp/faker",
@ -13675,16 +13686,16 @@
},
{
"name": "nunomaduro/collision",
"version": "v7.1.2",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "f502ff3b2051124c89b4dd3a8a497ca65f3ce26c"
"reference": "c680af93e414110b36056029f63120e6bc78f6e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/f502ff3b2051124c89b4dd3a8a497ca65f3ce26c",
"reference": "f502ff3b2051124c89b4dd3a8a497ca65f3ce26c",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/c680af93e414110b36056029f63120e6bc78f6e3",
"reference": "c680af93e414110b36056029f63120e6bc78f6e3",
"shasum": ""
},
"require": {
@ -13694,19 +13705,19 @@
"symfony/console": "^6.2.7"
},
"conflict": {
"phpunit/phpunit": "<10.0.16"
"phpunit/phpunit": "<10.0.17"
},
"require-dev": {
"brianium/paratest": "^7.1.1",
"laravel/framework": "^10.3.3",
"laravel/pint": "^1.6.0",
"brianium/paratest": "^7.1.2",
"laravel/framework": "^10.4.1",
"laravel/pint": "^1.7.0",
"laravel/sail": "^1.21.2",
"laravel/sanctum": "^3.2.1",
"laravel/tinker": "^2.8.1",
"nunomaduro/larastan": "^2.5.1",
"orchestra/testbench-core": "^8.0.5",
"pestphp/pest": "^2.0.0",
"phpunit/phpunit": "^10.0.16",
"orchestra/testbench-core": "^8.1.1",
"pestphp/pest": "^2.0.2",
"phpunit/phpunit": "^10.0.17",
"sebastian/environment": "^6.0.0",
"spatie/laravel-ignition": "^2.0.0"
},
@ -13767,7 +13778,7 @@
"type": "patreon"
}
],
"time": "2023-03-14T14:34:49+00:00"
"time": "2023-03-23T21:41:35+00:00"
},
{
"name": "phar-io/manifest",
@ -14200,16 +14211,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.0.16",
"version": "10.0.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "07d386a11ac7094032900f07cada1c8975d16607"
"reference": "20c23e85c86e5c06d63538ba464e8054f4744e62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/07d386a11ac7094032900f07cada1c8975d16607",
"reference": "07d386a11ac7094032900f07cada1c8975d16607",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20c23e85c86e5c06d63538ba464e8054f4744e62",
"reference": "20c23e85c86e5c06d63538ba464e8054f4744e62",
"shasum": ""
},
"require": {
@ -14280,7 +14291,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.16"
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.19"
},
"funding": [
{
@ -14296,7 +14308,7 @@
"type": "tidelift"
}
],
"time": "2023-03-13T09:02:40+00:00"
"time": "2023-03-27T11:46:33+00:00"
},
{
"name": "sebastian/cli-parser",
@ -14600,16 +14612,16 @@
},
{
"name": "sebastian/diff",
"version": "5.0.0",
"version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "70dd1b20bc198da394ad542e988381b44e64e39f"
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/70dd1b20bc198da394ad542e988381b44e64e39f",
"reference": "70dd1b20bc198da394ad542e988381b44e64e39f",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02",
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02",
"shasum": ""
},
"require": {
@ -14654,7 +14666,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.0"
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.1"
},
"funding": [
{
@ -14662,7 +14675,7 @@
"type": "github"
}
],
"time": "2023-02-03T07:00:31+00:00"
"time": "2023-03-23T05:12:41+00:00"
},
{
"name": "sebastian/environment",

View File

@ -214,6 +214,7 @@ class Document extends AbstractFactory
'recurring_frequency' => 'daily',
'recurring_interval' => '1',
'recurring_limit_count' => '7',
'recurring_send_email' => '1',
]);
}

View File

@ -4,6 +4,7 @@ namespace Database\Factories;
use App\Abstracts\Factory;
use App\Models\Banking\Transaction as Model;
use App\Models\Common\Contact;
use App\Traits\Transactions;
use App\Utilities\Date;
@ -61,10 +62,21 @@ class Transaction extends Factory
*/
public function income()
{
return $this->state([
return $this->state(function (array $attributes): array {
$contacts = Contact::customer()->enabled()->get();
if ($contacts->count()) {
$contact = $contacts->random(1)->first();
} else {
$contact = Contact::factory()->customer()->enabled()->create();
}
return [
'type' => 'income',
'contact_id' => $contact->id,
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
]);
];
});
}
/**
@ -74,10 +86,21 @@ class Transaction extends Factory
*/
public function expense()
{
return $this->state([
return $this->state(function (array $attributes): array {
$contacts = Contact::vendor()->enabled()->get();
if ($contacts->count()) {
$contact = $contacts->random(1)->first();
} else {
$contact = Contact::factory()->vendor()->enabled()->create();
}
return [
'type' => 'expense',
'contact_id' => $contact->id,
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
]);
];
});
}
/**

Binary file not shown.

Binary file not shown.

View File

@ -22,6 +22,7 @@
:config="dateConfig"
class="datepicker w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
v-model="real_model"
:placeholder="placeholder"
@input="change"
:readonly="readonly"
:disabled="disabled">

View File

@ -188,16 +188,6 @@ export default {
created: function() {
this.form = new Form('form-create');
// for override global currency variable..
this.currency = {
decimal: '.',
thousands: ',',
prefix: '$ ',
suffix: '',
precision: 2,
masked: false /* doesn't work with directive */
};
// Parent vue instance methods merge with child vue instance methods
if (this.$root.$options.methods) {
let parent_methods = this.$root.$options.methods;

View File

@ -126,6 +126,22 @@
<div class="text-red text-sm mt-1 ml-2 block" v-if="limitDateError" v-html="limitDateError"></div>
</div>
</div>
<div v-if="sendEmailShow" class="flex flex-wrap lg:flex-nowrap items-center space-y-1 lg:space-y-0">
<div class="w-24 sm:w-60 px-0 sm:px-2 text-sm">
{{ sendEmailText }}
</div>
<div class="flex items-center mt-1">
<label @click="sendEmail=1;change();" v-bind:class="[sendEmail == 1 ? ['bg-green-500','text-white'] : 'bg-black-100']" class="relative w-10 ltr:rounded-tl-lg ltr:rounded-bl-lg rtl:rounded-tr-lg rtl:rounded-br-lg py-2 px-1 text-sm text-center transition-all cursor-pointer">
{{ sendEmailYesText }}
</label>
<label @click="sendEmail=0;change();"v-bind:class="[sendEmail == 0 ? ['bg-red-500','text-white'] : 'bg-black-100']" class="relative w-10 ltr:rounded-tr-lg ltr:rounded-br-lg rtl:rounded-tl-lg rtl:rounded-bl-lg py-2 px-1 text-sm text-center transition-all cursor-pointer">
{{ sendEmailNoText }}
</label>
</div>
</div>
</div>
</template>
@ -246,6 +262,32 @@ export default {
default: 'dd MM yyyy',
description: "Default date format"
},
sendEmailShow: {
type: Number,
default: 1,
description: "Created recurring model send automatically option"
},
sendEmailText: {
type: String,
default: 'Send email automatically',
description: "Created recurring model send automatically option"
},
sendEmailYesText: {
type: String,
default: 'Yes',
description: "Send email option yes text"
},
sendEmailNoText: {
type: String,
default: 'No',
description: "Send email option no text"
},
sendEmailValue: {
type: [Number, String],
default: 0,
description: "Send Email value"
}
},
data() {
@ -258,6 +300,7 @@ export default {
limitCount: 0,
limitDate: '',
formatDate: 'dd MM YYYY',
sendEmail: 0,
}
},
@ -284,6 +327,8 @@ export default {
this.limit = 'on';
}
this.sendEmail = this.sendEmailValue;
setTimeout(function() {
this.change();
}.bind(this), 800);
@ -315,6 +360,8 @@ export default {
this.$emit('limit_count', 0);
break;
}
this.$emit('send_email', this.sendEmail);
},
convertToDarteFormat(format) {

View File

@ -13,7 +13,7 @@ export default {
new BrowserTracing({
tracingOrigins: [],
}),
new Sentry.Replay()
//new Sentry.Replay()
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
@ -22,11 +22,11 @@ export default {
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: exception_tracker.params.replays_session_sample_rate,
//replaysSessionSampleRate: exception_tracker.params.replays_session_sample_rate,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: exception_tracker.params.replays_on_error_sample_rate,
//replaysOnErrorSampleRate: exception_tracker.params.replays_on_error_sample_rate,
});
Sentry.setUser({

View File

@ -42,7 +42,7 @@ return [
'send_invoice' => 'Send Invoice',
'get_paid' => 'Get Paid',
'accept_payments' => 'Accept Online Payments',
'payment_received' => 'Payment received',
'payments_received' => 'Payments received',
'form_description' => [
'billing' => 'Billing details appear in your invoice. Invoice Date is used in the dashboard and reports. Select the date you expect to get paid as the Due Date.',

View File

@ -25,7 +25,8 @@ return [
'not_user_company' => 'Error: You are not allowed to manage this company!',
'customer' => 'Error: User not created! :name already uses this email address.',
'no_file' => 'Error: No file selected!',
'last_category' => 'Error: Can not delete the last :type category!',
'last_category' => 'Error: Can not delete the last <b>:type</b> category!',
'transfer_category' => 'Error: Can not delete the transfer <b>:type</b> category!',
'change_type' => 'Error: Can not change the type because it has :text related!',
'invalid_apikey' => 'Error: The API Key entered is invalid!',
'import_column' => 'Error: :message Column name: :column. Line number: :line.',

View File

@ -29,6 +29,7 @@ return [
'child' => ':url was automatically created on :date',
'message' => 'This is a recurring :type and the next :type will be automatically generated on :date',
'message_parent' => 'This :type was automatically generated from :link',
'send_email_auto' => 'Send email automatically',
'frequency_type' => 'Repeat this :type',
'limit_date' => 'Create first :type on',

View File

@ -11,34 +11,36 @@
<x-form.group.text name="name" label="{{ trans($textName) }}" form-group-class="{{ $classNameFromGroupClass }}" />
@endif
<div class="sm:col-span-3 grid gap-x-8 gap-y-6">
<div class="sm:col-span-3">
<div class="relative sm:col-span-6 grid gap-x-8 gap-y-6">
@if (! $hideEmail)
<x-form.group.text name="email" label="{{ trans($textEmail) }}" not-required />
<x-form.group.text name="email" label="{{ trans($textEmail) }}" form-group-class="sm:col-span-6" not-required />
@endif
@if (! $hidePhone)
<x-form.group.text name="phone" label="{{ trans($textPhone) }}" not-required />
<x-form.group.text name="phone" label="{{ trans($textPhone) }}" form-group-class="sm:col-span-6" not-required />
@endif
@if (! $hideWebsite)
<x-form.group.text name="website" label="{{ trans($textWebsite) }}" not-required />
<x-form.group.text name="website" label="{{ trans($textWebsite) }}" form-group-class="sm:col-span-6" not-required />
@endif
@if (! $hideReference)
<x-form.group.text name="reference" label="{{ trans($textReference) }}" not-required />
<x-form.group.text name="reference" label="{{ trans($textReference) }}" form-group-class="sm:col-span-6" not-required />
@endif
</div>
</div>
<div class="sm:col-span-3">
<div class="relative sm:col-span-6 grid gap-x-8 gap-y-6">
@if (! $hideCanLogin)
<div class="mt-9">
<div class="sm:col-span-6 mt-9 mb-2">
@if (empty($contact))
<x-tooltip id="tooltip-client_portal-text" placement="bottom" message="{{ trans('customers.can_login_description') }}">
<x-form.group.checkbox
name="create_user"
:options="['1' => trans('customers.can_login')]"
@input="onCanLogin($event)"
checkbox-class="sm:col-span-6"
/>
</x-tooltip>
@else
@ -46,7 +48,6 @@
<x-form.group.checkbox
name="create_user"
:options="['1' => trans('customers.user_created')]"
checkbox-class="sm:col-span-6"
checked
disabled
/>
@ -55,7 +56,6 @@
<x-form.group.checkbox
name="create_user"
:options="['1' => trans('customers.can_login')]"
checkbox-class="sm:col-span-6"
@input="onCanLogin($event)"
/>
</x-tooltip>
@ -65,8 +65,9 @@
@endif
@if (! $hideLogo)
<x-form.group.file name="logo" label="{{ trans_choice('general.pictures', 1) }}" :value="! empty($contact) ? $contact->logo : false" not-required />
<x-form.group.file name="logo" label="{{ trans_choice('general.pictures', 1) }}" :value="! empty($contact) ? $contact->logo : false" form-group-class="sm:col-span-6" not-required />
@endif
</div>
</div>
</x-slot>
</x-form.section>

View File

@ -7,23 +7,27 @@
</x-slot>
<x-slot name="body">
<div class="sm:col-span-2 grid gap-x-8 gap-y-6">
<div class="sm:col-span-2">
<div class="relative sm:col-span-6 grid gap-x-8 gap-y-6">
@if (! $hideDocumentTitle)
<x-form.group.text name="title" label="{{ trans('settings.invoice.title') }}" value="{{ $titleSetting }}" not-required data-field="setting" />
<x-form.group.text name="title" label="{{ trans('settings.invoice.title') }}" value="{{ $titleSetting }}" not-required data-field="setting" form-group-class="sm:col-span-6" />
@endif
@if (! $hideDocumentSubheading)
<x-form.group.text name="subheading" label="{{ trans('settings.invoice.subheading') }}" value="{{ $subheadingSetting }}" not-required data-field="setting" />
<x-form.group.text name="subheading" label="{{ trans('settings.invoice.subheading') }}" value="{{ $subheadingSetting }}" not-required data-field="setting" form-group-class="sm:col-span-6" />
@endif
</div>
</div>
<div class="sm:col-span-1"></div>
<div class="sm:col-span-2">
<div class="relative sm:col-span-6 grid gap-x-8 gap-y-6">
@if (! $hideLogo)
<x-form.group.file name="company_logo" label="{{ trans('settings.company.logo') }}" :value="setting('company.logo')" not-required data-field="setting" />
<x-form.group.file name="company_logo" label="{{ trans('settings.company.logo') }}" :value="setting('company.logo')" not-required data-field="setting" form-group-class="sm:col-span-6" />
@endif
</div>
</div>
<div class="sm:col-span-2 relative">
@if (! $hideCompanyEdit)

View File

@ -45,7 +45,7 @@
<div class="text-xs mt-6" style="margin-left: 0 !important;">
<span class="font-medium">
{{ trans('invoices.payment_received') }} :
{{ trans('invoices.payments_received') }}:
</span>
@if ($transactions->count())

View File

@ -52,7 +52,7 @@
@if (! $hideCompanyTaxNumber)
@if (setting('company.tax_number'))
<p>
<span class="text-medium text-default">
<span class="font-semibold">
{{ trans('general.tax_number') }}:
</span>
{{ setting('company.tax_number') }}
@ -153,7 +153,7 @@
@if (! $hideContactTaxNumber)
@if ($document->contact_tax_number)
<p>
<span class="text-medium text-default">
<span class="font-semibold">
{{ trans('general.tax_number') }}:
</span>
{{ $document->contact_tax_number }}

View File

@ -116,7 +116,7 @@
@if (! $hideContactTaxNumber)
@if ($document->contact_tax_number)
<p>
<span class="text-medium text-default">
<span class="font-semibold">
{{ trans('general.tax_number') }}:
</span>
{{ $document->contact_tax_number }}

View File

@ -52,7 +52,7 @@
@if (! $hideCompanyTaxNumber)
<p class="text-white">
@if (setting('company.tax_number'))
<span class="text-medium text-default">
<span class="font-semibold">
{{ trans('general.tax_number') }}:
</span>
@ -122,7 +122,7 @@
@if (! $hideContactTaxNumber)
@if ($document->contact_tax_number)
<p>
<span class="text-medium text-default">
<span class="font-semibold">
{{ trans('general.tax_number') }}:
</span>

View File

@ -1,5 +1,5 @@
<div class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap" {{ $attributes }}>
<a href="{!! $href !!}" class="w-full h-full flex items-center rounded-md px-2 text-sm font-normal hover:bg-lilac-100">
<a href="{!! $href !!}" target="{!! $target !!}" class="w-full h-full flex items-center rounded-md px-2 text-sm font-normal hover:bg-lilac-100">
{!! $slot !!}
</a>
</div>

View File

@ -3,6 +3,9 @@
:date-range-text="{{ json_encode(trans('general.date_range')) }}"
middle-text="{!! trans('recurring.limit_middle') !!}"
end-text="{{ Str::plural(Str::replace('-recurring', '', $type)) }}"
send-email-text="{{ trans('recurring.send_email_auto') }}"
send-email-yes-text="{{ trans('general.yes') }}"
send-email-no-text="{{ trans('general.no') }}"
:frequencies="{{ json_encode($frequencies) }}"
frequency-text="{!! trans('recurring.frequency_type', ['type' => Str::replace('-recurring', '', $type)]) !!}"
@ -65,6 +68,15 @@
@endif
:limit-date-error="form.errors.get('recurring_limit_date')"
send-email-show="{{ $sendEmailShow }}"
send-email-value="{{ $sendEmail }}"
@if ($attributes->has('@send_email'))
@send_email="form.recurring_send_email = $event;{{ $attributes['@send_email'] }}"
@else
@send_email="form.recurring_send_email = $event"
@endif
:send-email-error="form.errors.get('recurring_send_email')"
date-format="{{ company_date_format() }}"
{{ $attributes }}

View File

@ -1,5 +1,5 @@
@props([
'metaTitle', 'title',
'metaTitle', 'title', 'currency'
])
<head>

View File

@ -0,0 +1,5 @@
@if (! empty($payment_method))
{!! $payment_method !!}
@else
<x-empty-data />
@endif

View File

@ -8,7 +8,14 @@
}
@endphp
<div data-swiper="{{ $slides }}" x-data="{ active: window.location.hash.split('#')[1] == undefined ? '{{ $active }}' : window.location.hash.split('#')[1] }">
<div
data-swiper="{{ $slides }}"
@if(! $attributes->has('ignore-hash'))
x-data="{ active: window.location.hash.split('#')[1] == undefined ? '{{ $active }}' : window.location.hash.split('#')[1] }"
@else
x-data="{ active: '{{ $active }}' }"
@endif
>
<div data-tabs-swiper>
<ul data-tabs-swiper-wrapper {{ ((! $attributes->has('override')) || ($attributes->has('override') && ! in_array('class', explode(',', $attributes->get('override'))))) ? $attributes->merge(['class' => 'inline-flex overflow-x-scroll large-overflow-unset']) : $attributes }}>
{!! $navs !!}

View File

@ -132,7 +132,7 @@
</td>
<td valign="top" class="border-bottom-dashed-black" style="width:70%; margin: 0px; padding: 8px 0 0 0; font-size: 12px;">
{{ !empty($payment_methods[$transaction->payment_method]) ? $payment_methods[$transaction->payment_method] : trans('general.na') }}
<x-payment-method :method="$transaction->payment_method" />
</td>
</tr>
@endif

View File

@ -214,11 +214,7 @@
</td>
<td valign="top" valign="top" class="border-bottom-dashed-black" style="width:70%; margin: 0px; padding: 8px 0 0 0; font-size: 12px;">
@if (! empty($payment_methods[$transfer->expense_transaction->payment_method]))
{!! $payment_methods[$transfer->expense_transaction->payment_method] !!}
@else
<x-empty-data />
@endif
<x-payment-method :method="$transfer->expense_transaction->payment_method" />
</td>
</tr>
@stack('payment_method_input_end')

View File

@ -30,11 +30,7 @@
</td>
<td valign="top" style="width:70%; margin: 0px; padding: 8px 0 8px 0; font-size: 12px; border-bottom:1px solid #adadad;">
@if (! empty($payment_methods[$transfer->expense_transaction->payment_method]))
{!! $payment_methods[$transfer->expense_transaction->payment_method] !!}
@else
<x-empty-data />
@endif
<x-payment-method :method="$transfer->expense_transaction->payment_method" />
</td>
</tr>
@stack('payment_method_input_end')

View File

@ -224,11 +224,7 @@
</td>
<td valign="top" style="width:70%; margin: 0px; padding: 0; font-size: 12px; border-bottom:1px solid; line-height: 24px;">
@if (! empty($payment_methods[$transfer->expense_transaction->payment_method]))
{!! $payment_methods[$transfer->expense_transaction->payment_method] !!}
@else
<x-empty-data />
@endif
<x-payment-method :method="$transfer->expense_transaction->payment_method" />
</td>
</tr>
@stack('payment_method_input_end')

View File

@ -100,7 +100,7 @@
<x-slot name="body" class="block" override="class">
<div class="text-xs mt-1" style="margin-left: 0 !important;">
<span class="font-medium">
{{ trans('invoices.payment_received') }} :
{{ trans('invoices.payments_received') }}:
</span>
@if ($invoice->transactions->count())

View File

@ -98,7 +98,7 @@
<x-slot name="body" class="block" override="class">
<div class="text-xs mt-1" style="margin-left: 0 !important;">
<span class="font-medium">
{{ trans('invoices.payment_received') }} :
{{ trans('invoices.payments_received') }}:
</span>
@if ($invoice->transactions->count())

View File

@ -107,7 +107,7 @@
<x-slot name="body" class="block" override="class">
<div class="text-xs mt-1" style="margin-left: 0 !important;">
<span class="font-medium">
{{ trans('invoices.payment_received') }} :
{{ trans('invoices.payments_received') }}:
</span>
@if ($invoice->transactions->count())

View File

@ -41,7 +41,7 @@
</x-table.td>
<x-table.td class="w-4/12 sm:w-3/12">
{{ $payment_methods[$item->payment_method] }}
<x-payment-method :method="$item->payment_method" type="customer" />
</x-table.td>
<x-table.td class="w-3/12" hidden-mobile>

View File

@ -17,21 +17,23 @@
<x-form.group.color name="color" label="{{ trans('general.color') }}" />
@if ($type_disabled)
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" disabled />
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" v-disabled="true" />
<input type="hidden" name="type" value="{{ $category->type }}" />
@else
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" change="updateParentCategories" />
@endif
<x-form.group.select name="parent_id" label="{{ trans('general.parent') . ' ' . trans_choice('general.categories', 1) }}" :options="$parent_categories" not-required dynamicOptions="categoriesBasedTypes" sort-options="false" />
<x-form.input.hidden name="parent_category_id" value="{{ $category->parent_id }}" />
<x-form.input.hidden name="categories" value="{{ json_encode($categories) }}" />
@endif
</x-slot>
</x-form.section>
@if (! $type_disabled)
<x-form.group.switch name="enabled" label="{{ trans('general.enabled') }}" />
@endif
@can('update-settings-categories')
<x-form.section>

View File

@ -67,7 +67,11 @@
@foreach($categories as $item)
<x-table.tr href="{{ route('categories.edit', $item->id) }}">
<x-table.td kind="bulkaction">
<x-index.bulkaction.single id="{{ $item->id }}" name="{{ $item->name }}" />
<x-index.bulkaction.single
id="{{ $item->id }}"
name="{{ $item->name }}"
:disabled="($item->isTransferCategory()) ? true : false"
/>
</x-table.td>
<x-table.td class="w-5/12">

View File

@ -10,7 +10,13 @@
</x-slot>
<x-slot name="body">
<x-form.group.account name="account" not-required without-change />
<x-form.group.select
name="account"
label="{{ trans_choice('general.accounts', 1) }}"
:options="$accounts"
:selected="setting('default.account')"
not-required
/>
<x-form.group.currency name="currency" not-required />

View File

@ -4,9 +4,11 @@ namespace Tests\Feature\Banking;
use App\Exports\Banking\Transactions as Export;
use App\Jobs\Banking\CreateTransaction;
use App\Notifications\Banking\Transaction as Notification;
use App\Models\Banking\Transaction;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Notification as NotificationFacade;
use Maatwebsite\Excel\Facades\Excel;
use Tests\Feature\FeatureTestCase;
@ -70,6 +72,43 @@ class TransactionsTest extends FeatureTestCase
]);
}
public function testItShouldSendTransactionEmail()
{
NotificationFacade::fake();
$transaction = $this->dispatch(new CreateTransaction($this->getRequest()));
$this->loginAs()
->post(route('modals.transactions.emails.store', $transaction->id), $this->getEmailRequest($transaction))
->assertStatus(200);
$this->assertFlashLevel('success');
NotificationFacade::assertSentTo($transaction->contact, Notification::class);
}
public function testItShouldHitRateLimitForSendTransactionEmail()
{
NotificationFacade::fake();
$limit_per_minute = (int) config('app.throttles.email.minute');
$transaction = $this->dispatch(new CreateTransaction($this->getRequest()));
for ($i = 0; $i < $limit_per_minute; $i++) {
$this->loginAs()
->post(route('modals.transactions.emails.store', $transaction->id), $this->getEmailRequest($transaction));
}
$this->loginAs()
->post(route('modals.transactions.emails.store', $transaction->id), $this->getEmailRequest($transaction))
->assertJson([
'success' => false,
]);
NotificationFacade::assertSentTimes(Notification::class, $limit_per_minute);
}
public function testItShouldSeeTransactionUpdatePage()
{
$request = $this->getRequest();
@ -192,4 +231,18 @@ class TransactionsTest extends FeatureTestCase
return $factory->raw();
}
public function getEmailRequest($transaction)
{
$email_template = config('type.transaction.' . $transaction->type . '.email_template');
$notification = new Notification($transaction, $email_template, true);
return [
'transaction_id' => $transaction->id,
'to' => $transaction->contact->email,
'subject' => $notification->getSubject(),
'body' => $notification->getBody(),
];
}
}

View File

@ -5,9 +5,11 @@ namespace Tests\Feature\Sales;
use App\Exports\Sales\Invoices as Export;
use App\Jobs\Document\CreateDocument;
use App\Models\Document\Document;
use App\Notifications\Sale\Invoice as Notification;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Notification as NotificationFacade;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use Tests\Feature\FeatureTestCase;
@ -127,6 +129,43 @@ class InvoicesTest extends FeatureTestCase
]);
}
public function testItShouldSendInvoiceEmail()
{
NotificationFacade::fake();
$invoice = $this->dispatch(new CreateDocument($this->getRequest()));
$this->loginAs()
->post(route('modals.invoices.emails.store', $invoice->id), $this->getEmailRequest($invoice))
->assertStatus(200);
$this->assertFlashLevel('success');
NotificationFacade::assertSentTo($invoice->contact, Notification::class);
}
public function testItShouldHitRateLimitForSendInvoiceEmail()
{
NotificationFacade::fake();
$limit_per_minute = (int) config('app.throttles.email.minute');
$invoice = $this->dispatch(new CreateDocument($this->getRequest()));
for ($i = 0; $i < $limit_per_minute; $i++) {
$this->loginAs()
->post(route('modals.invoices.emails.store', $invoice->id), $this->getEmailRequest($invoice));
}
$this->loginAs()
->post(route('modals.invoices.emails.store', $invoice->id), $this->getEmailRequest($invoice))
->assertJson([
'success' => false,
]);
NotificationFacade::assertSentTimes(Notification::class, $limit_per_minute);
}
public function testItShouldSeeInvoiceUpdatePage()
{
$request = $this->getRequest();
@ -254,4 +293,16 @@ class InvoicesTest extends FeatureTestCase
return $factory->raw();
}
public function getEmailRequest($invoice)
{
$notification = new Notification($invoice, 'invoice_new_customer', true);
return [
'document_id' => $invoice->id,
'to' => $invoice->contact->email,
'subject' => $notification->getSubject(),
'body' => $notification->getBody(),
];
}
}