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
|
public function chunkSize(): int
|
||||||
{
|
{
|
||||||
return 100;
|
return config('excel.imports.chunk_size');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Abstracts\View\Components\Documents;
|
namespace App\Abstracts\View\Components\Documents;
|
||||||
|
|
||||||
use App\Abstracts\View\Component;
|
use App\Abstracts\View\Component;
|
||||||
|
use App\Interfaces\Utility\DocumentNumber;
|
||||||
use App\Models\Common\Contact;
|
use App\Models\Common\Contact;
|
||||||
use App\Models\Document\Document;
|
use App\Models\Document\Document;
|
||||||
use App\Models\Setting\Currency;
|
use App\Models\Setting\Currency;
|
||||||
@ -815,10 +816,14 @@ abstract class Form extends Component
|
|||||||
return $document->document_number;
|
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)) {
|
if (empty($document_number)) {
|
||||||
$document_number = $this->getNextDocumentNumber(Document::INVOICE_TYPE);
|
$document_number = $utility->getNextNumber(Document::INVOICE_TYPE, $contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $document_number;
|
return $document_number;
|
||||||
|
@ -741,9 +741,7 @@ abstract class Show extends Component
|
|||||||
return $textRecurringType;
|
return $textRecurringType;
|
||||||
}
|
}
|
||||||
|
|
||||||
$default_key = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.prefix');
|
$translation = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.tab_document');
|
||||||
|
|
||||||
$translation = $this->getTextFromConfig($type, 'recurring_tye', $default_key);
|
|
||||||
|
|
||||||
if (! empty($translation)) {
|
if (! empty($translation)) {
|
||||||
return $translation;
|
return $translation;
|
||||||
@ -1254,9 +1252,11 @@ abstract class Show extends Component
|
|||||||
return $hideName;
|
return $hideName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
|
if ($hideName === 'hide') {
|
||||||
return $hideName;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'name');
|
$hide = $this->getHideFromConfig($type, 'name');
|
||||||
@ -1265,8 +1265,7 @@ abstract class Show extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideDescription($type, $hideDescription)
|
protected function getHideDescription($type, $hideDescription)
|
||||||
@ -1276,8 +1275,8 @@ abstract class Show extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideDescription = setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||||
return $hideDescription;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'description');
|
$hide = $this->getHideFromConfig($type, 'description');
|
||||||
@ -1286,8 +1285,7 @@ abstract class Show extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.hide_item_description', $hideDescription);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideQuantity($type, $hideQuantity)
|
protected function getHideQuantity($type, $hideQuantity)
|
||||||
@ -1296,9 +1294,11 @@ abstract class Show extends Component
|
|||||||
return $hideQuantity;
|
return $hideQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideQuantity = setting($this->getDocumentSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
|
if ($hideQuantity === 'hide') {
|
||||||
return $hideQuantity;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'quantity');
|
$hide = $this->getHideFromConfig($type, 'quantity');
|
||||||
@ -1307,8 +1307,7 @@ abstract class Show extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHidePrice($type, $hidePrice)
|
protected function getHidePrice($type, $hidePrice)
|
||||||
@ -1317,9 +1316,11 @@ abstract class Show extends Component
|
|||||||
return $hidePrice;
|
return $hidePrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hidePrice = setting($this->getDocumentSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
|
if ($hidePrice === 'hide') {
|
||||||
return $hidePrice;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'price');
|
$hide = $this->getHideFromConfig($type, 'price');
|
||||||
@ -1328,8 +1329,7 @@ abstract class Show extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideDiscount($type, $hideDiscount)
|
protected function getHideDiscount($type, $hideDiscount)
|
||||||
@ -1360,8 +1360,8 @@ abstract class Show extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideAmount = setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||||
return $hideAmount;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'amount');
|
$hide = $this->getHideFromConfig($type, 'amount');
|
||||||
@ -1370,7 +1370,6 @@ abstract class Show extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.hide_amount', $hideAmount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,9 +589,11 @@ abstract class Template extends Component
|
|||||||
return $hideName;
|
return $hideName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
|
if ($hideName === 'hide') {
|
||||||
return $hideName;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'name');
|
$hide = $this->getHideFromConfig($type, 'name');
|
||||||
@ -600,8 +602,7 @@ abstract class Template extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideDescription($type, $hideDescription)
|
protected function getHideDescription($type, $hideDescription)
|
||||||
@ -611,8 +612,8 @@ abstract class Template extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideDescription = setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||||
return $hideDescription;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'description');
|
$hide = $this->getHideFromConfig($type, 'description');
|
||||||
@ -621,8 +622,7 @@ abstract class Template extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.hide_item_description', $hideDescription);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideQuantity($type, $hideQuantity)
|
protected function getHideQuantity($type, $hideQuantity)
|
||||||
@ -631,9 +631,11 @@ abstract class Template extends Component
|
|||||||
return $hideQuantity;
|
return $hideQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideQuantity = setting($this->getDocumentSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
|
if ($hideQuantity === 'hide') {
|
||||||
return $hideQuantity;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'quantity');
|
$hide = $this->getHideFromConfig($type, 'quantity');
|
||||||
@ -642,8 +644,7 @@ abstract class Template extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHidePrice($type, $hidePrice)
|
protected function getHidePrice($type, $hidePrice)
|
||||||
@ -652,9 +653,11 @@ abstract class Template extends Component
|
|||||||
return $hidePrice;
|
return $hidePrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hidePrice = setting($this->getDocumentSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
|
if ($hidePrice === 'hide') {
|
||||||
return $hidePrice;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'price');
|
$hide = $this->getHideFromConfig($type, 'price');
|
||||||
@ -663,8 +666,7 @@ abstract class Template extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHideDiscount($type, $hideDiscount)
|
protected function getHideDiscount($type, $hideDiscount)
|
||||||
@ -695,8 +697,8 @@ abstract class Template extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if you use settting translation
|
// if you use settting translation
|
||||||
if ($hideAmount = setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||||
return $hideAmount;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hide = $this->getHideFromConfig($type, 'amount');
|
$hide = $this->getHideFromConfig($type, 'amount');
|
||||||
@ -705,8 +707,7 @@ abstract class Template extends Component
|
|||||||
return $hide;
|
return $hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo what return value invoice or always false??
|
return false;
|
||||||
return setting('invoice.hide_amount', $hideAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPrint($print)
|
protected function getPrint($print)
|
||||||
|
@ -1113,9 +1113,7 @@ abstract class Show extends Component
|
|||||||
return $textRecurringType;
|
return $textRecurringType;
|
||||||
}
|
}
|
||||||
|
|
||||||
$default_key = config('type.transaction.' . $type . '.translation.transactions');
|
$translation = config('type.transaction.' . $type . '.translation.transactions');
|
||||||
|
|
||||||
$translation = $this->getTextFromConfig($type, 'recurring_type', $default_key);
|
|
||||||
|
|
||||||
if (! empty($translation)) {
|
if (! empty($translation)) {
|
||||||
return $translation;
|
return $translation;
|
||||||
|
@ -68,6 +68,14 @@ class Update extends Command
|
|||||||
|
|
||||||
$this->old = $this->getOldVersion();
|
$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();
|
company($this->company)->makeCurrent();
|
||||||
|
|
||||||
if (!$path = $this->download()) {
|
if (!$path = $this->download()) {
|
||||||
|
@ -74,6 +74,14 @@ class Handler extends ExceptionHandler
|
|||||||
*/
|
*/
|
||||||
public function report(Throwable $exception)
|
public function report(Throwable $exception)
|
||||||
{
|
{
|
||||||
|
if ($exception instanceof MailerHttpTransportException) {
|
||||||
|
$email = $this->handleMailerExceptions($exception);
|
||||||
|
|
||||||
|
if (! empty($email)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parent::report($exception);
|
parent::report($exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,23 +205,20 @@ class Handler extends ExceptionHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($exception instanceof MailerHttpTransportException) {
|
if ($exception instanceof MailerHttpTransportException) {
|
||||||
/**
|
$email = $this->handleMailerExceptions($exception);
|
||||||
* 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])) {
|
if (! empty($email)) {
|
||||||
event(new InvalidEmailDetected($matches[0], $exception->getMessage()));
|
$message = trans('notifications.menu.invalid_email.description', ['email' => $email]);
|
||||||
|
|
||||||
if ($request->ajax()) {
|
if ($request->ajax()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'error' => trans('notifications.menu.invalid_email.description', ['email' => $matches[0]]),
|
'error' => $message,
|
||||||
], $exception->getCode());
|
], $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));
|
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.
|
* 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 App\Abstracts\Http\Controller;
|
||||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
use Illuminate\Http\Request as BaseRequest;
|
||||||
use App\Http\Requests\Auth\Reset as Request;
|
use App\Http\Requests\Auth\Reset as Request;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -29,7 +30,7 @@ class Reset extends Controller
|
|||||||
$this->middleware('guest');
|
$this->middleware('guest');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Request $request, $token = null)
|
public function create(BaseRequest $request, $token = null)
|
||||||
{
|
{
|
||||||
return view('auth.reset.create')->with(
|
return view('auth.reset.create')->with(
|
||||||
['token' => $token, 'email' => $request->email]
|
['token' => $token, 'email' => $request->email]
|
||||||
|
@ -47,9 +47,19 @@ class Users extends Controller
|
|||||||
*
|
*
|
||||||
* @return Response
|
* @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;
|
$landing_pages = $u->landing_pages;
|
||||||
|
|
||||||
$roles = Role::all()->reject(function ($r) {
|
$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');
|
})->pluck('display_name', 'id');
|
||||||
|
|
||||||
$companies = user()->companies()->take(setting('default.select_limit'))->get()->sortBy('name')->pluck('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));
|
$response = $this->ajaxDispatch(new CreateUser($request));
|
||||||
|
|
||||||
if ($response['success']) {
|
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)]);
|
$message = trans('messages.success.invited', ['type' => trans_choice('general.users', 1)]);
|
||||||
|
|
||||||
@ -129,12 +145,21 @@ class Users extends Controller
|
|||||||
if ($user->isCustomer()) {
|
if ($user->isCustomer()) {
|
||||||
// Show only roles with customer permission
|
// Show only roles with customer permission
|
||||||
$roles = Role::all()->reject(function ($r) {
|
$roles = Role::all()->reject(function ($r) {
|
||||||
return !$r->hasPermission('read-client-portal');
|
return ! $r->hasPermission('read-client-portal');
|
||||||
})->pluck('display_name', 'id');
|
})->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 {
|
} else {
|
||||||
// Don't show roles with customer permission
|
// Don't show roles with customer permission
|
||||||
$roles = Role::all()->reject(function ($r) {
|
$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');
|
})->pluck('display_name', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +201,7 @@ class Users extends Controller
|
|||||||
$response = $this->ajaxDispatch(new UpdateUser($user, $request));
|
$response = $this->ajaxDispatch(new UpdateUser($user, $request));
|
||||||
|
|
||||||
if ($response['success']) {
|
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]);
|
$message = trans('messages.success.updated', ['type' => $user->name]);
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class Accounts extends Controller
|
|||||||
*/
|
*/
|
||||||
public function show(Account $account)
|
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')
|
$transfers = Transfer::with('expense_transaction', 'expense_transaction.account', 'income_transaction', 'income_transaction.account')
|
||||||
->whereHas('expense_transaction', fn ($query) => $query->where('account_id', $account->id))
|
->whereHas('expense_transaction', fn ($query) => $query->where('account_id', $account->id))
|
||||||
|
@ -63,9 +63,9 @@ class RecurringTransactions extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$type = request()->get('type', 'income-recurring');
|
$type = $this->getTypeRecurringTransaction(request()->get('type', 'income-recurring'));
|
||||||
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
|
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
|
||||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
|
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
|
||||||
|
|
||||||
$number = $this->getNextTransactionNumber('-recurring');
|
$number = $this->getNextTransactionNumber('-recurring');
|
||||||
|
|
||||||
@ -139,8 +139,8 @@ class RecurringTransactions extends Controller
|
|||||||
public function edit(Transaction $recurring_transaction)
|
public function edit(Transaction $recurring_transaction)
|
||||||
{
|
{
|
||||||
$type = $recurring_transaction->type;
|
$type = $recurring_transaction->type;
|
||||||
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
|
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
|
||||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
|
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
|
||||||
|
|
||||||
$number = $this->getNextTransactionNumber('-recurring');
|
$number = $this->getNextTransactionNumber('-recurring');
|
||||||
|
|
||||||
|
@ -98,10 +98,10 @@ class Transactions extends Controller
|
|||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$type = request()->get('type', 'income');
|
$type = $this->getTypeTransaction(request()->get('type', 'income'));
|
||||||
$real_type = $this->getRealTypeTransaction($type);
|
$real_type = $this->getRealTypeTransaction($type);
|
||||||
|
|
||||||
$number = $this->getNextTransactionNumber();
|
$number = $this->getNextTransactionNumber($type);
|
||||||
|
|
||||||
$contact_type = config('type.transaction.' . $type . '.contact_type');
|
$contact_type = config('type.transaction.' . $type . '.contact_type');
|
||||||
|
|
||||||
|
@ -16,6 +16,15 @@ class Companies extends Controller
|
|||||||
{
|
{
|
||||||
use Uploads, Users;
|
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.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
|
@ -34,6 +34,22 @@ class Uploads extends Controller
|
|||||||
return $this->streamMedia($media);
|
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.
|
* Get the specified resource.
|
||||||
*
|
*
|
||||||
|
@ -242,16 +242,30 @@ class Item extends Controller
|
|||||||
$this->dispatch(new InstallModule($request['alias'], company_id()));
|
$this->dispatch(new InstallModule($request['alias'], company_id()));
|
||||||
|
|
||||||
$name = module($request['alias'])->getName();
|
$name = module($request['alias'])->getName();
|
||||||
|
$module_routes = module_attribute($request['alias'], 'routes', []);
|
||||||
|
|
||||||
$message = trans('modules.installed', ['module' => $name]);
|
$message = trans('modules.installed', ['module' => $name]);
|
||||||
|
|
||||||
flash($message)->success();
|
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 = [
|
$json = [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'error' => false,
|
'error' => false,
|
||||||
'message' => null,
|
'message' => null,
|
||||||
'redirect' => route('apps.app.show', $request['alias']),
|
'redirect' => $redirect,
|
||||||
'data' => [
|
'data' => [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'alias' => $request['alias'],
|
'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\Common\Import as ImportRequest;
|
||||||
use App\Http\Requests\Document\Document as Request;
|
use App\Http\Requests\Document\Document as Request;
|
||||||
use App\Imports\Purchases\Bills as Import;
|
use App\Imports\Purchases\Bills as Import;
|
||||||
use App\Jobs\Banking\CreateBankingDocumentTransaction;
|
|
||||||
use App\Jobs\Document\CreateDocument;
|
use App\Jobs\Document\CreateDocument;
|
||||||
use App\Jobs\Document\DeleteDocument;
|
use App\Jobs\Document\DeleteDocument;
|
||||||
use App\Jobs\Document\DuplicateDocument;
|
use App\Jobs\Document\DuplicateDocument;
|
||||||
@ -31,7 +30,7 @@ class Bills extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
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'));
|
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\SendDocument;
|
||||||
use App\Jobs\Document\UpdateDocument;
|
use App\Jobs\Document\UpdateDocument;
|
||||||
use App\Models\Document\Document;
|
use App\Models\Document\Document;
|
||||||
use App\Notifications\Sale\Invoice as Notification;
|
|
||||||
use App\Traits\Documents;
|
use App\Traits\Documents;
|
||||||
|
|
||||||
class Invoices extends Controller
|
class Invoices extends Controller
|
||||||
@ -32,7 +31,7 @@ class Invoices extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
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'));
|
return $this->response('sales.invoices.index', compact('invoices'));
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ class CustomMail extends FormRequest
|
|||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'to' => 'required|email',
|
'to' => 'required|email',
|
||||||
'subject' => 'required|string',
|
'subject' => 'required|string',
|
||||||
'body' => 'required|string',
|
'body' => 'required|string',
|
||||||
|
'attachments.*' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ class Item extends JsonResource
|
|||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'sale_price' => $this->sale_price,
|
'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' => $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,
|
'category_id' => $this->category_id,
|
||||||
'picture' => $this->picture,
|
'picture' => $this->picture,
|
||||||
'enabled' => $this->enabled,
|
'enabled' => $this->enabled,
|
||||||
|
@ -8,6 +8,7 @@ use App\Http\Resources\Document\DocumentHistory;
|
|||||||
use App\Http\Resources\Document\DocumentItem;
|
use App\Http\Resources\Document\DocumentItem;
|
||||||
use App\Http\Resources\Document\DocumentItemTax;
|
use App\Http\Resources\Document\DocumentItemTax;
|
||||||
use App\Http\Resources\Document\DocumentTotal;
|
use App\Http\Resources\Document\DocumentTotal;
|
||||||
|
use App\Http\Resources\Setting\Category;
|
||||||
use App\Http\Resources\Setting\Currency;
|
use App\Http\Resources\Setting\Currency;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ class Document extends JsonResource
|
|||||||
'due_at' => $this->due_at ? $this->due_at->toIso8601String() : '',
|
'due_at' => $this->due_at ? $this->due_at->toIso8601String() : '',
|
||||||
'amount' => $this->amount,
|
'amount' => $this->amount,
|
||||||
'amount_formatted' => money($this->amount, $this->currency_code, true)->format(),
|
'amount_formatted' => money($this->amount, $this->currency_code, true)->format(),
|
||||||
|
'category_id' => $this->category_id,
|
||||||
'currency_code' => $this->currency_code,
|
'currency_code' => $this->currency_code,
|
||||||
'currency_rate' => $this->currency_rate,
|
'currency_rate' => $this->currency_rate,
|
||||||
'contact_id' => $this->contact_id,
|
'contact_id' => $this->contact_id,
|
||||||
@ -50,6 +52,7 @@ class Document extends JsonResource
|
|||||||
'created_by' => $this->created_by,
|
'created_by' => $this->created_by,
|
||||||
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',
|
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',
|
||||||
'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '',
|
'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '',
|
||||||
|
'category' => new Category($this->category),
|
||||||
'currency' => new Currency($this->currency),
|
'currency' => new Currency($this->currency),
|
||||||
'contact' => new Contact($this->contact),
|
'contact' => new Contact($this->contact),
|
||||||
'histories' => [static::$wrap => DocumentHistory::collection($this->histories)],
|
'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
|
public function handle(): UserInvitation
|
||||||
{
|
{
|
||||||
\DB::transaction(function () {
|
\DB::transaction(function () {
|
||||||
|
$invitations = UserInvitation::where('user_id', $this->user->id)->get();
|
||||||
|
|
||||||
|
foreach ($invitations as $invitation) {
|
||||||
|
$invitation->delete();
|
||||||
|
}
|
||||||
|
|
||||||
$this->invitation = UserInvitation::create([
|
$this->invitation = UserInvitation::create([
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
'token' => (string) Str::uuid(),
|
'token' => (string) Str::uuid(),
|
||||||
|
@ -12,6 +12,10 @@ class UpdateRole extends Job implements ShouldUpdate
|
|||||||
{
|
{
|
||||||
public function handle(): Role
|
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));
|
event(new RoleUpdating($this->model, $this->request));
|
||||||
|
|
||||||
\DB::transaction(function () {
|
\DB::transaction(function () {
|
||||||
|
@ -16,6 +16,12 @@ class CreateTransaction extends Job implements HasOwner, HasSource, ShouldCreate
|
|||||||
{
|
{
|
||||||
event(new TransactionCreating($this->request));
|
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 () {
|
\DB::transaction(function () {
|
||||||
$this->model = Transaction::create($this->request->all());
|
$this->model = Transaction::create($this->request->all());
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class DeleteAccount extends Job implements ShouldDelete
|
|||||||
{
|
{
|
||||||
$rels = [
|
$rels = [
|
||||||
'transactions' => 'transactions',
|
'transactions' => 'transactions',
|
||||||
|
'reconciliations' => 'reconciliations',
|
||||||
];
|
];
|
||||||
|
|
||||||
$relationships = $this->countRelationships($this->model, $rels);
|
$relationships = $this->countRelationships($this->model, $rels);
|
||||||
|
@ -16,6 +16,12 @@ class UpdateTransaction extends Job implements ShouldUpdate
|
|||||||
|
|
||||||
event(new TransactionUpdating($this->model, $this->request));
|
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 () {
|
\DB::transaction(function () {
|
||||||
$this->model->update($this->request->all());
|
$this->model->update($this->request->all());
|
||||||
|
|
||||||
|
@ -30,10 +30,15 @@ class SendDocumentAsCustomMail extends Job
|
|||||||
$custom_mail['cc'] = user()->email;
|
$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');
|
$notification = config('type.document.' . $document->type . '.notification.class');
|
||||||
|
|
||||||
// Notify the contact
|
// 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));
|
event(new DocumentSent($document));
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,10 @@ class SendDocumentPaymentNotification
|
|||||||
$document = $event->document;
|
$document = $event->document;
|
||||||
$transaction = $document->transactions()->latest()->first();
|
$transaction = $document->transactions()->latest()->first();
|
||||||
|
|
||||||
|
if (! $transaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Notify the customer
|
// Notify the customer
|
||||||
if ($document->contact && !empty($document->contact_email)) {
|
if ($document->contact && !empty($document->contact_email)) {
|
||||||
$document->contact->notify(new Notification($document, $transaction, "{$document->type}_payment_customer"), true);
|
$document->contact->notify(new Notification($document, $transaction, "{$document->type}_payment_customer"), true);
|
||||||
|
@ -29,6 +29,13 @@ class DisablePersonDueToInvalidEmail
|
|||||||
return;
|
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->enabled = false;
|
||||||
$event->user->save();
|
$event->user->save();
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,11 @@ class SendInvalidEmailNotification
|
|||||||
{
|
{
|
||||||
public function handle(Event $event): void
|
public function handle(Event $event): void
|
||||||
{
|
{
|
||||||
$users = company()->users;
|
$users = company()?->users;
|
||||||
|
|
||||||
|
if (empty($users)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->notifyAdminsAboutInvalidContactEmail($event, $users);
|
$this->notifyAdminsAboutInvalidContactEmail($event, $users);
|
||||||
|
|
||||||
@ -44,7 +48,7 @@ class SendInvalidEmailNotification
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = trans('general.users', 1);
|
$type = trans_choice('general.users', 1);
|
||||||
|
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
if ($user->cannot('read-notifications')) {
|
if ($user->cannot('read-notifications')) {
|
||||||
|
@ -30,12 +30,16 @@ class TellFirewallTooManyEmailsSent
|
|||||||
|
|
||||||
public function loadConfig(): void
|
public function loadConfig(): void
|
||||||
{
|
{
|
||||||
|
if (! empty(Config::get('firewall.middleware.' . $this->middleware))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$config = array_merge_recursive(
|
$config = array_merge_recursive(
|
||||||
Config::get('firewall'),
|
Config::get('firewall'),
|
||||||
[
|
[
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
$this->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'],
|
'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'];
|
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.
|
* Get the line actions.
|
||||||
*
|
*
|
||||||
@ -71,33 +100,4 @@ class Role extends LaratrustRole
|
|||||||
|
|
||||||
return $actions;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,11 @@ class User extends Authenticatable implements HasLocalePreference
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'enabled' => 'boolean',
|
'enabled' => 'boolean',
|
||||||
'deleted_at' => 'datetime',
|
'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'];
|
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.
|
* Sortable columns.
|
||||||
*
|
*
|
||||||
@ -244,6 +240,28 @@ class User extends Authenticatable implements HasLocalePreference
|
|||||||
return $query->wherePermissionIs('read-admin-panel');
|
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)
|
public function scopeEmail($query, $email)
|
||||||
{
|
{
|
||||||
return $query->where('email', '=', $email);
|
return $query->where('email', '=', $email);
|
||||||
@ -293,6 +311,26 @@ class User extends Authenticatable implements HasLocalePreference
|
|||||||
return (bool) $this->can('read-admin-panel');
|
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)
|
public function scopeSource($query, $source)
|
||||||
{
|
{
|
||||||
return $query->where($this->qualifyColumn('created_from'), $source);
|
return $query->where($this->qualifyColumn('created_from'), $source);
|
||||||
@ -340,13 +378,23 @@ class User extends Authenticatable implements HasLocalePreference
|
|||||||
return $actions;
|
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[] = [
|
$actions[] = [
|
||||||
'title' => trans('general.edit'),
|
'title' => trans('general.edit'),
|
||||||
'icon' => 'edit',
|
'icon' => 'edit',
|
||||||
'url' => route('users.edit', $this->id),
|
'url' => route('users.edit', $this->id),
|
||||||
'permission' => 'update-auth-users',
|
'permission' => 'update-auth-users',
|
||||||
'attributes' => [
|
'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');
|
return $this->hasMany('App\Models\Banking\Transaction');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reconciliations()
|
||||||
|
{
|
||||||
|
return $this->hasMany('App\Models\Banking\Reconciliation');
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeName($query, $name)
|
public function scopeName($query, $name)
|
||||||
{
|
{
|
||||||
return $query->where('name', '=', $name);
|
return $query->where('name', '=', $name);
|
||||||
|
@ -11,8 +11,6 @@ class Reconciliation extends Model
|
|||||||
|
|
||||||
protected $table = 'reconciliations';
|
protected $table = 'reconciliations';
|
||||||
|
|
||||||
protected $dates = ['deleted_at', 'started_at', 'ended_at'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attributes that should be mass-assignable.
|
* Attributes that should be mass-assignable.
|
||||||
*
|
*
|
||||||
@ -30,6 +28,8 @@ class Reconciliation extends Model
|
|||||||
'reconciled' => 'boolean',
|
'reconciled' => 'boolean',
|
||||||
'transactions' => 'array',
|
'transactions' => 'array',
|
||||||
'deleted_at' => 'datetime',
|
'deleted_at' => 'datetime',
|
||||||
|
'started_at' => 'datetime',
|
||||||
|
'ended_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +41,7 @@ class Reconciliation extends Model
|
|||||||
|
|
||||||
public function account()
|
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->number = $this->getNextTransactionNumber($suffix);
|
||||||
$this->document_id = null;
|
$this->document_id = null;
|
||||||
$this->split_id = null;
|
$this->split_id = null;
|
||||||
|
unset($this->reconciled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,6 +27,8 @@ class Company extends Eloquent implements Ownable
|
|||||||
|
|
||||||
protected $table = 'companies';
|
protected $table = 'companies';
|
||||||
|
|
||||||
|
//protected $with = ['settings'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The accessors to append to the model's array form.
|
* The accessors to append to the model's array form.
|
||||||
*
|
*
|
||||||
@ -527,7 +529,7 @@ class Company extends Eloquent implements Ownable
|
|||||||
|
|
||||||
$country = setting('company.country');
|
$country = setting('company.country');
|
||||||
|
|
||||||
if ($country && in_array($country, trans('countries'))) {
|
if ($country && array_key_exists($country, trans('countries'))) {
|
||||||
$location[] = trans('countries.' . $country);
|
$location[] = trans('countries.' . $country);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +550,7 @@ class Company extends Eloquent implements Ownable
|
|||||||
'title' => trans('general.switch'),
|
'title' => trans('general.switch'),
|
||||||
'icon' => 'settings_ethernet',
|
'icon' => 'settings_ethernet',
|
||||||
'url' => route('companies.switch', $this->id),
|
'url' => route('companies.switch', $this->id),
|
||||||
'permission' => 'read-common-companies',
|
//'permission' => 'read-common-companies', remove this permission to allow switching to any company
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'id' => 'index-line-actions-switch-company-' . $this->id,
|
'id' => 'index-line-actions-switch-company-' . $this->id,
|
||||||
],
|
],
|
||||||
|
@ -26,6 +26,13 @@ class Contact extends Model
|
|||||||
|
|
||||||
protected $table = 'contacts';
|
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.
|
* 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());
|
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)
|
public function scopeEmail($query, $email)
|
||||||
{
|
{
|
||||||
return $query->where('email', '=', $email);
|
return $query->where('email', '=', $email);
|
||||||
@ -260,7 +278,7 @@ class Contact extends Model
|
|||||||
$location[] = $this->state;
|
$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);
|
$location[] = trans('countries.' . $this->country);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,13 @@ class Item extends Model
|
|||||||
|
|
||||||
protected $table = 'items';
|
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.
|
* The accessors to append to the model's array form.
|
||||||
*
|
*
|
||||||
@ -94,6 +101,15 @@ class Item extends Model
|
|||||||
return $query->whereNotNull($price_type . '_price');
|
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.
|
* Get the item id.
|
||||||
*
|
*
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models\Document;
|
namespace App\Models\Document;
|
||||||
|
|
||||||
use App\Abstracts\Model;
|
use App\Abstracts\Model;
|
||||||
|
use App\Interfaces\Utility\DocumentNumber;
|
||||||
use App\Models\Common\Media as MediaModel;
|
use App\Models\Common\Media as MediaModel;
|
||||||
use App\Models\Setting\Tax;
|
use App\Models\Setting\Tax;
|
||||||
use App\Scopes\Document as Scope;
|
use App\Scopes\Document as Scope;
|
||||||
@ -251,7 +252,7 @@ class Document extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->status = 'draft';
|
$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)
|
public function getSentAtAttribute(string $value = null)
|
||||||
@ -470,7 +471,7 @@ class Document extends Model
|
|||||||
$location[] = $this->contact_state;
|
$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);
|
$location[] = trans('countries.' . $this->contact_country);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,13 @@ class DocumentItem extends Model
|
|||||||
|
|
||||||
protected $table = 'document_items';
|
protected $table = 'document_items';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationships that should always be loaded.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $with = ['taxes'];
|
||||||
|
|
||||||
protected $appends = ['discount'];
|
protected $appends = ['discount'];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class ExportCompleted extends Notification implements ShouldQueue
|
class ExportCompleted extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -52,6 +53,7 @@ class ExportCompleted extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(trans('notifications.export.completed.title'))
|
->subject(trans('notifications.export.completed.title'))
|
||||||
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(trans('notifications.export.completed.description'))
|
->line(trans('notifications.export.completed.description'))
|
||||||
->action(trans('general.download'), $this->download_url);
|
->action(trans('general.download'), $this->download_url);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class ExportFailed extends Notification implements ShouldQueue
|
class ExportFailed extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -51,8 +52,11 @@ class ExportFailed extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(trans('notifications.export.failed.title'))
|
->subject(trans('notifications.export.failed.title'))
|
||||||
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(trans('notifications.export.failed.description'))
|
->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\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class ImportCompleted extends Notification implements ShouldQueue
|
class ImportCompleted extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -49,6 +50,7 @@ class ImportCompleted extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(trans('notifications.import.completed.title'))
|
->subject(trans('notifications.import.completed.title'))
|
||||||
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(trans('notifications.import.completed.description'))
|
->line(trans('notifications.import.completed.description'))
|
||||||
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use Illuminate\Bus\Queueable;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
class ImportFailed extends Notification implements ShouldQueue
|
class ImportFailed extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -51,12 +52,16 @@ class ImportFailed extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
$message = (new MailMessage)
|
$message = (new MailMessage)
|
||||||
->subject(trans('notifications.import.failed.title'))
|
->subject(trans('notifications.import.failed.title'))
|
||||||
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(trans('notifications.import.failed.description'));
|
->line(trans('notifications.import.failed.description'));
|
||||||
|
|
||||||
foreach ($this->errors as $error) {
|
foreach ($this->errors as $error) {
|
||||||
|
$message->line(new HtmlString('<br><br>'));
|
||||||
$message->line($error);
|
$message->line($error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$message->line(new HtmlString('<br><br>'));
|
||||||
|
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +55,9 @@ class InvalidEmail extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(trans('notifications.email.invalid.title', ['type' => $this->type]))
|
->subject(trans('notifications.email.invalid.title', ['type' => $this->type]))
|
||||||
->line(new HtmlString('<br>'))
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(new HtmlString('<br>'))
|
|
||||||
->line(trans('notifications.email.invalid.description', ['email' => $this->email]))
|
->line(trans('notifications.email.invalid.description', ['email' => $this->email]))
|
||||||
->line(new HtmlString('<br>'))
|
->line(new HtmlString('<br><br>'))
|
||||||
->line(new HtmlString('<br>'))
|
|
||||||
->line(new HtmlString('<i>' . $this->error . '</i>'))
|
->line(new HtmlString('<i>' . $this->error . '</i>'))
|
||||||
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
->action(trans_choice('general.dashboards', 1), $dashboard_url);
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,17 @@ class Invoice extends Notification
|
|||||||
*/
|
*/
|
||||||
public $attach_pdf;
|
public $attach_pdf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of document attachments to attach when sending the email.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $attachments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a notification instance.
|
* 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();
|
parent::__construct();
|
||||||
|
|
||||||
@ -46,6 +53,7 @@ class Invoice extends Notification
|
|||||||
$this->template = EmailTemplate::alias($template_alias)->first();
|
$this->template = EmailTemplate::alias($template_alias)->first();
|
||||||
$this->attach_pdf = $attach_pdf;
|
$this->attach_pdf = $attach_pdf;
|
||||||
$this->custom_mail = $custom_mail;
|
$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;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +43,13 @@ class App extends Provider
|
|||||||
Model::preventLazyLoading(config('app.eager_load'));
|
Model::preventLazyLoading(config('app.eager_load'));
|
||||||
|
|
||||||
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
|
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
|
||||||
$class = get_class($model);
|
if (config('logging.default') == 'sentry') {
|
||||||
|
\Sentry\Laravel\Integration::lazyLoadingViolationReporter();
|
||||||
report("Attempted to lazy load [{$relation}] on model [{$class}].");
|
} 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); ?>";
|
return "<?php echo show_widget($expression); ?>";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Facade::directive('moduleIsEnabled', function ($expression) {
|
||||||
|
return "<?php echo module_is_enabled($expression); ?>";
|
||||||
|
});
|
||||||
|
|
||||||
Facade::if('readonly', function () {
|
Facade::if('readonly', function () {
|
||||||
return config('read-only.enabled');
|
return config('read-only.enabled');
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ class Event extends Provider
|
|||||||
'App\Listeners\Update\V30\Version309',
|
'App\Listeners\Update\V30\Version309',
|
||||||
'App\Listeners\Update\V30\Version3013',
|
'App\Listeners\Update\V30\Version3013',
|
||||||
'App\Listeners\Update\V30\Version3014',
|
'App\Listeners\Update\V30\Version3014',
|
||||||
|
'App\Listeners\Update\V30\Version3015',
|
||||||
],
|
],
|
||||||
'Illuminate\Auth\Events\Login' => [
|
'Illuminate\Auth\Events\Login' => [
|
||||||
'App\Listeners\Auth\Login',
|
'App\Listeners\Auth\Login',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Traits;
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Interfaces\Utility\DocumentNumber;
|
||||||
use App\Models\Document\Document;
|
use App\Models\Document\Document;
|
||||||
use App\Abstracts\View\Components\Documents\Document as DocumentComponent;
|
use App\Abstracts\View\Components\Documents\Document as DocumentComponent;
|
||||||
use App\Utilities\Date;
|
use App\Utilities\Date;
|
||||||
@ -44,29 +45,24 @@ trait Documents
|
|||||||
return $recurring_types;
|
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
|
public function getNextDocumentNumber(string $type): string
|
||||||
{
|
{
|
||||||
if ($alias = config('type.document.' . $type . '.alias')) {
|
return app(DocumentNumber::class)->getNextNumber($type, null);
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
public function increaseNextDocumentNumber(string $type): void
|
||||||
{
|
{
|
||||||
if ($alias = config('type.document.' . $type . '.alias')) {
|
app(DocumentNumber::class)->increaseNextNumber($type, null);
|
||||||
$type = $alias . '.' . str_replace('-', '_', $type);
|
|
||||||
}
|
|
||||||
|
|
||||||
$next = setting($type . '.number_next', 1) + 1;
|
|
||||||
|
|
||||||
setting([$type . '.number_next' => $next]);
|
|
||||||
setting()->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDocumentStatuses(string $type): Collection
|
public function getDocumentStatuses(string $type): Collection
|
||||||
|
@ -137,12 +137,14 @@ trait Import
|
|||||||
return is_null($id) ? $id : (int) $id;
|
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;
|
$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'])) {
|
if (empty($id) && !empty($row['item_name'])) {
|
||||||
$id = $this->getItemIdFromName($row);
|
$id = $this->getItemIdFromName($row, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_null($id) ? $id : (int) $id;
|
return is_null($id) ? $id : (int) $id;
|
||||||
@ -246,7 +248,7 @@ trait Import
|
|||||||
|
|
||||||
public function getCategoryIdFromName($row, $type)
|
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)) {
|
if (!empty($category_id)) {
|
||||||
return $category_id;
|
return $category_id;
|
||||||
@ -271,7 +273,7 @@ trait Import
|
|||||||
|
|
||||||
public function getContactIdFromEmail($row, $type)
|
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)) {
|
if (!empty($contact_id)) {
|
||||||
return $contact_id;
|
return $contact_id;
|
||||||
@ -297,7 +299,7 @@ trait Import
|
|||||||
|
|
||||||
public function getContactIdFromName($row, $type)
|
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)) {
|
if (!empty($contact_id)) {
|
||||||
return $contact_id;
|
return $contact_id;
|
||||||
@ -321,9 +323,11 @@ trait Import
|
|||||||
return $contact->id;
|
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)) {
|
if (!empty($item_id)) {
|
||||||
return $item_id;
|
return $item_id;
|
||||||
@ -331,7 +335,7 @@ trait Import
|
|||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'company_id' => company_id(),
|
'company_id' => company_id(),
|
||||||
'type' => !empty($row['item_type']) ? $row['item_type'] : (!empty($row['type']) ? $row['type'] : 'product'),
|
'type' => $type,
|
||||||
'name' => $row['item_name'],
|
'name' => $row['item_name'],
|
||||||
'description' => !empty($row['item_description']) ? $row['item_description'] : null,
|
'description' => !empty($row['item_description']) ? $row['item_description'] : null,
|
||||||
'sale_price' => !empty($row['sale_price']) ? $row['sale_price'] : (!empty($row['price']) ? $row['price'] : 0),
|
'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')
|
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)) {
|
if (!empty($tax_id)) {
|
||||||
return $tax_id;
|
return $tax_id;
|
||||||
|
@ -440,7 +440,7 @@ trait Modules
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if module is installed in cloud
|
// 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', []);
|
$modules = Cache::get('cloud.companies.' . company_id() . '.modules.installed', []);
|
||||||
|
|
||||||
if (in_array($alias, $modules)) {
|
if (in_array($alias, $modules)) {
|
||||||
|
@ -186,26 +186,19 @@ trait Recurring
|
|||||||
return $limit;
|
return $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCurrentRecurring()
|
|
||||||
{
|
|
||||||
if (! $schedule = $this->getRecurringSchedule()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $current = $schedule->current()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $current->getStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNextRecurring()
|
public function getNextRecurring()
|
||||||
{
|
{
|
||||||
if (! $schedule = $this->getRecurringSchedule()) {
|
if (! $schedule = $this->getRecurringSchedule()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $next = $schedule->next()) {
|
$schedule = $schedule->startsAfter($this->getRecurringRuleTodayDate());
|
||||||
|
|
||||||
|
if ($schedule->count() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $next = $schedule->current()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Traits;
|
|||||||
|
|
||||||
use App\Events\Banking\TransactionPrinting;
|
use App\Events\Banking\TransactionPrinting;
|
||||||
use App\Models\Banking\Transaction;
|
use App\Models\Banking\Transaction;
|
||||||
|
use App\Interfaces\Utility\TransactionNumber;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
trait Transactions
|
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
|
public function getRealTypeTransaction(string $type): string
|
||||||
{
|
{
|
||||||
$type = $this->getRealTypeOfRecurringTransaction($type);
|
$type = $this->getRealTypeOfRecurringTransaction($type);
|
||||||
@ -213,6 +219,15 @@ trait Transactions
|
|||||||
return $type;
|
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
|
public function getRealTypeOfRecurringTransaction(string $recurring_type): string
|
||||||
{
|
{
|
||||||
return Str::replace('-recurring', '', $recurring_type);
|
return Str::replace('-recurring', '', $recurring_type);
|
||||||
@ -228,36 +243,13 @@ trait Transactions
|
|||||||
return Str::replace('-split', '', $transfer_type);
|
return Str::replace('-split', '', $transfer_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNextTransactionNumber($suffix = ''): string
|
public function getNextTransactionNumber($type = 'income', $suffix = ''): string
|
||||||
{
|
{
|
||||||
$prefix = setting('transaction' . $suffix . '.number_prefix');
|
return app(TransactionNumber::class)->getNextNumber($type, $suffix, null);
|
||||||
$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 increaseNextTransactionNumber($suffix = ''): void
|
public function increaseNextTransactionNumber($type = 'income', $suffix = ''): void
|
||||||
{
|
{
|
||||||
$next = setting('transaction' . $suffix . '.number_next', 1) + 1;
|
app(TransactionNumber::class)->increaseNextNumber($type, $suffix, null);
|
||||||
|
|
||||||
setting(['transaction' . $suffix . '.number_next' => $next]);
|
|
||||||
setting()->save();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ trait Uploads
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function streamMedia($media)
|
public function streamMedia($media, $disposition = 'attachment')
|
||||||
{
|
{
|
||||||
return response()->streamDownload(
|
return response()->streamDownload(
|
||||||
function() use ($media) {
|
function() use ($media) {
|
||||||
@ -133,6 +133,7 @@ trait Uploads
|
|||||||
'Content-Type' => $media->mime_type,
|
'Content-Type' => $media->mime_type,
|
||||||
'Content-Length' => $media->size,
|
'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\Company;
|
||||||
use App\Models\Common\Contact;
|
use App\Models\Common\Contact;
|
||||||
use App\Models\Document\Document;
|
use App\Models\Document\Document;
|
||||||
|
use App\Traits\Cloud;
|
||||||
use Composer\InstalledVersions;
|
use Composer\InstalledVersions;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
@ -13,7 +14,15 @@ class Info
|
|||||||
{
|
{
|
||||||
public static function all()
|
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'),
|
'api_key' => setting('apps.api_key'),
|
||||||
'ip' => static::ip(),
|
'ip' => static::ip(),
|
||||||
'companies' => Company::count(),
|
'companies' => Company::count(),
|
||||||
@ -22,11 +31,19 @@ class Info
|
|||||||
'customers' => Contact::customer()->count(),
|
'customers' => Contact::customer()->count(),
|
||||||
'php_extensions' => static::phpExtensions(),
|
'php_extensions' => static::phpExtensions(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
return $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function versions()
|
public static function versions()
|
||||||
{
|
{
|
||||||
return [
|
static $versions = [];
|
||||||
|
|
||||||
|
if (! empty($versions)) {
|
||||||
|
return $versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
$versions = [
|
||||||
'akaunting' => version('short'),
|
'akaunting' => version('short'),
|
||||||
'laravel' => InstalledVersions::getPrettyVersion('laravel/framework'),
|
'laravel' => InstalledVersions::getPrettyVersion('laravel/framework'),
|
||||||
'php' => static::phpVersion(),
|
'php' => static::phpVersion(),
|
||||||
@ -35,6 +52,8 @@ class Info
|
|||||||
'livewire' => InstalledVersions::getPrettyVersion('livewire/livewire'),
|
'livewire' => InstalledVersions::getPrettyVersion('livewire/livewire'),
|
||||||
'omnipay' => InstalledVersions::getPrettyVersion('league/omnipay'),
|
'omnipay' => InstalledVersions::getPrettyVersion('league/omnipay'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return $versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function phpVersion()
|
public static function phpVersion()
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
namespace App\Utilities;
|
namespace App\Utilities;
|
||||||
|
|
||||||
use App\Events\Module\PaymentMethodShowing;
|
use App\Events\Module\PaymentMethodShowing;
|
||||||
use Cache;
|
use App\Utilities\Date;
|
||||||
use Date;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class Modules
|
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;
|
namespace App\Utilities;
|
||||||
|
|
||||||
use App\Traits\SiteApi;
|
use App\Traits\SiteApi;
|
||||||
use Cache;
|
use App\Utilities\Date;
|
||||||
use Date;
|
|
||||||
use GrahamCampbell\Markdown\Facades\Markdown;
|
use GrahamCampbell\Markdown\Facades\Markdown;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class Versions
|
class Versions
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ class Versions
|
|||||||
$versions = static::all($alias);
|
$versions = static::all($alias);
|
||||||
|
|
||||||
if (empty($versions[$alias])) {
|
if (empty($versions[$alias])) {
|
||||||
return false;
|
return static::getVersionByAlias($alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $versions[$alias];
|
return $versions[$alias];
|
||||||
@ -128,6 +128,30 @@ class Versions
|
|||||||
return $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)
|
public static function getLatestVersion($url, $latest)
|
||||||
{
|
{
|
||||||
$version = new \stdClass();
|
$version = new \stdClass();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use App\Models\Common\Company;
|
use App\Models\Common\Company;
|
||||||
use App\Traits\DateTime;
|
use App\Traits\DateTime;
|
||||||
use App\Traits\Sources;
|
use App\Traits\Sources;
|
||||||
|
use App\Traits\Modules;
|
||||||
use App\Utilities\Date;
|
use App\Utilities\Date;
|
||||||
use App\Utilities\Widgets;
|
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')) {
|
if (! function_exists('company_id')) {
|
||||||
/**
|
/**
|
||||||
* Get id of current company.
|
* Get id of current company.
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\View\Components\Documents\Form;
|
namespace App\View\Components\Documents\Form;
|
||||||
|
|
||||||
use App\Abstracts\View\Components\Documents\Form as Component;
|
use App\Abstracts\View\Components\Documents\Form as Component;
|
||||||
use App\Models\Common\Company as Model;
|
|
||||||
|
|
||||||
class Company extends Component
|
class Company extends Component
|
||||||
{
|
{
|
||||||
@ -14,7 +13,7 @@ class Company extends Component
|
|||||||
*/
|
*/
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$company = Model::find(company_id());
|
$company = company();
|
||||||
|
|
||||||
$inputNameType = config('type.document.' . $this->type . '.route.parameter');
|
$inputNameType = config('type.document.' . $this->type . '.route.parameter');
|
||||||
|
|
||||||
|
@ -20,11 +20,7 @@ class NumberDigit extends Form
|
|||||||
if (empty($this->name)) {
|
if (empty($this->name)) {
|
||||||
$this->name = 'number_digit';
|
$this->name = 'number_digit';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($this->label)) {
|
|
||||||
$this->label = trans('settings.invoice.digit');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->number_digits = [
|
$this->number_digits = [
|
||||||
'1' => '1',
|
'1' => '1',
|
||||||
'2' => '2',
|
'2' => '2',
|
||||||
|
@ -37,7 +37,7 @@ class Country extends Component
|
|||||||
*/
|
*/
|
||||||
public function render()
|
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);
|
$this->country = trans('countries.' . $this->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,10 @@ class Menu extends Component
|
|||||||
|
|
||||||
public function getCompanies()
|
public function getCompanies()
|
||||||
{
|
{
|
||||||
|
$companies = [];
|
||||||
|
|
||||||
if ($user = user()) {
|
if ($user = user()) {
|
||||||
$companies = $user->companies()->enabled()->limit(10)->get()->sortBy('name');
|
$companies = $user->companies()->enabled()->limit(10)->get()->sortBy('name');
|
||||||
} else {
|
|
||||||
$companies = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $companies;
|
return $companies;
|
||||||
|
@ -50,7 +50,7 @@ class PaymentMethod extends Component
|
|||||||
|
|
||||||
// check here protal or admin panel..
|
// check here protal or admin panel..
|
||||||
if (empty($type)) {
|
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);
|
$payment_methods = Modules::getPaymentMethods($type);
|
||||||
|
@ -75,6 +75,7 @@ class SearchString extends Component
|
|||||||
'type' => $this->getFilterType($options),
|
'type' => $this->getFilterType($options),
|
||||||
'url' => $this->getFilterUrl($column, $options),
|
'url' => $this->getFilterUrl($column, $options),
|
||||||
'values' => $this->getFilterValues($column, $options),
|
'values' => $this->getFilterValues($column, $options),
|
||||||
|
'value_option_fields' => $options['fields'] ?? [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class ExpensesByCategory extends Widget
|
|||||||
|
|
||||||
public function show()
|
public function show()
|
||||||
{
|
{
|
||||||
Category::with('expense_transactions')->expense()->each(function ($category) {
|
Category::with('expense_transactions')->expense()->withSubCategory()->getWithoutChildren()->each(function ($category) {
|
||||||
$amount = 0;
|
$amount = 0;
|
||||||
|
|
||||||
$this->applyFilters($category->expense_transactions)->each(function ($transaction) use (&$amount) {
|
$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\App::class,
|
||||||
App\Providers\Auth::class,
|
App\Providers\Auth::class,
|
||||||
|
App\Providers\Binding::class,
|
||||||
App\Providers\Blade::class,
|
App\Providers\Blade::class,
|
||||||
// App\Providers\Broadcast::class,
|
// App\Providers\Broadcast::class,
|
||||||
App\Providers\Event::class,
|
App\Providers\Event::class,
|
||||||
|
@ -372,7 +372,11 @@ return [
|
|||||||
'contact_phone' => ['searchable' => true],
|
'contact_phone' => ['searchable' => true],
|
||||||
'contact_address' => ['searchable' => true],
|
'contact_address' => ['searchable' => true],
|
||||||
'category_id' => [
|
'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',
|
'parent_id',
|
||||||
'recurring' => [
|
'recurring' => [
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||||
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
||||||
|
|
||||||
// capture release as git sha
|
// capture release as git sha
|
||||||
@ -81,4 +82,6 @@ return [
|
|||||||
|
|
||||||
'traces_sampler' => [env('SENTRY_TRACES_SAMPLER_CLASS', 'App\\Exceptions\\Trackers\\Sentry'), 'tracesSampler'],
|
'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',
|
'minor' => '0',
|
||||||
|
|
||||||
'patch' => '14',
|
'patch' => '15',
|
||||||
|
|
||||||
'build' => '',
|
'build' => '',
|
||||||
|
|
||||||
'status' => 'Stable',
|
'status' => 'Stable',
|
||||||
|
|
||||||
'date' => '25-April-2023',
|
'date' => '31-May-2023',
|
||||||
|
|
||||||
'time' => '17:00',
|
'time' => '17:00',
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ use App\Events\Document\DocumentReceived;
|
|||||||
use App\Events\Document\DocumentSent;
|
use App\Events\Document\DocumentSent;
|
||||||
use App\Events\Document\DocumentViewed;
|
use App\Events\Document\DocumentViewed;
|
||||||
use App\Events\Document\PaymentReceived;
|
use App\Events\Document\PaymentReceived;
|
||||||
|
use App\Interfaces\Utility\DocumentNumber;
|
||||||
use App\Jobs\Document\UpdateDocument;
|
use App\Jobs\Document\UpdateDocument;
|
||||||
use App\Models\Common\Contact;
|
use App\Models\Common\Contact;
|
||||||
use App\Models\Common\Item;
|
use App\Models\Common\Item;
|
||||||
@ -70,7 +71,7 @@ class Document extends AbstractFactory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => Model::INVOICE_TYPE,
|
'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(),
|
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
|
||||||
'contact_id' => $contact->id,
|
'contact_id' => $contact->id,
|
||||||
'contact_name' => $contact->name,
|
'contact_name' => $contact->name,
|
||||||
@ -101,7 +102,7 @@ class Document extends AbstractFactory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => Model::BILL_TYPE,
|
'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(),
|
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
|
||||||
'contact_id' => $contact->id,
|
'contact_id' => $contact->id,
|
||||||
'contact_name' => $contact->name,
|
'contact_name' => $contact->name,
|
||||||
@ -207,9 +208,11 @@ class Document extends AbstractFactory
|
|||||||
{
|
{
|
||||||
$type = $this->getRawAttribute('type') . '-recurring';
|
$type = $this->getRawAttribute('type') . '-recurring';
|
||||||
|
|
||||||
|
$contact = Contact::find($this->getRawAttribute('contact_id'));
|
||||||
|
|
||||||
return $this->state([
|
return $this->state([
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'document_number' => $this->getDocumentNumber($type),
|
'document_number' => $this->getDocumentNumber($type, $contact),
|
||||||
'recurring_started_at' => $this->getRawAttribute('issued_at'),
|
'recurring_started_at' => $this->getRawAttribute('issued_at'),
|
||||||
'recurring_frequency' => 'daily',
|
'recurring_frequency' => 'daily',
|
||||||
'recurring_interval' => '1',
|
'recurring_interval' => '1',
|
||||||
@ -263,11 +266,13 @@ class Document extends AbstractFactory
|
|||||||
* Get document number
|
* 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;
|
return $document_number;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Abstracts\Factory;
|
use App\Abstracts\Factory;
|
||||||
|
use App\Interfaces\Utility\TransactionNumber;
|
||||||
use App\Models\Banking\Transaction as Model;
|
use App\Models\Banking\Transaction as Model;
|
||||||
use App\Models\Common\Contact;
|
use App\Models\Common\Contact;
|
||||||
use App\Traits\Transactions;
|
use App\Traits\Transactions;
|
||||||
@ -41,7 +42,7 @@ class Transaction extends Factory
|
|||||||
return [
|
return [
|
||||||
'company_id' => $this->company->id,
|
'company_id' => $this->company->id,
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'number' => $this->getNumber(),
|
'number' => $this->getNumber($this->type),
|
||||||
'account_id' => setting('default.account'),
|
'account_id' => setting('default.account'),
|
||||||
'paid_at' => $this->faker->dateTimeBetween(now()->startOfYear(), now()->endOfYear())->format('Y-m-d H:i:s'),
|
'paid_at' => $this->faker->dateTimeBetween(now()->startOfYear(), now()->endOfYear())->format('Y-m-d H:i:s'),
|
||||||
'amount' => $this->faker->randomFloat(2, 1, 1000),
|
'amount' => $this->faker->randomFloat(2, 1, 1000),
|
||||||
@ -73,6 +74,7 @@ class Transaction extends Factory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'income',
|
'type' => 'income',
|
||||||
|
'number' => $this->getNumber('income', '', $contact),
|
||||||
'contact_id' => $contact->id,
|
'contact_id' => $contact->id,
|
||||||
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
|
'category_id' => $this->company->categories()->income()->get()->random(1)->pluck('id')->first(),
|
||||||
];
|
];
|
||||||
@ -97,6 +99,7 @@ class Transaction extends Factory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'expense',
|
'type' => 'expense',
|
||||||
|
'number' => $this->getNumber('expense', '', $contact),
|
||||||
'contact_id' => $contact->id,
|
'contact_id' => $contact->id,
|
||||||
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
|
'category_id' => $this->company->categories()->expense()->get()->random(1)->pluck('id')->first(),
|
||||||
];
|
];
|
||||||
@ -110,9 +113,11 @@ class Transaction extends Factory
|
|||||||
*/
|
*/
|
||||||
public function recurring()
|
public function recurring()
|
||||||
{
|
{
|
||||||
|
$type = $this->getRawAttribute('type') . '-recurring';
|
||||||
|
|
||||||
return $this->state([
|
return $this->state([
|
||||||
'type' => $this->getRawAttribute('type') . '-recurring',
|
'type' => $type,
|
||||||
'number' => $this->getNumber('-recurring'),
|
'number' => $this->getNumber($type, '-recurring'),
|
||||||
'recurring_started_at' => Date::now()->format('Y-m-d H:i:s'),
|
'recurring_started_at' => Date::now()->format('Y-m-d H:i:s'),
|
||||||
'recurring_frequency' => 'daily',
|
'recurring_frequency' => 'daily',
|
||||||
'recurring_custom_frequency' => 'daily',
|
'recurring_custom_frequency' => 'daily',
|
||||||
@ -129,11 +134,13 @@ class Transaction extends Factory
|
|||||||
* Get transaction number
|
* 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;
|
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()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
3066
package-lock.json
generated
3066
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@
|
|||||||
"glightbox": "^3.2.0",
|
"glightbox": "^3.2.0",
|
||||||
"json-schema": ">=0.4.0",
|
"json-schema": ">=0.4.0",
|
||||||
"laravel-mix-tailwind": "^0.1.2",
|
"laravel-mix-tailwind": "^0.1.2",
|
||||||
"lodash": ">=4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"swiper": "^9.2.0",
|
"swiper": "^9.2.0",
|
||||||
@ -47,7 +47,10 @@
|
|||||||
"vue-router": "^3.6.5",
|
"vue-router": "^3.6.5",
|
||||||
"vue2-editor": "^2.10.3",
|
"vue2-editor": "^2.10.3",
|
||||||
"vue2-transitions": "^0.3.0",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.4",
|
"@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;
|
font-family: element-icons;
|
||||||
src: url(fonts/element-icons.woff) format("woff"),url(fonts/element-icons.ttf) format("truetype");
|
src: url(fonts/element-icons.woff) format("woff"),url(fonts/element-icons.ttf) format("truetype");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display:"auto";font-style: normal
|
font-display:"auto";
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*=" el-icon-"],[class^=el-icon-] {
|
[class*=" el-icon-"],[class^=el-icon-] {
|
||||||
|
@ -264,8 +264,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
sendEmailShow: {
|
sendEmailShow: {
|
||||||
type: Number,
|
type: [String, Number, Array, Object, Boolean],
|
||||||
default: 1,
|
default: '1',
|
||||||
description: "Created recurring model send automatically option"
|
description: "Created recurring model send automatically option"
|
||||||
},
|
},
|
||||||
sendEmailText: {
|
sendEmailText: {
|
||||||
|
@ -15,33 +15,34 @@
|
|||||||
<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="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-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-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>
|
||||||
|
|
||||||
<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">
|
<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 }}
|
{{ 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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full h-full flex">
|
<div class="relative w-full h-full flex">
|
||||||
<input
|
<input
|
||||||
v-if="!show_date"
|
v-if="!show_date"
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full h-12 lg:h-auto bg-transparent text-black text-sm border-0 pb-0 focus:outline-none focus:ring-transparent focus:border-purple-100"
|
class="w-full h-12 lg:h-auto bg-transparent text-black text-sm border-0 pb-0 focus:outline-none focus:ring-transparent focus:border-purple-100"
|
||||||
:class="!show_icon ? 'ltr:pr-4 rtl:pl-4' : 'ltr:pr-10 rtl:pl-10'"
|
:class="!show_icon ? 'ltr:pr-4 rtl:pl-4' : 'ltr:pr-10 rtl:pl-10'"
|
||||||
:placeholder="dynamicPlaceholder"
|
:placeholder="dynamicPlaceholder"
|
||||||
:ref="'input-search-field-' + _uid"
|
:ref="'input-search-field-' + _uid"
|
||||||
v-model="search"
|
v-model="search"
|
||||||
@focus="onInputFocus"
|
@focus="onInputFocus"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@keyup.enter="onInputConfirm"
|
@keyup.enter="onInputConfirm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<flat-picker
|
<flat-picker
|
||||||
@ -148,47 +149,57 @@ export default {
|
|||||||
default: 'Search or filter results...',
|
default: 'Search or filter results...',
|
||||||
description: 'Input placeholder'
|
description: 'Input placeholder'
|
||||||
},
|
},
|
||||||
|
|
||||||
selectPlaceholder: {
|
selectPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
enterPlaceholder: {
|
enterPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
searchText: {
|
searchText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Search for this text',
|
default: 'Search for this text',
|
||||||
description: 'Input placeholder'
|
description: 'Input placeholder'
|
||||||
},
|
},
|
||||||
|
|
||||||
operatorIsText: {
|
operatorIsText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'is',
|
default: 'is',
|
||||||
description: 'Operator is "="'
|
description: 'Operator is "="'
|
||||||
},
|
},
|
||||||
|
|
||||||
operatorIsNotText: {
|
operatorIsNotText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'is not',
|
default: 'is not',
|
||||||
description: 'Operator is not "!="'
|
description: 'Operator is not "!="'
|
||||||
},
|
},
|
||||||
|
|
||||||
noDataText: {
|
noDataText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'No Data',
|
default: 'No Data',
|
||||||
description: "Selectbox empty options message"
|
description: "Selectbox empty options message"
|
||||||
},
|
},
|
||||||
|
|
||||||
noMatchingDataText: {
|
noMatchingDataText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'No Matchign Data',
|
default: 'No Matchign Data',
|
||||||
description: "Selectbox search option not found item message"
|
description: "Selectbox search option not found item message"
|
||||||
},
|
},
|
||||||
|
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
description: 'Search attribute value'
|
description: 'Search attribute value'
|
||||||
},
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
description: 'List of filters'
|
description: 'List of filters'
|
||||||
},
|
},
|
||||||
|
|
||||||
defaultFiltered: {
|
defaultFiltered: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@ -196,7 +207,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
dateConfig: null
|
dateConfig: null
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
model: {
|
model: {
|
||||||
@ -317,7 +327,7 @@ export default {
|
|||||||
onInput(evt) {
|
onInput(evt) {
|
||||||
this.search = evt.target.value;
|
this.search = evt.target.value;
|
||||||
this.show_button = true;
|
this.show_button = true;
|
||||||
|
|
||||||
let option_url = this.selected_options.length > 0 && this.selected_options[this.filter_index] !== undefined ? this.selected_options[this.filter_index].url : '';
|
let option_url = this.selected_options.length > 0 && this.selected_options[this.filter_index] !== undefined ? this.selected_options[this.filter_index].url : '';
|
||||||
|
|
||||||
if (this.search) {
|
if (this.search) {
|
||||||
@ -423,10 +433,12 @@ export default {
|
|||||||
|
|
||||||
let option = false;
|
let option = false;
|
||||||
let option_url = false;
|
let option_url = false;
|
||||||
|
let option_fields = {};
|
||||||
|
|
||||||
for (let i = 0; i < this.filter_list.length; i++) {
|
for (let i = 0; i < this.filter_list.length; i++) {
|
||||||
if (this.filter_list[i].key == value) {
|
if (this.filter_list[i].key == value) {
|
||||||
option = this.filter_list[i].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) {
|
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);
|
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) {
|
if (option_url.indexOf('limit') === -1) {
|
||||||
option_url += ' limit:10';
|
option_url += ' limit:10';
|
||||||
}
|
}
|
||||||
@ -487,11 +499,19 @@ export default {
|
|||||||
this.values = [];
|
this.values = [];
|
||||||
|
|
||||||
data.forEach(function (item) {
|
data.forEach(function (item) {
|
||||||
this.values.push({
|
if (Object.keys(option_fields).length) {
|
||||||
key: (item.code) ? item.code : item.id,
|
this.values.push({
|
||||||
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
key: (option_fields['key']) ? item[option_fields['key']] : (item.code) ? item.code : item.id,
|
||||||
level: (item.level) ? item.level : null,
|
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);
|
||||||
|
|
||||||
this.option_values[value] = this.values;
|
this.option_values[value] = this.values;
|
||||||
@ -617,7 +637,7 @@ export default {
|
|||||||
this.selected_values.splice(index, 1);
|
this.selected_values.splice(index, 1);
|
||||||
|
|
||||||
this.show_date = false;
|
this.show_date = false;
|
||||||
|
|
||||||
if (this.filter_index == 0) {
|
if (this.filter_index == 0) {
|
||||||
this.onChangeSearchAndFilterText(this.defaultPlaceholder, true);
|
this.onChangeSearchAndFilterText(this.defaultPlaceholder, true);
|
||||||
this.show_close_icon = false;
|
this.show_close_icon = false;
|
||||||
@ -647,7 +667,7 @@ export default {
|
|||||||
let values = [];
|
let values = [];
|
||||||
|
|
||||||
// Option set sort_option data
|
// Option set sort_option data
|
||||||
if (!Array.isArray(options)) {
|
if (! Array.isArray(options)) {
|
||||||
for (const [key, value] of Object.entries(options)) {
|
for (const [key, value] of Object.entries(options)) {
|
||||||
values.push({
|
values.push({
|
||||||
key: (key).toString(),
|
key: (key).toString(),
|
||||||
@ -850,6 +870,7 @@ export default {
|
|||||||
this.values.sort(function (a, b) {
|
this.values.sort(function (a, b) {
|
||||||
var nameA = a.value.toUpperCase(); // ignore upper and lowercase
|
var nameA = a.value.toUpperCase(); // ignore upper and lowercase
|
||||||
var nameB = b.value.toUpperCase(); // ignore upper and lowercase
|
var nameB = b.value.toUpperCase(); // ignore upper and lowercase
|
||||||
|
|
||||||
if (nameA < nameB) {
|
if (nameA < nameB) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -857,6 +878,7 @@ export default {
|
|||||||
if (nameA > nameB) {
|
if (nameA > nameB) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// names must be equal
|
// names must be equal
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
@ -936,7 +958,7 @@ export default {
|
|||||||
|
|
||||||
.searh-field .btn:not(:disabled):not(.disabled):active:focus,
|
.searh-field .btn:not(:disabled):not(.disabled):active:focus,
|
||||||
.searh-field .btn:not(:disabled):not(.disabled).active:focus {
|
.searh-field .btn:not(:disabled):not(.disabled).active:focus {
|
||||||
-webkit-box-shadow: none !important;
|
-webkit-box-shadow: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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);
|
<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;
|
transform-style: preserve-3d;
|
||||||
transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85);
|
transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85);
|
||||||
|
@ -1,204 +1,257 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
@click="tryClose"
|
@click="tryClose"
|
||||||
data-notify="container"
|
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="[
|
||||||
:class="[
|
'alert alert-notify',
|
||||||
{ 'alert-with-icon': icon },
|
'fixed w-full sm:w-500 flex items-center justify-between',
|
||||||
verticalAlign,
|
{
|
||||||
horizontalAlign,
|
'rtl:right-0 ltr:left-0' : horizontalAlign == 'left',
|
||||||
alertType
|
'sm:rtl:right-4 sm:ltr:left-4' : horizontalAlign == 'left',
|
||||||
]"
|
},
|
||||||
role="alert"
|
{
|
||||||
:style="customPosition"
|
'ltr:right-0 rtl:left-0' : horizontalAlign == 'right',
|
||||||
data-notify-position="top-center"
|
'sm:ltr:right-4 sm:rtl:left-4' : horizontalAlign == 'right',
|
||||||
>
|
},
|
||||||
<div class="flex items-center ltr:pr-3 rtl:pl-3">
|
'p-4',
|
||||||
<template v-if="icon || $slots.icon">
|
'text-black font-bold',
|
||||||
<slot name="icon">
|
'rounded-lg',
|
||||||
<span class="alert-icon flex items-center ltr:mr-2 rtl:ml-2" data-notify="icon">
|
'z-30',
|
||||||
<span class="material-icons text-2xl">{{ icon }}</span>
|
{
|
||||||
|
'alert-with-icon': icon
|
||||||
|
},
|
||||||
|
verticalAlign,
|
||||||
|
horizontalAlign,
|
||||||
|
alertType
|
||||||
|
]"
|
||||||
|
role="alert"
|
||||||
|
:style="customPosition"
|
||||||
|
data-notify-position="top-center"
|
||||||
|
>
|
||||||
|
<div class="flex items-center ltr:pr-3 rtl:pl-3">
|
||||||
|
<template v-if="icon || $slots.icon">
|
||||||
|
<slot name="icon">
|
||||||
|
<span class="alert-icon flex items-center ltr:mr-2 rtl:ml-2" data-notify="icon">
|
||||||
|
<span class="material-icons text-2xl">{{ icon }}</span>
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<span class="alert-text">
|
||||||
|
<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>
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<span class="alert-text">
|
<slot name="dismiss-icon">
|
||||||
<span v-if="title" class="title">
|
<button type="button"
|
||||||
<b>{{ title }}<br/></b>
|
class="close text-2xl"
|
||||||
</span>
|
data-dismiss="alert"
|
||||||
<span v-if="message" v-html="message"></span>
|
aria-label="Close"
|
||||||
<content-render
|
@click="close"
|
||||||
v-if="!message && component"
|
>
|
||||||
:component="component"
|
<span aria-hidden="true">×</span>
|
||||||
></content-render>
|
</button>
|
||||||
</span>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot name="dismiss-icon">
|
|
||||||
<button type="button"
|
|
||||||
class="close text-2xl"
|
|
||||||
data-dismiss="alert"
|
|
||||||
aria-label="Close"
|
|
||||||
@click="close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'notification',
|
name: 'notification',
|
||||||
components: {
|
|
||||||
contentRender: {
|
components: {
|
||||||
props: ['component'],
|
contentRender: {
|
||||||
render: h => h(this.component)
|
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)'
|
|
||||||
},
|
props: {
|
||||||
horizontalAlign: {
|
message: String,
|
||||||
type: String,
|
|
||||||
default: 'right',
|
title: {
|
||||||
validator: value => {
|
type: String,
|
||||||
let acceptedValues = ['left', 'center', 'right'];
|
description: 'Notification title'
|
||||||
return acceptedValues.indexOf(value) !== -1;
|
},
|
||||||
|
|
||||||
|
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',
|
||||||
|
validator: value => {
|
||||||
|
let acceptedValues = [
|
||||||
|
'default',
|
||||||
|
'info',
|
||||||
|
'primary',
|
||||||
|
'danger',
|
||||||
|
'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,
|
||||||
|
validator: value => {
|
||||||
|
return value >= 0;
|
||||||
|
},
|
||||||
|
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'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: 'Horizontal alignment of notification (left|center|right)'
|
|
||||||
},
|
data() {
|
||||||
type: {
|
return {
|
||||||
type: String,
|
elmHeight: 0,
|
||||||
default: 'info',
|
|
||||||
validator: value => {
|
typeByClass: {
|
||||||
let acceptedValues = [
|
'default': 'black-100',
|
||||||
'default',
|
'info': 'blue-100',
|
||||||
'info',
|
'primary': 'black-100',
|
||||||
'primary',
|
'danger': 'red-100',
|
||||||
'danger',
|
'warning': 'orange-100',
|
||||||
'warning',
|
'success': 'green-100',
|
||||||
'success'
|
},
|
||||||
];
|
|
||||||
return acceptedValues.indexOf(value) !== -1;
|
textByClass: {
|
||||||
|
'default': 'black-600',
|
||||||
|
'info': 'blue-600',
|
||||||
|
'primary': 'black-600',
|
||||||
|
'danger': 'red-600',
|
||||||
|
'warning': 'orange-600',
|
||||||
|
'success': 'green-600',
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
|
|
||||||
},
|
computed: {
|
||||||
timeout: {
|
hasIcon() {
|
||||||
type: Number,
|
return this.icon && this.icon.length > 0;
|
||||||
default: 5000,
|
},
|
||||||
validator: value => {
|
|
||||||
return value >= 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 &&
|
||||||
|
alert.verticalAlign === this.verticalAlign &&
|
||||||
|
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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
|
|
||||||
},
|
methods: {
|
||||||
timestamp: {
|
close() {
|
||||||
type: Date,
|
this.$emit('close', this.timestamp);
|
||||||
default: () => new Date(),
|
},
|
||||||
description: 'Notification timestamp (used internally to handle notification removal correctly)'
|
|
||||||
},
|
tryClose(evt) {
|
||||||
component: {
|
if (this.clickHandler) {
|
||||||
type: [Object, Function],
|
this.clickHandler(evt, this);
|
||||||
description: 'Custom content component. Cane be a `.vue` component or render function'
|
}
|
||||||
},
|
|
||||||
showClose: {
|
if (this.closeOnClick) {
|
||||||
type: Boolean,
|
this.close();
|
||||||
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',
|
|
||||||
'primary': 'black-100',
|
|
||||||
'danger': 'red-100',
|
|
||||||
'warning': 'orange-100',
|
|
||||||
'success': 'green-100',
|
|
||||||
},
|
},
|
||||||
textByClass: {
|
|
||||||
'default': 'black-600',
|
mounted() {
|
||||||
'info': 'blue-600',
|
this.elmHeight = this.$el.clientHeight;
|
||||||
'primary': 'black-600',
|
|
||||||
'danger': 'red-600',
|
if (this.timeout) {
|
||||||
'warning': 'orange-600',
|
setTimeout(this.close, this.timeout);
|
||||||
'success': 'green-600',
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
</script>
|
||||||
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 &&
|
|
||||||
alert.verticalAlign === this.verticalAlign &&
|
|
||||||
alert.timestamp <= this.timestamp
|
|
||||||
);
|
|
||||||
}).length;
|
|
||||||
if (this.$notifications.settings.overlap) {
|
|
||||||
sameAlertsCount = 1;
|
|
||||||
}
|
|
||||||
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
|
|
||||||
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>
|
|
||||||
|
@ -1,55 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="notifications">
|
<div class="notifications">
|
||||||
<slide-y-up-transition :duration="transitionDuration"
|
<slide-y-up-transition :duration="transitionDuration"
|
||||||
group
|
group
|
||||||
mode="out-in">
|
mode="out-in"
|
||||||
<notification
|
>
|
||||||
v-for="notification in notifications"
|
<notification
|
||||||
v-bind="notification"
|
v-for="notification in notifications"
|
||||||
:clickHandler="notification.onClick"
|
v-bind="notification"
|
||||||
:key="notification.timestamp.getTime()"
|
:clickHandler="notification.onClick"
|
||||||
@close="removeNotification"
|
:key="notification.timestamp.getTime()"
|
||||||
>
|
@close="removeNotification"
|
||||||
</notification>
|
>
|
||||||
</slide-y-up-transition>
|
</notification>
|
||||||
</div>
|
</slide-y-up-transition>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import Notification from './Notification.vue';
|
|
||||||
import { SlideYUpTransition } from 'vue2-transitions';
|
|
||||||
|
|
||||||
export default {
|
<script>
|
||||||
components: {
|
import Notification from './Notification.vue';
|
||||||
SlideYUpTransition,
|
import { SlideYUpTransition } from 'vue2-transitions';
|
||||||
Notification
|
|
||||||
},
|
export default {
|
||||||
props: {
|
components: {
|
||||||
transitionDuration: {
|
SlideYUpTransition,
|
||||||
type: Number,
|
Notification
|
||||||
default: 200
|
},
|
||||||
},
|
|
||||||
overlap: {
|
props: {
|
||||||
type: Boolean,
|
transitionDuration: {
|
||||||
default: false
|
type: Number,
|
||||||
}
|
default: 200
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
overlap: {
|
||||||
notifications: this.$notifications.state
|
type: Boolean,
|
||||||
};
|
default: false
|
||||||
},
|
}
|
||||||
methods: {
|
},
|
||||||
removeNotification(timestamp) {
|
|
||||||
this.$notifications.removeNotification(timestamp);
|
data() {
|
||||||
}
|
return {
|
||||||
},
|
notifications: this.$notifications.state
|
||||||
created() {
|
};
|
||||||
this.$notifications.settings.overlap = this.overlap;
|
},
|
||||||
},
|
|
||||||
watch: {
|
methods: {
|
||||||
overlap: function (newVal) {
|
removeNotification(timestamp) {
|
||||||
this.$notifications.settings.overlap = newVal;
|
this.$notifications.removeNotification(timestamp);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
|
||||||
|
created() {
|
||||||
|
this.$notifications.settings.overlap = this.overlap;
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
overlap: function (newVal) {
|
||||||
|
this.$notifications.settings.overlap = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,66 +1,81 @@
|
|||||||
import Notifications from './Notifications.vue';
|
import Notifications from './Notifications.vue';
|
||||||
|
|
||||||
const NotificationStore = {
|
const NotificationStore = {
|
||||||
state: [], // here the notifications will be added
|
state: [], // here the notifications will be added
|
||||||
settings: {
|
|
||||||
overlap: false,
|
settings: {
|
||||||
verticalAlign: 'top',
|
overlap: false,
|
||||||
horizontalAlign: 'right',
|
verticalAlign: 'top',
|
||||||
type: 'info',
|
horizontalAlign: 'right',
|
||||||
timeout: 5000,
|
type: 'info',
|
||||||
closeOnClick: true,
|
timeout: 5000,
|
||||||
showClose: true
|
closeOnClick: true,
|
||||||
},
|
showClose: true
|
||||||
setOptions(options) {
|
},
|
||||||
this.settings = Object.assign(this.settings, options);
|
|
||||||
},
|
setOptions(options) {
|
||||||
removeNotification(timestamp) {
|
this.settings = Object.assign(this.settings, options);
|
||||||
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
|
},
|
||||||
if (indexToDelete !== -1) {
|
|
||||||
this.state.splice(indexToDelete, 1);
|
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.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 => {
|
||||||
|
this.addNotification(notificationInstance);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.addNotification(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
addNotification(notification) {
|
|
||||||
if (typeof notification === 'string' || notification instanceof String) {
|
|
||||||
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 => {
|
|
||||||
this.addNotification(notificationInstance);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.addNotification(notification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotificationsPlugin = {
|
const NotificationsPlugin = {
|
||||||
install(Vue, options) {
|
install(Vue, options) {
|
||||||
let app = new Vue({
|
let app = new Vue({
|
||||||
data: {
|
data: {
|
||||||
notificationStore: NotificationStore
|
notificationStore: NotificationStore
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
notify(notification) {
|
methods: {
|
||||||
this.notificationStore.notify(notification);
|
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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
Vue.prototype.$notify = app.notify;
|
|
||||||
Vue.prototype.$notifications = app.notificationStore;
|
|
||||||
Vue.component('Notifications', Notifications);
|
|
||||||
if (options) {
|
|
||||||
NotificationStore.setOptions(options);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotificationsPlugin;
|
export default NotificationsPlugin;
|
||||||
|
25
resources/assets/js/mixins/global.js
vendored
25
resources/assets/js/mixins/global.js
vendored
@ -271,6 +271,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: notify.message,
|
message: notify.message,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
icon: 'error_outline',
|
icon: 'error_outline',
|
||||||
@ -467,6 +469,9 @@ export default {
|
|||||||
onChangePaginationLimit(event) {
|
onChangePaginationLimit(event) {
|
||||||
let path = '';
|
let path = '';
|
||||||
|
|
||||||
|
let split_href = window.location.href.split('#');
|
||||||
|
let href = split_href[0];
|
||||||
|
|
||||||
if (window.location.search.length) {
|
if (window.location.search.length) {
|
||||||
if (window.location.search.includes('limit')) {
|
if (window.location.search.includes('limit')) {
|
||||||
let queries = [];
|
let queries = [];
|
||||||
@ -494,10 +499,14 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
path = window.location.href + '&limit=' + event.target.getAttribute("value");
|
path = href + '&limit=' + event.target.getAttribute("value");
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
window.location.href = path;
|
||||||
@ -505,6 +514,10 @@ export default {
|
|||||||
|
|
||||||
// Dynamic component get path view and show it.
|
// Dynamic component get path view and show it.
|
||||||
onDynamicComponent(path) {
|
onDynamicComponent(path) {
|
||||||
|
if (! path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
axios.get(path)
|
axios.get(path)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let html = response.data.html;
|
let html = response.data.html;
|
||||||
@ -562,6 +575,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onDynamicFormParams(path, params) {
|
onDynamicFormParams(path, params) {
|
||||||
|
if (! path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let data = {};
|
let data = {};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
for (const [key, value] of Object.entries(params)) {
|
||||||
@ -1001,7 +1018,7 @@ export default {
|
|||||||
|
|
||||||
this.component = Vue.component('add-new-component', (resolve, reject) => {
|
this.component = Vue.component('add-new-component', (resolve, reject) => {
|
||||||
resolve({
|
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: {
|
components: {
|
||||||
AkauntingDropzoneFileUpload,
|
AkauntingDropzoneFileUpload,
|
||||||
@ -1112,6 +1129,8 @@ export default {
|
|||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
|
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: this.share.success_message,
|
message: this.share.success_message,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
icon: 'error_outline',
|
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({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
icon: "error_outline",
|
icon: "error_outline",
|
||||||
@ -92,6 +94,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: event.message,
|
message: event.message,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
icon: "error_outline",
|
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;
|
let type = notify.level;
|
||||||
|
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: notify.message,
|
message: notify.message,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
icon: '',
|
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'),
|
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) {
|
if (response.data.error) {
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
icon: 'fas fa-bell',
|
icon: 'fas fa-bell',
|
||||||
|
@ -621,6 +621,7 @@ const app = new Vue({
|
|||||||
onChangeCurrency(currency_code) {
|
onChangeCurrency(currency_code) {
|
||||||
if (this.edit.status && this.edit.currency <= 2) {
|
if (this.edit.status && this.edit.currency <= 2) {
|
||||||
this.edit.currency++;
|
this.edit.currency++;
|
||||||
|
|
||||||
return;
|
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 => {
|
add_to_cart_promise.then(response => {
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
icon: "shopping_cart_checkout",
|
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) {
|
onChangeCurrency(currency_code) {
|
||||||
if (! currency_code) {
|
if (! currency_code) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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>
|
<WizardSteps :active_state="active"></WizardSteps>
|
||||||
|
|
||||||
<div class="flex flex-col justify-between -mt-5 sm:mt-0" style="height:565px;">
|
<div class="flex flex-col justify-between -mt-5 sm:mt-0" style="height:565px;">
|
||||||
@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="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">
|
<div class="w-48 text-white text-left text-2xl font-semibold leading-9">
|
||||||
{{ translations.finish.apps_managing }}
|
{{ translations.finish.apps_managing }}
|
||||||
@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
<div style="width:372px; height:372px;"></div>
|
<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>
|
</div>
|
||||||
|
|
||||||
<base-button
|
<base-button
|
||||||
@ -59,7 +59,7 @@
|
|||||||
@click="finish()"
|
@click="finish()"
|
||||||
>
|
>
|
||||||
<i v-if="anchor_loading" class="animate-submit_second delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-2.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit_second before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit_second before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"></i>
|
<i v-if="anchor_loading" class="animate-submit_second delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-2.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit_second before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit_second before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"></i>
|
||||||
|
|
||||||
<span :class="[{'opacity-0': anchor_loading}]">
|
<span :class="[{'opacity-0': anchor_loading}]">
|
||||||
{{ translations.finish.create_first_invoice }}
|
{{ translations.finish.create_first_invoice }}
|
||||||
</span>
|
</span>
|
||||||
@ -112,6 +112,8 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$notify({
|
this.$notify({
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
horizontalAlign: 'left',
|
||||||
message: this.translations.finish.error_message,
|
message: this.translations.finish.error_message,
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
icon: "",
|
icon: "",
|
||||||
|
@ -72,6 +72,7 @@ return [
|
|||||||
'attachments' => 'Adjunt|Adjunts',
|
'attachments' => 'Adjunt|Adjunts',
|
||||||
'histories' => 'Història|Històries',
|
'histories' => 'Història|Històries',
|
||||||
'your_notifications' => 'La teva notificació|Les teves notificacions',
|
'your_notifications' => 'La teva notificació|Les teves notificacions',
|
||||||
|
'employees' => 'Empleat|Empleats',
|
||||||
|
|
||||||
'welcome' => 'Benvingut/da',
|
'welcome' => 'Benvingut/da',
|
||||||
'banking' => 'Bancs',
|
'banking' => 'Bancs',
|
||||||
@ -228,6 +229,7 @@ return [
|
|||||||
'preview_mode' => 'Mode de previsualització',
|
'preview_mode' => 'Mode de previsualització',
|
||||||
'go_back' => 'Torna a :type',
|
'go_back' => 'Torna a :type',
|
||||||
'validation_error' => 'Error de validació',
|
'validation_error' => 'Error de validació',
|
||||||
|
'dismiss' => 'Ignora',
|
||||||
|
|
||||||
'card' => [
|
'card' => [
|
||||||
'cards' => 'Tarjeta|Tarjetes',
|
'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