Merge branch 'akaunting:master' into master
This commit is contained in:
commit
8771f99fda
@ -20,6 +20,6 @@ abstract class ImportMultipleSheets implements ShouldQueue, WithChunkReading, Wi
|
||||
|
||||
public function chunkSize(): int
|
||||
{
|
||||
return 100;
|
||||
return config('excel.imports.chunk_size');
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Abstracts\View\Components\Documents;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Interfaces\Utility\DocumentNumber;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Document\Document;
|
||||
use App\Models\Setting\Currency;
|
||||
@ -815,10 +816,14 @@ abstract class Form extends Component
|
||||
return $document->document_number;
|
||||
}
|
||||
|
||||
$document_number = $this->getNextDocumentNumber($type);
|
||||
$contact = ($this->contact instanceof Contact) ? $this->contact : null;
|
||||
|
||||
$utility = app(DocumentNumber::class);
|
||||
|
||||
$document_number = $utility->getNextNumber($type, $contact);
|
||||
|
||||
if (empty($document_number)) {
|
||||
$document_number = $this->getNextDocumentNumber(Document::INVOICE_TYPE);
|
||||
$document_number = $utility->getNextNumber(Document::INVOICE_TYPE, $contact);
|
||||
}
|
||||
|
||||
return $document_number;
|
||||
|
@ -741,9 +741,7 @@ abstract class Show extends Component
|
||||
return $textRecurringType;
|
||||
}
|
||||
|
||||
$default_key = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.prefix');
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'recurring_tye', $default_key);
|
||||
$translation = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.tab_document');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
@ -1254,9 +1252,11 @@ abstract class Show extends Component
|
||||
return $hideName;
|
||||
}
|
||||
|
||||
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
|
||||
return $hideName;
|
||||
if ($hideName === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'name');
|
||||
@ -1265,8 +1265,7 @@ abstract class Show extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDescription($type, $hideDescription)
|
||||
@ -1276,8 +1275,8 @@ abstract class Show extends Component
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideDescription = setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||
return $hideDescription;
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'description');
|
||||
@ -1286,8 +1285,7 @@ abstract class Show extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.hide_item_description', $hideDescription);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideQuantity($type, $hideQuantity)
|
||||
@ -1296,9 +1294,11 @@ abstract class Show extends Component
|
||||
return $hideQuantity;
|
||||
}
|
||||
|
||||
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideQuantity = setting($this->getDocumentSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
|
||||
return $hideQuantity;
|
||||
if ($hideQuantity === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'quantity');
|
||||
@ -1307,8 +1307,7 @@ abstract class Show extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHidePrice($type, $hidePrice)
|
||||
@ -1317,9 +1316,11 @@ abstract class Show extends Component
|
||||
return $hidePrice;
|
||||
}
|
||||
|
||||
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hidePrice = setting($this->getDocumentSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
|
||||
return $hidePrice;
|
||||
if ($hidePrice === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'price');
|
||||
@ -1328,8 +1329,7 @@ abstract class Show extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDiscount($type, $hideDiscount)
|
||||
@ -1360,8 +1360,8 @@ abstract class Show extends Component
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideAmount = setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||
return $hideAmount;
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'amount');
|
||||
@ -1370,7 +1370,6 @@ abstract class Show extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.hide_amount', $hideAmount);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -589,9 +589,11 @@ abstract class Template extends Component
|
||||
return $hideName;
|
||||
}
|
||||
|
||||
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
|
||||
return $hideName;
|
||||
if ($hideName === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'name');
|
||||
@ -600,8 +602,7 @@ abstract class Template extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDescription($type, $hideDescription)
|
||||
@ -611,8 +612,8 @@ abstract class Template extends Component
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideDescription = setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||
return $hideDescription;
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'description');
|
||||
@ -621,8 +622,7 @@ abstract class Template extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.hide_item_description', $hideDescription);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideQuantity($type, $hideQuantity)
|
||||
@ -631,9 +631,11 @@ abstract class Template extends Component
|
||||
return $hideQuantity;
|
||||
}
|
||||
|
||||
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideQuantity = setting($this->getDocumentSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
|
||||
return $hideQuantity;
|
||||
if ($hideQuantity === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'quantity');
|
||||
@ -642,8 +644,7 @@ abstract class Template extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHidePrice($type, $hidePrice)
|
||||
@ -652,9 +653,11 @@ abstract class Template extends Component
|
||||
return $hidePrice;
|
||||
}
|
||||
|
||||
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hidePrice = setting($this->getDocumentSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
|
||||
return $hidePrice;
|
||||
if ($hidePrice === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'price');
|
||||
@ -663,8 +666,7 @@ abstract class Template extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDiscount($type, $hideDiscount)
|
||||
@ -695,8 +697,8 @@ abstract class Template extends Component
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideAmount = setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||
return $hideAmount;
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'amount');
|
||||
@ -705,8 +707,7 @@ abstract class Template extends Component
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.hide_amount', $hideAmount);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getPrint($print)
|
||||
|
@ -1113,9 +1113,7 @@ abstract class Show extends Component
|
||||
return $textRecurringType;
|
||||
}
|
||||
|
||||
$default_key = config('type.transaction.' . $type . '.translation.transactions');
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'recurring_type', $default_key);
|
||||
$translation = config('type.transaction.' . $type . '.translation.transactions');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
|
@ -68,6 +68,14 @@ class Update extends Command
|
||||
|
||||
$this->old = $this->getOldVersion();
|
||||
|
||||
if (version_compare($this->old, $this->new, '>=')) {
|
||||
$message = 'The current version for the ' . $this->alias . ' is the latest version!';
|
||||
|
||||
$this->info($message);
|
||||
|
||||
return self::CMD_SUCCESS;
|
||||
}
|
||||
|
||||
company($this->company)->makeCurrent();
|
||||
|
||||
if (!$path = $this->download()) {
|
||||
|
@ -74,6 +74,14 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if ($exception instanceof MailerHttpTransportException) {
|
||||
$email = $this->handleMailerExceptions($exception);
|
||||
|
||||
if (! empty($email)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
@ -197,23 +205,20 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
|
||||
if ($exception instanceof MailerHttpTransportException) {
|
||||
/**
|
||||
* Couldn't access the SentMessage object to get the email address
|
||||
* https://symfony.com/doc/current/mailer.html#debugging-emails
|
||||
*
|
||||
* https://codespeedy.com/extract-email-addresses-from-a-string-in-php
|
||||
* https://phpliveregex.com/p/IMG
|
||||
*/
|
||||
preg_match("/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/", $exception->getMessage(), $matches);
|
||||
$email = $this->handleMailerExceptions($exception);
|
||||
|
||||
if (! empty($matches[0])) {
|
||||
event(new InvalidEmailDetected($matches[0], $exception->getMessage()));
|
||||
if (! empty($email)) {
|
||||
$message = trans('notifications.menu.invalid_email.description', ['email' => $email]);
|
||||
|
||||
if ($request->ajax()) {
|
||||
return response()->json([
|
||||
'error' => trans('notifications.menu.invalid_email.description', ['email' => $matches[0]]),
|
||||
'error' => $message,
|
||||
], $exception->getCode());
|
||||
}
|
||||
|
||||
return response()->view('errors.403', [
|
||||
'message' => $message,
|
||||
], $exception->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +242,28 @@ class Handler extends ExceptionHandler
|
||||
return new Response($response, $this->getStatusCode($exception), $this->getHeaders($exception));
|
||||
}
|
||||
|
||||
protected function handleMailerExceptions(MailerHttpTransportException $exception): string
|
||||
{
|
||||
/**
|
||||
* Couldn't access the SentMessage object to get the email address
|
||||
* https://symfony.com/doc/current/mailer.html#debugging-emails
|
||||
*
|
||||
* https://codespeedy.com/extract-email-addresses-from-a-string-in-php
|
||||
* https://phpliveregex.com/p/IMG
|
||||
*/
|
||||
preg_match("/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/", $exception->getMessage(), $matches);
|
||||
|
||||
if (empty($matches[0])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$email = $matches[0];
|
||||
|
||||
event(new InvalidEmailDetected($email, $exception->getMessage()));
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the replacements array by gathering the keys and values.
|
||||
*
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Abstracts\Http\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request as BaseRequest;
|
||||
use App\Http\Requests\Auth\Reset as Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
@ -29,7 +30,7 @@ class Reset extends Controller
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
public function create(Request $request, $token = null)
|
||||
public function create(BaseRequest $request, $token = null)
|
||||
{
|
||||
return view('auth.reset.create')->with(
|
||||
['token' => $token, 'email' => $request->email]
|
||||
|
@ -47,9 +47,19 @@ class Users extends Controller
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function show()
|
||||
public function show(User $user)
|
||||
{
|
||||
return redirect()->route('users.index');
|
||||
$u = new \stdClass();
|
||||
$u->role = $user->roles()->first();
|
||||
$u->landing_pages = [];
|
||||
|
||||
event(new LandingPageShowing($u));
|
||||
|
||||
$landing_pages = $u->landing_pages;
|
||||
|
||||
$companies = $user->companies()->collect();
|
||||
|
||||
return view('auth.users.show', compact('user', 'landing_pages', 'companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +77,13 @@ class Users extends Controller
|
||||
$landing_pages = $u->landing_pages;
|
||||
|
||||
$roles = Role::all()->reject(function ($r) {
|
||||
return $r->hasPermission('read-client-portal');
|
||||
$status = $r->hasPermission('read-client-portal');
|
||||
|
||||
if ($r->name == 'employee') {
|
||||
$status = true;
|
||||
}
|
||||
|
||||
return $status;
|
||||
})->pluck('display_name', 'id');
|
||||
|
||||
$companies = user()->companies()->take(setting('default.select_limit'))->get()->sortBy('name')->pluck('name', 'id');
|
||||
@ -89,7 +105,7 @@ class Users extends Controller
|
||||
$response = $this->ajaxDispatch(new CreateUser($request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('users.index');
|
||||
$response['redirect'] = route('users.show', $response['data']->id);
|
||||
|
||||
$message = trans('messages.success.invited', ['type' => trans_choice('general.users', 1)]);
|
||||
|
||||
@ -129,12 +145,21 @@ class Users extends Controller
|
||||
if ($user->isCustomer()) {
|
||||
// Show only roles with customer permission
|
||||
$roles = Role::all()->reject(function ($r) {
|
||||
return !$r->hasPermission('read-client-portal');
|
||||
return ! $r->hasPermission('read-client-portal');
|
||||
})->pluck('display_name', 'id');
|
||||
} else if ($user->isEmployee()) {
|
||||
// Show only roles with employee permission
|
||||
$roles = Role::where('name', 'employee')->get()->pluck('display_name', 'id');
|
||||
} else {
|
||||
// Don't show roles with customer permission
|
||||
$roles = Role::all()->reject(function ($r) {
|
||||
return $r->hasPermission('read-client-portal');
|
||||
$status = $r->hasPermission('read-client-portal');
|
||||
|
||||
if ($r->name == 'employee') {
|
||||
$status = true;
|
||||
}
|
||||
|
||||
return $status;
|
||||
})->pluck('display_name', 'id');
|
||||
}
|
||||
|
||||
@ -176,7 +201,7 @@ class Users extends Controller
|
||||
$response = $this->ajaxDispatch(new UpdateUser($user, $request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = user()->can('read-auth-users') ? route('users.index') : route('users.edit', $user->id);
|
||||
$response['redirect'] = user()->can('read-auth-users') ? route('users.show', $user->id) : route('users.edit', $user->id);
|
||||
|
||||
$message = trans('messages.success.updated', ['type' => $user->name]);
|
||||
|
||||
|
@ -35,7 +35,7 @@ class Accounts extends Controller
|
||||
*/
|
||||
public function show(Account $account)
|
||||
{
|
||||
$transactions = Transaction::with('category', 'contact', 'document')->where('account_id', $account->id)->collect(['paid_at'=> 'desc']);
|
||||
$transactions = Transaction::with('category', 'contact', 'contact.media', 'document', 'document.totals', 'document.media', 'recurring', 'media')->where('account_id', $account->id)->collect(['paid_at'=> 'desc']);
|
||||
|
||||
$transfers = Transfer::with('expense_transaction', 'expense_transaction.account', 'income_transaction', 'income_transaction.account')
|
||||
->whereHas('expense_transaction', fn ($query) => $query->where('account_id', $account->id))
|
||||
|
@ -63,9 +63,9 @@ class RecurringTransactions extends Controller
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$type = request()->get('type', 'income-recurring');
|
||||
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
|
||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
|
||||
$type = $this->getTypeRecurringTransaction(request()->get('type', 'income-recurring'));
|
||||
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
|
||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
|
||||
|
||||
$number = $this->getNextTransactionNumber('-recurring');
|
||||
|
||||
@ -139,8 +139,8 @@ class RecurringTransactions extends Controller
|
||||
public function edit(Transaction $recurring_transaction)
|
||||
{
|
||||
$type = $recurring_transaction->type;
|
||||
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
|
||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
|
||||
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
|
||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
|
||||
|
||||
$number = $this->getNextTransactionNumber('-recurring');
|
||||
|
||||
|
@ -98,10 +98,10 @@ class Transactions extends Controller
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$type = request()->get('type', 'income');
|
||||
$type = $this->getTypeTransaction(request()->get('type', 'income'));
|
||||
$real_type = $this->getRealTypeTransaction($type);
|
||||
|
||||
$number = $this->getNextTransactionNumber();
|
||||
$number = $this->getNextTransactionNumber($type);
|
||||
|
||||
$contact_type = config('type.transaction.' . $type . '.contact_type');
|
||||
|
||||
|
@ -16,6 +16,15 @@ class Companies extends Controller
|
||||
{
|
||||
use Uploads, Users;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Add CRUD permission checks to all methods only remove index method for all companies list.
|
||||
$this->middleware('permission:create-common-companies')->only('create', 'store', 'duplicate', 'import');
|
||||
$this->middleware('permission:read-common-companies')->only('show', 'edit', 'export');
|
||||
$this->middleware('permission:update-common-companies')->only('update', 'enable', 'disable');
|
||||
$this->middleware('permission:delete-common-companies')->only('destroy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
|
@ -34,6 +34,22 @@ class Uploads extends Controller
|
||||
return $this->streamMedia($media);
|
||||
}
|
||||
|
||||
public function inline($id)
|
||||
{
|
||||
try {
|
||||
$media = Media::find($id);
|
||||
} catch (\Exception $e) {
|
||||
return response(null, 204);
|
||||
}
|
||||
|
||||
// Get file path
|
||||
if (!$this->getMediaPathOnStorage($media)) {
|
||||
return response(null, 204);
|
||||
}
|
||||
|
||||
return $this->streamMedia($media, 'inline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specified resource.
|
||||
*
|
||||
|
@ -242,16 +242,30 @@ class Item extends Controller
|
||||
$this->dispatch(new InstallModule($request['alias'], company_id()));
|
||||
|
||||
$name = module($request['alias'])->getName();
|
||||
$module_routes = module_attribute($request['alias'], 'routes', []);
|
||||
|
||||
$message = trans('modules.installed', ['module' => $name]);
|
||||
|
||||
flash($message)->success();
|
||||
|
||||
$redirect = route('apps.app.show', $request['alias']);
|
||||
|
||||
// Get module.json redirect route
|
||||
if (! empty($module_routes['redirect_after_install'])) {
|
||||
if (is_array($module_routes['redirect_after_install'])) {
|
||||
$route = array_shift($module_routes['redirect_after_install']);
|
||||
|
||||
$redirect = route($route, $module_routes['redirect_after_install']);
|
||||
} else {
|
||||
$redirect = route($module_routes['redirect_after_install']);
|
||||
}
|
||||
}
|
||||
|
||||
$json = [
|
||||
'success' => true,
|
||||
'error' => false,
|
||||
'message' => null,
|
||||
'redirect' => route('apps.app.show', $request['alias']),
|
||||
'redirect' => $redirect,
|
||||
'data' => [
|
||||
'name' => $name,
|
||||
'alias' => $request['alias'],
|
||||
|
@ -7,7 +7,6 @@ use App\Exports\Purchases\Bills as Export;
|
||||
use App\Http\Requests\Common\Import as ImportRequest;
|
||||
use App\Http\Requests\Document\Document as Request;
|
||||
use App\Imports\Purchases\Bills as Import;
|
||||
use App\Jobs\Banking\CreateBankingDocumentTransaction;
|
||||
use App\Jobs\Document\CreateDocument;
|
||||
use App\Jobs\Document\DeleteDocument;
|
||||
use App\Jobs\Document\DuplicateDocument;
|
||||
@ -31,7 +30,7 @@ class Bills extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$bills = Document::bill()->with('contact', 'items', 'last_history', 'transactions')->collect(['issued_at' => 'desc']);
|
||||
$bills = Document::bill()->with('contact', 'items', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['issued_at' => 'desc']);
|
||||
|
||||
return $this->response('purchases.bills.index', compact('bills'));
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ use App\Jobs\Document\DuplicateDocument;
|
||||
use App\Jobs\Document\SendDocument;
|
||||
use App\Jobs\Document\UpdateDocument;
|
||||
use App\Models\Document\Document;
|
||||
use App\Notifications\Sale\Invoice as Notification;
|
||||
use App\Traits\Documents;
|
||||
|
||||
class Invoices extends Controller
|
||||
@ -32,7 +31,7 @@ class Invoices extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$invoices = Document::invoice()->with('contact', 'items', 'last_history', 'transactions')->collect(['document_number'=> 'desc']);
|
||||
$invoices = Document::invoice()->with('contact', 'items', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['document_number'=> 'desc']);
|
||||
|
||||
return $this->response('sales.invoices.index', compact('invoices'));
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ class CustomMail extends FormRequest
|
||||
'to' => 'required|email',
|
||||
'subject' => 'required|string',
|
||||
'body' => 'required|string',
|
||||
'attachments.*' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ class Item extends JsonResource
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'sale_price' => $this->sale_price,
|
||||
'sale_price_formatted' => money($this->sale_price, default_currency(), true)->format(),
|
||||
'sale_price_formatted' => money((double) $this->sale_price, default_currency(), true)->format(),
|
||||
'purchase_price' => $this->purchase_price,
|
||||
'purchase_price_formatted' => money($this->purchase_price, default_currency(), true)->format(),
|
||||
'purchase_price_formatted' => money((double) $this->purchase_price, default_currency(), true)->format(),
|
||||
'category_id' => $this->category_id,
|
||||
'picture' => $this->picture,
|
||||
'enabled' => $this->enabled,
|
||||
|
@ -8,6 +8,7 @@ use App\Http\Resources\Document\DocumentHistory;
|
||||
use App\Http\Resources\Document\DocumentItem;
|
||||
use App\Http\Resources\Document\DocumentItemTax;
|
||||
use App\Http\Resources\Document\DocumentTotal;
|
||||
use App\Http\Resources\Setting\Category;
|
||||
use App\Http\Resources\Setting\Currency;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@ -32,6 +33,7 @@ class Document extends JsonResource
|
||||
'due_at' => $this->due_at ? $this->due_at->toIso8601String() : '',
|
||||
'amount' => $this->amount,
|
||||
'amount_formatted' => money($this->amount, $this->currency_code, true)->format(),
|
||||
'category_id' => $this->category_id,
|
||||
'currency_code' => $this->currency_code,
|
||||
'currency_rate' => $this->currency_rate,
|
||||
'contact_id' => $this->contact_id,
|
||||
@ -50,6 +52,7 @@ class Document extends JsonResource
|
||||
'created_by' => $this->created_by,
|
||||
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',
|
||||
'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '',
|
||||
'category' => new Category($this->category),
|
||||
'currency' => new Currency($this->currency),
|
||||
'contact' => new Contact($this->contact),
|
||||
'histories' => [static::$wrap => DocumentHistory::collection($this->histories)],
|
||||
|
12
app/Interfaces/Utility/DocumentNumber.php
Normal file
12
app/Interfaces/Utility/DocumentNumber.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interfaces\Utility;
|
||||
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
interface DocumentNumber
|
||||
{
|
||||
public function getNextNumber(string $type, ?Contact $contact): string;
|
||||
|
||||
public function increaseNextNumber(string $type, ?Contact $contact): void;
|
||||
}
|
12
app/Interfaces/Utility/TransactionNumber.php
Normal file
12
app/Interfaces/Utility/TransactionNumber.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interfaces\Utility;
|
||||
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
interface TransactionNumber
|
||||
{
|
||||
public function getNextNumber(string $type, string $suffix, ?Contact $contact): string;
|
||||
|
||||
public function increaseNextNumber(string $type, string $suffix, ?Contact $contact): void;
|
||||
}
|
@ -23,6 +23,12 @@ class CreateInvitation extends Job
|
||||
public function handle(): UserInvitation
|
||||
{
|
||||
\DB::transaction(function () {
|
||||
$invitations = UserInvitation::where('user_id', $this->user->id)->get();
|
||||
|
||||
foreach ($invitations as $invitation) {
|
||||
$invitation->delete();
|
||||
}
|
||||
|
||||
$this->invitation = UserInvitation::create([
|
||||
'user_id' => $this->user->id,
|
||||
'token' => (string) Str::uuid(),
|
||||
|
@ -12,6 +12,10 @@ class UpdateRole extends Job implements ShouldUpdate
|
||||
{
|
||||
public function handle(): Role
|
||||
{
|
||||
if (in_array($this->model->name, config('roles.defaults', ['admin', 'manager', 'accountant', 'employee']))) {
|
||||
$this->request->name = $this->model->name;
|
||||
}
|
||||
|
||||
event(new RoleUpdating($this->model, $this->request));
|
||||
|
||||
\DB::transaction(function () {
|
||||
|
@ -16,6 +16,12 @@ class CreateTransaction extends Job implements HasOwner, HasSource, ShouldCreate
|
||||
{
|
||||
event(new TransactionCreating($this->request));
|
||||
|
||||
if (! array_key_exists($this->request->get('type'), config('type.transaction'))) {
|
||||
$type = (empty($this->request->get('recurring_frequency')) || ($this->request->get('recurring_frequency') == 'no')) ? Transaction::INCOME_TYPE : Transaction::INCOME_RECURRING_TYPE;
|
||||
|
||||
$this->request->merge(['type' => $type]);
|
||||
}
|
||||
|
||||
\DB::transaction(function () {
|
||||
$this->model = Transaction::create($this->request->all());
|
||||
|
||||
|
@ -34,6 +34,7 @@ class DeleteAccount extends Job implements ShouldDelete
|
||||
{
|
||||
$rels = [
|
||||
'transactions' => 'transactions',
|
||||
'reconciliations' => 'reconciliations',
|
||||
];
|
||||
|
||||
$relationships = $this->countRelationships($this->model, $rels);
|
||||
|
@ -16,6 +16,12 @@ class UpdateTransaction extends Job implements ShouldUpdate
|
||||
|
||||
event(new TransactionUpdating($this->model, $this->request));
|
||||
|
||||
if (! array_key_exists($this->request->get('type'), config('type.transaction'))) {
|
||||
$type = (empty($this->request->get('recurring_frequency')) || ($this->request->get('recurring_frequency') == 'no')) ? Transaction::INCOME_TYPE : Transaction::INCOME_RECURRING_TYPE;
|
||||
|
||||
$this->request->merge(['type' => $type]);
|
||||
}
|
||||
|
||||
\DB::transaction(function () {
|
||||
$this->model->update($this->request->all());
|
||||
|
||||
|
@ -30,10 +30,15 @@ class SendDocumentAsCustomMail extends Job
|
||||
$custom_mail['cc'] = user()->email;
|
||||
}
|
||||
|
||||
$attachments = collect($this->request->get('attachments', []))
|
||||
->filter(fn($value) => $value == true)
|
||||
->keys()
|
||||
->all();
|
||||
|
||||
$notification = config('type.document.' . $document->type . '.notification.class');
|
||||
|
||||
// Notify the contact
|
||||
$document->contact->notify(new $notification($document, $this->template_alias, true, $custom_mail));
|
||||
$document->contact->notify(new $notification($document, $this->template_alias, true, $custom_mail, $attachments));
|
||||
|
||||
event(new DocumentSent($document));
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ class SendDocumentPaymentNotification
|
||||
$document = $event->document;
|
||||
$transaction = $document->transactions()->latest()->first();
|
||||
|
||||
if (! $transaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the customer
|
||||
if ($document->contact && !empty($document->contact_email)) {
|
||||
$document->contact->notify(new Notification($document, $transaction, "{$document->type}_payment_customer"), true);
|
||||
|
@ -29,6 +29,13 @@ class DisablePersonDueToInvalidEmail
|
||||
return;
|
||||
}
|
||||
|
||||
// If only one user is left, don't disable it
|
||||
$users = company()?->users;
|
||||
|
||||
if ($users && $users->count() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->user->enabled = false;
|
||||
$event->user->save();
|
||||
}
|
||||
|
@ -10,7 +10,11 @@ class SendInvalidEmailNotification
|
||||
{
|
||||
public function handle(Event $event): void
|
||||
{
|
||||
$users = company()->users;
|
||||
$users = company()?->users;
|
||||
|
||||
if (empty($users)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->notifyAdminsAboutInvalidContactEmail($event, $users);
|
||||
|
||||
@ -44,7 +48,7 @@ class SendInvalidEmailNotification
|
||||
return;
|
||||
}
|
||||
|
||||
$type = trans('general.users', 1);
|
||||
$type = trans_choice('general.users', 1);
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($user->cannot('read-notifications')) {
|
||||
|
@ -30,12 +30,16 @@ class TellFirewallTooManyEmailsSent
|
||||
|
||||
public function loadConfig(): void
|
||||
{
|
||||
if (! empty(Config::get('firewall.middleware.' . $this->middleware))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = array_merge_recursive(
|
||||
Config::get('firewall'),
|
||||
[
|
||||
'middleware' => [
|
||||
$this->middleware => [
|
||||
'enabled' => env('FIREWALL_MIDDLEWARE_' . strtoupper($this->middleware) . '_ENABLED', env('FIREWALL_ENABLED', true)),
|
||||
'enabled' => env('FIREWALL_MIDDLEWARE_' . strtoupper($this->middleware) . '_ENABLED', Config::get('firewall.enabled', true)),
|
||||
|
||||
'methods' => ['post'],
|
||||
|
||||
|
43
app/Listeners/Update/V30/Version3015.php
Normal file
43
app/Listeners/Update/V30/Version3015.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Update\V30;
|
||||
|
||||
use App\Abstracts\Listeners\Update as Listener;
|
||||
use App\Events\Install\UpdateFinished as Event;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Version3015 extends Listener
|
||||
{
|
||||
const ALIAS = 'core';
|
||||
|
||||
const VERSION = '3.0.15';
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Event $event)
|
||||
{
|
||||
if ($this->skipThisUpdate($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('stdout')->info('Updating to 3.0.15 version...');
|
||||
|
||||
$this->updateDatabase();
|
||||
|
||||
Log::channel('stdout')->info('Done!');
|
||||
}
|
||||
|
||||
public function updateDatabase(): void
|
||||
{
|
||||
Log::channel('stdout')->info('Updating database...');
|
||||
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
|
||||
Log::channel('stdout')->info('Database updated.');
|
||||
}
|
||||
}
|
@ -29,6 +29,35 @@ class Role extends LaratrustRole
|
||||
*/
|
||||
public $cloneable_relations = ['permissions'];
|
||||
|
||||
/**
|
||||
* Scope to get all rows filtered, sorted and paginated.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $sort
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeCollect($query, $sort = 'display_name')
|
||||
{
|
||||
$request = request();
|
||||
|
||||
$search = $request->get('search');
|
||||
$limit = (int) $request->get('limit', setting('default.list_limit', '25'));
|
||||
|
||||
return $query->usingSearchString($search)->sortable($sort)->paginate($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param Document $src
|
||||
* @param boolean $child
|
||||
*/
|
||||
public function onCloning($src, $child = null)
|
||||
{
|
||||
$this->name = $src->name . '-' . Role::max('id') + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line actions.
|
||||
*
|
||||
@ -71,33 +100,4 @@ class Role extends LaratrustRole
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to get all rows filtered, sorted and paginated.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $sort
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeCollect($query, $sort = 'display_name')
|
||||
{
|
||||
$request = request();
|
||||
|
||||
$search = $request->get('search');
|
||||
$limit = (int) $request->get('limit', setting('default.list_limit', '25'));
|
||||
|
||||
return $query->usingSearchString($search)->sortable($sort)->paginate($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param Document $src
|
||||
* @param boolean $child
|
||||
*/
|
||||
public function onCloning($src, $child = null)
|
||||
{
|
||||
$this->name = $src->name . '-' . Role::max('id') + 1;
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,9 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
*/
|
||||
protected $casts = [
|
||||
'enabled' => 'boolean',
|
||||
'last_logged_in_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
@ -48,13 +51,6 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
*/
|
||||
protected $hidden = ['password', 'remember_token'];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['last_logged_in_at', 'created_at', 'updated_at', 'deleted_at'];
|
||||
|
||||
/**
|
||||
* Sortable columns.
|
||||
*
|
||||
@ -244,6 +240,28 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
return $query->wherePermissionIs('read-admin-panel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only employees.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeIsEmployee($query)
|
||||
{
|
||||
return $query->whereHasRole('employee');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only users.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeIsNotEmployee($query)
|
||||
{
|
||||
return $query->wherePermissionIs('read-admin-panel');
|
||||
}
|
||||
|
||||
public function scopeEmail($query, $email)
|
||||
{
|
||||
return $query->where('email', '=', $email);
|
||||
@ -293,6 +311,26 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
return (bool) $this->can('read-admin-panel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if user is a employee.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmployee()
|
||||
{
|
||||
return (bool) $this->hasRole('employee');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if user is not a employee.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNotEmployee()
|
||||
{
|
||||
return (bool) ! $this->hasRole('employee');
|
||||
}
|
||||
|
||||
public function scopeSource($query, $source)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('created_from'), $source);
|
||||
@ -340,13 +378,23 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$actions[] = [
|
||||
'title' => trans('general.show'),
|
||||
'icon' => 'visibility',
|
||||
'url' => route('users.show', $this->id),
|
||||
'permission' => 'read-auth-users',
|
||||
'attributes' => [
|
||||
'id' => 'index-line-actions-show-user-' . $this->id,
|
||||
],
|
||||
];
|
||||
|
||||
$actions[] = [
|
||||
'title' => trans('general.edit'),
|
||||
'icon' => 'edit',
|
||||
'url' => route('users.edit', $this->id),
|
||||
'permission' => 'update-auth-users',
|
||||
'attributes' => [
|
||||
'id' => 'index-line-actions-show-user-' . $this->id,
|
||||
'id' => 'index-line-actions-edit-user-' . $this->id,
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -65,6 +65,11 @@ class Account extends Model
|
||||
return $this->hasMany('App\Models\Banking\Transaction');
|
||||
}
|
||||
|
||||
public function reconciliations()
|
||||
{
|
||||
return $this->hasMany('App\Models\Banking\Reconciliation');
|
||||
}
|
||||
|
||||
public function scopeName($query, $name)
|
||||
{
|
||||
return $query->where('name', '=', $name);
|
||||
|
@ -11,8 +11,6 @@ class Reconciliation extends Model
|
||||
|
||||
protected $table = 'reconciliations';
|
||||
|
||||
protected $dates = ['deleted_at', 'started_at', 'ended_at'];
|
||||
|
||||
/**
|
||||
* Attributes that should be mass-assignable.
|
||||
*
|
||||
@ -30,6 +28,8 @@ class Reconciliation extends Model
|
||||
'reconciled' => 'boolean',
|
||||
'transactions' => 'array',
|
||||
'deleted_at' => 'datetime',
|
||||
'started_at' => 'datetime',
|
||||
'ended_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -41,7 +41,7 @@ class Reconciliation extends Model
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Banking\Account');
|
||||
return $this->belongsTo('App\Models\Banking\Account')->withDefault(['name' => trans('general.na')]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,6 +304,7 @@ class Transaction extends Model
|
||||
$this->number = $this->getNextTransactionNumber($suffix);
|
||||
$this->document_id = null;
|
||||
$this->split_id = null;
|
||||
unset($this->reconciled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,8 @@ class Company extends Eloquent implements Ownable
|
||||
|
||||
protected $table = 'companies';
|
||||
|
||||
//protected $with = ['settings'];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
@ -527,7 +529,7 @@ class Company extends Eloquent implements Ownable
|
||||
|
||||
$country = setting('company.country');
|
||||
|
||||
if ($country && in_array($country, trans('countries'))) {
|
||||
if ($country && array_key_exists($country, trans('countries'))) {
|
||||
$location[] = trans('countries.' . $country);
|
||||
}
|
||||
|
||||
@ -548,7 +550,7 @@ class Company extends Eloquent implements Ownable
|
||||
'title' => trans('general.switch'),
|
||||
'icon' => 'settings_ethernet',
|
||||
'url' => route('companies.switch', $this->id),
|
||||
'permission' => 'read-common-companies',
|
||||
//'permission' => 'read-common-companies', remove this permission to allow switching to any company
|
||||
'attributes' => [
|
||||
'id' => 'index-line-actions-switch-company-' . $this->id,
|
||||
],
|
||||
|
@ -26,6 +26,13 @@ class Contact extends Model
|
||||
|
||||
protected $table = 'contacts';
|
||||
|
||||
/**
|
||||
* The relationships that should always be loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['media'];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
@ -171,6 +178,17 @@ class Contact extends Model
|
||||
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getCustomerTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to include only employees.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeEmployee($query)
|
||||
{
|
||||
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getEmployeeTypes());
|
||||
}
|
||||
|
||||
public function scopeEmail($query, $email)
|
||||
{
|
||||
return $query->where('email', '=', $email);
|
||||
@ -260,7 +278,7 @@ class Contact extends Model
|
||||
$location[] = $this->state;
|
||||
}
|
||||
|
||||
if ($this->country && in_array($this->country, trans('countries'))) {
|
||||
if ($this->country && array_key_exists($this->country, trans('countries'))) {
|
||||
$location[] = trans('countries.' . $this->country);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,13 @@ class Item extends Model
|
||||
|
||||
protected $table = 'items';
|
||||
|
||||
/**
|
||||
* The relationships that should always be loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['taxes'];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
@ -94,6 +101,15 @@ class Item extends Model
|
||||
return $query->whereNotNull($price_type . '_price');
|
||||
}
|
||||
|
||||
public function scopeType($query, $type)
|
||||
{
|
||||
if (empty($type)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->where($this->qualifyColumn('type'), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item id.
|
||||
*
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Models\Document;
|
||||
|
||||
use App\Abstracts\Model;
|
||||
use App\Interfaces\Utility\DocumentNumber;
|
||||
use App\Models\Common\Media as MediaModel;
|
||||
use App\Models\Setting\Tax;
|
||||
use App\Scopes\Document as Scope;
|
||||
@ -251,7 +252,7 @@ class Document extends Model
|
||||
}
|
||||
|
||||
$this->status = 'draft';
|
||||
$this->document_number = $this->getNextDocumentNumber($type);
|
||||
$this->document_number = app(DocumentNumber::class)->getNextNumber($type, $src->contact);
|
||||
}
|
||||
|
||||
public function getSentAtAttribute(string $value = null)
|
||||
@ -470,7 +471,7 @@ class Document extends Model
|
||||
$location[] = $this->contact_state;
|
||||
}
|
||||
|
||||
if ($this->contact_country && in_array($this->contact_country, trans('countries'))) {
|
||||
if ($this->contact_country && array_key_exists($this->contact_country, trans('countries'))) {
|
||||
$location[] = trans('countries.' . $this->contact_country);
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,13 @@ class DocumentItem extends Model
|
||||
|
||||
protected $table = 'document_items';
|
||||
|
||||
/**
|
||||
* The relationships that should always be loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['taxes'];
|
||||
|
||||
protected $appends = ['discount'];
|
||||
|
||||
protected $fillable = [
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ExportCompleted extends Notification implements ShouldQueue
|
||||
{
|
||||
@ -52,6 +53,7 @@ class ExportCompleted extends Notification implements ShouldQueue
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(trans('notifications.export.completed.title'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(trans('notifications.export.completed.description'))
|
||||
->action(trans('general.download'), $this->download_url);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ExportFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
@ -51,8 +52,11 @@ class ExportFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(trans('notifications.export.failed.title'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(trans('notifications.export.failed.description'))
|
||||
->line($this->message);
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line($this->message)
|
||||
->line(new HtmlString('<br><br>'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ImportCompleted extends Notification implements ShouldQueue
|
||||
{
|
||||
@ -49,6 +50,7 @@ class ImportCompleted extends Notification implements ShouldQueue
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(trans('notifications.import.completed.title'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(trans('notifications.import.completed.description'))
|
||||
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ImportFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
@ -51,12 +52,16 @@ class ImportFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
$message = (new MailMessage)
|
||||
->subject(trans('notifications.import.failed.title'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(trans('notifications.import.failed.description'));
|
||||
|
||||
foreach ($this->errors as $error) {
|
||||
$message->line(new HtmlString('<br><br>'));
|
||||
$message->line($error);
|
||||
}
|
||||
|
||||
$message->line(new HtmlString('<br><br>'));
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
@ -55,11 +55,9 @@ class InvalidEmail extends Notification implements ShouldQueue
|
||||
|
||||
return (new MailMessage)
|
||||
->subject(trans('notifications.email.invalid.title', ['type' => $this->type]))
|
||||
->line(new HtmlString('<br>'))
|
||||
->line(new HtmlString('<br>'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(trans('notifications.email.invalid.description', ['email' => $this->email]))
|
||||
->line(new HtmlString('<br>'))
|
||||
->line(new HtmlString('<br>'))
|
||||
->line(new HtmlString('<br><br>'))
|
||||
->line(new HtmlString('<i>' . $this->error . '</i>'))
|
||||
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
||||
}
|
||||
|
@ -35,10 +35,17 @@ class Invoice extends Notification
|
||||
*/
|
||||
public $attach_pdf;
|
||||
|
||||
/**
|
||||
* List of document attachments to attach when sending the email.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $attachments;
|
||||
|
||||
/**
|
||||
* Create a notification instance.
|
||||
*/
|
||||
public function __construct(Document $invoice = null, string $template_alias = null, bool $attach_pdf = false, array $custom_mail = [])
|
||||
public function __construct(Document $invoice = null, string $template_alias = null, bool $attach_pdf = false, array $custom_mail = [], $attachments = [])
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
@ -46,6 +53,7 @@ class Invoice extends Notification
|
||||
$this->template = EmailTemplate::alias($template_alias)->first();
|
||||
$this->attach_pdf = $attach_pdf;
|
||||
$this->custom_mail = $custom_mail;
|
||||
$this->attachments = $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,6 +76,17 @@ class Invoice extends Notification
|
||||
]);
|
||||
}
|
||||
|
||||
// Attach selected attachments
|
||||
if (! empty($this->invoice->attachment)) {
|
||||
foreach ($this->invoice->attachment as $attachment) {
|
||||
if (in_array($attachment->id, $this->attachments)) {
|
||||
$message->attach($attachment->getAbsolutePath(), [
|
||||
'mime' => $attachment->mime_type,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,13 @@ class App extends Provider
|
||||
Model::preventLazyLoading(config('app.eager_load'));
|
||||
|
||||
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
|
||||
if (config('logging.default') == 'sentry') {
|
||||
\Sentry\Laravel\Integration::lazyLoadingViolationReporter();
|
||||
} else {
|
||||
$class = get_class($model);
|
||||
|
||||
report("Attempted to lazy load [{$relation}] on model [{$class}].");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
22
app/Providers/Binding.php
Normal file
22
app/Providers/Binding.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Interfaces\Utility\DocumentNumber as DocumentNumberInterface;
|
||||
use App\Interfaces\Utility\TransactionNumber as TransactionNumberInterface;
|
||||
use App\Utilities\DocumentNumber;
|
||||
use App\Utilities\TransactionNumber;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class Binding extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* All container bindings that should be registered.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public array $bindings = [
|
||||
DocumentNumberInterface::class => DocumentNumber::class,
|
||||
TransactionNumberInterface::class => TransactionNumber::class,
|
||||
];
|
||||
}
|
@ -29,6 +29,10 @@ class Blade extends ServiceProvider
|
||||
return "<?php echo show_widget($expression); ?>";
|
||||
});
|
||||
|
||||
Facade::directive('moduleIsEnabled', function ($expression) {
|
||||
return "<?php echo module_is_enabled($expression); ?>";
|
||||
});
|
||||
|
||||
Facade::if('readonly', function () {
|
||||
return config('read-only.enabled');
|
||||
});
|
||||
|
@ -23,6 +23,7 @@ class Event extends Provider
|
||||
'App\Listeners\Update\V30\Version309',
|
||||
'App\Listeners\Update\V30\Version3013',
|
||||
'App\Listeners\Update\V30\Version3014',
|
||||
'App\Listeners\Update\V30\Version3015',
|
||||
],
|
||||
'Illuminate\Auth\Events\Login' => [
|
||||
'App\Listeners\Auth\Login',
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Interfaces\Utility\DocumentNumber;
|
||||
use App\Models\Document\Document;
|
||||
use App\Abstracts\View\Components\Documents\Document as DocumentComponent;
|
||||
use App\Utilities\Date;
|
||||
@ -44,29 +45,24 @@ trait Documents
|
||||
return $recurring_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Use the DocumentNumber::getNextNumber() method instead.
|
||||
*
|
||||
* @deprecated This method is deprecated and will be removed in future versions.
|
||||
*/
|
||||
public function getNextDocumentNumber(string $type): string
|
||||
{
|
||||
if ($alias = config('type.document.' . $type . '.alias')) {
|
||||
$type = $alias . '.' . str_replace('-', '_', $type);
|
||||
}
|
||||
|
||||
$prefix = setting($type . '.number_prefix');
|
||||
$next = (string) setting($type . '.number_next');
|
||||
$digit = (int) setting($type . '.number_digit');
|
||||
|
||||
return $prefix . str_pad($next, $digit, '0', STR_PAD_LEFT);
|
||||
return app(DocumentNumber::class)->getNextNumber($type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Use the DocumentNumber::increaseNextNumber() method instead.
|
||||
*
|
||||
* @deprecated This method is deprecated and will be removed in future versions.
|
||||
*/
|
||||
public function increaseNextDocumentNumber(string $type): void
|
||||
{
|
||||
if ($alias = config('type.document.' . $type . '.alias')) {
|
||||
$type = $alias . '.' . str_replace('-', '_', $type);
|
||||
}
|
||||
|
||||
$next = setting($type . '.number_next', 1) + 1;
|
||||
|
||||
setting([$type . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
app(DocumentNumber::class)->increaseNextNumber($type, null);
|
||||
}
|
||||
|
||||
public function getDocumentStatuses(string $type): Collection
|
||||
|
@ -137,12 +137,14 @@ trait Import
|
||||
return is_null($id) ? $id : (int) $id;
|
||||
}
|
||||
|
||||
public function getItemId($row)
|
||||
public function getItemId($row, $type = null)
|
||||
{
|
||||
$id = isset($row['item_id']) ? $row['item_id'] : null;
|
||||
|
||||
$type = !empty($type) ? $type : (!empty($row['item_type']) ? $row['item_type'] : 'product');
|
||||
|
||||
if (empty($id) && !empty($row['item_name'])) {
|
||||
$id = $this->getItemIdFromName($row);
|
||||
$id = $this->getItemIdFromName($row, $type);
|
||||
}
|
||||
|
||||
return is_null($id) ? $id : (int) $id;
|
||||
@ -246,7 +248,7 @@ trait Import
|
||||
|
||||
public function getCategoryIdFromName($row, $type)
|
||||
{
|
||||
$category_id = Category::withSubCategory()->where('name', $row['category_name'])->pluck('id')->first();
|
||||
$category_id = Category::type($type)->withSubCategory()->where('name', $row['category_name'])->pluck('id')->first();
|
||||
|
||||
if (!empty($category_id)) {
|
||||
return $category_id;
|
||||
@ -271,7 +273,7 @@ trait Import
|
||||
|
||||
public function getContactIdFromEmail($row, $type)
|
||||
{
|
||||
$contact_id = Contact::where('email', $row['contact_email'])->pluck('id')->first();
|
||||
$contact_id = Contact::type($type)->where('email', $row['contact_email'])->pluck('id')->first();
|
||||
|
||||
if (!empty($contact_id)) {
|
||||
return $contact_id;
|
||||
@ -297,7 +299,7 @@ trait Import
|
||||
|
||||
public function getContactIdFromName($row, $type)
|
||||
{
|
||||
$contact_id = Contact::where('name', $row['contact_name'])->pluck('id')->first();
|
||||
$contact_id = Contact::type($type)->where('name', $row['contact_name'])->pluck('id')->first();
|
||||
|
||||
if (!empty($contact_id)) {
|
||||
return $contact_id;
|
||||
@ -321,9 +323,11 @@ trait Import
|
||||
return $contact->id;
|
||||
}
|
||||
|
||||
public function getItemIdFromName($row)
|
||||
public function getItemIdFromName($row, $type = null)
|
||||
{
|
||||
$item_id = Item::where('name', $row['item_name'])->pluck('id')->first();
|
||||
$type = !empty($type) ? $type : (!empty($row['item_type']) ? $row['item_type'] : 'product');
|
||||
|
||||
$item_id = Item::type($type)->where('name', $row['item_name'])->pluck('id')->first();
|
||||
|
||||
if (!empty($item_id)) {
|
||||
return $item_id;
|
||||
@ -331,7 +335,7 @@ trait Import
|
||||
|
||||
$data = [
|
||||
'company_id' => company_id(),
|
||||
'type' => !empty($row['item_type']) ? $row['item_type'] : (!empty($row['type']) ? $row['type'] : 'product'),
|
||||
'type' => $type,
|
||||
'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),
|
||||
@ -350,7 +354,7 @@ trait Import
|
||||
|
||||
public function getTaxIdFromRate($row, $type = 'normal')
|
||||
{
|
||||
$tax_id = Tax::where('rate', $row['tax_rate'])->pluck('id')->first();
|
||||
$tax_id = Tax::type($type)->where('rate', $row['tax_rate'])->pluck('id')->first();
|
||||
|
||||
if (!empty($tax_id)) {
|
||||
return $tax_id;
|
||||
|
@ -440,7 +440,7 @@ trait Modules
|
||||
}
|
||||
|
||||
// Check if module is installed in cloud
|
||||
if (request()->getHost() == 'app.akaunting.com') {
|
||||
if (request()->getHost() == 'app.akaunting.com' || request()->getHost() == 'localhost') {
|
||||
$modules = Cache::get('cloud.companies.' . company_id() . '.modules.installed', []);
|
||||
|
||||
if (in_array($alias, $modules)) {
|
||||
|
@ -186,26 +186,19 @@ trait Recurring
|
||||
return $limit;
|
||||
}
|
||||
|
||||
public function getCurrentRecurring()
|
||||
{
|
||||
if (! $schedule = $this->getRecurringSchedule()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $current = $schedule->current()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $current->getStart();
|
||||
}
|
||||
|
||||
public function getNextRecurring()
|
||||
{
|
||||
if (! $schedule = $this->getRecurringSchedule()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $next = $schedule->next()) {
|
||||
$schedule = $schedule->startsAfter($this->getRecurringRuleTodayDate());
|
||||
|
||||
if ($schedule->count() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $next = $schedule->current()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ namespace App\Traits;
|
||||
|
||||
use App\Events\Banking\TransactionPrinting;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Interfaces\Utility\TransactionNumber;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait Transactions
|
||||
@ -204,6 +205,11 @@ trait Transactions
|
||||
];
|
||||
}
|
||||
|
||||
public function getTypeTransaction(string $type = Transaction::INCOME_TYPE): string
|
||||
{
|
||||
return array_key_exists($type, config('type.transaction')) ? $type : Transaction::INCOME_TYPE;
|
||||
}
|
||||
|
||||
public function getRealTypeTransaction(string $type): string
|
||||
{
|
||||
$type = $this->getRealTypeOfRecurringTransaction($type);
|
||||
@ -213,6 +219,15 @@ trait Transactions
|
||||
return $type;
|
||||
}
|
||||
|
||||
public function getTypeRecurringTransaction(string $type = Transaction::INCOME_RECURRING_TYPE): string
|
||||
{
|
||||
if (! Str::contains($type, '-recurring')) {
|
||||
return Transaction::INCOME_RECURRING_TYPE;
|
||||
}
|
||||
|
||||
return array_key_exists($type, config('type.transaction')) ? $type : Transaction::INCOME_RECURRING_TYPE;
|
||||
}
|
||||
|
||||
public function getRealTypeOfRecurringTransaction(string $recurring_type): string
|
||||
{
|
||||
return Str::replace('-recurring', '', $recurring_type);
|
||||
@ -228,36 +243,13 @@ trait Transactions
|
||||
return Str::replace('-split', '', $transfer_type);
|
||||
}
|
||||
|
||||
public function getNextTransactionNumber($suffix = ''): string
|
||||
public function getNextTransactionNumber($type = 'income', $suffix = ''): string
|
||||
{
|
||||
$prefix = setting('transaction' . $suffix . '.number_prefix');
|
||||
$next = (string) setting('transaction' . $suffix . '.number_next');
|
||||
$digit = (int) setting('transaction' . $suffix . '.number_digit');
|
||||
|
||||
$get_number = fn($prefix, $next, $digit) => $prefix . str_pad($next, $digit, '0', STR_PAD_LEFT);
|
||||
$number_exists = fn($number) => Transaction::where('number', $number)->exists();
|
||||
|
||||
$transaction_number = $get_number($prefix, $next, $digit);
|
||||
|
||||
if ($number_exists($transaction_number)) {
|
||||
do {
|
||||
$next++;
|
||||
|
||||
$transaction_number = $get_number($prefix, $next, $digit);
|
||||
} while ($number_exists($transaction_number));
|
||||
|
||||
setting(['transaction' . $suffix . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
return app(TransactionNumber::class)->getNextNumber($type, $suffix, null);
|
||||
}
|
||||
|
||||
return $transaction_number;
|
||||
}
|
||||
|
||||
public function increaseNextTransactionNumber($suffix = ''): void
|
||||
public function increaseNextTransactionNumber($type = 'income', $suffix = ''): void
|
||||
{
|
||||
$next = setting('transaction' . $suffix . '.number_next', 1) + 1;
|
||||
|
||||
setting(['transaction' . $suffix . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
app(TransactionNumber::class)->increaseNextNumber($type, $suffix, null);
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ trait Uploads
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function streamMedia($media)
|
||||
public function streamMedia($media, $disposition = 'attachment')
|
||||
{
|
||||
return response()->streamDownload(
|
||||
function() use ($media) {
|
||||
@ -133,6 +133,7 @@ trait Uploads
|
||||
'Content-Type' => $media->mime_type,
|
||||
'Content-Length' => $media->size,
|
||||
],
|
||||
$disposition,
|
||||
);
|
||||
}
|
||||
|
||||
|
39
app/Utilities/DocumentNumber.php
Normal file
39
app/Utilities/DocumentNumber.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utilities;
|
||||
|
||||
use App\Interfaces\Utility\DocumentNumber as DocumentNumberInterface;
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
class DocumentNumber implements DocumentNumberInterface
|
||||
{
|
||||
public function getNextNumber(string $type, ?Contact $contact): string
|
||||
{
|
||||
$type = $this->resolveTypeAlias($type);
|
||||
|
||||
$prefix = setting($type . '.number_prefix');
|
||||
$next = (string) setting($type . '.number_next');
|
||||
$digit = (int) setting($type . '.number_digit');
|
||||
|
||||
return $prefix . str_pad($next, $digit, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
public function increaseNextNumber(string $type, ?Contact $contact): void
|
||||
{
|
||||
$type = $this->resolveTypeAlias($type);
|
||||
|
||||
$next = setting($type . '.number_next', 1) + 1;
|
||||
|
||||
setting([$type . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
}
|
||||
|
||||
protected function resolveTypeAlias(string $type): string
|
||||
{
|
||||
if ($alias = config('type.document.' . $type . '.alias')) {
|
||||
return $alias . '.' . str_replace('-', '_', $type);
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use App\Models\Auth\User;
|
||||
use App\Models\Common\Company;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Document\Document;
|
||||
use App\Traits\Cloud;
|
||||
use Composer\InstalledVersions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@ -13,7 +14,15 @@ class Info
|
||||
{
|
||||
public static function all()
|
||||
{
|
||||
return array_merge(static::versions(), [
|
||||
static $info = [];
|
||||
|
||||
$is_cloud = (new class { use Cloud; })->isCloud();
|
||||
|
||||
if (! empty($info) || $is_cloud) {
|
||||
return $info;
|
||||
}
|
||||
|
||||
$info = array_merge(static::versions(), [
|
||||
'api_key' => setting('apps.api_key'),
|
||||
'ip' => static::ip(),
|
||||
'companies' => Company::count(),
|
||||
@ -22,11 +31,19 @@ class Info
|
||||
'customers' => Contact::customer()->count(),
|
||||
'php_extensions' => static::phpExtensions(),
|
||||
]);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
public static function versions()
|
||||
{
|
||||
return [
|
||||
static $versions = [];
|
||||
|
||||
if (! empty($versions)) {
|
||||
return $versions;
|
||||
}
|
||||
|
||||
$versions = [
|
||||
'akaunting' => version('short'),
|
||||
'laravel' => InstalledVersions::getPrettyVersion('laravel/framework'),
|
||||
'php' => static::phpVersion(),
|
||||
@ -35,6 +52,8 @@ class Info
|
||||
'livewire' => InstalledVersions::getPrettyVersion('livewire/livewire'),
|
||||
'omnipay' => InstalledVersions::getPrettyVersion('league/omnipay'),
|
||||
];
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
public static function phpVersion()
|
||||
|
@ -3,8 +3,8 @@
|
||||
namespace App\Utilities;
|
||||
|
||||
use App\Events\Module\PaymentMethodShowing;
|
||||
use Cache;
|
||||
use Date;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Modules
|
||||
{
|
||||
|
44
app/Utilities/TransactionNumber.php
Normal file
44
app/Utilities/TransactionNumber.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utilities;
|
||||
|
||||
use App\Interfaces\Utility\TransactionNumber as TransactionNumberInterface;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
class TransactionNumber implements TransactionNumberInterface
|
||||
{
|
||||
public function getNextNumber($type, $suffix = '', ?Contact $contact): string
|
||||
{
|
||||
$prefix = setting('transaction' . $suffix . '.number_prefix');
|
||||
$next = (string) setting('transaction' . $suffix . '.number_next');
|
||||
$digit = (int) setting('transaction' . $suffix . '.number_digit');
|
||||
|
||||
$get_number = fn($prefix, $next, $digit) => $prefix . str_pad($next, $digit, '0', STR_PAD_LEFT);
|
||||
$number_exists = fn($number) => Transaction::where('number', $number)->exists();
|
||||
|
||||
$transaction_number = $get_number($prefix, $next, $digit);
|
||||
|
||||
if ($number_exists($transaction_number)) {
|
||||
do {
|
||||
$next++;
|
||||
|
||||
$transaction_number = $get_number($prefix, $next, $digit);
|
||||
} while ($number_exists($transaction_number));
|
||||
|
||||
setting(['transaction' . $suffix . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
}
|
||||
|
||||
return $transaction_number;
|
||||
|
||||
}
|
||||
|
||||
public function increaseNextNumber($type, $suffix = '', ?Contact $contact): void
|
||||
{
|
||||
$next = setting('transaction' . $suffix . '.number_next', 1) + 1;
|
||||
|
||||
setting(['transaction' . $suffix . '.number_next' => $next]);
|
||||
setting()->save();
|
||||
}
|
||||
}
|
@ -3,10 +3,10 @@
|
||||
namespace App\Utilities;
|
||||
|
||||
use App\Traits\SiteApi;
|
||||
use Cache;
|
||||
use Date;
|
||||
use App\Utilities\Date;
|
||||
use GrahamCampbell\Markdown\Facades\Markdown;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Versions
|
||||
{
|
||||
@ -56,7 +56,7 @@ class Versions
|
||||
$versions = static::all($alias);
|
||||
|
||||
if (empty($versions[$alias])) {
|
||||
return false;
|
||||
return static::getVersionByAlias($alias);
|
||||
}
|
||||
|
||||
return $versions[$alias];
|
||||
@ -128,6 +128,30 @@ class Versions
|
||||
return $versions;
|
||||
}
|
||||
|
||||
public static function getVersionByAlias($alias)
|
||||
{
|
||||
$info = Info::all();
|
||||
|
||||
// Check core first
|
||||
$url = 'core/version/' . $info['akaunting'] . '/' . $info['php'] . '/' . $info['mysql'] . '/' . $info['companies'];
|
||||
$version = $info['akaunting'];
|
||||
|
||||
if ($alias != 'core') {
|
||||
$version = module($alias)->get('version');
|
||||
|
||||
$url = 'apps/' . $alias . '/version/' . $version . '/' . $info['akaunting'];
|
||||
}
|
||||
|
||||
// Get data from cache
|
||||
$versions = Cache::get('versions', []);
|
||||
|
||||
$versions[$alias] = static::getLatestVersion($url, $version);
|
||||
|
||||
Cache::put('versions', $versions, Date::now()->addHour(6));
|
||||
|
||||
return $versions[$alias];
|
||||
}
|
||||
|
||||
public static function getLatestVersion($url, $latest)
|
||||
{
|
||||
$version = new \stdClass();
|
||||
|
@ -3,6 +3,7 @@
|
||||
use App\Models\Common\Company;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Sources;
|
||||
use App\Traits\Modules;
|
||||
use App\Utilities\Date;
|
||||
use App\Utilities\Widgets;
|
||||
|
||||
@ -88,6 +89,20 @@ if (! function_exists('company')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('module_is_enabled')) {
|
||||
/**
|
||||
* Check if a module is enabled.
|
||||
*/
|
||||
function module_is_enabled(string $alias): bool
|
||||
{
|
||||
$module = new class() {
|
||||
use Modules;
|
||||
};
|
||||
|
||||
return $module->moduleIsEnabled($alias);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('company_id')) {
|
||||
/**
|
||||
* Get id of current company.
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\View\Components\Documents\Form;
|
||||
|
||||
use App\Abstracts\View\Components\Documents\Form as Component;
|
||||
use App\Models\Common\Company as Model;
|
||||
|
||||
class Company extends Component
|
||||
{
|
||||
@ -14,7 +13,7 @@ class Company extends Component
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$company = Model::find(company_id());
|
||||
$company = company();
|
||||
|
||||
$inputNameType = config('type.document.' . $this->type . '.route.parameter');
|
||||
|
||||
|
@ -21,10 +21,6 @@ class NumberDigit extends Form
|
||||
$this->name = 'number_digit';
|
||||
}
|
||||
|
||||
if (empty($this->label)) {
|
||||
$this->label = trans('settings.invoice.digit');
|
||||
}
|
||||
|
||||
$this->number_digits = [
|
||||
'1' => '1',
|
||||
'2' => '2',
|
||||
|
@ -37,7 +37,7 @@ class Country extends Component
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if (! empty($this->code) && in_array($this->code, trans('countries'))) {
|
||||
if (! empty($this->code) && array_key_exists($this->code, trans('countries'))) {
|
||||
$this->country = trans('countries.' . $this->code);
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,10 @@ class Menu extends Component
|
||||
|
||||
public function getCompanies()
|
||||
{
|
||||
$companies = [];
|
||||
|
||||
if ($user = user()) {
|
||||
$companies = $user->companies()->enabled()->limit(10)->get()->sortBy('name');
|
||||
} else {
|
||||
$companies = [];
|
||||
}
|
||||
|
||||
return $companies;
|
||||
|
@ -50,7 +50,7 @@ class PaymentMethod extends Component
|
||||
|
||||
// check here protal or admin panel..
|
||||
if (empty($type)) {
|
||||
$type = Str::contains(request()->route()->getName(), 'portal') ? 'customer' : 'all';
|
||||
$type = Str::contains(request()?->route()?->getName(), 'portal') ? 'customer' : 'all';
|
||||
}
|
||||
|
||||
$payment_methods = Modules::getPaymentMethods($type);
|
||||
|
@ -75,6 +75,7 @@ class SearchString extends Component
|
||||
'type' => $this->getFilterType($options),
|
||||
'url' => $this->getFilterUrl($column, $options),
|
||||
'values' => $this->getFilterValues($column, $options),
|
||||
'value_option_fields' => $options['fields'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class ExpensesByCategory extends Widget
|
||||
|
||||
public function show()
|
||||
{
|
||||
Category::with('expense_transactions')->expense()->each(function ($category) {
|
||||
Category::with('expense_transactions')->expense()->withSubCategory()->getWithoutChildren()->each(function ($category) {
|
||||
$amount = 0;
|
||||
|
||||
$this->applyFilters($category->expense_transactions)->each(function ($transaction) use (&$amount) {
|
||||
|
1144
composer.lock
generated
1144
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -188,6 +188,7 @@ return [
|
||||
*/
|
||||
App\Providers\App::class,
|
||||
App\Providers\Auth::class,
|
||||
App\Providers\Binding::class,
|
||||
App\Providers\Blade::class,
|
||||
// App\Providers\Broadcast::class,
|
||||
App\Providers\Event::class,
|
||||
|
@ -372,7 +372,11 @@ return [
|
||||
'contact_phone' => ['searchable' => true],
|
||||
'contact_address' => ['searchable' => true],
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:income enabled:1']
|
||||
'route' => ['categories.index', 'search=type:income enabled:1'],
|
||||
'fields' => [
|
||||
'key' => 'id',
|
||||
'value' => 'name',
|
||||
],
|
||||
],
|
||||
'parent_id',
|
||||
'recurring' => [
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
return [
|
||||
|
||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
||||
|
||||
// capture release as git sha
|
||||
@ -81,4 +82,6 @@ return [
|
||||
|
||||
'traces_sampler' => [env('SENTRY_TRACES_SAMPLER_CLASS', 'App\\Exceptions\\Trackers\\Sentry'), 'tracesSampler'],
|
||||
|
||||
'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_PROFILES_SAMPLE_RATE'),
|
||||
|
||||
];
|
||||
|
@ -10,13 +10,13 @@ return [
|
||||
|
||||
'minor' => '0',
|
||||
|
||||
'patch' => '14',
|
||||
'patch' => '15',
|
||||
|
||||
'build' => '',
|
||||
|
||||
'status' => 'Stable',
|
||||
|
||||
'date' => '25-April-2023',
|
||||
'date' => '31-May-2023',
|
||||
|
||||
'time' => '17:00',
|
||||
|
||||
|
@ -9,6 +9,7 @@ use App\Events\Document\DocumentReceived;
|
||||
use App\Events\Document\DocumentSent;
|
||||
use App\Events\Document\DocumentViewed;
|
||||
use App\Events\Document\PaymentReceived;
|
||||
use App\Interfaces\Utility\DocumentNumber;
|
||||
use App\Jobs\Document\UpdateDocument;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Common\Item;
|
||||
@ -70,7 +71,7 @@ class Document extends AbstractFactory
|
||||
|
||||
return [
|
||||
'type' => Model::INVOICE_TYPE,
|
||||
'document_number' => $this->getDocumentNumber(Model::INVOICE_TYPE),
|
||||
'document_number' => $this->getDocumentNumber(Model::INVOICE_TYPE, $contact),
|
||||
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
|
||||
'contact_id' => $contact->id,
|
||||
'contact_name' => $contact->name,
|
||||
@ -101,7 +102,7 @@ class Document extends AbstractFactory
|
||||
|
||||
return [
|
||||
'type' => Model::BILL_TYPE,
|
||||
'document_number' => $this->getDocumentNumber(Model::BILL_TYPE),
|
||||
'document_number' => $this->getDocumentNumber(Model::BILL_TYPE, $contact),
|
||||
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
|
||||
'contact_id' => $contact->id,
|
||||
'contact_name' => $contact->name,
|
||||
@ -207,9 +208,11 @@ class Document extends AbstractFactory
|
||||
{
|
||||
$type = $this->getRawAttribute('type') . '-recurring';
|
||||
|
||||
$contact = Contact::find($this->getRawAttribute('contact_id'));
|
||||
|
||||
return $this->state([
|
||||
'type' => $type,
|
||||
'document_number' => $this->getDocumentNumber($type),
|
||||
'document_number' => $this->getDocumentNumber($type, $contact),
|
||||
'recurring_started_at' => $this->getRawAttribute('issued_at'),
|
||||
'recurring_frequency' => 'daily',
|
||||
'recurring_interval' => '1',
|
||||
@ -263,11 +266,13 @@ class Document extends AbstractFactory
|
||||
* Get document number
|
||||
*
|
||||
*/
|
||||
public function getDocumentNumber($type)
|
||||
public function getDocumentNumber($type, Contact $contact)
|
||||
{
|
||||
$document_number = $this->getNextDocumentNumber($type);
|
||||
$utility = app(DocumentNumber::class);
|
||||
|
||||
$this->increaseNextDocumentNumber($type);
|
||||
$document_number = $utility->getNextNumber($type, $contact);
|
||||
|
||||
$utility->increaseNextNumber($type, $contact);
|
||||
|
||||
return $document_number;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Abstracts\Factory;
|
||||
use App\Interfaces\Utility\TransactionNumber;
|
||||
use App\Models\Banking\Transaction as Model;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Traits\Transactions;
|
||||
@ -41,7 +42,7 @@ class Transaction extends Factory
|
||||
return [
|
||||
'company_id' => $this->company->id,
|
||||
'type' => $this->type,
|
||||
'number' => $this->getNumber(),
|
||||
'number' => $this->getNumber($this->type),
|
||||
'account_id' => setting('default.account'),
|
||||
'paid_at' => $this->faker->dateTimeBetween(now()->startOfYear(), now()->endOfYear())->format('Y-m-d H:i:s'),
|
||||
'amount' => $this->faker->randomFloat(2, 1, 1000),
|
||||
@ -73,6 +74,7 @@ class Transaction extends Factory
|
||||
|
||||
return [
|
||||
'type' => 'income',
|
||||
'number' => $this->getNumber('income', '', $contact),
|
||||
'contact_id' => $contact->id,
|
||||
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
|
||||
];
|
||||
@ -97,6 +99,7 @@ class Transaction extends Factory
|
||||
|
||||
return [
|
||||
'type' => 'expense',
|
||||
'number' => $this->getNumber('expense', '', $contact),
|
||||
'contact_id' => $contact->id,
|
||||
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
|
||||
];
|
||||
@ -110,9 +113,11 @@ class Transaction extends Factory
|
||||
*/
|
||||
public function recurring()
|
||||
{
|
||||
$type = $this->getRawAttribute('type') . '-recurring';
|
||||
|
||||
return $this->state([
|
||||
'type' => $this->getRawAttribute('type') . '-recurring',
|
||||
'number' => $this->getNumber('-recurring'),
|
||||
'type' => $type,
|
||||
'number' => $this->getNumber($type, '-recurring'),
|
||||
'recurring_started_at' => Date::now()->format('Y-m-d H:i:s'),
|
||||
'recurring_frequency' => 'daily',
|
||||
'recurring_custom_frequency' => 'daily',
|
||||
@ -129,11 +134,13 @@ class Transaction extends Factory
|
||||
* Get transaction number
|
||||
*
|
||||
*/
|
||||
public function getNumber($suffix = '')
|
||||
public function getNumber($type, $suffix = '', $contact = null)
|
||||
{
|
||||
$number = $this->getNextTransactionNumber($suffix);
|
||||
$utility = app(TransactionNumber::class);
|
||||
|
||||
$this->increaseNextTransactionNumber($suffix);
|
||||
$number = $utility->getNextNumber($type, $suffix, $contact);
|
||||
|
||||
$utility->increaseNextNumber($type, $suffix ,$contact);
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
53
database/migrations/2022_08_29_000000_core_v3015.php
Normal file
53
database/migrations/2022_08_29_000000_core_v3015.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Documents
|
||||
Schema::table('documents', function(Blueprint $table) {
|
||||
$table->index('contact_id');
|
||||
});
|
||||
|
||||
// User Companies
|
||||
Schema::table('user_companies', function(Blueprint $table) {
|
||||
$table->index('user_id');
|
||||
$table->index('company_id');
|
||||
});
|
||||
|
||||
// User Roles
|
||||
Schema::table('user_roles', function(Blueprint $table) {
|
||||
$table->index('user_id');
|
||||
$table->index('role_id');
|
||||
});
|
||||
|
||||
// Transactions
|
||||
Schema::table('transactions', function(Blueprint $table) {
|
||||
$table->index('number');
|
||||
});
|
||||
|
||||
// Roles
|
||||
Schema::table('roles', function(Blueprint $table) {
|
||||
$table->index('name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
3054
package-lock.json
generated
3054
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@
|
||||
"glightbox": "^3.2.0",
|
||||
"json-schema": ">=0.4.0",
|
||||
"laravel-mix-tailwind": "^0.1.2",
|
||||
"lodash": ">=4.17.21",
|
||||
"lodash": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"swiper": "^9.2.0",
|
||||
@ -47,7 +47,10 @@
|
||||
"vue-router": "^3.6.5",
|
||||
"vue2-editor": "^2.10.3",
|
||||
"vue2-transitions": "^0.3.0",
|
||||
"vuedraggable": "^2.24.3"
|
||||
"vuedraggable": "^2.24.3",
|
||||
"moment": ">=2.29.4",
|
||||
"qs": "^6.11.1",
|
||||
"jsonwebtoken": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
|
2431
public/css/app.css
vendored
2431
public/css/app.css
vendored
File diff suppressed because it is too large
Load Diff
3
public/css/element.css
vendored
3
public/css/element.css
vendored
@ -14,7 +14,8 @@
|
||||
font-family: element-icons;
|
||||
src: url(fonts/element-icons.woff) format("woff"),url(fonts/element-icons.ttf) format("truetype");
|
||||
font-weight: 400;
|
||||
font-display:"auto";font-style: normal
|
||||
font-display:"auto";
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class*=" el-icon-"],[class^=el-icon-] {
|
||||
|
@ -264,8 +264,8 @@ export default {
|
||||
},
|
||||
|
||||
sendEmailShow: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
type: [String, Number, Array, Object, Boolean],
|
||||
default: '1',
|
||||
description: "Created recurring model send automatically option"
|
||||
},
|
||||
sendEmailText: {
|
||||
|
@ -15,16 +15,17 @@
|
||||
<span v-if="filter.operator" class="flex items-center bg-purple-lighter text-black border-2 border-body border-l border-r border-t-0 border-b-0 mt-3 px-3 py-4 text-sm cursor-pointer el-tag el-tag--small el-tag-operator" style="margin-left:0; margin-right:0;">
|
||||
<span v-if="filter.operator == '='" class="material-icons text-2xl">drag_handle</span>
|
||||
<span v-else-if="filter.operator == '><'" class="material-icons text-2xl transform rotate-90">height</span>
|
||||
<span v-else class="w-5">
|
||||
<img :src="not_equal_image" class="w-5 h-5 object-cover block" />
|
||||
</span>
|
||||
|
||||
<img v-else :src="not_equal_image" class="w-5 h-5 object-cover block" />
|
||||
|
||||
<i v-if="!filter.value" class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close " style="font-size: 16px;" @click="onFilterDelete(index)"></i>
|
||||
<i v-if="!filter.value" class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close" style="font-size: 16px;" @click="onFilterDelete(index)"></i>
|
||||
</span>
|
||||
|
||||
<span v-if="filter.value" class="flex items-center bg-purple-lighter text-black border-0 mt-3 px-3 py-4 text-sm cursor-pointer el-tag el-tag--small el-tag-value">
|
||||
{{ filter.value }}
|
||||
|
||||
<i class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close " style="font-size: 16px;" @click="onFilterDelete(index)"></i>
|
||||
<i class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close" style="font-size: 16px;" @click="onFilterDelete(index)"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,47 +149,57 @@ export default {
|
||||
default: 'Search or filter results...',
|
||||
description: 'Input placeholder'
|
||||
},
|
||||
|
||||
selectPlaceholder: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
enterPlaceholder: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
searchText: {
|
||||
type: String,
|
||||
default: 'Search for this text',
|
||||
description: 'Input placeholder'
|
||||
},
|
||||
|
||||
operatorIsText: {
|
||||
type: String,
|
||||
default: 'is',
|
||||
description: 'Operator is "="'
|
||||
},
|
||||
|
||||
operatorIsNotText: {
|
||||
type: String,
|
||||
default: 'is not',
|
||||
description: 'Operator is not "!="'
|
||||
},
|
||||
|
||||
noDataText: {
|
||||
type: String,
|
||||
default: 'No Data',
|
||||
description: "Selectbox empty options message"
|
||||
},
|
||||
|
||||
noMatchingDataText: {
|
||||
type: String,
|
||||
default: 'No Matchign Data',
|
||||
description: "Selectbox search option not found item message"
|
||||
},
|
||||
|
||||
value: {
|
||||
type: String,
|
||||
default: null,
|
||||
description: 'Search attribute value'
|
||||
},
|
||||
|
||||
filters: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
description: 'List of filters'
|
||||
},
|
||||
|
||||
defaultFiltered: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@ -196,7 +207,6 @@ export default {
|
||||
},
|
||||
|
||||
dateConfig: null
|
||||
|
||||
},
|
||||
|
||||
model: {
|
||||
@ -423,10 +433,12 @@ export default {
|
||||
|
||||
let option = false;
|
||||
let option_url = false;
|
||||
let option_fields = {};
|
||||
|
||||
for (let i = 0; i < this.filter_list.length; i++) {
|
||||
if (this.filter_list[i].key == value) {
|
||||
option = this.filter_list[i].value;
|
||||
option_fields = (this.filter_list[i]['value_option_fields']) ? this.filter_list[i].value_option_fields : {};
|
||||
|
||||
if (this.filter_list[i].values !== 'undefined' && Object.keys(this.filter_list[i].values).length) {
|
||||
this.option_values[value] = this.convertOption(this.filter_list[i].values);
|
||||
@ -475,7 +487,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.option_values[value] && option_url) {
|
||||
if (! this.option_values[value] && option_url) {
|
||||
if (option_url.indexOf('limit') === -1) {
|
||||
option_url += ' limit:10';
|
||||
}
|
||||
@ -487,11 +499,19 @@ export default {
|
||||
this.values = [];
|
||||
|
||||
data.forEach(function (item) {
|
||||
if (Object.keys(option_fields).length) {
|
||||
this.values.push({
|
||||
key: (option_fields['key']) ? item[option_fields['key']] : (item.code) ? item.code : item.id,
|
||||
value: (option_fields['value']) ? item[option_fields['value']] : (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
level: (option_fields['level']) ? item[option_fields['level']] : (item.level) ? item.level : null,
|
||||
});
|
||||
} else {
|
||||
this.values.push({
|
||||
key: (item.code) ? item.code : item.id,
|
||||
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
level: (item.level) ? item.level : null,
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.option_values[value] = this.values;
|
||||
@ -647,7 +667,7 @@ export default {
|
||||
let values = [];
|
||||
|
||||
// Option set sort_option data
|
||||
if (!Array.isArray(options)) {
|
||||
if (! Array.isArray(options)) {
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
values.push({
|
||||
key: (key).toString(),
|
||||
@ -850,6 +870,7 @@ export default {
|
||||
this.values.sort(function (a, b) {
|
||||
var nameA = a.value.toUpperCase(); // ignore upper and lowercase
|
||||
var nameB = b.value.toUpperCase(); // ignore upper and lowercase
|
||||
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
@ -857,6 +878,7 @@ export default {
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// names must be equal
|
||||
return 0;
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="card-item relative w-2/4 lg:w-3/4 h-48 m-auto" :class="{ '-active' : isCardFlipped }">
|
||||
<div class="card-item relative w-2/4 lg:w-3/4 h-48 m-auto my-5" :class="{ '-active' : isCardFlipped }">
|
||||
<div class="card-item__side h-full rounded-lg shadow-lg overflow-hidden" style="transform: perspective(2000px) rotateY(0deg) rotateX(0deg) rotate(0deg);
|
||||
transform-style: preserve-3d;
|
||||
transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85);
|
||||
|
@ -2,9 +2,24 @@
|
||||
<div
|
||||
@click="tryClose"
|
||||
data-notify="container"
|
||||
class="alert alert-notify fixed w-full sm:w-500 flex items-center justify-between ltr:right-0 rtl:left-0 sm:ltr:right-4 sm:rtl:left-4 p-4 text-black font-bold rounded-lg z-30"
|
||||
:class="[
|
||||
{ 'alert-with-icon': icon },
|
||||
'alert alert-notify',
|
||||
'fixed w-full sm:w-500 flex items-center justify-between',
|
||||
{
|
||||
'rtl:right-0 ltr:left-0' : horizontalAlign == 'left',
|
||||
'sm:rtl:right-4 sm:ltr:left-4' : horizontalAlign == 'left',
|
||||
},
|
||||
{
|
||||
'ltr:right-0 rtl:left-0' : horizontalAlign == 'right',
|
||||
'sm:ltr:right-4 sm:rtl:left-4' : horizontalAlign == 'right',
|
||||
},
|
||||
'p-4',
|
||||
'text-black font-bold',
|
||||
'rounded-lg',
|
||||
'z-30',
|
||||
{
|
||||
'alert-with-icon': icon
|
||||
},
|
||||
verticalAlign,
|
||||
horizontalAlign,
|
||||
alertType
|
||||
@ -26,11 +41,10 @@
|
||||
<span v-if="title" class="title">
|
||||
<b>{{ title }}<br/></b>
|
||||
</span>
|
||||
|
||||
<span v-if="message" v-html="message"></span>
|
||||
<content-render
|
||||
v-if="!message && component"
|
||||
:component="component"
|
||||
></content-render>
|
||||
|
||||
<content-render v-if="!message && component" :component="component"></content-render>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -39,49 +53,60 @@
|
||||
class="close text-2xl"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
@click="close">
|
||||
@click="close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'notification',
|
||||
|
||||
components: {
|
||||
contentRender: {
|
||||
props: ['component'],
|
||||
render: h => h(this.component)
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
message: String,
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
description: 'Notification title'
|
||||
},
|
||||
|
||||
icon: {
|
||||
type: String,
|
||||
description: 'Notification icon'
|
||||
},
|
||||
|
||||
verticalAlign: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: value => {
|
||||
let acceptedValues = ['top', 'bottom'];
|
||||
|
||||
return acceptedValues.indexOf(value) !== -1;
|
||||
},
|
||||
description: 'Vertical alignment of notification (top|bottom)'
|
||||
},
|
||||
|
||||
horizontalAlign: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
validator: value => {
|
||||
let acceptedValues = ['left', 'center', 'right'];
|
||||
|
||||
return acceptedValues.indexOf(value) !== -1;
|
||||
},
|
||||
description: 'Horizontal alignment of notification (left|center|right)'
|
||||
},
|
||||
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
@ -94,10 +119,12 @@
|
||||
'warning',
|
||||
'success'
|
||||
];
|
||||
|
||||
return acceptedValues.indexOf(value) !== -1;
|
||||
},
|
||||
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
|
||||
},
|
||||
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 5000,
|
||||
@ -106,33 +133,40 @@
|
||||
},
|
||||
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
|
||||
},
|
||||
|
||||
timestamp: {
|
||||
type: Date,
|
||||
default: () => new Date(),
|
||||
description: 'Notification timestamp (used internally to handle notification removal correctly)'
|
||||
},
|
||||
|
||||
component: {
|
||||
type: [Object, Function],
|
||||
description: 'Custom content component. Cane be a `.vue` component or render function'
|
||||
},
|
||||
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
description: 'Whether to show close button'
|
||||
},
|
||||
|
||||
closeOnClick: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
description: 'Whether to close notification when clicking it\' body'
|
||||
},
|
||||
|
||||
clickHandler: {
|
||||
type: Function,
|
||||
description: 'Custom notification click handler'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
elmHeight: 0,
|
||||
|
||||
typeByClass: {
|
||||
'default': 'black-100',
|
||||
'info': 'blue-100',
|
||||
@ -141,6 +175,7 @@
|
||||
'warning': 'orange-100',
|
||||
'success': 'green-100',
|
||||
},
|
||||
|
||||
textByClass: {
|
||||
'default': 'black-600',
|
||||
'info': 'blue-600',
|
||||
@ -151,16 +186,20 @@
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasIcon() {
|
||||
return this.icon && this.icon.length > 0;
|
||||
},
|
||||
|
||||
alertType() {
|
||||
return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`;
|
||||
},
|
||||
|
||||
customPosition() {
|
||||
let initialMargin = 20;
|
||||
let alertHeight = this.elmHeight + 10;
|
||||
|
||||
let sameAlertsCount = this.$notifications.state.filter(alert => {
|
||||
return (
|
||||
alert.horizontalAlign === this.horizontalAlign &&
|
||||
@ -168,37 +207,51 @@
|
||||
alert.timestamp <= this.timestamp
|
||||
);
|
||||
}).length;
|
||||
|
||||
if (this.$notifications.settings.overlap) {
|
||||
sameAlertsCount = 1;
|
||||
}
|
||||
|
||||
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
|
||||
|
||||
if (sameAlertsCount > 1) {
|
||||
pixels = 30 + this.$parent.children[sameAlertsCount - 2].elm.offsetHeight;
|
||||
}
|
||||
|
||||
let styles = {};
|
||||
|
||||
if (this.verticalAlign === 'top') {
|
||||
styles.top = `${pixels}px`;
|
||||
} else {
|
||||
styles.bottom = `${pixels}px`;
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('close', this.timestamp);
|
||||
},
|
||||
|
||||
tryClose(evt) {
|
||||
if (this.clickHandler) {
|
||||
this.clickHandler(evt, this);
|
||||
}
|
||||
|
||||
if (this.closeOnClick) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.elmHeight = this.$el.clientHeight;
|
||||
|
||||
if (this.timeout) {
|
||||
setTimeout(this.close, this.timeout);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
@ -2,7 +2,8 @@
|
||||
<div class="notifications">
|
||||
<slide-y-up-transition :duration="transitionDuration"
|
||||
group
|
||||
mode="out-in">
|
||||
mode="out-in"
|
||||
>
|
||||
<notification
|
||||
v-for="notification in notifications"
|
||||
v-bind="notification"
|
||||
@ -14,6 +15,7 @@
|
||||
</slide-y-up-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Notification from './Notification.vue';
|
||||
import { SlideYUpTransition } from 'vue2-transitions';
|
||||
@ -23,29 +25,35 @@
|
||||
SlideYUpTransition,
|
||||
Notification
|
||||
},
|
||||
|
||||
props: {
|
||||
transitionDuration: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
|
||||
overlap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
notifications: this.$notifications.state
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
removeNotification(timestamp) {
|
||||
this.$notifications.removeNotification(timestamp);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$notifications.settings.overlap = this.overlap;
|
||||
},
|
||||
|
||||
watch: {
|
||||
overlap: function (newVal) {
|
||||
this.$notifications.settings.overlap = newVal;
|
||||
|
@ -2,6 +2,7 @@ import Notifications from './Notifications.vue';
|
||||
|
||||
const NotificationStore = {
|
||||
state: [], // here the notifications will be added
|
||||
|
||||
settings: {
|
||||
overlap: false,
|
||||
verticalAlign: 'top',
|
||||
@ -11,26 +12,37 @@ const NotificationStore = {
|
||||
closeOnClick: true,
|
||||
showClose: true
|
||||
},
|
||||
|
||||
setOptions(options) {
|
||||
this.settings = Object.assign(this.settings, options);
|
||||
},
|
||||
|
||||
removeNotification(timestamp) {
|
||||
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
|
||||
|
||||
if (indexToDelete !== -1) {
|
||||
this.state.splice(indexToDelete, 1);
|
||||
}
|
||||
},
|
||||
|
||||
addNotification(notification) {
|
||||
if (typeof notification === 'string' || notification instanceof String) {
|
||||
notification = { message: notification };
|
||||
notification = {
|
||||
message: notification
|
||||
};
|
||||
}
|
||||
|
||||
notification.timestamp = new Date();
|
||||
|
||||
notification.timestamp.setMilliseconds(
|
||||
notification.timestamp.getMilliseconds() + this.state.length
|
||||
);
|
||||
|
||||
notification = Object.assign({}, this.settings, notification);
|
||||
|
||||
this.state.push(notification);
|
||||
},
|
||||
|
||||
notify(notification) {
|
||||
if (Array.isArray(notification)) {
|
||||
notification.forEach(notificationInstance => {
|
||||
@ -48,15 +60,18 @@ const NotificationsPlugin = {
|
||||
data: {
|
||||
notificationStore: NotificationStore
|
||||
},
|
||||
|
||||
methods: {
|
||||
notify(notification) {
|
||||
this.notificationStore.notify(notification);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.prototype.$notify = app.notify;
|
||||
Vue.prototype.$notifications = app.notificationStore;
|
||||
Vue.component('Notifications', Notifications);
|
||||
|
||||
if (options) {
|
||||
NotificationStore.setOptions(options);
|
||||
}
|
||||
|
25
resources/assets/js/mixins/global.js
vendored
25
resources/assets/js/mixins/global.js
vendored
@ -271,6 +271,8 @@ export default {
|
||||
}
|
||||
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: notify.message,
|
||||
timeout: timeout,
|
||||
icon: 'error_outline',
|
||||
@ -467,6 +469,9 @@ export default {
|
||||
onChangePaginationLimit(event) {
|
||||
let path = '';
|
||||
|
||||
let split_href = window.location.href.split('#');
|
||||
let href = split_href[0];
|
||||
|
||||
if (window.location.search.length) {
|
||||
if (window.location.search.includes('limit')) {
|
||||
let queries = [];
|
||||
@ -494,10 +499,14 @@ export default {
|
||||
});
|
||||
|
||||
} else {
|
||||
path = window.location.href + '&limit=' + event.target.getAttribute("value");
|
||||
path = href + '&limit=' + event.target.getAttribute("value");
|
||||
}
|
||||
} else {
|
||||
path = window.location.href + '?limit=' + event.target.getAttribute("value");
|
||||
path = href + '?limit=' + event.target.getAttribute("value");
|
||||
}
|
||||
|
||||
if (split_href[1]) {
|
||||
path += '#' + split_href[1];
|
||||
}
|
||||
|
||||
window.location.href = path;
|
||||
@ -505,6 +514,10 @@ export default {
|
||||
|
||||
// Dynamic component get path view and show it.
|
||||
onDynamicComponent(path) {
|
||||
if (! path) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.get(path)
|
||||
.then(response => {
|
||||
let html = response.data.html;
|
||||
@ -562,6 +575,10 @@ export default {
|
||||
},
|
||||
|
||||
onDynamicFormParams(path, params) {
|
||||
if (! path) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {};
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
@ -1001,7 +1018,7 @@ export default {
|
||||
|
||||
this.component = Vue.component('add-new-component', (resolve, reject) => {
|
||||
resolve({
|
||||
template: '<div id="dynamic-email-component"><akaunting-modal-add-new modal-dialog-class="max-w-screen-md" :show="email.modal" @submit="onSubmit" @cancel="onCancel" :buttons="email.buttons" :title="email.title" :is_component=true :message="email.html"></akaunting-modal-add-new></div>',
|
||||
template: '<div id="dynamic-email-component"><akaunting-modal-add-new modal-dialog-class="max-w-screen-md" modal-position-top :show="email.modal" @submit="onSubmit" @cancel="onCancel" :buttons="email.buttons" :title="email.title" :is_component=true :message="email.html"></akaunting-modal-add-new></div>',
|
||||
|
||||
components: {
|
||||
AkauntingDropzoneFileUpload,
|
||||
@ -1112,6 +1129,8 @@ export default {
|
||||
document.execCommand('copy');
|
||||
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: this.share.success_message,
|
||||
timeout: 5000,
|
||||
icon: 'error_outline',
|
||||
|
4
resources/assets/js/mixins/wizardAction.js
vendored
4
resources/assets/js/mixins/wizardAction.js
vendored
@ -72,6 +72,8 @@ export default {
|
||||
}
|
||||
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: response.data.message,
|
||||
timeout: timeout,
|
||||
icon: "error_outline",
|
||||
@ -92,6 +94,8 @@ export default {
|
||||
}
|
||||
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: event.message,
|
||||
timeout: timeout,
|
||||
icon: "error_outline",
|
||||
|
2
resources/assets/js/views/auth/common.js
vendored
2
resources/assets/js/views/auth/common.js
vendored
@ -54,6 +54,8 @@ const login = new Vue({
|
||||
let type = notify.level;
|
||||
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: notify.message,
|
||||
timeout: 5000,
|
||||
icon: '',
|
||||
|
25
resources/assets/js/views/banking/accounts.js
vendored
25
resources/assets/js/views/banking/accounts.js
vendored
@ -31,29 +31,4 @@ const app = new Vue({
|
||||
bulk_action: new BulkAction('accounts'),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onType(event) {
|
||||
return;
|
||||
let type = event.target.value;
|
||||
|
||||
switch(type) {
|
||||
case 'credit_card':
|
||||
this.onCreditCard();
|
||||
break;
|
||||
case 'bank':
|
||||
default:
|
||||
this.onBank();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onCreditCard() {
|
||||
|
||||
},
|
||||
|
||||
onBank() {
|
||||
|
||||
},
|
||||
}
|
||||
});
|
||||
|
2
resources/assets/js/views/common/contacts.js
vendored
2
resources/assets/js/views/common/contacts.js
vendored
@ -69,6 +69,8 @@ const app = new Vue({
|
||||
|
||||
if (response.data.error) {
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: response.data.message,
|
||||
timeout: 0,
|
||||
icon: 'fas fa-bell',
|
||||
|
@ -621,6 +621,7 @@ const app = new Vue({
|
||||
onChangeCurrency(currency_code) {
|
||||
if (this.edit.status && this.edit.currency <= 2) {
|
||||
this.edit.currency++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
2
resources/assets/js/views/modules/apps.js
vendored
2
resources/assets/js/views/modules/apps.js
vendored
@ -106,6 +106,8 @@ const app = new Vue({
|
||||
add_to_cart_promise.then(response => {
|
||||
if (response.data.success) {
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: response.data.message,
|
||||
timeout: 0,
|
||||
icon: "shopping_cart_checkout",
|
||||
|
@ -141,7 +141,7 @@ const app = new Vue({
|
||||
});
|
||||
},
|
||||
|
||||
// Change currency get money
|
||||
// Change currency get money override because remove form currency_code and currency_rate column
|
||||
onChangeCurrency(currency_code) {
|
||||
if (! currency_code) {
|
||||
return;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative bg-body z-10 rounded-lg shadow-2xl p-5 ltr:pr-0 rtl:pl-0 sm:py-10 sm:ltr:pl-10 sm:rtl:pr-10 overflow-hidden">
|
||||
<div class="relative bg-body z-10 rounded-lg shadow-2xl p-5 sm:p-10 full-height-mobile overflow-hidden">
|
||||
<WizardSteps :active_state="active"></WizardSteps>
|
||||
|
||||
<div class="flex flex-col justify-between -mt-5 sm:mt-0" style="height:565px;">
|
||||
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative w-1/2 right-0 ltr:pl-10 rtl:pr-10 mt-3 hidden lg:flex lg:flex-col">
|
||||
<div class="absolute w-1/2 right-0 ltr:pl-10 rtl:pr-10 mt-3 hidden lg:flex lg:flex-col">
|
||||
<div class="flex flex-col ltr:items-start rtl:items-end bg-purple ltr:rounded-tl-lg ltr:rounded-bl-lg rtl:rounded-tr-lg rtl:rounded-br-lg p-6">
|
||||
<div class="w-48 text-white text-left text-2xl font-semibold leading-9">
|
||||
{{ translations.finish.apps_managing }}
|
||||
@ -50,7 +50,7 @@
|
||||
|
||||
<div style="width:372px; height:372px;"></div>
|
||||
|
||||
<img :src="image_src" class="absolute top-3 right-2" alt="" />
|
||||
<img :src="image_src" class="absolute top-3 right-2" alt="Akaunting" />
|
||||
</div>
|
||||
|
||||
<base-button
|
||||
@ -112,6 +112,8 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$notify({
|
||||
verticalAlign: 'bottom',
|
||||
horizontalAlign: 'left',
|
||||
message: this.translations.finish.error_message,
|
||||
timeout: 1000,
|
||||
icon: "",
|
||||
|
@ -72,6 +72,7 @@ return [
|
||||
'attachments' => 'Adjunt|Adjunts',
|
||||
'histories' => 'Història|Històries',
|
||||
'your_notifications' => 'La teva notificació|Les teves notificacions',
|
||||
'employees' => 'Empleat|Empleats',
|
||||
|
||||
'welcome' => 'Benvingut/da',
|
||||
'banking' => 'Bancs',
|
||||
@ -228,6 +229,7 @@ return [
|
||||
'preview_mode' => 'Mode de previsualització',
|
||||
'go_back' => 'Torna a :type',
|
||||
'validation_error' => 'Error de validació',
|
||||
'dismiss' => 'Ignora',
|
||||
|
||||
'card' => [
|
||||
'cards' => 'Tarjeta|Tarjetes',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user