mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
Merge branch 'v5-develop' into v5-stable
This commit is contained in:
commit
e5595cf914
@ -61,3 +61,11 @@ SENTRY_LARAVEL_DSN=https://39389664f3f14969b4c43dadda00a40b@sentry2.invoicing.co
|
||||
|
||||
GOOGLE_PLAY_PACKAGE_NAME=
|
||||
APPSTORE_PASSWORD=
|
||||
|
||||
MICROSOFT_CLIENT_ID=
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
MICROSOFT_REDIRECT_URI=
|
||||
|
||||
APPLE_CLIENT_ID=
|
||||
APPLE_CLIENT_SECRET=
|
||||
APPLE_REDIRECT_URI=
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/react
|
||||
/storage/*.key
|
||||
/vendor
|
||||
/.idea
|
||||
|
@ -1 +1 @@
|
||||
5.3.98
|
||||
5.4.0
|
@ -62,7 +62,7 @@ class EmailTemplateDefaults
|
||||
case 'email_template_custom3':
|
||||
return self::emailInvoiceTemplate();
|
||||
case 'email_template_purchase_order':
|
||||
return self::emailPurchaseOrderSubject();
|
||||
return self::emailPurchaseOrderTemplate();
|
||||
break;
|
||||
|
||||
/* Subject */
|
||||
@ -157,7 +157,7 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailPurchaseOrderSubject()
|
||||
{
|
||||
return ctrans('texts.purchase_order_subject', ['number' => '$number']);
|
||||
return ctrans('texts.purchase_order_subject', ['number' => '$number', 'account' => '$account']);
|
||||
}
|
||||
|
||||
public static function emailPurchaseOrderTemplate()
|
||||
|
51
app/Events/PurchaseOrder/PurchaseOrderWasAccepted.php
Normal file
51
app/Events/PurchaseOrder/PurchaseOrderWasAccepted.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Events\PurchaseOrder;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\VendorContact;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class PurchaseOrderWasAccepted.
|
||||
*/
|
||||
class PurchaseOrderWasAccepted
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var PurchaseOrder
|
||||
*/
|
||||
public $purchase_order;
|
||||
|
||||
public $company;
|
||||
|
||||
public $event_vars;
|
||||
|
||||
public $contact;
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(PurchaseOrder $purchase_order, VendorContact $contact, Company $company, array $event_vars)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->contact = $contact;
|
||||
$this->company = $company;
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace App\Events\PurchaseOrder;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
@ -25,7 +26,7 @@ class PurchaseOrderWasEmailed
|
||||
/**
|
||||
* @var PurchaseOrder
|
||||
*/
|
||||
public $purchase_order;
|
||||
public $invitation;
|
||||
|
||||
public $company;
|
||||
|
||||
@ -38,9 +39,9 @@ class PurchaseOrderWasEmailed
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
|
||||
public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->invitation = $invitation;
|
||||
$this->company = $company;
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace App\Events\PurchaseOrder;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
@ -25,7 +26,7 @@ class PurchaseOrderWasViewed
|
||||
/**
|
||||
* @var PurchaseOrder
|
||||
*/
|
||||
public $purchase_order;
|
||||
public $invitation;
|
||||
|
||||
public $company;
|
||||
|
||||
@ -38,9 +39,9 @@ class PurchaseOrderWasViewed
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
|
||||
public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->invitation = $invitation;
|
||||
$this->company = $company;
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
|
@ -222,6 +222,9 @@ class Handler extends ExceptionHandler
|
||||
case 'user':
|
||||
$login = 'login';
|
||||
break;
|
||||
case 'vendor':
|
||||
$login = 'vendor.catchall';
|
||||
break;
|
||||
default:
|
||||
$login = 'default';
|
||||
break;
|
||||
|
@ -12,9 +12,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Account\CreateAccountRequest;
|
||||
use App\Http\Requests\Account\UpdateAccountRequest;
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Models\Account;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Transformers\AccountTransformer;
|
||||
use App\Transformers\CompanyUserTransformer;
|
||||
use App\Utils\TruthSource;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
@ -157,4 +159,22 @@ class AccountController extends BaseController
|
||||
|
||||
return $this->listResponse($ct);
|
||||
}
|
||||
|
||||
public function update(UpdateAccountRequest $request, Account $account)
|
||||
{
|
||||
|
||||
$fi = new \FilesystemIterator(public_path('react'), \FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
if(iterator_count($fi) < 30)
|
||||
return response()->json(['message' => 'React App Not Installed, Please install the React app before attempting to switch.'], 400);
|
||||
|
||||
$account->fill($request->all());
|
||||
$account->save();
|
||||
|
||||
$this->entity_type = Account::class;
|
||||
|
||||
$this->entity_transformer = AccountTransformer::class;
|
||||
|
||||
return $this->itemResponse($account);
|
||||
}
|
||||
}
|
||||
|
@ -330,8 +330,7 @@ class LoginController extends BaseController
|
||||
|
||||
$cu->first()->account->companies->each(function ($company) use ($cu, $request) {
|
||||
|
||||
if($company->tokens()->where('is_system', true)->count() == 0)
|
||||
{
|
||||
if ($company->tokens()->where('is_system', true)->count() == 0) {
|
||||
CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
|
||||
}
|
||||
});
|
||||
@ -359,16 +358,126 @@ class LoginController extends BaseController
|
||||
*/
|
||||
public function oauthApiLogin()
|
||||
{
|
||||
|
||||
$message = 'Provider not supported';
|
||||
if (request()->input('provider') == 'google') {
|
||||
return $this->handleGoogleOauth();
|
||||
} elseif (request()->input('provider') == 'microsoft') {
|
||||
if (request()->has('token')) {
|
||||
return $this->handleSocialiteLogin('microsoft', request()->get('token'));
|
||||
} else {
|
||||
$message = 'Bearer token missing for the microsoft login';
|
||||
}
|
||||
} elseif (request()->input('provider') == 'apple') {
|
||||
if (request()->has('token')) {
|
||||
return $this->handleSocialiteLogin('apple', request()->get('token'));
|
||||
} else {
|
||||
$message = 'Token is missing for the apple login';
|
||||
}
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(['message' => 'Provider not supported'], 400)
|
||||
->json(['message' => $message], 400)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
private function getSocialiteUser(string $provider, string $token)
|
||||
{
|
||||
return Socialite::driver($provider)->userFromToken($token);
|
||||
}
|
||||
|
||||
private function handleSocialiteLogin($provider, $token)
|
||||
{
|
||||
$user = $this->getSocialiteUser($provider, $token);
|
||||
if ($user) {
|
||||
return $this->loginOrCreateFromSocialite($user, $provider);
|
||||
}
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function loginOrCreateFromSocialite($user, $provider)
|
||||
{
|
||||
$query = [
|
||||
'oauth_user_id' => $user->id,
|
||||
'oauth_provider_id' => $provider,
|
||||
];
|
||||
if ($existing_user = MultiDB::hasUser($query)) {
|
||||
|
||||
if (!$existing_user->account)
|
||||
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
|
||||
|
||||
Auth::login($existing_user, true);
|
||||
|
||||
$cu = $this->hydrateCompanyUser();
|
||||
|
||||
if ($cu->count() == 0)
|
||||
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
|
||||
|
||||
if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient())
|
||||
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
|
||||
}
|
||||
//If this is a result user/email combo - lets add their OAuth details details
|
||||
if ($existing_login_user = MultiDB::hasUser(['email' => $user->email])) {
|
||||
if (!$existing_login_user->account)
|
||||
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
|
||||
|
||||
Auth::login($existing_login_user, true);
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $user->id,
|
||||
'oauth_provider_id' => $provider,
|
||||
]);
|
||||
|
||||
$cu = $this->hydrateCompanyUser();
|
||||
|
||||
if ($cu->count() == 0)
|
||||
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
|
||||
|
||||
if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
|
||||
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
}
|
||||
$name = OAuth::splitName($user->name);
|
||||
|
||||
$new_account = [
|
||||
'first_name' => $name[0],
|
||||
'last_name' => $name[1],
|
||||
'password' => '',
|
||||
'email' => $user->email,
|
||||
'oauth_user_id' => $user->id,
|
||||
'oauth_provider_id' => $provider,
|
||||
];
|
||||
|
||||
MultiDB::setDefaultDatabase();
|
||||
|
||||
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
|
||||
|
||||
Auth::login($account->default_company->owner(), true);
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
$cu = $this->hydrateCompanyUser();
|
||||
|
||||
if ($cu->count() == 0)
|
||||
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
|
||||
|
||||
if (Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient())
|
||||
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
}
|
||||
|
||||
|
||||
private function hydrateCompanyUser(): Builder
|
||||
{
|
||||
|
||||
@ -392,8 +501,7 @@ class LoginController extends BaseController
|
||||
if($cu->count() == 0)
|
||||
return $cu;
|
||||
|
||||
if(auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count())
|
||||
{
|
||||
if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) {
|
||||
|
||||
auth()->user()->companies->each(function ($company) {
|
||||
|
||||
|
58
app/Http/Controllers/Auth/VendorContactLoginController.php
Normal file
58
app/Http/Controllers/Auth/VendorContactLoginController.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Events\Contact\ContactLoggedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\ViewComposers\PortalComposer;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Ninja;
|
||||
use Auth;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Route;
|
||||
|
||||
class VendorContactLoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
protected $redirectTo = '/vendor/purchase_orders';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest:vendor', ['except' => ['logout']]);
|
||||
}
|
||||
|
||||
public function catch()
|
||||
{
|
||||
$data = [
|
||||
|
||||
];
|
||||
|
||||
return $this->render('purchase_orders.catch');
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
Auth::guard('vendor')->logout();
|
||||
request()->session()->invalidate();
|
||||
|
||||
return redirect('/vendors');
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -84,6 +84,7 @@ class BaseController extends Controller
|
||||
'company.products.documents',
|
||||
'company.payments.paymentables',
|
||||
'company.payments.documents',
|
||||
'company.purchase_orders.documents',
|
||||
'company.payment_terms.company',
|
||||
'company.projects.documents',
|
||||
'company.recurring_expenses',
|
||||
@ -171,7 +172,12 @@ class BaseController extends Controller
|
||||
*/
|
||||
public function notFoundClient()
|
||||
{
|
||||
abort(404, 'Page not found in client portal.');
|
||||
abort(404, 'Page not found in the client portal.');
|
||||
}
|
||||
|
||||
public function notFoundVendor()
|
||||
{
|
||||
abort(404, 'Page not found in the vendor portal.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,6 +302,13 @@ class BaseController extends Controller
|
||||
if(!$user->hasPermission('view_project'))
|
||||
$query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.purchase_orders'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at)->with('documents');
|
||||
|
||||
if(!$user->hasPermission('view_purchase_order'))
|
||||
$query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.quotes'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents');
|
||||
@ -533,6 +546,13 @@ class BaseController extends Controller
|
||||
if(!$user->hasPermission('view_project'))
|
||||
$query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.purchase_orders'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('documents');
|
||||
|
||||
if(!$user->hasPermission('view_purchase_order'))
|
||||
$query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.quotes'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents');
|
||||
@ -780,7 +800,7 @@ class BaseController extends Controller
|
||||
|
||||
$this->buildCache();
|
||||
|
||||
if(config('ninja.react_app_enabled'))
|
||||
if(Ninja::isSelfHost() && $account->set_react_as_default_ap)
|
||||
return response()->view('react.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
|
||||
else
|
||||
return response()->view('index.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
|
||||
|
@ -154,8 +154,10 @@ class EntityViewController extends Controller
|
||||
if (! $invitation->viewed_date) {
|
||||
$invitation->markViewed();
|
||||
|
||||
if(!session()->get('is_silent'))
|
||||
event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars()));
|
||||
|
||||
if(!session()->get('is_silent'))
|
||||
$this->fireEntityViewedEvent($invitation, $request->entity_type);
|
||||
}
|
||||
|
||||
|
@ -129,8 +129,10 @@ class InvitationController extends Controller
|
||||
if (auth()->guard('contact')->user() && ! request()->has('silent') && ! $invitation->viewed_date) {
|
||||
$invitation->markViewed();
|
||||
|
||||
if(!session()->get('is_silent'))
|
||||
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
|
||||
|
||||
if(!session()->get('is_silent'))
|
||||
$this->fireEntityViewedEvent($invitation, $entity);
|
||||
}
|
||||
else{
|
||||
|
@ -61,7 +61,7 @@ class InvoiceController extends Controller
|
||||
|
||||
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||
|
||||
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
|
||||
if ($invitation && auth()->guard('contact') && !session()->get('is_silent') && ! $invitation->viewed_date) {
|
||||
|
||||
$invitation->markViewed();
|
||||
|
||||
|
@ -154,6 +154,7 @@ class NinjaPlanController extends Controller
|
||||
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
|
||||
$recurring_invoice->due_date_days = 'terms';
|
||||
$recurring_invoice->next_send_date = now()->addDays(14)->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = now()->addDays(14)->format('Y-m-d');
|
||||
|
||||
$recurring_invoice->save();
|
||||
$r = $recurring_invoice->calc()->getRecurringInvoice();
|
||||
|
@ -24,6 +24,8 @@ use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
|
||||
use App\Jobs\Invoice\ZipInvoices;
|
||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
|
||||
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
|
||||
use App\Models\Client;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
@ -31,6 +33,7 @@ use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PurchaseOrderController extends BaseController
|
||||
{
|
||||
@ -183,6 +186,7 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
$purchase_order = $purchase_order->service()
|
||||
->fillDefaults()
|
||||
->triggeredActions($request)
|
||||
->save();
|
||||
|
||||
event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
@ -485,7 +489,7 @@ class PurchaseOrderController extends BaseController
|
||||
* Download Purchase Order/s
|
||||
*/
|
||||
|
||||
if ($action == 'bulk_download' && $purchase_orders->count() > 1) {
|
||||
if ($action == 'bulk_download' && $purchase_orders->count() >= 1) {
|
||||
$purchase_orders->each(function ($purchase_order) {
|
||||
if (auth()->user()->cannot('view', $purchase_order)) {
|
||||
nlog("access denied");
|
||||
@ -493,7 +497,7 @@ class PurchaseOrderController extends BaseController
|
||||
}
|
||||
});
|
||||
|
||||
ZipInvoices::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
|
||||
ZipPurchaseOrders::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
|
||||
|
||||
return response()->json(['message' => ctrans('texts.sent_message')], 200);
|
||||
}
|
||||
@ -579,7 +583,7 @@ class PurchaseOrderController extends BaseController
|
||||
*/
|
||||
public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action)
|
||||
{
|
||||
return $this->performAction($invoice, $action);
|
||||
return $this->performAction($purchase_order, $action);
|
||||
}
|
||||
|
||||
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
|
||||
@ -627,8 +631,13 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
|
||||
|
||||
|
||||
if (! $bulk) {
|
||||
return response()->json(['message' => 'email sent'], 200);
|
||||
}
|
||||
|
||||
default:
|
||||
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
|
||||
break;
|
||||
|
@ -135,6 +135,9 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
nlog("Extracting zip");
|
||||
|
||||
//clean up old snappdf installations
|
||||
$this->cleanOldSnapChromeBinaries();
|
||||
|
||||
// try{
|
||||
// $s = new Snappdf;
|
||||
// $s->getChromiumPath();
|
||||
@ -190,6 +193,46 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
private function cleanOldSnapChromeBinaries()
|
||||
{
|
||||
$current_revision = base_path('vendor/beganovich/snappdf/versions/revision.txt');
|
||||
$current_revision_text = file_get_contents($current_revision);
|
||||
|
||||
$iterator = new \DirectoryIterator(base_path('vendor/beganovich/snappdf/versions'));
|
||||
|
||||
foreach ($iterator as $file)
|
||||
{
|
||||
|
||||
if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName()))
|
||||
{
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
|
||||
foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex)
|
||||
{
|
||||
unlink($filex->getPathName());
|
||||
}
|
||||
|
||||
$this->deleteDirectory(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function deleteDirectory($dir) {
|
||||
if (!file_exists($dir)) return true;
|
||||
|
||||
if (!is_dir($dir) || is_link($dir)) return unlink($dir);
|
||||
foreach (scandir($dir) as $item) {
|
||||
if ($item == '.' || $item == '..') continue;
|
||||
if (!$this->deleteDirectory($dir . "/" . $item)) {
|
||||
if (!$this->deleteDirectory($dir . "/" . $item)) return false;
|
||||
};
|
||||
}
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
private function postHookUpdate()
|
||||
{
|
||||
if(config('ninja.app_version') == '5.3.82')
|
||||
|
@ -35,6 +35,7 @@ class SubdomainController extends BaseController
|
||||
'html',
|
||||
'lb',
|
||||
'shopify',
|
||||
'beta',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
144
app/Http/Controllers/VendorPortal/InvitationController.php
Normal file
144
app/Http/Controllers/VendorPortal/InvitationController.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\VendorPortal;
|
||||
|
||||
use App\Events\Credit\CreditWasViewed;
|
||||
use App\Events\Invoice\InvoiceWasViewed;
|
||||
use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Events\Quote\QuoteWasViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Services\ClientPortal\InstantPayment;
|
||||
use App\Utils\CurlUtils;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class InvitationController.
|
||||
*/
|
||||
class InvitationController extends Controller
|
||||
{
|
||||
use MakesHash;
|
||||
use MakesDates;
|
||||
|
||||
public function purchaseOrder(string $invitation_key)
|
||||
{
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('key', $invitation_key)
|
||||
->whereHas('purchase_order', function ($query) {
|
||||
$query->where('is_deleted',0);
|
||||
})
|
||||
->with('contact.vendor')
|
||||
->first();
|
||||
|
||||
if(!$invitation)
|
||||
return abort(404,'The resource is no longer available.');
|
||||
|
||||
if($invitation->contact->trashed())
|
||||
$invitation->contact->restore();
|
||||
|
||||
$vendor_contact = $invitation->contact;
|
||||
$entity = 'purchase_order';
|
||||
|
||||
if(empty($vendor_contact->email))
|
||||
$vendor_contact->email = Str::random(15) . "@example.com"; $vendor_contact->save();
|
||||
|
||||
if (request()->has('vendor_hash') && request()->input('vendor_hash') == $invitation->contact->vendor->vendor_hash) {
|
||||
request()->session()->invalidate();
|
||||
auth()->guard('vendor')->loginUsingId($vendor_contact->id, true);
|
||||
|
||||
} else {
|
||||
nlog("else - default - login contact");
|
||||
request()->session()->invalidate();
|
||||
auth()->guard('vendor')->loginUsingId($vendor_contact->id, true);
|
||||
}
|
||||
|
||||
session()->put('is_silent', request()->has('silent'));
|
||||
|
||||
if (auth()->guard('vendor')->user() && ! session()->get('is_silent') && ! $invitation->viewed_date) {
|
||||
|
||||
$invitation->markViewed();
|
||||
event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars()));
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
return redirect()->route('vendor.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->purchase_order_id), 'silent' => session()->get('is_silent')]);
|
||||
|
||||
}
|
||||
|
||||
return redirect()->route('vendor.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->purchase_order_id)]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public function routerForDownload(string $entity, string $invitation_key)
|
||||
// {
|
||||
|
||||
// set_time_limit(45);
|
||||
|
||||
// if(Ninja::isHosted())
|
||||
// return $this->returnRawPdf($entity, $invitation_key);
|
||||
|
||||
// return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf');
|
||||
// }
|
||||
|
||||
// private function returnRawPdf(string $entity, string $invitation_key)
|
||||
// {
|
||||
|
||||
// if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
|
||||
// return response()->json(['message' => 'Invalid resource request']);
|
||||
|
||||
// $key = $entity.'_id';
|
||||
|
||||
// $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
|
||||
// $invitation = $entity_obj::where('key', $invitation_key)
|
||||
// ->with('contact.client')
|
||||
// ->firstOrFail();
|
||||
|
||||
// if(!$invitation)
|
||||
// return response()->json(["message" => "no record found"], 400);
|
||||
|
||||
// $file_name = $invitation->purchase_order->numberFormatter().'.pdf';
|
||||
|
||||
// $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
|
||||
|
||||
// $headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
// if(request()->input('inline') == 'true')
|
||||
// $headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
// return response()->streamDownload(function () use($file) {
|
||||
// echo $file;
|
||||
// }, $file_name, $headers);
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
}
|
229
app/Http/Controllers/VendorPortal/PurchaseOrderController.php
Normal file
229
app/Http/Controllers/VendorPortal/PurchaseOrderController.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\VendorPortal;
|
||||
|
||||
use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
|
||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
|
||||
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PurchaseOrderController extends Controller
|
||||
{
|
||||
use MakesHash, MakesDates;
|
||||
|
||||
public const MODULE_RECURRING_INVOICES = 1;
|
||||
public const MODULE_CREDITS = 2;
|
||||
public const MODULE_QUOTES = 4;
|
||||
public const MODULE_TASKS = 8;
|
||||
public const MODULE_EXPENSES = 16;
|
||||
public const MODULE_PROJECTS = 32;
|
||||
public const MODULE_VENDORS = 64;
|
||||
public const MODULE_TICKETS = 128;
|
||||
public const MODULE_PROPOSALS = 256;
|
||||
public const MODULE_RECURRING_EXPENSES = 512;
|
||||
public const MODULE_RECURRING_TASKS = 1024;
|
||||
public const MODULE_RECURRING_QUOTES = 2048;
|
||||
public const MODULE_INVOICES = 4096;
|
||||
public const MODULE_PROFORMAL_INVOICES = 8192;
|
||||
public const MODULE_PURCHASE_ORDERS = 16384;
|
||||
|
||||
/**
|
||||
* Display list of invoices.
|
||||
*
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function index(ShowPurchaseOrdersRequest $request)
|
||||
{
|
||||
|
||||
return $this->render('purchase_orders.index', ['company' => auth()->user()->company, 'settings' => auth()->user()->company->settings, 'sidebar' => $this->sidebarMenu()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show specific invoice.
|
||||
*
|
||||
* @param ShowInvoiceRequest $request
|
||||
* @param Invoice $invoice
|
||||
*
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function show(ShowPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
$invitation = $purchase_order->invitations()->where('vendor_contact_id', auth()->guard('vendor')->user()->id)->first();
|
||||
|
||||
if ($invitation && auth()->guard('vendor') && !session()->get('is_silent') && ! $invitation->viewed_date) {
|
||||
|
||||
$invitation->markViewed();
|
||||
|
||||
event(new InvitationWasViewed($purchase_order, $invitation, $purchase_order->company, Ninja::eventVars()));
|
||||
event(new PurchaseOrderWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
'purchase_order' => $purchase_order,
|
||||
'key' => $invitation ? $invitation->key : false,
|
||||
'settings' => $purchase_order->company->settings,
|
||||
'sidebar' => $this->sidebarMenu(),
|
||||
'company' => $purchase_order->company
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
return render('purchase_orders.show-fullscreen', $data);
|
||||
}
|
||||
|
||||
return $this->render('purchase_orders.show', $data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function sidebarMenu() :array
|
||||
{
|
||||
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
|
||||
$data = [];
|
||||
|
||||
// TODO: Enable dashboard once it's completed.
|
||||
// $this->settings->enable_client_portal_dashboard
|
||||
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||
|
||||
if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
|
||||
$data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
|
||||
}
|
||||
|
||||
// $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function bulk(ProcessPurchaseOrdersInBulkRequest $request)
|
||||
{
|
||||
|
||||
$transformed_ids = $this->transformKeys($request->purchase_orders);
|
||||
|
||||
if ($request->input('action') == 'download') {
|
||||
return $this->downloadInvoices((array) $transformed_ids);
|
||||
}
|
||||
elseif ($request->input('action') == 'accept'){
|
||||
return $this->acceptPurchaseOrder($request->all());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('message', ctrans('texts.no_action_provided'));
|
||||
}
|
||||
|
||||
public function acceptPurchaseOrder($data)
|
||||
{
|
||||
$purchase_orders = PurchaseOrder::query()
|
||||
->whereIn('id', $this->transformKeys($data['purchase_orders']))
|
||||
->where('company_id', auth()->guard('vendor')->user()->vendor->company_id)
|
||||
->whereIn('status_id', [PurchaseOrder::STATUS_DRAFT, PurchaseOrder::STATUS_SENT])
|
||||
->cursor()->each(function ($purchase_order){
|
||||
|
||||
$purchase_order->service()
|
||||
->markSent()
|
||||
->applyNumber()
|
||||
->setStatus(PurchaseOrder::STATUS_ACCEPTED)
|
||||
->save();
|
||||
|
||||
if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) {
|
||||
InjectSignature::dispatch($purchase_order, request()->signature);
|
||||
}
|
||||
|
||||
event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars()));
|
||||
|
||||
});
|
||||
|
||||
if(count($data['purchase_orders']) == 1){
|
||||
|
||||
$purchase_order = PurchaseOrder::whereIn('id', $this->transformKeys($data['purchase_orders']))->first();
|
||||
|
||||
return redirect()->route('vendor.purchase_order.show', ['purchase_order' => $purchase_order->hashed_id]);
|
||||
|
||||
}
|
||||
else
|
||||
return redirect()->route('vendor.purchase_orders.index');
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function downloadInvoices($ids)
|
||||
{
|
||||
|
||||
$purchase_orders = PurchaseOrder::whereIn('id', $ids)
|
||||
->where('vendor_id', auth()->guard('vendor')->user()->vendor_id)
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
if(count($purchase_orders) == 0)
|
||||
return back()->with(['message' => ctrans('texts.no_items_selected')]);
|
||||
|
||||
|
||||
if(count($purchase_orders) == 1){
|
||||
|
||||
$purchase_order = $purchase_orders->first();
|
||||
|
||||
$file = $purchase_order->service()->getPurchaseOrderPdf(auth()->guard('vendor')->user());
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
return $this->buildZip($purchase_orders);
|
||||
}
|
||||
|
||||
|
||||
private function buildZip($purchase_orders)
|
||||
{
|
||||
// create new archive
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
try{
|
||||
|
||||
foreach ($purchase_orders as $purchase_order) {
|
||||
|
||||
#add it to the zip
|
||||
$zipFile->addFromString(basename($purchase_order->pdf_file_path()), file_get_contents($purchase_order->pdf_file_path(null, 'url', true)));
|
||||
|
||||
}
|
||||
|
||||
$filename = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.purchase_orders')).'.zip';
|
||||
$filepath = sys_get_temp_dir() . '/' . $filename;
|
||||
|
||||
$zipFile->saveAsFile($filepath) // save the archive to a file
|
||||
->close(); // close archive
|
||||
|
||||
return response()->download($filepath, $filename)->deleteFileAfterSend(true);
|
||||
|
||||
}
|
||||
catch(\PhpZip\Exception\ZipException $e){
|
||||
// handle exception
|
||||
}
|
||||
finally{
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\VendorPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\TranslationHelper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VendorContactController extends Controller
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public const MODULE_RECURRING_INVOICES = 1;
|
||||
public const MODULE_CREDITS = 2;
|
||||
public const MODULE_QUOTES = 4;
|
||||
public const MODULE_TASKS = 8;
|
||||
public const MODULE_EXPENSES = 16;
|
||||
public const MODULE_PROJECTS = 32;
|
||||
public const MODULE_VENDORS = 64;
|
||||
public const MODULE_TICKETS = 128;
|
||||
public const MODULE_PROPOSALS = 256;
|
||||
public const MODULE_RECURRING_EXPENSES = 512;
|
||||
public const MODULE_RECURRING_TASKS = 1024;
|
||||
public const MODULE_RECURRING_QUOTES = 2048;
|
||||
public const MODULE_INVOICES = 4096;
|
||||
public const MODULE_PROFORMAL_INVOICES = 8192;
|
||||
public const MODULE_PURCHASE_ORDERS = 16384;
|
||||
|
||||
public function edit(VendorContact $vendor_contact)
|
||||
{
|
||||
|
||||
return $this->render('vendor_profile.edit', [
|
||||
'contact' => $vendor_contact,
|
||||
'vendor' => $vendor_contact->vendor,
|
||||
'settings' => $vendor_contact->vendor->company->settings,
|
||||
'company' => $vendor_contact->vendor->company,
|
||||
'sidebar' => $this->sidebarMenu(),
|
||||
'countries' => TranslationHelper::getCountries()
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(VendorContact $vendor_contact)
|
||||
{
|
||||
$vendor_contact->fill(request()->all());
|
||||
$vendor_contact->vendor->fill(request()->all());
|
||||
$vendor_contact->push();
|
||||
|
||||
return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
|
||||
|
||||
}
|
||||
|
||||
private function sidebarMenu() :array
|
||||
{
|
||||
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
|
||||
$data = [];
|
||||
|
||||
// TODO: Enable dashboard once it's completed.
|
||||
// $this->settings->enable_client_portal_dashboard
|
||||
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||
|
||||
if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
|
||||
$data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
|
||||
}
|
||||
|
||||
// $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
@ -29,7 +29,7 @@ class WePayController extends BaseController
|
||||
*/
|
||||
public function signup(string $token)
|
||||
{
|
||||
return render('gateways.wepay.signup.finished');
|
||||
// return render('gateways.wepay.signup.finished');
|
||||
|
||||
$hash = Cache::get($token);
|
||||
|
||||
|
@ -42,6 +42,7 @@ use App\Http\Middleware\TrimStrings;
|
||||
use App\Http\Middleware\TrustProxies;
|
||||
use App\Http\Middleware\UrlSetDb;
|
||||
use App\Http\Middleware\UserVerified;
|
||||
use App\Http\Middleware\VendorLocale;
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Illuminate\Auth\Middleware\Authorize;
|
||||
@ -158,6 +159,7 @@ class Kernel extends HttpKernel
|
||||
'api_db' => SetDb::class,
|
||||
'company_key_db' => SetDbByCompanyKey::class,
|
||||
'locale' => Locale::class,
|
||||
'vendor_locale' => VendorLocale::class,
|
||||
'contact_register' => ContactRegister::class,
|
||||
'shop_token_auth' => ShopTokenAuth::class,
|
||||
'phantom_secret' => PhantomSecret::class,
|
||||
|
76
app/Http/Livewire/PurchaseOrdersTable.php
Normal file
76
app/Http/Livewire/PurchaseOrdersTable.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\WithSorting;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class PurchaseOrdersTable extends Component
|
||||
{
|
||||
use WithPagination, WithSorting;
|
||||
|
||||
public $per_page = 10;
|
||||
|
||||
public $status = [];
|
||||
|
||||
public $company;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->sort_asc = false;
|
||||
|
||||
$this->sort_field = 'date';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$local_status = [];
|
||||
|
||||
$query = PurchaseOrder::query()
|
||||
->with('vendor.contacts')
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->whereIn('status_id', [PurchaseOrder::STATUS_SENT, PurchaseOrder::STATUS_ACCEPTED])
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false);
|
||||
|
||||
if (in_array('sent', $this->status)) {
|
||||
$local_status[] = PurchaseOrder::STATUS_SENT;
|
||||
}
|
||||
|
||||
if (in_array('accepted', $this->status)) {
|
||||
$local_status[] = PurchaseOrder::STATUS_ACCEPTED;
|
||||
}
|
||||
|
||||
if (count($local_status) > 0) {
|
||||
$query = $query->whereIn('status_id', array_unique($local_status));
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->where('vendor_id', auth()->guard('vendor')->user()->vendor_id)
|
||||
// ->where('status_id', '<>', Invoice::STATUS_DRAFT)
|
||||
// ->where('status_id', '<>', Invoice::STATUS_CANCELLED)
|
||||
->withTrashed()
|
||||
->paginate($this->per_page);
|
||||
|
||||
return render('components.livewire.purchase-orders-table', [
|
||||
'purchase_orders' => $query
|
||||
]);
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@ class CheckClientExistence
|
||||
|
||||
session()->put('multiple_contacts', $multiple_contacts);
|
||||
|
||||
session()->put('is_silent', request()->has('silent'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ class RedirectIfAuthenticated
|
||||
// return redirect()->route('dashboard.index');
|
||||
// }
|
||||
break;
|
||||
case 'vendor':
|
||||
if (Auth::guard($guard)->check()) {
|
||||
//TODO create routes for vendor
|
||||
// return redirect()->route('vendor.dashboard');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Auth::logout();
|
||||
// if (Auth::guard($guard)->check()) {
|
||||
|
@ -46,7 +46,7 @@ class SetInviteDb
|
||||
if($entity == "pay")
|
||||
$entity = "invoice";
|
||||
|
||||
if(!in_array($entity, ['invoice','quote','credit','recurring_invoice']))
|
||||
if(!in_array($entity, ['invoice','quote','credit','recurring_invoice','purchase_order']))
|
||||
abort(404,'I could not find this resource.');
|
||||
|
||||
/* Try and determine the DB from the invitation key STRING*/
|
||||
|
57
app/Http/Middleware/VendorLocale.php
Normal file
57
app/Http/Middleware/VendorLocale.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class VendorLocale
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
if (auth()->guard('contact')->check()) {
|
||||
auth()->guard('contact')->logout();
|
||||
$request->session()->invalidate();
|
||||
}
|
||||
|
||||
|
||||
/*LOCALE SET */
|
||||
if ($request->has('lang')) {
|
||||
$locale = $request->input('lang');
|
||||
App::setLocale($locale);
|
||||
} elseif (auth()->guard('vendor')->user()) {
|
||||
App::setLocale(auth()->guard('vendor')->user()->company->locale());
|
||||
} elseif (auth()->user()) {
|
||||
|
||||
try{
|
||||
App::setLocale(auth()->user()->company()->getLocale());
|
||||
}
|
||||
catch(\Exception $e){
|
||||
}
|
||||
|
||||
} else {
|
||||
App::setLocale(config('ninja.i18n.locale'));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
54
app/Http/Requests/Account/UpdateAccountRequest.php
Normal file
54
app/Http/Requests/Account/UpdateAccountRequest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Account;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Account\BlackListRule;
|
||||
use App\Http\ValidationRules\Account\EmailBlackListRule;
|
||||
use App\Http\ValidationRules\NewUniqueUserRule;
|
||||
use App\Utils\Ninja;
|
||||
|
||||
class UpdateAccountRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && (int)$this->account->id === auth()->user()->account_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'set_react_as_default_ap' => 'required|bail|bool'
|
||||
];
|
||||
}
|
||||
|
||||
/* Only allow single field to update account table */
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$cleaned_input = array_intersect_key( $input, array_flip(['set_react_as_default_ap']));
|
||||
|
||||
$this->replace($cleaned_input);
|
||||
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ class ShowInvoiceRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->guard('contact')->user()->client_id === $this->invoice->client_id
|
||||
return (int)auth()->guard('contact')->user()->client_id === (int)$this->invoice->client_id
|
||||
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES;
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,8 @@ class CreatePaymentMethodRequest extends FormRequest
|
||||
$available_methods[] = $method['gateway_type_id'];
|
||||
});
|
||||
|
||||
if (in_array($this->query('method'), $available_methods)) {
|
||||
if (in_array($this->query('method'), $available_methods))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class ShowQuoteRequest extends FormRequest
|
||||
{
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->guard('contact')->user()->client->id === $this->quote->client_id
|
||||
return auth()->guard('contact')->user()->client->id === (int)$this->quote->client_id
|
||||
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES;
|
||||
}
|
||||
|
||||
|
@ -39,21 +39,28 @@ class Checkout3dsRequest extends FormRequest
|
||||
public function getCompany()
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
return Company::where('company_key', $this->company_key)->first();
|
||||
}
|
||||
|
||||
public function getCompanyGateway()
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
|
||||
}
|
||||
|
||||
public function getPaymentHash()
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
return PaymentHash::where('hash', $this->hash)->first();
|
||||
}
|
||||
|
||||
public function getClient()
|
||||
{
|
||||
return Client::find($this->getPaymentHash()->data->client_id);
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
return Client::withTrashed()->find($this->getPaymentHash()->data->client_id);
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class StorePurchaseOrderRequest extends Request
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
$rules['vendor_id'] = 'required';
|
||||
$rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\VendorPortal\PurchaseOrders;
|
||||
|
||||
use App\Http\ViewComposers\PortalComposer;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProcessPurchaseOrdersInBulkRequest extends FormRequest
|
||||
{
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->guard('vendor')->user()->vendor->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'purchase_orders' => ['array'],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\VendorPortal\PurchaseOrders;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ViewComposers\PortalComposer;
|
||||
|
||||
class ShowPurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return (int)auth()->guard('vendor')->user()->vendor_id === (int)$this->purchase_order->vendor_id
|
||||
&& auth()->guard('vendor')->user()->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\VendorPortal\PurchaseOrders;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ViewComposers\PortalComposer;
|
||||
|
||||
class ShowPurchaseOrdersRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->guard('vendor')->user()->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS;
|
||||
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
*
|
||||
* @param $invitation
|
||||
*/
|
||||
public function __construct($invitation, $disk = 'public')
|
||||
public function __construct($invitation, $disk = null)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
|
||||
@ -99,7 +99,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$this->client = $invitation->contact->client;
|
||||
$this->client->load('company');
|
||||
|
||||
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class ClientLedgerBalanceUpdate implements ShouldQueue
|
||||
*/
|
||||
public function handle() :void
|
||||
{
|
||||
nlog("Updating company ledger for client ". $this->client->id);
|
||||
// nlog("Updating company ledger for client ". $this->client->id);
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
|
102
app/Jobs/PurchaseOrder/PurchaseOrderEmail.php
Normal file
102
app/Jobs/PurchaseOrder/PurchaseOrderEmail.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\PurchaseOrder;
|
||||
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Engine\PurchaseOrderEmailEngine;
|
||||
use App\Mail\VendorTemplateEmail;
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Ninja;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class PurchaseOrderEmail implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public Company $company;
|
||||
|
||||
public $template_data;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order, Company $company, $template_data = null)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->company = $company;
|
||||
$this->template_data = $template_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->purchase_order->last_sent_date = now();
|
||||
|
||||
$this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) {
|
||||
|
||||
/* Don't fire emails if the company is disabled */
|
||||
if ($this->company->is_disabled)
|
||||
return true;
|
||||
|
||||
/* Set DB */
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($invitation->contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
/* Mark entity sent */
|
||||
$invitation->purchase_order->service()->markSent()->save();
|
||||
|
||||
$email_builder = (new PurchaseOrderEmailEngine($invitation, 'purchase_order', $this->template_data))->build();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation);
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $invitation->contact;
|
||||
$nmo->entity_string = 'purchase_order';
|
||||
$nmo->invitation = $invitation;
|
||||
$nmo->reminder_template = 'purchase_order';
|
||||
$nmo->entity = $invitation->purchase_order;
|
||||
|
||||
NinjaMailerJob::dispatchNow($nmo);
|
||||
|
||||
|
||||
});
|
||||
|
||||
if ($this->purchase_order->invitations->count() >= 1) {
|
||||
event(new PurchaseOrderWasEmailed($this->purchase_order->invitations->first(), $this->purchase_order->invitations->first()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
126
app/Jobs/PurchaseOrder/ZipPurchaseOrders.php
Normal file
126
app/Jobs/PurchaseOrder/ZipPurchaseOrders.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\PurchaseOrder;
|
||||
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\DownloadInvoices;
|
||||
use App\Mail\DownloadPurchaseOrders;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Utils\TempFile;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipPurchaseOrders implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $purchase_orders;
|
||||
|
||||
private $company;
|
||||
|
||||
private $user;
|
||||
|
||||
public $settings;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* @param $purchase_orders
|
||||
* @param Company $company
|
||||
* @param $email
|
||||
* @deprecated confirm to be deleted
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct($purchase_orders, Company $company, User $user)
|
||||
{
|
||||
$this->purchase_orders = $purchase_orders;
|
||||
|
||||
$this->company = $company;
|
||||
|
||||
$this->user = $user;
|
||||
|
||||
$this->settings = $company->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
* @throws \ZipStream\Exception\FileNotFoundException
|
||||
* @throws \ZipStream\Exception\FileNotReadableException
|
||||
* @throws \ZipStream\Exception\OverflowException
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
# create new zip object
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
|
||||
$invitation = $this->purchase_orders->first()->invitations->first();
|
||||
$path = $this->purchase_orders->first()->vendor->purchase_order_filepath($invitation);
|
||||
|
||||
$this->purchase_orders->each(function ($purchase_order){
|
||||
|
||||
CreatePurchaseOrderPdf::dispatchNow($purchase_order->invitations()->first());
|
||||
|
||||
});
|
||||
|
||||
try{
|
||||
|
||||
foreach ($this->purchase_orders as $purchase_order) {
|
||||
|
||||
$file = $purchase_order->service()->getPurchaseOrderPdf();
|
||||
$zip_file_name = basename($file);
|
||||
$zipFile->addFromString($zip_file_name, Storage::get($file));
|
||||
|
||||
}
|
||||
|
||||
Storage::put($path.$file_name, $zipFile->outputAsString());
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new DownloadPurchaseOrders(Storage::url($path.$file_name), $this->company);
|
||||
$nmo->to_user = $this->user;
|
||||
$nmo->settings = $this->settings;
|
||||
$nmo->company = $this->company;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
|
||||
|
||||
|
||||
}
|
||||
catch(\PhpZip\Exception\ZipException $e){
|
||||
nlog("could not make zip => ". $e->getMessage());
|
||||
}
|
||||
finally{
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
4
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
4
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
@ -70,7 +70,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
*
|
||||
* @param $invitation
|
||||
*/
|
||||
public function __construct($invitation, $disk = 'public')
|
||||
public function __construct($invitation, $disk = null)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
$this->company = $invitation->company;
|
||||
@ -83,7 +83,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
$this->vendor = $invitation->contact->vendor;
|
||||
$this->vendor->load('company');
|
||||
|
||||
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,8 @@ class OAuth
|
||||
const SOCIAL_LINKEDIN = 4;
|
||||
const SOCIAL_TWITTER = 5;
|
||||
const SOCIAL_BITBUCKET = 6;
|
||||
const SOCIAL_MICROSOFT = 7;
|
||||
const SOCIAL_APPLE = 8;
|
||||
|
||||
/**
|
||||
* @param Socialite $user
|
||||
@ -74,6 +76,10 @@ class OAuth
|
||||
return 'twitter';
|
||||
case SOCIAL_BITBUCKET:
|
||||
return 'bitbucket';
|
||||
case SOCIAL_MICROSOFT:
|
||||
return 'microsoft';
|
||||
case SOCIAL_APPLE:
|
||||
return 'apple';
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +98,10 @@ class OAuth
|
||||
return SOCIAL_TWITTER;
|
||||
case 'bitbucket':
|
||||
return SOCIAL_BITBUCKET;
|
||||
case 'microsoft':
|
||||
return SOCIAL_MICROSOFT;
|
||||
case 'apple':
|
||||
return SOCIAL_APPLE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +113,6 @@ class OAuth
|
||||
$this->provider_id = self::SOCIAL_GOOGLE;
|
||||
|
||||
return $this;
|
||||
|
||||
default:
|
||||
return null;
|
||||
break;
|
||||
|
@ -49,6 +49,8 @@ class InvitationViewedListener implements ShouldQueue
|
||||
|
||||
if($entity_name == 'recurringInvoice')
|
||||
return;
|
||||
elseif($entity_name == 'purchaseOrder')
|
||||
$entity_name = 'purchase_order';
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() );
|
||||
@ -60,6 +62,8 @@ class InvitationViewedListener implements ShouldQueue
|
||||
$entity_viewed = "{$entity_name}_viewed";
|
||||
$entity_viewed_all = "{$entity_name}_viewed_all";
|
||||
|
||||
|
||||
|
||||
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all]);
|
||||
|
||||
if (($key = array_search('mail', $methods)) !== false) {
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\PurchaseOrder;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use stdClass;
|
||||
|
||||
class PurchaseOrderAcceptedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
public $delay = 5;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$fields = new stdClass;
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
|
||||
|
||||
$event->purchase_order->service()->markSent()->save();
|
||||
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $event->purchase_order->company_id;
|
||||
$fields->activity_type_id = Activity::ACCEPT_PURCHASE_ORDER;
|
||||
$fields->vendor_id = $event->purchase_order->vendor_id;
|
||||
$fields->vendor_contact_id = $event->contact->id;
|
||||
$fields->purchase_order_id = $event->purchase_order->id;
|
||||
|
||||
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Quote Ninja (https://quoteninja.com).
|
||||
*
|
||||
* @link https://github.com/quoteninja/quoteninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://quoteninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\PurchaseOrder;
|
||||
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Admin\EntityCreatedObject;
|
||||
use App\Mail\Admin\PurchaseOrderAcceptedObject;
|
||||
use App\Notifications\Admin\EntitySentNotification;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class PurchaseOrderAcceptedNotification implements ShouldQueue
|
||||
{
|
||||
use UserNotifies;
|
||||
|
||||
public $delay = 5;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$first_notification_sent = true;
|
||||
|
||||
$purchase_order = $event->purchase_order;
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer( (new PurchaseOrderAcceptedObject($purchase_order, $event->company))->build() );
|
||||
$nmo->company = $event->company;
|
||||
$nmo->settings = $event->company->settings;
|
||||
|
||||
/* We loop through each user and determine whether they need to be notified */
|
||||
foreach ($event->company->company_users as $company_user) {
|
||||
|
||||
/* The User */
|
||||
$user = $company_user->user;
|
||||
|
||||
if(!$user)
|
||||
continue;
|
||||
|
||||
/* Returns an array of notification methods */
|
||||
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_all']);
|
||||
|
||||
/* If one of the methods is email then we fire the EntitySentMailer */
|
||||
if (($key = array_search('mail', $methods)) !== false) {
|
||||
unset($methods[$key]);
|
||||
|
||||
$nmo->to_user = $user;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
/* This prevents more than one notification being sent */
|
||||
$first_notification_sent = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -65,7 +65,12 @@ class EntityViewedObject
|
||||
|
||||
private function getAmount()
|
||||
{
|
||||
return Number::formatMoney($this->entity->amount, $this->entity->client);
|
||||
if($this->entity->client)
|
||||
$currency_entity = $this->entity->client;
|
||||
else
|
||||
$currency_entity = $this->company;
|
||||
|
||||
return Number::formatMoney($this->entity->amount, $currency_entity);
|
||||
}
|
||||
|
||||
private function getSubject()
|
||||
@ -82,7 +87,10 @@ class EntityViewedObject
|
||||
|
||||
private function getData()
|
||||
{
|
||||
if($this->entity->client)
|
||||
$settings = $this->entity->client->getMergedSettings();
|
||||
else
|
||||
$settings = $this->company->settings;
|
||||
|
||||
$data = [
|
||||
'title' => $this->getSubject(),
|
||||
|
103
app/Mail/Admin/PurchaseOrderAcceptedObject.php
Normal file
103
app/Mail/Admin/PurchaseOrderAcceptedObject.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use stdClass;
|
||||
|
||||
class PurchaseOrderAcceptedObject
|
||||
{
|
||||
|
||||
public $purchase_order;
|
||||
|
||||
public $company;
|
||||
|
||||
public $settings;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order, Company $company)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if(!$this->purchase_order)
|
||||
return;
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
$mail_obj->amount = $this->getAmount();
|
||||
$mail_obj->subject = $this->getSubject();
|
||||
$mail_obj->data = $this->getData();
|
||||
$mail_obj->markdown = 'email.admin.generic';
|
||||
$mail_obj->tag = $this->company->company_key;
|
||||
|
||||
return $mail_obj;
|
||||
}
|
||||
|
||||
private function getAmount()
|
||||
{
|
||||
return Number::formatMoney($this->purchase_order->amount, $this->company);
|
||||
}
|
||||
|
||||
private function getSubject()
|
||||
{
|
||||
return
|
||||
ctrans(
|
||||
"texts.notification_purchase_order_accepted_subject",
|
||||
[
|
||||
'vendor' => $this->purchase_order->vendor->present()->name(),
|
||||
'purchase_order' => $this->purchase_order->number,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function getData()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
|
||||
$data = [
|
||||
'title' => $this->getSubject(),
|
||||
'message' => ctrans(
|
||||
"texts.notification_purchase_order_accepted",
|
||||
[
|
||||
'amount' => $this->getAmount(),
|
||||
'vendor' => $this->purchase_order->vendor->present()->name(),
|
||||
'purchase_order' => $this->purchase_order->number,
|
||||
]
|
||||
),
|
||||
'url' => $this->purchase_order->invitations->first()->getAdminLink(),
|
||||
'button' => ctrans("texts.view_purchase_order"),
|
||||
'signature' => $settings->email_signature,
|
||||
'logo' => $this->company->present()->logo(),
|
||||
'settings' => $settings,
|
||||
'whitelabel' => $this->company->account->isPaid() ? true : false,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
56
app/Mail/DownloadPurchaseOrders.php
Normal file
56
app/Mail/DownloadPurchaseOrders.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class DownloadPurchaseOrders extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
public $file_path;
|
||||
|
||||
public $company;
|
||||
|
||||
public function __construct($file_path, Company $company)
|
||||
{
|
||||
$this->file_path = $file_path;
|
||||
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::setLocale($this->company->getLocale());
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject(ctrans('texts.download_files'))
|
||||
->text('email.admin.download_invoices_text', [
|
||||
'url' => $this->file_path,
|
||||
])
|
||||
->view('email.admin.download_purchase_orders', [
|
||||
'url' => $this->file_path,
|
||||
'logo' => $this->company->present()->logo,
|
||||
'whitelabel' => $this->company->account->isPaid() ? true : false,
|
||||
'settings' => $this->company->settings,
|
||||
'greeting' => $this->company->present()->name(),
|
||||
]);
|
||||
}
|
||||
}
|
207
app/Mail/Engine/PurchaseOrderEmailEngine.php
Normal file
207
app/Mail/Engine/PurchaseOrderEmailEngine.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail\Engine;
|
||||
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Models\Account;
|
||||
use App\Models\Expense;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Task;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
|
||||
class PurchaseOrderEmailEngine extends BaseEmailEngine
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public Vendor $vendor;
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $reminder_template;
|
||||
|
||||
public $template_data;
|
||||
|
||||
public function __construct($invitation, $reminder_template, $template_data)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
$this->reminder_template = $reminder_template; //'purchase_order'
|
||||
$this->vendor = $invitation->contact->vendor;
|
||||
$this->purchase_order = $invitation->purchase_order;
|
||||
$this->contact = $invitation->contact;
|
||||
$this->template_data = $template_data;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->vendor->company->settings));
|
||||
|
||||
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
|
||||
$body_template = $this->template_data['body'];
|
||||
} elseif (strlen($this->vendor->getSetting('email_template_'.$this->reminder_template)) > 0) {
|
||||
$body_template = $this->vendor->getSetting('email_template_'.$this->reminder_template);
|
||||
} else {
|
||||
$body_template = EmailTemplateDefaults::getDefaultTemplate('email_template_'.$this->reminder_template, $this->vendor->company->locale());
|
||||
}
|
||||
|
||||
/* Use default translations if a custom message has not been set*/
|
||||
if (iconv_strlen($body_template) == 0) {
|
||||
$body_template = trans(
|
||||
'texts.invoice_message',
|
||||
[
|
||||
'invoice' => $this->purchase_order->number,
|
||||
'company' => $this->purchase_order->company->present()->name(),
|
||||
'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor),
|
||||
],
|
||||
null,
|
||||
$this->vendor->company->locale()
|
||||
);
|
||||
|
||||
$body_template .= '<div class="center">$view_button</div>';
|
||||
|
||||
}
|
||||
$text_body = trans(
|
||||
'texts.purchase_order_message',
|
||||
[
|
||||
'purchase_order' => $this->purchase_order->number,
|
||||
'company' => $this->purchase_order->company->present()->name(),
|
||||
'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor),
|
||||
],
|
||||
null,
|
||||
$this->vendor->company->locale()
|
||||
) . "\n\n" . $this->invitation->getLink();
|
||||
|
||||
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {
|
||||
$subject_template = $this->template_data['subject'];
|
||||
} elseif (strlen($this->vendor->getSetting('email_subject_'.$this->reminder_template)) > 0) {
|
||||
$subject_template = $this->vendor->getSetting('email_subject_'.$this->reminder_template);
|
||||
} else {
|
||||
$subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->vendor->company->locale());
|
||||
|
||||
}
|
||||
|
||||
if (iconv_strlen($subject_template) == 0) {
|
||||
$subject_template = trans(
|
||||
'texts.purchase_order_subject',
|
||||
[
|
||||
'number' => $this->purchase_order->number,
|
||||
'account' => $this->purchase_order->company->present()->name(),
|
||||
],
|
||||
null,
|
||||
$this->vendor->company->locale()
|
||||
);
|
||||
}
|
||||
|
||||
$this->setTemplate($this->vendor->getSetting('email_style'))
|
||||
->setContact($this->contact)
|
||||
->setVariables((new VendorHtmlEngine($this->invitation))->makeValues())//move make values into the htmlengine
|
||||
->setSubject($subject_template)
|
||||
->setBody($body_template)
|
||||
->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_purchase_order').'</a>')
|
||||
->setViewLink($this->invitation->getLink())
|
||||
->setViewText(ctrans('texts.view_purchase_order'))
|
||||
->setInvitation($this->invitation)
|
||||
->setTextBody($text_body);
|
||||
|
||||
if ($this->vendor->getSetting('pdf_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation, 'url', true)]);
|
||||
else
|
||||
$this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation)]);
|
||||
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->vendor->getSetting('document_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->purchase_order->documents as $document){
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
}
|
||||
|
||||
foreach($this->purchase_order->company->documents as $document){
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
}
|
||||
|
||||
// $line_items = $this->purchase_order->line_items;
|
||||
|
||||
|
||||
// foreach($line_items as $item)
|
||||
// {
|
||||
|
||||
// $expense_ids = [];
|
||||
|
||||
// if(property_exists($item, 'expense_id'))
|
||||
// {
|
||||
// $expense_ids[] = $item->expense_id;
|
||||
// }
|
||||
|
||||
// if(count($expense_ids) > 0){
|
||||
|
||||
// $expenses = Expense::whereIn('id', $this->transformKeys($expense_ids))
|
||||
// ->where('invoice_documents', 1)
|
||||
// ->cursor()
|
||||
// ->each(function ($expense){
|
||||
|
||||
// foreach($expense->documents as $document)
|
||||
// {
|
||||
// $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
// }
|
||||
|
||||
// });
|
||||
// }
|
||||
|
||||
// $task_ids = [];
|
||||
|
||||
// if(property_exists($item, 'task_id'))
|
||||
// {
|
||||
// $task_ids[] = $item->task_id;
|
||||
// }
|
||||
|
||||
// if(count($task_ids) > 0 && $this->purchase_order->company->purchase_order_task_documents){
|
||||
|
||||
// $tasks = Task::whereIn('id', $this->transformKeys($task_ids))
|
||||
// ->cursor()
|
||||
// ->each(function ($task){
|
||||
|
||||
// foreach($task->documents as $document)
|
||||
// {
|
||||
// $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
// }
|
||||
|
||||
// });
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
132
app/Mail/VendorTemplateEmail.php
Normal file
132
app/Mail/VendorTemplateEmail.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Jobs\Invoice\CreateUbl;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\User;
|
||||
use App\Models\VendorContact;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\TemplateEngine;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorTemplateEmail extends Mailable
|
||||
{
|
||||
|
||||
private $build_email;
|
||||
|
||||
private $vendor;
|
||||
|
||||
private $contact;
|
||||
|
||||
private $company;
|
||||
|
||||
private $invitation;
|
||||
|
||||
public function __construct($build_email, VendorContact $contact, $invitation = null)
|
||||
{
|
||||
$this->build_email = $build_email;
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
$this->vendor = $contact->vendor;
|
||||
|
||||
$this->company = $contact->company;
|
||||
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
$template_name = 'email.template.'.$this->build_email->getTemplate();
|
||||
|
||||
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
|
||||
$template_name = 'email.template.client';
|
||||
}
|
||||
|
||||
if($this->build_email->getTemplate() == 'custom') {
|
||||
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
|
||||
}
|
||||
|
||||
$settings = $this->company->settings;
|
||||
|
||||
if ($this->build_email->getTemplate() !== 'custom') {
|
||||
$this->build_email->setBody(
|
||||
DesignHelpers::parseMarkdownToHtml($this->build_email->getBody())
|
||||
);
|
||||
}
|
||||
|
||||
if($this->invitation)
|
||||
{
|
||||
$html_variables = (new VendorHtmlEngine($this->invitation))->makeValues();
|
||||
$signature = str_replace(array_keys($html_variables), array_values($html_variables), $settings->email_signature);
|
||||
}
|
||||
else
|
||||
$signature = $settings->email_signature;
|
||||
|
||||
if(property_exists($settings, 'email_from_name') && strlen($settings->email_from_name) > 1)
|
||||
$email_from_name = $settings->email_from_name;
|
||||
else
|
||||
$email_from_name = $this->company->present()->name();
|
||||
|
||||
$this->from(config('mail.from.address'), $email_from_name);
|
||||
|
||||
if (strlen($settings->bcc_email) > 1)
|
||||
$this->bcc(explode(",",str_replace(" ", "", $settings->bcc_email)));//remove whitespace if any has been inserted.
|
||||
|
||||
$this->subject($this->build_email->getSubject())
|
||||
->text('email.template.text', [
|
||||
'text_body' => $this->build_email->getTextBody(),
|
||||
'whitelabel' => $this->vendor->user->account->isPaid() ? true : false,
|
||||
'settings' => $settings,
|
||||
])
|
||||
->view($template_name, [
|
||||
'greeting' => ctrans('texts.email_salutation', ['name' => $this->contact->present()->name()]),
|
||||
'body' => $this->build_email->getBody(),
|
||||
'footer' => $this->build_email->getFooter(),
|
||||
'view_link' => $this->build_email->getViewLink(),
|
||||
'view_text' => $this->build_email->getViewText(),
|
||||
'title' => '',
|
||||
'signature' => $signature,
|
||||
'settings' => $settings,
|
||||
'company' => $this->company,
|
||||
'whitelabel' => $this->vendor->user->account->isPaid() ? true : false,
|
||||
'logo' => $this->company->present()->logo($settings),
|
||||
])
|
||||
->withSwiftMessage(function ($message) {
|
||||
$message->getHeaders()->addTextHeader('Tag', $this->company->company_key);
|
||||
$message->invitation = $this->invitation;
|
||||
});
|
||||
|
||||
/*In the hosted platform we need to slow things down a little for Storage to catch up.*/
|
||||
if(Ninja::isHosted())
|
||||
sleep(1);
|
||||
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
|
||||
if(is_string($file))
|
||||
$this->attach($file);
|
||||
elseif(is_array($file))
|
||||
$this->attach($file['path'], ['as' => $file['name'], 'mime' => $file['mime']]);
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ModelNotFoundException;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\Ninja\EmailQuotaExceeded;
|
||||
@ -57,6 +58,7 @@ class Account extends BaseModel
|
||||
'utm_content',
|
||||
'user_agent',
|
||||
'platform',
|
||||
'set_react_as_default_ap',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -74,7 +76,8 @@ class Account extends BaseModel
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'onboarding' => 'object'
|
||||
'onboarding' => 'object',
|
||||
'set_react_as_default_ap' => 'bool'
|
||||
];
|
||||
|
||||
const PLAN_FREE = 'free';
|
||||
@ -87,6 +90,7 @@ class Account extends BaseModel
|
||||
const FEATURE_TASKS = 'tasks';
|
||||
const FEATURE_EXPENSES = 'expenses';
|
||||
const FEATURE_QUOTES = 'quotes';
|
||||
const FEATURE_PURCHASE_ORDERS = 'purchase_orders';
|
||||
const FEATURE_CUSTOMIZE_INVOICE_DESIGN = 'custom_designs';
|
||||
const FEATURE_DIFFERENT_DESIGNS = 'different_designs';
|
||||
const FEATURE_EMAIL_TEMPLATES_REMINDERS = 'template_reminders';
|
||||
@ -162,6 +166,7 @@ class Account extends BaseModel
|
||||
case self::FEATURE_TASKS:
|
||||
case self::FEATURE_EXPENSES:
|
||||
case self::FEATURE_QUOTES:
|
||||
case self::FEATURE_PURCHASE_ORDERS:
|
||||
return true;
|
||||
|
||||
case self::FEATURE_CUSTOMIZE_INVOICE_DESIGN:
|
||||
@ -468,4 +473,14 @@ class Account extends BaseModel
|
||||
|
||||
}
|
||||
|
||||
public function resolveRouteBinding($value, $field = null)
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
throw new ModelNotFoundException("Record with value {$value} not found");
|
||||
}
|
||||
|
||||
return $this
|
||||
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ class Activity extends StaticModel
|
||||
const RESTORE_PURCHASE_ORDER = 134;
|
||||
const EMAIL_PURCHASE_ORDER = 135;
|
||||
const VIEW_PURCHASE_ORDER = 136;
|
||||
const ACCEPT_PURCHASE_ORDER = 137;
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'boolean',
|
||||
|
@ -486,7 +486,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
}
|
||||
|
||||
if($this->currency()->code == 'EUR' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))){
|
||||
if($this->currency()->code == 'EUR' && (in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id')) || in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id'))) ){
|
||||
|
||||
foreach($pms as $pm){
|
||||
|
||||
@ -501,18 +501,6 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
}
|
||||
|
||||
// if ($this->currency()->code == 'EUR' && in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id'))) {
|
||||
// foreach ($pms as $pm) {
|
||||
// if ($pm['gateway_type_id'] == GatewayType::SEPA) {
|
||||
// $cg = CompanyGateway::find($pm['company_gateway_id']);
|
||||
|
||||
// if ($cg && $cg->fees_and_limits->{GatewayType::SEPA}->is_enabled) {
|
||||
// return $cg;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if ($this->country && $this->country->iso_3166_3 == 'GBR' && in_array(GatewayType::DIRECT_DEBIT, array_column($pms, 'gateway_type_id'))) {
|
||||
foreach ($pms as $pm) {
|
||||
if ($pm['gateway_type_id'] == GatewayType::DIRECT_DEBIT) {
|
||||
|
@ -14,6 +14,7 @@ namespace App\Models;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\Language;
|
||||
use App\Models\Presenters\CompanyPresenter;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\User;
|
||||
use App\Services\Notification\NotificationService;
|
||||
use App\Utils\Ninja;
|
||||
@ -192,6 +193,11 @@ class Company extends BaseModel
|
||||
return $this->hasMany(Subscription::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function purchase_orders()
|
||||
{
|
||||
return $this->hasMany(PurchaseOrder::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function task_statuses()
|
||||
{
|
||||
return $this->hasMany(TaskStatus::class)->withTrashed();
|
||||
|
@ -346,7 +346,7 @@ class Invoice extends BaseModel
|
||||
return '<h5><span class="badge badge-danger">'.ctrans('texts.overdue').'</span></h5>';
|
||||
break;
|
||||
case self::STATUS_UNPAID:
|
||||
return '<h5><span class="badge badge-warning">'.ctrans('texts.unpaid').'</span></h5>';
|
||||
return '<h5><span class="badge badge-warning text-white">'.ctrans('texts.unpaid').'</span></h5>';
|
||||
break;
|
||||
case self::STATUS_REVERSED:
|
||||
return '<h5><span class="badge badge-info">'.ctrans('texts.reversed').'</span></h5>';
|
||||
|
@ -212,7 +212,7 @@ class Payment extends BaseModel
|
||||
return '<h6><span class="badge badge-secondary">'.ctrans('texts.payment_status_1').'</span></h6>';
|
||||
break;
|
||||
case self::STATUS_CANCELLED:
|
||||
return '<h6><span class="badge badge-warning">'.ctrans('texts.payment_status_2').'</span></h6>';
|
||||
return '<h6><span class="badge badge-warning text-white">'.ctrans('texts.payment_status_2').'</span></h6>';
|
||||
break;
|
||||
case self::STATUS_FAILED:
|
||||
return '<h6><span class="badge badge-danger">'.ctrans('texts.payment_status_3').'</span></h6>';
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Services\PurchaseOrder\PurchaseOrderService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -26,6 +27,7 @@ class PurchaseOrder extends BaseModel
|
||||
{
|
||||
use Filterable;
|
||||
use SoftDeletes;
|
||||
use MakesDates;
|
||||
|
||||
protected $fillable = [
|
||||
'number',
|
||||
@ -99,8 +101,51 @@ class PurchaseOrder extends BaseModel
|
||||
|
||||
const STATUS_DRAFT = 1;
|
||||
const STATUS_SENT = 2;
|
||||
const STATUS_PARTIAL = 3;
|
||||
const STATUS_APPLIED = 4;
|
||||
const STATUS_ACCEPTED = 3;
|
||||
const STATUS_CANCELLED = 4;
|
||||
|
||||
public static function stringStatus(int $status)
|
||||
{
|
||||
switch ($status) {
|
||||
case self::STATUS_DRAFT:
|
||||
return ctrans('texts.draft');
|
||||
break;
|
||||
case self::STATUS_SENT:
|
||||
return ctrans('texts.sent');
|
||||
break;
|
||||
case self::STATUS_ACCEPTED:
|
||||
return ctrans('texts.accepted');
|
||||
break;
|
||||
case self::STATUS_CANCELLED:
|
||||
return ctrans('texts.cancelled');
|
||||
break;
|
||||
// code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function badgeForStatus(int $status)
|
||||
{
|
||||
switch ($status) {
|
||||
case self::STATUS_DRAFT:
|
||||
return '<h5><span class="badge badge-light">'.ctrans('texts.draft').'</span></h5>';
|
||||
break;
|
||||
case self::STATUS_SENT:
|
||||
return '<h5><span class="badge badge-primary">'.ctrans('texts.sent').'</span></h5>';
|
||||
break;
|
||||
case self::STATUS_ACCEPTED:
|
||||
return '<h5><span class="badge badge-primary">'.ctrans('texts.accepted').'</span></h5>';
|
||||
break;
|
||||
case self::STATUS_CANCELLED:
|
||||
return '<h5><span class="badge badge-secondary">'.ctrans('texts.cancelled').'</span></h5>';
|
||||
break;
|
||||
default:
|
||||
// code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function assigned_user()
|
||||
{
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
@ -9,6 +18,7 @@ use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PurchaseOrderInvitation extends BaseModel
|
||||
{
|
||||
@ -104,4 +114,37 @@ class PurchaseOrderInvitation extends BaseModel
|
||||
|
||||
}
|
||||
|
||||
public function getLink() :string
|
||||
{
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
$domain = $this->company->domain();
|
||||
}
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
case 'subdomain':
|
||||
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
|
||||
break;
|
||||
case 'iframe':
|
||||
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
|
||||
break;
|
||||
case 'domain':
|
||||
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
|
||||
break;
|
||||
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdminLink() :string
|
||||
{
|
||||
return $this->getLink().'?silent=true';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -73,6 +73,24 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
'vendor_id',
|
||||
];
|
||||
|
||||
public function avatar()
|
||||
{
|
||||
if ($this->avatar) {
|
||||
return $this->avatar;
|
||||
}
|
||||
|
||||
return asset('images/svg/user.svg');
|
||||
}
|
||||
|
||||
public function setAvatarAttribute($value)
|
||||
{
|
||||
if (! filter_var($value, FILTER_VALIDATE_URL) && $value) {
|
||||
$this->attributes['avatar'] = url('/').$value;
|
||||
} else {
|
||||
$this->attributes['avatar'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
@ -110,7 +128,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
|
||||
public function sendPasswordResetNotification($token)
|
||||
{
|
||||
$this->notify(new ClientContactResetPassword($token));
|
||||
// $this->notify(new ClientContactResetPassword($token));
|
||||
}
|
||||
|
||||
public function preferredLocale()
|
||||
@ -118,12 +136,9 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
$languages = Cache::get('languages');
|
||||
|
||||
return $languages->filter(function ($item) {
|
||||
return $item->id == $this->client->getSetting('language_id');
|
||||
return $item->id == $this->company->getSetting('language_id');
|
||||
})->first()->locale;
|
||||
|
||||
//$lang = Language::find($this->client->getSetting('language_id'));
|
||||
|
||||
//return $lang->locale;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -407,11 +407,6 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
$this->unWindGatewayFees($this->payment_hash);
|
||||
}
|
||||
|
||||
if ($e instanceof CheckoutHttpException) {
|
||||
$error = $e->getBody();
|
||||
} else if ($e instanceof Exception) {
|
||||
$error = $e->getMessage();
|
||||
} else
|
||||
$error = $e->getMessage();
|
||||
|
||||
if(!$this->payment_hash)
|
||||
|
@ -14,18 +14,24 @@ namespace App\PaymentDrivers\CheckoutCom;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\PaymentDrivers\CheckoutComPaymentDriver;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Checkout\CheckoutApiException;
|
||||
use Checkout\CheckoutArgumentException;
|
||||
use Checkout\CheckoutAuthorizationException;
|
||||
use Checkout\Library\Exceptions\CheckoutHttpException;
|
||||
use Checkout\Models\Payments\IdSource;
|
||||
use Checkout\Models\Payments\Payment;
|
||||
use Checkout\Models\Payments\TokenSource;
|
||||
use Checkout\Payments\Four\Request\Source\RequestTokenSource;
|
||||
use Checkout\Payments\Source\RequestTokenSource as SourceRequestTokenSource;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest;
|
||||
use Checkout\Payments\Four\Request\PaymentRequest;
|
||||
|
||||
class CreditCard implements MethodInterface
|
||||
{
|
||||
@ -57,6 +63,31 @@ class CreditCard implements MethodInterface
|
||||
return render('gateways.checkout.credit_card.authorize', $data);
|
||||
}
|
||||
|
||||
public function bootRequest($token)
|
||||
{
|
||||
|
||||
if($this->checkout->is_four_api){
|
||||
|
||||
$token_source = new RequestTokenSource();
|
||||
$token_source->token = $token;
|
||||
$request = new PaymentRequest();
|
||||
$request->source = $token_source;
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$token_source = new SourceRequestTokenSource();
|
||||
$token_source->token = $token;
|
||||
$request = new PaymentsPaymentRequest();
|
||||
$request->source = $token_source;
|
||||
|
||||
}
|
||||
|
||||
return $request;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization for credit card.
|
||||
*
|
||||
@ -67,41 +98,54 @@ class CreditCard implements MethodInterface
|
||||
{
|
||||
$gateway_response = \json_decode($request->gateway_response);
|
||||
|
||||
$method = new TokenSource(
|
||||
$gateway_response->token
|
||||
);
|
||||
$customerRequest = $this->checkout->getCustomer();
|
||||
$request = $this->bootRequest($gateway_response->token);
|
||||
$request->capture = false;
|
||||
$request->reference = '$1 payment for authorization.';
|
||||
$request->amount = 100;
|
||||
$request->currency = $this->checkout->client->getCurrencyCode();
|
||||
$request->customer = $customerRequest;
|
||||
|
||||
$payment = new Payment($method, 'USD');
|
||||
$payment->amount = 100; // $1
|
||||
$payment->reference = '$1 payment for authorization.';
|
||||
$payment->capture = false;
|
||||
|
||||
try {
|
||||
$response = $this->checkout->gateway->payments()->request($payment);
|
||||
|
||||
if ($response->approved && $response->status === 'Authorized') {
|
||||
$response = $this->checkout->gateway->getPaymentsClient()->requestPayment($request);
|
||||
|
||||
if ($response['approved'] && $response['status'] === 'Authorized') {
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = (string) $response->source['expiry_month'];
|
||||
$payment_meta->exp_year = (string) $response->source['expiry_year'];
|
||||
$payment_meta->brand = (string) $response->source['scheme'];
|
||||
$payment_meta->last4 = (string) $response->source['last4'];
|
||||
$payment_meta->exp_month = (string) $response['source']['expiry_month'];
|
||||
$payment_meta->exp_year = (string) $response['source']['expiry_year'];
|
||||
$payment_meta->brand = (string) $response['source']['scheme'];
|
||||
$payment_meta->last4 = (string) $response['source']['last4'];
|
||||
$payment_meta->type = (int) GatewayType::CREDIT_CARD;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $response->source['id'],
|
||||
'token' => $response['source']['id'],
|
||||
'payment_method_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
$payment_method = $this->checkout->storeGatewayToken($data);
|
||||
$payment_method = $this->checkout->storeGatewayToken($data,['gateway_customer_reference' => $customerRequest['id']]);
|
||||
|
||||
return redirect()->route('client.payment_methods.show', $payment_method->hashed_id);
|
||||
}
|
||||
} catch (CheckoutHttpException $exception) {
|
||||
throw new PaymentFailed(
|
||||
$exception->getMessage()
|
||||
);
|
||||
|
||||
|
||||
} catch (CheckoutApiException $e) {
|
||||
// API error
|
||||
$request_id = $e->request_id;
|
||||
$http_status_code = $e->http_status_code;
|
||||
$error_details = $e->error_details;
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
} catch (CheckoutArgumentException $e) {
|
||||
// Bad arguments
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
} catch (CheckoutAuthorizationException $e) {
|
||||
// Bad Invalid authorization
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function paymentView($data)
|
||||
@ -145,89 +189,102 @@ class CreditCard implements MethodInterface
|
||||
{
|
||||
$cgt = ClientGatewayToken::query()
|
||||
->where('id', $this->decodePrimaryKey($request->input('token')))
|
||||
->where('company_id', auth()->guard('contact')->user()->client->company->id)
|
||||
->where('company_id', auth()->guard('contact')->user()->client->company_id)
|
||||
->first();
|
||||
|
||||
if (!$cgt) {
|
||||
throw new PaymentFailed(ctrans('texts.payment_token_not_found'), 401);
|
||||
}
|
||||
|
||||
$method = new IdSource($cgt->token);
|
||||
$paymentRequest = $this->checkout->bootTokenRequest($cgt->token);
|
||||
|
||||
return $this->completePayment($method, $request);
|
||||
return $this->completePayment($paymentRequest, $request);
|
||||
}
|
||||
|
||||
private function attemptPaymentUsingCreditCard(PaymentResponseRequest $request)
|
||||
{
|
||||
$checkout_response = $this->checkout->payment_hash->data->server_response;
|
||||
|
||||
$method = new TokenSource(
|
||||
$checkout_response->token
|
||||
);
|
||||
$paymentRequest = $this->bootRequest($checkout_response->token);
|
||||
|
||||
return $this->completePayment($method, $request);
|
||||
return $this->completePayment($paymentRequest, $request);
|
||||
}
|
||||
|
||||
private function completePayment($method, PaymentResponseRequest $request)
|
||||
private function completePayment($paymentRequest, PaymentResponseRequest $request)
|
||||
{
|
||||
|
||||
$payment = new Payment($method, $this->checkout->payment_hash->data->currency);
|
||||
$payment->amount = $this->checkout->payment_hash->data->value;
|
||||
$payment->reference = $this->checkout->getDescription();
|
||||
$payment->customer = [
|
||||
'name' => $this->checkout->client->present()->name() ,
|
||||
'email' => $this->checkout->client->present()->email(),
|
||||
];
|
||||
$paymentRequest->amount = $this->checkout->payment_hash->data->value;
|
||||
$paymentRequest->reference = $this->checkout->getDescription();
|
||||
$paymentRequest->customer = $this->checkout->getCustomer();
|
||||
$paymentRequest->metadata = ['udf1' => "Invoice Ninja"];
|
||||
$paymentRequest->currency = $this->checkout->client->getCurrencyCode();
|
||||
|
||||
$payment->metadata = [
|
||||
'udf1' => "Invoice Ninja",
|
||||
];
|
||||
|
||||
$this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]);
|
||||
$this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $paymentRequest]);
|
||||
$this->checkout->payment_hash->save();
|
||||
|
||||
if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) {
|
||||
$payment->{'3ds'} = ['enabled' => true];
|
||||
|
||||
$payment->{'success_url'} = route('checkout.3ds_redirect', [
|
||||
$paymentRequest->{'3ds'} = ['enabled' => true];
|
||||
|
||||
$paymentRequest->{'success_url'} = route('checkout.3ds_redirect', [
|
||||
'company_key' => $this->checkout->client->company->company_key,
|
||||
'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
|
||||
'hash' => $this->checkout->payment_hash->hash,
|
||||
]);
|
||||
|
||||
$payment->{'failure_url'} = route('checkout.3ds_redirect', [
|
||||
$paymentRequest->{'failure_url'} = route('checkout.3ds_redirect', [
|
||||
'company_key' => $this->checkout->client->company->company_key,
|
||||
'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
|
||||
'hash' => $this->checkout->payment_hash->hash,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->checkout->gateway->payments()->request($payment);
|
||||
// $response = $this->checkout->gateway->payments()->request($payment);
|
||||
|
||||
if ($response->status == 'Authorized') {
|
||||
$response = $this->checkout->gateway->getPaymentsClient()->requestPayment($paymentRequest);
|
||||
|
||||
if ($response['status'] == 'Authorized') {
|
||||
return $this->processSuccessfulPayment($response);
|
||||
}
|
||||
|
||||
if ($response->status == 'Pending') {
|
||||
if ($response['status'] == 'Pending') {
|
||||
$this->checkout->confirmGatewayFee();
|
||||
|
||||
return $this->processPendingPayment($response);
|
||||
}
|
||||
|
||||
if ($response->status == 'Declined') {
|
||||
if ($response['status'] == 'Declined') {
|
||||
|
||||
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
|
||||
|
||||
// $this->checkout->sendFailureMail($response->response_summary);
|
||||
|
||||
//@todo - this will double up the checkout . com failed mails
|
||||
// $this->checkout->clientPaymentFailureMailer($response->status);
|
||||
|
||||
return $this->processUnsuccessfulPayment($response);
|
||||
}
|
||||
} catch (CheckoutHttpException $e) {
|
||||
}
|
||||
catch (CheckoutApiException $e) {
|
||||
// API error
|
||||
$request_id = $e->request_id;
|
||||
$http_status_code = $e->http_status_code;
|
||||
$error_details = $e->error_details;
|
||||
|
||||
|
||||
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
|
||||
return $this->checkout->processInternallyFailedPayment($this->checkout, $e);
|
||||
}
|
||||
|
||||
} catch (CheckoutArgumentException $e) {
|
||||
// Bad arguments
|
||||
|
||||
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
|
||||
return $this->checkout->processInternallyFailedPayment($this->checkout, $e);
|
||||
|
||||
} catch (CheckoutAuthorizationException $e) {
|
||||
// Bad Invalid authorization
|
||||
|
||||
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
|
||||
return $this->checkout->processInternallyFailedPayment($this->checkout, $e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -54,17 +54,18 @@ trait Utilities
|
||||
return round($amount * 100);
|
||||
}
|
||||
|
||||
private function processSuccessfulPayment(Payment $_payment)
|
||||
private function processSuccessfulPayment($_payment)
|
||||
{
|
||||
|
||||
if ($this->getParent()->payment_hash->data->store_card) {
|
||||
$this->storeLocalPaymentMethod($_payment);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_method' => $_payment->source['id'],
|
||||
'payment_method' => $_payment['source']['id'],
|
||||
'payment_type' => 12,
|
||||
'amount' => $this->getParent()->payment_hash->data->raw_value,
|
||||
'transaction_reference' => $_payment->id,
|
||||
'transaction_reference' => $_payment['id'],
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
@ -82,15 +83,15 @@ trait Utilities
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->getParent()->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
public function processUnsuccessfulPayment(Payment $_payment, $throw_exception = true)
|
||||
public function processUnsuccessfulPayment($_payment, $throw_exception = true)
|
||||
{
|
||||
|
||||
$error_message = '';
|
||||
|
||||
if(property_exists($_payment, 'server_response'))
|
||||
$error_message = $_payment->response_summary;
|
||||
elseif(property_exists($_payment, 'status'))
|
||||
$error_message = $_payment->status;
|
||||
if(array_key_exists('response_summary',$_payment))
|
||||
$error_message = $_payment['response_summary'];
|
||||
elseif(array_key_exists('status',$_payment))
|
||||
$error_message = $_payment['status'];
|
||||
|
||||
$this->getParent()->sendFailureMail($error_message);
|
||||
|
||||
@ -110,36 +111,37 @@ trait Utilities
|
||||
|
||||
if ($throw_exception) {
|
||||
|
||||
throw new PaymentFailed($_payment->status . " " . $error_message, $_payment->http_code);
|
||||
throw new PaymentFailed($_payment['status'] . " " . $error_message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function processPendingPayment(Payment $_payment)
|
||||
private function processPendingPayment($_payment)
|
||||
{
|
||||
|
||||
try {
|
||||
return redirect($_payment->_links['redirect']['href']);
|
||||
return redirect($_payment['_links']['redirect']['href']);
|
||||
} catch (Exception $e) {
|
||||
return $this->processInternallyFailedPayment($this->getParent(), $e);
|
||||
return $this->getParent()->processInternallyFailedPayment($this->getParent(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function storeLocalPaymentMethod(Payment $response)
|
||||
private function storeLocalPaymentMethod($response)
|
||||
{
|
||||
try {
|
||||
$payment_meta = new stdClass;
|
||||
$payment_meta->exp_month = (string) $response->source['expiry_month'];
|
||||
$payment_meta->exp_year = (string) $response->source['expiry_year'];
|
||||
$payment_meta->brand = (string) $response->source['scheme'];
|
||||
$payment_meta->last4 = (string) $response->source['last4'];
|
||||
$payment_meta->exp_month = (string) $response['source']['expiry_month'];
|
||||
$payment_meta->exp_year = (string) $response['source']['expiry_year'];
|
||||
$payment_meta->brand = (string) $response['source']['scheme'];
|
||||
$payment_meta->last4 = (string) $response['source']['last4'];
|
||||
$payment_meta->type = (int) GatewayType::CREDIT_CARD;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $response->source['id'],
|
||||
'token' => $response['source']['id'],
|
||||
'payment_method_id' => $this->getParent()->payment_hash->data->payment_method_id,
|
||||
];
|
||||
|
||||
return $this->getParent()->storePaymentMethod($data);
|
||||
return $this->getParent()->storePaymentMethod($data, ['gateway_customer_reference' => $response['customer']['id']]);
|
||||
} catch (Exception $e) {
|
||||
session()->flash('message', ctrans('texts.payment_method_saving_failed'));
|
||||
}
|
||||
|
@ -28,10 +28,22 @@ use App\PaymentDrivers\CheckoutCom\CreditCard;
|
||||
use App\PaymentDrivers\CheckoutCom\Utilities;
|
||||
use App\Utils\Traits\SystemLogTrait;
|
||||
use Checkout\CheckoutApi;
|
||||
use Checkout\CheckoutApiException;
|
||||
use Checkout\CheckoutArgumentException;
|
||||
use Checkout\CheckoutAuthorizationException;
|
||||
use Checkout\CheckoutDefaultSdk;
|
||||
use Checkout\CheckoutFourSdk;
|
||||
use Checkout\Environment;
|
||||
use Checkout\Library\Exceptions\CheckoutHttpException;
|
||||
use Checkout\Models\Payments\IdSource;
|
||||
use Checkout\Models\Payments\Refund;
|
||||
use Exception;
|
||||
use Checkout\Payments\Four\Request\PaymentRequest;
|
||||
use Checkout\Payments\Four\Request\Source\RequestIdSource as SourceRequestIdSource;
|
||||
use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest;
|
||||
use Checkout\Payments\Source\RequestIdSource;
|
||||
use Checkout\Common\CustomerRequest;
|
||||
use Checkout\Payments\RefundRequest;
|
||||
|
||||
class CheckoutComPaymentDriver extends BaseDriver
|
||||
{
|
||||
@ -52,6 +64,8 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
/* Authorise payment methods */
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
public $is_four_api = false;
|
||||
|
||||
/**
|
||||
* @var CheckoutApi;
|
||||
*/
|
||||
@ -109,7 +123,22 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
'sandbox' => $this->company_gateway->getConfigField('testMode'),
|
||||
];
|
||||
|
||||
$this->gateway = new CheckoutApi($config['secret'], $config['sandbox'], $config['public']);
|
||||
|
||||
if(strlen($config['secret']) <= 38){
|
||||
$this->is_four_api = true;
|
||||
$builder = CheckoutFourSdk::staticKeys();
|
||||
$builder->setPublicKey($config['public']); // optional, only required for operations related with tokens
|
||||
$builder->setSecretKey($config['secret']);
|
||||
$builder->setEnvironment($config['sandbox'] ? Environment::sandbox(): Environment::production());
|
||||
$this->gateway = $builder->build();
|
||||
}
|
||||
else {
|
||||
$builder = CheckoutDefaultSdk::staticKeys();
|
||||
$builder->setPublicKey($config['public']); // optional, only required for operations related with tokens
|
||||
$builder->setSecretKey($config['secret']);
|
||||
$builder->setEnvironment($config['sandbox'] ? Environment::sandbox(): Environment::production());
|
||||
$this->gateway = $builder->build();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -121,9 +150,6 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
*/
|
||||
public function viewForType($gateway_type_id)
|
||||
{
|
||||
// At the moment Checkout.com payment
|
||||
// driver only supports payments using credit card.
|
||||
|
||||
return 'gateways.checkout.credit_card.pay';
|
||||
}
|
||||
|
||||
@ -211,22 +237,40 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$checkout_payment = new Refund($payment->transaction_reference);
|
||||
$request = new RefundRequest();
|
||||
$request->reference = "{$payment->transaction_reference} " . now();
|
||||
$request->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode());
|
||||
|
||||
try {
|
||||
$refund = $this->gateway->payments()->refund($checkout_payment);
|
||||
$checkout_payment = $this->gateway->payments()->details($refund->id);
|
||||
|
||||
$response = ['refund_response' => $refund, 'checkout_payment_fetch' => $checkout_payment];
|
||||
// or, refundPayment("payment_id") for a full refund
|
||||
$response = $this->gateway->getPaymentsClient()->refundPayment($payment->transaction_reference, $request);
|
||||
|
||||
return [
|
||||
'transaction_reference' => $refund->action_id,
|
||||
'transaction_reference' => $response['action_id'],
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => $checkout_payment->status == 'Refunded',
|
||||
'description' => $checkout_payment->status,
|
||||
'code' => $checkout_payment->http_code,
|
||||
'success' => true,
|
||||
'description' => $response['reference'],
|
||||
'code' => 202,
|
||||
];
|
||||
} catch (CheckoutHttpException $e) {
|
||||
|
||||
} catch (CheckoutApiException $e) {
|
||||
// API error
|
||||
$request_id = $e->request_id;
|
||||
$http_status_code = $e->http_status_code;
|
||||
$error_details = $e->error_details;
|
||||
} catch (CheckoutArgumentException $e) {
|
||||
// Bad arguments
|
||||
|
||||
return [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => json_encode($e->getMessage()),
|
||||
'success' => false,
|
||||
'description' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
} catch (CheckoutAuthorizationException $e) {
|
||||
// Bad Invalid authorization
|
||||
|
||||
return [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => json_encode($e->getMessage()),
|
||||
@ -235,6 +279,49 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getCustomer()
|
||||
{
|
||||
try{
|
||||
|
||||
$response = $this->gateway->getCustomersClient()->get($this->client->present()->email());
|
||||
|
||||
return $response;
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
$request = new CustomerRequest();
|
||||
$request->email = $this->client->present()->email();
|
||||
$request->name = $this->client->present()->name();
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
|
||||
public function bootTokenRequest($token)
|
||||
{
|
||||
|
||||
if($this->is_four_api){
|
||||
|
||||
$token_source = new SourceRequestIdSource();
|
||||
$token_source->id = $token;
|
||||
$request = new PaymentRequest();
|
||||
$request->source = $token_source;
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$token_source = new RequestIdSource();
|
||||
$token_source->id = $token;
|
||||
$request = new PaymentsPaymentRequest();
|
||||
$request->source = $token_source;
|
||||
|
||||
}
|
||||
|
||||
return $request;
|
||||
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
@ -244,27 +331,29 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
$this->init();
|
||||
|
||||
$method = new IdSource($cgt->token);
|
||||
|
||||
$payment = new \Checkout\Models\Payments\Payment($method, $this->client->getCurrencyCode());
|
||||
$payment->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode());
|
||||
$payment->reference = $invoice->number . '-' . now();
|
||||
$paymentRequest = $this->bootTokenRequest($cgt->token);
|
||||
$paymentRequest->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode());
|
||||
$paymentRequest->reference = '#' . $invoice->number . ' - ' . now();
|
||||
$paymentRequest->customer = $this->getCustomer();
|
||||
$paymentRequest->metadata = ['udf1' => "Invoice Ninja"];
|
||||
$paymentRequest->currency = $this->client->getCurrencyCode();
|
||||
|
||||
$request = new PaymentResponseRequest();
|
||||
$request->setMethod('POST');
|
||||
$request->request->add(['payment_hash' => $payment_hash->hash]);
|
||||
|
||||
try {
|
||||
$response = $this->gateway->payments()->request($payment);
|
||||
// $response = $this->gateway->payments()->request($payment);
|
||||
$response = $this->gateway->getPaymentsClient()->requestPayment($paymentRequest);
|
||||
|
||||
if ($response->status == 'Authorized') {
|
||||
if ($response['status'] == 'Authorized') {
|
||||
$this->confirmGatewayFee($request);
|
||||
|
||||
$data = [
|
||||
'payment_method' => $response->source['id'],
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($response->source['scheme'])),
|
||||
'payment_method' => $response['source']['id'],
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($response['source']['scheme'])),
|
||||
'amount' => $amount,
|
||||
'transaction_reference' => $response->id,
|
||||
'transaction_reference' => $response['id'],
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
@ -280,10 +369,10 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
return $payment;
|
||||
}
|
||||
|
||||
if ($response->status == 'Declined') {
|
||||
if ($response['status'] == 'Declined') {
|
||||
$this->unWindGatewayFees($payment_hash);
|
||||
|
||||
$this->sendFailureMail($response->status . " " . $response->response_summary);
|
||||
$this->sendFailureMail($response['status'] . " " . $response['response_summary']);
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
@ -300,11 +389,9 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (Exception | CheckoutHttpException $e) {
|
||||
} catch (Exception | CheckoutApiException $e) {
|
||||
$this->unWindGatewayFees($payment_hash);
|
||||
$message = $e instanceof CheckoutHttpException
|
||||
? $e->getBody()
|
||||
: $e->getMessage();
|
||||
$message = $e->getMessage();
|
||||
|
||||
$data = [
|
||||
'status' => '',
|
||||
@ -334,20 +421,21 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
public function process3dsConfirmation(Checkout3dsRequest $request)
|
||||
{
|
||||
|
||||
$this->init();
|
||||
$this->setPaymentHash($request->getPaymentHash());
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments()->details(
|
||||
$payment = $this->gateway->getPaymentsClient()->getPaymentDetails(
|
||||
$request->query('cko-session-id')
|
||||
);
|
||||
|
||||
if ($payment->approved) {
|
||||
if ($payment['approved']) {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
} else {
|
||||
return $this->processUnsuccessfulPayment($payment);
|
||||
}
|
||||
} catch (CheckoutHttpException | Exception $e) {
|
||||
} catch (CheckoutApiException | Exception $e) {
|
||||
return $this->processInternallyFailedPayment($this, $e);
|
||||
}
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
nlog("GoCardless Event");
|
||||
nlog($request->all());
|
||||
|
||||
if(!is_array($request->events) || !is_object($request->events)){
|
||||
if(!$request->has("events")){
|
||||
|
||||
nlog("No GoCardless events to process in response?");
|
||||
return response()->json([], 200);
|
||||
@ -245,18 +245,19 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
sleep(1);
|
||||
|
||||
foreach ($request->events as $event) {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out' || $event['action'] === 'paid') {
|
||||
if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out') {
|
||||
|
||||
nlog("Searching for transaction reference");
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $event['links']['payment'])
|
||||
// ->where('company_id', $request->getCompany()->id)
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->first();
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
nlog("GoCardless completed");
|
||||
}
|
||||
else
|
||||
nlog("I was unable to find the payment for this reference");
|
||||
@ -268,12 +269,13 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $event['links']['payment'])
|
||||
// ->where('company_id', $request->getCompany()->id)
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->first();
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_FAILED;
|
||||
$payment->save();
|
||||
nlog("GoCardless completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,11 +60,12 @@ use App\Events\Payment\PaymentWasRefunded;
|
||||
use App\Events\Payment\PaymentWasRestored;
|
||||
use App\Events\Payment\PaymentWasUpdated;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasDeleted;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
|
||||
@ -179,6 +180,8 @@ use App\Listeners\Payment\PaymentEmailedActivity;
|
||||
use App\Listeners\Payment\PaymentNotification;
|
||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||
use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
|
||||
use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity;
|
||||
use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedNotification;
|
||||
use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity;
|
||||
use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity;
|
||||
use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity;
|
||||
@ -471,6 +474,10 @@ class EventServiceProvider extends ServiceProvider
|
||||
PurchaseOrderWasViewed::class => [
|
||||
PurchaseOrderViewedActivity::class,
|
||||
],
|
||||
PurchaseOrderWasAccepted::class => [
|
||||
PurchaseOrderAcceptedActivity::class,
|
||||
PurchaseOrderAcceptedNotification::class
|
||||
],
|
||||
CompanyDocumentsDeleted::class => [
|
||||
DeleteCompanyDocuments::class,
|
||||
],
|
||||
@ -593,7 +600,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
],
|
||||
VendorWasUpdated::class => [
|
||||
VendorUpdatedActivity::class,
|
||||
]
|
||||
],
|
||||
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||
// ... Manager won't register drivers that are not added to this listener.
|
||||
\SocialiteProviders\Apple\AppleExtendSocialite::class . '@handle',
|
||||
\SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class . '@handle',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
|
@ -50,6 +50,8 @@ class RouteServiceProvider extends ServiceProvider
|
||||
|
||||
$this->mapContactApiRoutes();
|
||||
|
||||
$this->mapVendorsApiRoutes();
|
||||
|
||||
$this->mapClientApiRoutes();
|
||||
|
||||
$this->mapShopApiRoutes();
|
||||
@ -121,4 +123,12 @@ class RouteServiceProvider extends ServiceProvider
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/shop.php'));
|
||||
}
|
||||
|
||||
protected function mapVendorsApiRoutes()
|
||||
{
|
||||
Route::prefix('')
|
||||
->middleware('client')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/vendor.php'));
|
||||
}
|
||||
}
|
||||
|
@ -49,8 +49,6 @@ class GenerateDeliveryNote
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
// $this->disk = 'public';
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
|
@ -341,7 +341,7 @@ class InvoiceService
|
||||
if(Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf'))
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
|
||||
|
||||
if(Ninja::isHosted() && Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) {
|
||||
if(Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) {
|
||||
Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,10 @@ class Design extends BaseDesign
|
||||
/** @var Payment[] */
|
||||
public $payments;
|
||||
|
||||
public $settings_object;
|
||||
|
||||
public $company;
|
||||
|
||||
/** @var array */
|
||||
public $aging = [];
|
||||
|
||||
@ -80,6 +84,7 @@ class Design extends BaseDesign
|
||||
Str::endsWith('.html', $design) ? $this->design = $design : $this->design = "{$design}.html";
|
||||
|
||||
$this->options = $options;
|
||||
|
||||
}
|
||||
|
||||
public function html(): ?string
|
||||
@ -574,19 +579,19 @@ class Design extends BaseDesign
|
||||
|
||||
foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) {
|
||||
if (array_key_exists($column, $aliases)) {
|
||||
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.discount' && !$this->client->company->enable_product_discount) {
|
||||
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.discount' && !$this->company->enable_product_discount) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
|
||||
} elseif ($column == '$product.quantity' && !$this->client->company->enable_product_quantity) {
|
||||
} elseif ($column == '$product.quantity' && !$this->company->enable_product_quantity) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
|
||||
} elseif ($column == '$product.tax_rate1') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.tax_rate2') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.tax_rate3') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} else {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
}
|
||||
}
|
||||
|
||||
@ -677,9 +682,9 @@ class Design extends BaseDesign
|
||||
|
||||
if ($cell == '$task.rate') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']];
|
||||
} elseif ($cell == '$product.discount' && !$this->client->company->enable_product_discount) {
|
||||
} elseif ($cell == '$product.discount' && !$this->company->enable_product_discount) {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']];
|
||||
} elseif ($cell == '$product.quantity' && !$this->client->company->enable_product_quantity) {
|
||||
} elseif ($cell == '$product.quantity' && !$this->company->enable_product_quantity) {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']];
|
||||
} elseif ($cell == '$task.hours') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']];
|
||||
@ -799,7 +804,7 @@ class Design extends BaseDesign
|
||||
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
|
||||
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
|
||||
|
||||
$visible = $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0';
|
||||
$visible = (int)$this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0' || !$this->entity->{$_variable};
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
@ -807,7 +812,7 @@ class Design extends BaseDesign
|
||||
]];
|
||||
} elseif (Str::startsWith($variable, '$custom')) {
|
||||
$field = explode('_', $variable);
|
||||
$visible = is_object($this->client->company->custom_fields) && property_exists($this->client->company->custom_fields, $field[1]) && !empty($this->client->company->custom_fields->{$field[1]});
|
||||
$visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]});
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
|
@ -60,6 +60,10 @@ trait DesignHelpers
|
||||
|
||||
$this->document();
|
||||
|
||||
$this->settings_object = $this->vendor ? $this->vendor->company : $this->client;
|
||||
|
||||
$this->company = $this->vendor ? $this->vendor->company : $this->client->company;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -180,7 +184,7 @@ trait DesignHelpers
|
||||
|
||||
$key = array_search(sprintf('%s%s.tax', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true);
|
||||
|
||||
if ($key) {
|
||||
if ($key !== false) {
|
||||
array_splice($this->context['pdf_variables']["{$type}_columns"], $key, 1, $taxes);
|
||||
}
|
||||
}
|
||||
@ -338,7 +342,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
$key = array_search(sprintf('%s%s.description', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true);
|
||||
|
||||
if ($key) {
|
||||
if ($key !== false) {
|
||||
array_splice($this->context['pdf_variables']["{$type}_columns"], $key + 1, 0, $custom_columns);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class CreateInvitations extends AbstractService
|
||||
|
||||
public function run()
|
||||
{
|
||||
$contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get();
|
||||
$contacts = $this->purchase_order->vendor->contacts()->get();
|
||||
|
||||
if($contacts->count() == 0){
|
||||
$this->createBlankContact();
|
||||
|
@ -31,7 +31,7 @@ class GetPurchaseOrderPdf extends AbstractService
|
||||
{
|
||||
|
||||
if (! $this->contact) {
|
||||
$this->contact = $this->purchase_order->vendor->contacts()->where('send_email', true)->first();
|
||||
$this->contact = $this->purchase_order->vendor->contacts()->orderBy('send_email', 'DESC')->first();
|
||||
}
|
||||
|
||||
$invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first();
|
||||
|
@ -11,11 +11,12 @@
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Services\PurchaseOrder\ApplyNumber;
|
||||
use App\Services\PurchaseOrder\CreateInvitations;
|
||||
use App\Services\PurchaseOrder\GetPurchaseOrderPdf;
|
||||
use App\Services\PurchaseOrder\TriggeredActions;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderService
|
||||
@ -62,6 +63,13 @@ class PurchaseOrderService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function triggeredActions($request)
|
||||
{
|
||||
$this->purchase_order = (new TriggeredActions($this->purchase_order->load('invitations'), $request))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPurchaseOrderPdf($contact = null)
|
||||
{
|
||||
return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run();
|
||||
@ -81,6 +89,34 @@ class PurchaseOrderService
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function touchPdf($force = false)
|
||||
{
|
||||
try {
|
||||
|
||||
if($force){
|
||||
|
||||
$this->purchase_order->invitations->each(function ($invitation) {
|
||||
CreatePurchaseOrderPdf::dispatchNow($invitation);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->purchase_order->invitations->each(function ($invitation) {
|
||||
CreatePurchaseOrderPdf::dispatch($invitation);
|
||||
});
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
nlog("failed creating purchase orders in Touch PDF");
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the purchase order.
|
||||
* @return \App\Models\PurchaseOrder object
|
||||
|
65
app/Services/PurchaseOrder/TriggeredActions.php
Normal file
65
app/Services/PurchaseOrder/TriggeredActions.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TriggeredActions extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private $request;
|
||||
|
||||
private $purchase_order;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order, Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
|
||||
$this->purchase_order->service()->markSent()->touchPdf()->save();
|
||||
$this->sendEmail();
|
||||
}
|
||||
|
||||
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
|
||||
$this->purchase_order = $this->purchase_order->service()->markSent()->touchPdf()->save();
|
||||
}
|
||||
|
||||
// if ($this->request->has('cancel') && $this->request->input('cancel') == 'true') {
|
||||
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
|
||||
// }
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
private function sendEmail()
|
||||
{
|
||||
|
||||
PurchaseOrderEmail::dispatch($this->purchase_order, $this->purchase_order->company);
|
||||
|
||||
}
|
||||
}
|
@ -164,6 +164,7 @@ class SubscriptionService
|
||||
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($client_contact->client_id);
|
||||
$recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration);
|
||||
$recurring_invoice->next_send_date_client = now()->addSeconds($this->subscription->trial_duration);
|
||||
$recurring_invoice->backup = 'is_trial';
|
||||
|
||||
if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||
@ -620,7 +621,9 @@ class SubscriptionService
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id);
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
|
||||
/* Start the recurring service */
|
||||
$recurring_invoice->service()
|
||||
@ -754,8 +757,9 @@ class SubscriptionService
|
||||
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
|
||||
$recurring_invoice->due_date_days = 'terms';
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
return $recurring_invoice;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,10 @@ class ZeroCostProduct extends AbstractService
|
||||
|
||||
$recurring_invoice->next_send_date = now();
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
$recurring_invoice->next_send_date = now();
|
||||
$recurring_invoice->next_send_date_client = now();
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
|
||||
/* Start the recurring service */
|
||||
$recurring_invoice->service()
|
||||
|
@ -86,6 +86,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'hosted_client_count' => (int) $account->hosted_client_count,
|
||||
'hosted_company_count' => (int) $account->hosted_company_count,
|
||||
'is_hosted' => (bool) Ninja::isHosted(),
|
||||
'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ class ActivityTransformer extends EntityTransformer
|
||||
'created_at' => (int) $activity->created_at,
|
||||
'expense_id' => $activity->expense_id ? (string) $this->encodePrimaryKey($activity->expense_id) : '',
|
||||
'is_system' => (bool) $activity->is_system,
|
||||
'contact_id' => $activity->contact_id ? (string) $this->encodePrimaryKey($activity->contact_id) : '',
|
||||
'contact_id' => $activity->client_contact_id ? (string) $this->encodePrimaryKey($activity->client_contact_id) : '',
|
||||
'task_id' => $activity->task_id ? (string) $this->encodePrimaryKey($activity->task_id) : '',
|
||||
'token_id' => $activity->token_id ? (string) $this->encodePrimaryKey($activity->token_id) : '',
|
||||
'notes' => $activity->notes ? (string) $activity->notes : '',
|
||||
|
@ -29,6 +29,7 @@ use App\Models\Payment;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
@ -39,6 +40,7 @@ use App\Models\TaskStatus;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Models\Webhook;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Transformers\RecurringExpenseTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use stdClass;
|
||||
@ -95,6 +97,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'task_statuses',
|
||||
'subscriptions',
|
||||
'recurring_expenses',
|
||||
'purchase_orders',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -391,4 +394,11 @@ class CompanyTransformer extends EntityTransformer
|
||||
|
||||
return $this->includeCollection($company->subscriptions, $transformer, Subscription::class);
|
||||
}
|
||||
|
||||
public function includePurchaseOrders(Company $company)
|
||||
{
|
||||
$transformer = new PurchaseOrderTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company->purchase_orders, $transformer, PurchaseOrder::class);
|
||||
}
|
||||
}
|
||||
|
@ -265,6 +265,8 @@ trait MakesInvoiceValues
|
||||
*/
|
||||
public function transformLineItems($items, $table_type = '$product') :array
|
||||
{
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
|
||||
$data = [];
|
||||
|
||||
if (! is_array($items)) {
|
||||
@ -294,23 +296,23 @@ trait MakesInvoiceValues
|
||||
$data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item;
|
||||
$data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service;
|
||||
|
||||
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $entity);
|
||||
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $entity);
|
||||
|
||||
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}1", $item->custom_value1, $entity);
|
||||
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}2", $item->custom_value2, $entity);
|
||||
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}3", $item->custom_value3, $entity);
|
||||
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}4", $item->custom_value4, $entity);
|
||||
|
||||
if($item->quantity > 0 || $item->cost > 0){
|
||||
|
||||
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $this->client->currency());
|
||||
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $entity->currency());
|
||||
|
||||
$data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $this->client);
|
||||
$data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $entity);
|
||||
|
||||
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
|
||||
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $entity);
|
||||
|
||||
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client);
|
||||
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $entity);
|
||||
|
||||
}
|
||||
else {
|
||||
@ -326,13 +328,13 @@ trait MakesInvoiceValues
|
||||
}
|
||||
|
||||
if(property_exists($item, 'gross_line_total'))
|
||||
$data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $this->client);
|
||||
$data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $entity);
|
||||
else
|
||||
$data[$key][$table_type.'.gross_line_total'] = '';
|
||||
|
||||
if (isset($item->discount) && $item->discount > 0) {
|
||||
if ($item->is_amount_discount) {
|
||||
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->client);
|
||||
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $entity);
|
||||
} else {
|
||||
$data[$key][$table_type.'.discount'] = floatval($item->discount).'%';
|
||||
}
|
||||
@ -376,13 +378,14 @@ trait MakesInvoiceValues
|
||||
private function makeLineTaxes() :string
|
||||
{
|
||||
$tax_map = $this->calc()->getTaxMap();
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<tr class="line_taxes">';
|
||||
$data .= '<td>'.$tax['name'].'</td>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -395,6 +398,7 @@ trait MakesInvoiceValues
|
||||
private function makeTotalTaxes() :string
|
||||
{
|
||||
$data = '';
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
|
||||
if (! $this->calc()->getTotalTaxMap()) {
|
||||
return $data;
|
||||
@ -403,7 +407,7 @@ trait MakesInvoiceValues
|
||||
foreach ($this->calc()->getTotalTaxMap() as $tax) {
|
||||
$data .= '<tr class="total_taxes">';
|
||||
$data .= '<td>'.$tax['name'].'</td>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -427,13 +431,14 @@ trait MakesInvoiceValues
|
||||
private function totalTaxValues() :string
|
||||
{
|
||||
$data = '';
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
|
||||
if (! $this->calc()->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->calc()->getTotalTaxMap() as $tax) {
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>';
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -455,11 +460,12 @@ trait MakesInvoiceValues
|
||||
private function lineTaxValues() :string
|
||||
{
|
||||
$tax_map = $this->calc()->getTaxMap();
|
||||
$entity = $this->client ? $this->client : $this->company;
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>';
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -481,7 +487,8 @@ trait MakesInvoiceValues
|
||||
*/
|
||||
public function generateCustomCSS() :string
|
||||
{
|
||||
$settings = $this->client->getMergedSettings();
|
||||
|
||||
$settings = $this->client ? $this->client->getMergedSettings() : $this->company->settings;
|
||||
|
||||
$header_and_footer = '
|
||||
.header, .header-space {
|
||||
|
@ -15,6 +15,7 @@ use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
|
||||
/**
|
||||
@ -100,6 +101,9 @@ trait UserNotifies
|
||||
case ($entity instanceof Credit):
|
||||
return array_merge($required_permissions, ["all_notifications","all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
|
||||
break;
|
||||
case ($entity instanceof PurchaseOrder):
|
||||
return array_merge($required_permissions, ["all_notifications","all_user_notifications","purchase_order_created_user","purchase_order_sent_user","purchase_order_viewed_user"]);
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
break;
|
||||
@ -123,6 +127,9 @@ trait UserNotifies
|
||||
case ($entity instanceof Credit):
|
||||
return array_diff($required_permissions, ["all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
|
||||
break;
|
||||
case ($entity instanceof PurchaseOrder):
|
||||
return array_diff($required_permissions, ["all_user_notifications","purchase_order_created_user","purchase_order_sent_user","purchase_order_viewed_user"]);
|
||||
break;
|
||||
default:
|
||||
// code...
|
||||
break;
|
||||
|
@ -134,20 +134,15 @@ class VendorHtmlEngine
|
||||
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')];
|
||||
$data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')];
|
||||
|
||||
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number_short')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_purchase_order').'</a>', 'label' => ctrans('texts.view_purchase_order')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$paymentButton'] = &$data['$payment_button'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
|
||||
@ -390,11 +385,6 @@ class VendorHtmlEngine
|
||||
$data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => ''];
|
||||
$data['$auto_bill'] = &$data['$autoBill'];
|
||||
|
||||
/*Payment Aliases*/
|
||||
$data['$paymentLink'] = &$data['$payment_link'];
|
||||
$data['$payment_url'] = &$data['$payment_link'];
|
||||
$data['$portalButton'] = &$data['$paymentLink'];
|
||||
|
||||
$data['$dir'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => ''];
|
||||
$data['$dir_text_align'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'right' : 'left', 'label' => ''];
|
||||
|
||||
|
@ -11,7 +11,12 @@
|
||||
"Credit card billing",
|
||||
"projects",
|
||||
"tasks",
|
||||
"freelancer"
|
||||
"freelancer",
|
||||
"quotes",
|
||||
"purchase orders",
|
||||
"stripe billing",
|
||||
"invoices",
|
||||
"subscriptions"
|
||||
],
|
||||
"license": "Elastic License",
|
||||
"authors": [
|
||||
@ -37,7 +42,7 @@
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"beganovich/snappdf": "^1.7",
|
||||
"braintree/braintree_php": "^6.0",
|
||||
"checkout/checkout-sdk-php": "^1.0",
|
||||
"checkout/checkout-sdk-php": "^2.5",
|
||||
"cleverit/ubl_invoice": "^1.3",
|
||||
"coconutcraig/laravel-postmark": "^2.10",
|
||||
"doctrine/dbal": "^3.0",
|
||||
@ -77,6 +82,8 @@
|
||||
"sentry/sentry-laravel": "^2",
|
||||
"setasign/fpdf": "^1.8",
|
||||
"setasign/fpdi": "^2.3",
|
||||
"socialiteproviders/apple": "^5.2",
|
||||
"socialiteproviders/microsoft": "^4.1",
|
||||
"square/square": "13.0.0.20210721",
|
||||
"stripe/stripe-php": "^7.50",
|
||||
"symfony/http-client": "^5.2",
|
||||
|
1012
composer.lock
generated
1012
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -56,6 +56,10 @@ return [
|
||||
'driver' => 'session',
|
||||
'provider' => 'contacts',
|
||||
],
|
||||
'vendor' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'vendors',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
@ -85,6 +89,11 @@ return [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\ClientContact::class,
|
||||
],
|
||||
'vendors' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\VendorContact::class,
|
||||
],
|
||||
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
@ -120,6 +129,11 @@ return [
|
||||
'table' => 'password_resets',
|
||||
'expire' => 60,
|
||||
],
|
||||
'vendors' => [
|
||||
'provider' => 'vendors',
|
||||
'table' => 'password_resets',
|
||||
'expire' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.98',
|
||||
'app_tag' => '5.3.98',
|
||||
'app_version' => '5.4.0',
|
||||
'app_tag' => '5.4.0',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -80,4 +80,14 @@ return [
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_SECRET'),
|
||||
],
|
||||
'microsoft' => [
|
||||
'client_id' => env('MICROSOFT_CLIENT_ID'),
|
||||
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
|
||||
'redirect' => env('MICROSOFT_REDIRECT_URI')
|
||||
],
|
||||
'apple' => [
|
||||
'client_id' => env('APPLE_CLIENT_ID'),
|
||||
'client_secret' => env('APPLE_CLIENT_SECRET'),
|
||||
'redirect' => env('APPLE_REDIRECT_URI')
|
||||
],
|
||||
];
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SetAccountFlagForReact extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddReactSwitchingFlag extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->boolean('set_react_as_default_ap')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -5,9 +5,9 @@ const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"/": "f4932ba24bfa72c97f7578b8951891c2",
|
||||
"main.dart.js": "68a04477f6ce39dcf894f583120e1c46",
|
||||
"version.json": "3afb81924daf4f751571755436069115",
|
||||
"/": "2e739a78eec983322924f724ebfa09ba",
|
||||
"main.dart.js": "fa4a0263712be1ce1df7d59ca0ede10e",
|
||||
"version.json": "d72bd323e3b8e22ce5acdc247f4e6f62",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
|
2
public/js/clients/purchase_orders/accept.js
vendored
Normal file
2
public/js/clients/purchase_orders/accept.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see accept.js.LICENSE.txt */
|
||||
(()=>{function e(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}var t=function(){function t(e,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.shouldDisplaySignature=e,this.shouldDisplayTerms=n,this.termsAccepted=!1}var n,a,r;return n=t,(a=[{key:"submitForm",value:function(){document.getElementById("approve-form").submit()}},{key:"displaySignature",value:function(){document.getElementById("displaySignatureModal").removeAttribute("style");var e=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});this.signaturePad=e}},{key:"displayTerms",value:function(){document.getElementById("displayTermsModal").removeAttribute("style")}},{key:"handle",value:function(){var e=this;document.getElementById("approve-button").addEventListener("click",(function(){e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.termsAccepted=!0,e.submitForm()}))}))),e.shouldDisplaySignature&&!e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.submitForm()}))),!e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplaySignature||e.shouldDisplayTerms||e.submitForm()}))}}])&&e(n.prototype,a),r&&e(n,r),Object.defineProperty(n,"prototype",{writable:!1}),t}(),n=document.querySelector('meta[name="require-purchase_order-signature"]').content,a=document.querySelector('meta[name="show-purchase_order-terms"]').content;new t(Boolean(+n),Boolean(+a)).handle()})();
|
9
public/js/clients/purchase_orders/accept.js.LICENSE.txt
Normal file
9
public/js/clients/purchase_orders/accept.js.LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
2
public/js/clients/purchase_orders/action-selectors.js
vendored
Normal file
2
public/js/clients/purchase_orders/action-selectors.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see action-selectors.js.LICENSE.txt */
|
||||
(()=>{function e(e,n){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=function(e,n){if(!e)return;if("string"==typeof e)return t(e,n);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return t(e,n)}(e))||n&&e&&"number"==typeof e.length){r&&(e=r);var o=0,c=function(){};return{s:c,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:c}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,l=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return a=e.done,e},e:function(e){l=!0,i=e},f:function(){try{a||null==r.return||r.return()}finally{if(l)throw i}}}}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function t(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.parentElement=document.querySelector(".form-check-parent"),this.parentForm=document.getElementById("bulkActions")}var r,o,c;return r=t,o=[{key:"watchCheckboxes",value:function(e){var t=this;document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})),document.querySelectorAll(".form-check-child").forEach((function(n){e.checked?(n.checked=e.checked,t.processChildItem(n,document.getElementById("bulkActions"))):(n.checked=!1,document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})))}))}},{key:"processChildItem",value:function(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(r.hasOwnProperty("single")&&document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})),!1!==t.checked){var o=document.createElement("INPUT");o.setAttribute("name","purchase_orders[]"),o.setAttribute("value",t.dataset.value),o.setAttribute("class","child-hidden-input"),o.hidden=!0,n.append(o)}else{var c,i=document.querySelectorAll("input.child-hidden-input"),a=e(i);try{for(a.s();!(c=a.n()).done;){var l=c.value;l.value==t.dataset.value&&l.remove()}}catch(e){a.e(e)}finally{a.f()}}}},{key:"handle",value:function(){var t=this;this.parentElement.addEventListener("click",(function(){t.watchCheckboxes(t.parentElement)}));var n,r=e(document.querySelectorAll(".form-check-child"));try{var o=function(){var e=n.value;e.addEventListener("click",(function(){t.processChildItem(e,t.parentForm)}))};for(r.s();!(n=r.n()).done;)o()}catch(e){r.e(e)}finally{r.f()}}}],o&&n(r.prototype,o),c&&n(r,c),Object.defineProperty(r,"prototype",{writable:!1}),t}())).handle()})();
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
297768
public/main.dart.js
vendored
297768
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
295590
public/main.foss.dart.js
vendored
295590
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
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