mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
commit
bcd3383b0b
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.100
|
||||
5.4.0
|
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 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);
|
||||
}
|
||||
}
|
||||
|
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,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();
|
||||
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
}
|
||||
|
||||
|
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');
|
||||
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,9 @@ 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() );
|
||||
$nmo->company = $invitation->company;
|
||||
@ -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()
|
||||
{
|
||||
$settings = $this->entity->client->getMergedSettings();
|
||||
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;
|
||||
}
|
||||
}
|
@ -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,7 +58,7 @@ class Account extends BaseModel
|
||||
'utm_content',
|
||||
'user_agent',
|
||||
'platform',
|
||||
// 'set_react_as_default_ap',
|
||||
'set_react_as_default_ap',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -75,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';
|
||||
@ -88,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';
|
||||
@ -163,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:
|
||||
@ -469,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,7 +116,8 @@ 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',
|
||||
'updated_at' => 'timestamp',
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -407,12 +407,7 @@ 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();
|
||||
$error = $e->getMessage();
|
||||
|
||||
if(!$this->payment_hash)
|
||||
throw new PaymentFailed($error, $e->getCode());
|
||||
|
@ -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'],
|
||||
'payment_method_id' => GatewayType::CREDIT_CARD,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ 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");
|
||||
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -50,6 +50,8 @@ class RouteServiceProvider extends ServiceProvider
|
||||
|
||||
$this->mapContactApiRoutes();
|
||||
|
||||
$this->mapVendorsApiRoutes();
|
||||
|
||||
$this->mapClientApiRoutes();
|
||||
|
||||
$this->mapShopApiRoutes();
|
||||
@ -125,7 +127,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
protected function mapVendorsApiRoutes()
|
||||
{
|
||||
Route::prefix('')
|
||||
->middleware('vendor')
|
||||
->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');
|
||||
}
|
||||
|
||||
|
@ -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,7 +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
|
||||
'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -99,7 +100,10 @@ trait UserNotifies
|
||||
break;
|
||||
case ($entity instanceof Credit):
|
||||
return array_merge($required_permissions, ["all_notifications","all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
|
||||
break;
|
||||
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;
|
||||
@ -122,7 +126,10 @@ trait UserNotifies
|
||||
break;
|
||||
case ($entity instanceof Credit):
|
||||
return array_diff($required_permissions, ["all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]);
|
||||
break;
|
||||
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;
|
||||
|
@ -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",
|
||||
|
103
composer.lock
generated
103
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c9278efe297c252de6bc0b5a48540c0b",
|
||||
"content-hash": "6845489fdc254427c4536e22f025ff51",
|
||||
"packages": [
|
||||
{
|
||||
"name": "afosto/yaac",
|
||||
@ -110,16 +110,16 @@
|
||||
},
|
||||
{
|
||||
"name": "apimatic/unirest-php",
|
||||
"version": "2.2.2",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apimatic/unirest-php.git",
|
||||
"reference": "a45c4c71a1ea3659b118042a67cc1b6486bcf03a"
|
||||
"reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/apimatic/unirest-php/zipball/a45c4c71a1ea3659b118042a67cc1b6486bcf03a",
|
||||
"reference": "a45c4c71a1ea3659b118042a67cc1b6486bcf03a",
|
||||
"url": "https://api.github.com/repos/apimatic/unirest-php/zipball/52e226fb3b7081dc9ef64aee876142a240a5f0f9",
|
||||
"reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -168,9 +168,9 @@
|
||||
"support": {
|
||||
"email": "opensource@apimatic.io",
|
||||
"issues": "https://github.com/apimatic/unirest-php/issues",
|
||||
"source": "https://github.com/apimatic/unirest-php/tree/2.2.2"
|
||||
"source": "https://github.com/apimatic/unirest-php/tree/2.3.0"
|
||||
},
|
||||
"time": "2022-03-24T08:19:20+00:00"
|
||||
"time": "2022-06-15T08:29:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "asm/php-ansible",
|
||||
@ -434,16 +434,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.225.2",
|
||||
"version": "3.225.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "f846724ad842916061127d20da4fe4e129f7d4b8"
|
||||
"reference": "09b404c6b80b9c31be15fa245e647a2f9fb5e733"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f846724ad842916061127d20da4fe4e129f7d4b8",
|
||||
"reference": "f846724ad842916061127d20da4fe4e129f7d4b8",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/09b404c6b80b9c31be15fa245e647a2f9fb5e733",
|
||||
"reference": "09b404c6b80b9c31be15fa245e647a2f9fb5e733",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -451,7 +451,7 @@
|
||||
"ext-json": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-simplexml": "*",
|
||||
"guzzlehttp/guzzle": "^5.3.3 || ^6.5.6 || ^7.4.3",
|
||||
"guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0",
|
||||
"guzzlehttp/promises": "^1.4.0",
|
||||
"guzzlehttp/psr7": "^1.7.0 || ^2.1.1",
|
||||
"mtdowling/jmespath.php": "^2.6",
|
||||
@ -519,9 +519,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.225.2"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.225.5"
|
||||
},
|
||||
"time": "2022-06-10T19:03:26+00:00"
|
||||
"time": "2022-06-15T19:35:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -741,28 +741,37 @@
|
||||
},
|
||||
{
|
||||
"name": "checkout/checkout-sdk-php",
|
||||
"version": "1.0.19",
|
||||
"version": "2.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/checkout/checkout-sdk-php.git",
|
||||
"reference": "c2c323ea2f95f74bff3055c42801e7ce22999791"
|
||||
"reference": "097b862487f7583fd0fab47a08e3dc0800f5c3e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/c2c323ea2f95f74bff3055c42801e7ce22999791",
|
||||
"reference": "c2c323ea2f95f74bff3055c42801e7ce22999791",
|
||||
"url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/097b862487f7583fd0fab47a08e3dc0800f5c3e4",
|
||||
"reference": "097b862487f7583fd0fab47a08e3dc0800f5c3e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
"ext-fileinfo": "*",
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.5 || ^7.4",
|
||||
"monolog/monolog": "^1.27 || ^2.4",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6"
|
||||
"mockery/mockery": "^1.3 || ^1.4",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0",
|
||||
"smgladkovskiy/phpcs-git-pre-commit": "dev-master",
|
||||
"squizlabs/php_codesniffer": "^3.3",
|
||||
"symfony/phpunit-bridge": "^5.2 || ^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Checkout\\": "src/"
|
||||
"Checkout\\": "lib/Checkout"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@ -772,7 +781,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Checkout.com",
|
||||
"email": "platforms@checkout.com",
|
||||
"email": "integrations@checkout.com",
|
||||
"homepage": "https://github.com/checkout/checkout-sdk-php/graphs/contributors"
|
||||
}
|
||||
],
|
||||
@ -794,9 +803,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/checkout/checkout-sdk-php/issues",
|
||||
"source": "https://github.com/checkout/checkout-sdk-php/tree/1.0.19"
|
||||
"source": "https://github.com/checkout/checkout-sdk-php/tree/2.5.1"
|
||||
},
|
||||
"time": "2021-11-19T15:08:38+00:00"
|
||||
"time": "2022-06-13T00:23:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "cleverit/ubl_invoice",
|
||||
@ -2285,16 +2294,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/apiclient-services",
|
||||
"version": "v0.252.0",
|
||||
"version": "v0.253.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-api-php-client-services.git",
|
||||
"reference": "9941c959f6a1f781e49019b78f453d54554dff73"
|
||||
"reference": "70c62b17f7821526cb52c6f125254dc51f256109"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9941c959f6a1f781e49019b78f453d54554dff73",
|
||||
"reference": "9941c959f6a1f781e49019b78f453d54554dff73",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/70c62b17f7821526cb52c6f125254dc51f256109",
|
||||
"reference": "70c62b17f7821526cb52c6f125254dc51f256109",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2323,22 +2332,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
|
||||
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.252.0"
|
||||
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.253.0"
|
||||
},
|
||||
"time": "2022-06-06T01:20:11+00:00"
|
||||
"time": "2022-06-13T01:06:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/auth",
|
||||
"version": "v1.21.0",
|
||||
"version": "v1.21.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-auth-library-php.git",
|
||||
"reference": "73392bad2eb6852eea9084b6bbdec752515cb849"
|
||||
"reference": "aa3b9ca29258ac6347ce3c8937a2418c5d78f840"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/73392bad2eb6852eea9084b6bbdec752515cb849",
|
||||
"reference": "73392bad2eb6852eea9084b6bbdec752515cb849",
|
||||
"url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/aa3b9ca29258ac6347ce3c8937a2418c5d78f840",
|
||||
"reference": "aa3b9ca29258ac6347ce3c8937a2418c5d78f840",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2381,9 +2390,9 @@
|
||||
"support": {
|
||||
"docs": "https://googleapis.github.io/google-auth-library-php/main/",
|
||||
"issues": "https://github.com/googleapis/google-auth-library-php/issues",
|
||||
"source": "https://github.com/googleapis/google-auth-library-php/tree/v1.21.0"
|
||||
"source": "https://github.com/googleapis/google-auth-library-php/tree/v1.21.1"
|
||||
},
|
||||
"time": "2022-04-13T20:35:52+00:00"
|
||||
"time": "2022-05-16T19:34:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
@ -6016,16 +6025,16 @@
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v2.6.1",
|
||||
"version": "v2.6.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9"
|
||||
"reference": "58c3f47f650c94ec05a151692652a868995d2938"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9",
|
||||
"reference": "d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
|
||||
"reference": "58c3f47f650c94ec05a151692652a868995d2938",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -6079,7 +6088,7 @@
|
||||
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||
},
|
||||
"time": "2022-06-11T00:43:46+00:00"
|
||||
"time": "2022-06-14T06:56:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
@ -6815,16 +6824,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "8.0.0",
|
||||
"version": "v8.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b"
|
||||
"reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b",
|
||||
"reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3",
|
||||
"reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -6861,9 +6870,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/antonioribeiro/google2fa/issues",
|
||||
"source": "https://github.com/antonioribeiro/google2fa/tree/8.0.0"
|
||||
"source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1"
|
||||
},
|
||||
"time": "2020-04-05T10:47:18+00:00"
|
||||
"time": "2022-06-13T21:57:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
|
@ -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.100',
|
||||
'app_tag' => '5.3.100',
|
||||
'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', ''),
|
||||
|
@ -23,9 +23,6 @@ class SetAccountFlagForReact extends Migration
|
||||
{
|
||||
Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||
|
||||
// Schema::table('accounts', function (Blueprint $table) {
|
||||
// $table->boolean('set_react_as_default_ap')->default(0);
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -5,8 +5,8 @@ const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"/": "94862dc60be2e82c49106de806115c42",
|
||||
"main.dart.js": "bb75daec9d3cdf8374011435e63376d2",
|
||||
"/": "2e739a78eec983322924f724ebfa09ba",
|
||||
"main.dart.js": "fa4a0263712be1ce1df7d59ca0ede10e",
|
||||
"version.json": "d72bd323e3b8e22ce5acdc247f4e6f62",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
|
||||
|
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
|
||||
*/
|
295831
public/main.dart.js
vendored
295831
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
292909
public/main.foss.dart.js
vendored
292909
public/main.foss.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
295245
public/main.next.dart.js
vendored
295245
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
21875
public/main.profile.dart.js
vendored
21875
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -4,6 +4,8 @@
|
||||
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=803182f668c39d631ca5c55437876da4",
|
||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=7bed15f51bca764378d9a3aa605b8664",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=d4f86ddee4e8a1d6e9719010aa0fe62b",
|
||||
"/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=160b8161599fc2429b449b0970d3ba6c",
|
||||
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=2b5fed3ae34a6fd4db171a77ba72496e",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=b88ad7c8881cc87df07b129c5a7c76df",
|
||||
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=1c5493a4c53a5b862d07ee1818179ea9",
|
||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0274ab4f8d2b411f2a2fe5142301e7af",
|
||||
@ -38,7 +40,7 @@
|
||||
"/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=3d53d2f7d0291d9f92cf7414dd2d351c",
|
||||
"/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=db71055862995fd6ae21becfc587a3de",
|
||||
"/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=914a6846ad1e5584635e7430fef76875",
|
||||
"/css/app.css": "/css/app.css?id=db0f69e335622f720583392dd572d264",
|
||||
"/css/app.css": "/css/app.css?id=6419fb85c22d562d4ec14800980801e7",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c",
|
||||
"/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d"
|
||||
}
|
||||
|
BIN
public/react/invoiceninja-logo@dark.365f6449.png
Normal file
BIN
public/react/invoiceninja-logo@dark.365f6449.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
103
resources/js/clients/purchase_orders/accept.js
vendored
Normal file
103
resources/js/clients/purchase_orders/accept.js
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
class Accept {
|
||||
constructor(displaySignature, displayTerms) {
|
||||
this.shouldDisplaySignature = displaySignature;
|
||||
this.shouldDisplayTerms = displayTerms;
|
||||
this.termsAccepted = false;
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
document.getElementById('approve-form').submit();
|
||||
}
|
||||
|
||||
displaySignature() {
|
||||
let displaySignatureModal = document.getElementById(
|
||||
'displaySignatureModal'
|
||||
);
|
||||
displaySignatureModal.removeAttribute('style');
|
||||
|
||||
const signaturePad = new SignaturePad(
|
||||
document.getElementById('signature-pad'),
|
||||
{
|
||||
penColor: 'rgb(0, 0, 0)',
|
||||
}
|
||||
);
|
||||
|
||||
this.signaturePad = signaturePad;
|
||||
}
|
||||
|
||||
displayTerms() {
|
||||
let displayTermsModal = document.getElementById("displayTermsModal");
|
||||
displayTermsModal.removeAttribute("style");
|
||||
}
|
||||
|
||||
handle() {
|
||||
document
|
||||
.getElementById('approve-button')
|
||||
.addEventListener('click', () => {
|
||||
if (this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||
this.displaySignature();
|
||||
|
||||
document
|
||||
.getElementById('signature-next-step')
|
||||
.addEventListener('click', () => {
|
||||
this.displayTerms();
|
||||
|
||||
document
|
||||
.getElementById('accept-terms-button')
|
||||
.addEventListener('click', () => {
|
||||
document.querySelector(
|
||||
'input[name="signature"'
|
||||
).value = this.signaturePad.toDataURL();
|
||||
this.termsAccepted = true;
|
||||
this.submitForm();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||
this.displaySignature();
|
||||
|
||||
document
|
||||
.getElementById('signature-next-step')
|
||||
.addEventListener('click', () => {
|
||||
document.querySelector(
|
||||
'input[name="signature"'
|
||||
).value = this.signaturePad.toDataURL();
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.shouldDisplaySignature && this.shouldDisplayTerms) {
|
||||
this.displayTerms();
|
||||
|
||||
document
|
||||
.getElementById('accept-terms-button')
|
||||
.addEventListener('click', () => {
|
||||
this.termsAccepted = true;
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms) {
|
||||
this.submitForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const signature = document.querySelector('meta[name="require-purchase_order-signature"]')
|
||||
.content;
|
||||
|
||||
const terms = document.querySelector('meta[name="show-purchase_order-terms"]').content;
|
||||
|
||||
new Accept(Boolean(+signature), Boolean(+terms)).handle();
|
79
resources/js/clients/purchase_orders/action-selectors.js
vendored
Normal file
79
resources/js/clients/purchase_orders/action-selectors.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
class ActionSelectors {
|
||||
constructor() {
|
||||
this.parentElement = document.querySelector('.form-check-parent');
|
||||
this.parentForm = document.getElementById('bulkActions');
|
||||
}
|
||||
|
||||
watchCheckboxes(parentElement) {
|
||||
document
|
||||
.querySelectorAll('.child-hidden-input')
|
||||
.forEach((element) => element.remove());
|
||||
|
||||
document.querySelectorAll('.form-check-child').forEach((child) => {
|
||||
if (parentElement.checked) {
|
||||
child.checked = parentElement.checked;
|
||||
this.processChildItem(
|
||||
child,
|
||||
document.getElementById('bulkActions')
|
||||
);
|
||||
} else {
|
||||
child.checked = false;
|
||||
document
|
||||
.querySelectorAll('.child-hidden-input')
|
||||
.forEach((element) => element.remove());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processChildItem(element, parent, options = {}) {
|
||||
if (options.hasOwnProperty('single')) {
|
||||
document
|
||||
.querySelectorAll('.child-hidden-input')
|
||||
.forEach((element) => element.remove());
|
||||
}
|
||||
|
||||
if (element.checked === false) {
|
||||
let inputs = document.querySelectorAll('input.child-hidden-input');
|
||||
|
||||
for (let i of inputs) {
|
||||
if (i.value == element.dataset.value) i.remove();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let _temp = document.createElement('INPUT');
|
||||
|
||||
_temp.setAttribute('name', 'purchase_orders[]');
|
||||
_temp.setAttribute('value', element.dataset.value);
|
||||
_temp.setAttribute('class', 'child-hidden-input');
|
||||
_temp.hidden = true;
|
||||
|
||||
parent.append(_temp);
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.parentElement.addEventListener('click', () => {
|
||||
this.watchCheckboxes(this.parentElement);
|
||||
});
|
||||
|
||||
for (let child of document.querySelectorAll('.form-check-child')) {
|
||||
child.addEventListener('click', () => {
|
||||
this.processChildItem(child, this.parentForm);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @handle **/
|
||||
new ActionSelectors().handle();
|
@ -4623,7 +4623,16 @@ $LANG = array(
|
||||
'purchase_order_message' => 'To view your purchase order for :amount, click the link below.',
|
||||
'view_purchase_order' => 'View Purchase Order',
|
||||
'purchase_orders_backup_subject' => 'Your purchase orders are ready for download',
|
||||
|
||||
'notification_purchase_order_viewed_subject' => 'Purchase Order :invoice was viewed by :client',
|
||||
'notification_purchase_order_viewed' => 'The following vendor :client viewed Purchase Order :invoice for :amount.',
|
||||
'purchase_order_date' => 'Purchase Order Date',
|
||||
'purchase_orders' => 'Purchase Orders',
|
||||
'purchase_order_number_placeholder' => 'Purchase Order # :purchase_order',
|
||||
'accepted' => 'Accepted',
|
||||
'activity_137' => ':contact accepted purchase order :purchase_order',
|
||||
'vendor_information' => 'Vendor Information',
|
||||
'notification_purchase_order_accepted_subject' => 'Purchase Order :purchase_order was accepted by :vendor',
|
||||
'notification_purchase_order_accepted' => 'The following vendor :vendor accepted Purchase Order :purchase_order for :amount.',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
2
resources/sass/components/badge.scss
vendored
2
resources/sass/components/badge.scss
vendored
@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
@apply bg-yellow-500 text-yellow-500;
|
||||
@apply bg-yellow-100 text-yellow-600;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
|
@ -0,0 +1,76 @@
|
||||
<div class="hidden md:flex md:flex-shrink-0">
|
||||
<div class="flex flex-col w-64">
|
||||
<div class="flex items-center h-16 flex-shrink-0 px-4 bg-white border-r justify-center z-10">
|
||||
<a href="{{ route('vendor.dashboard') }}">
|
||||
<img class="h-10 w-auto" src="{!! auth()->guard('vendor')->user()->company->present()->logo($settings) !!}"
|
||||
alt="{{ auth()->guard('vendor')->user()->company->present()->name() }} logo"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="h-0 flex-1 flex flex-col overflow-y-auto z-0 border-r">
|
||||
<nav class="flex-1 pb-4 pt-0 bg-white">
|
||||
@foreach($sidebar as $row)
|
||||
<a class="group flex items-center p-4 text-sm leading-5 font-medium hover:font-semibold focus:outline-none focus:bg-primary-darken transition ease-in-out duration-150 {{ isActive($row['url'], true) ? 'bg-primary text-white' : 'text-gray-900' }}"
|
||||
href="{{ route($row['url']) }}">
|
||||
@if(isActive($row['url'], true))
|
||||
<img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}"
|
||||
class="w-5 h-5 fill-current text-white mr-3" alt=""/>
|
||||
@else
|
||||
<img src="{{ asset('images/svg/dark/' . $row['icon'] . '.svg') }}"
|
||||
class="w-5 h-5 fill-current text-white mr-3" alt=""/>
|
||||
@endif
|
||||
|
||||
<span>{{ $row['title'] }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
@if(!auth()->guard('vendor')->user()->user->account->isPaid())
|
||||
<div class="flex-shrink-0 flex bg-white p-4 justify-center">
|
||||
<div class="flex items-center">
|
||||
<a target="_blank" href="https://www.facebook.com/invoiceninja/">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/invoiceninja">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://github.com/invoiceninja/invoiceninja">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.invoiceninja.com/contact">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path
|
||||
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex-shrink-0 w-14"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,42 @@
|
||||
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white shadow" xmlns:x-transition="http://www.w3.org/1999/xhtml">
|
||||
<button @click.stop="sidebarOpen = true" class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:bg-gray-100 focus:text-gray-600 md:hidden">
|
||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex-1 px-3 md:px-8 flex justify-between items-center">
|
||||
<span class="text-xl text-gray-900" data-ref="meta-title">@yield('meta_title')</span>
|
||||
<div class="flex items-center md:ml-6 md:mr-2">
|
||||
|
||||
<div @click.away="open = false" class="ml-3 relative" x-data="{ open: false }">
|
||||
<div>
|
||||
<button data-ref="client-profile-dropdown" @click="open = !open"
|
||||
class="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:ring">
|
||||
<img class="h-8 w-8 rounded-full" src="{{ auth()->guard('vendor')->user()->avatar() }}" alt=""/>
|
||||
<span class="ml-2 hidden sm:block">{{ auth()->guard('vendor')->user()->present()->name() }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="open" style="display:none;" x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
|
||||
<div class="py-1 rounded-md bg-white ring-1 ring-black ring-opacity-5">
|
||||
<a data-ref="client-profile-dropdown-settings"
|
||||
href="{{ route('vendor.profile.edit', ['vendor_contact' => auth()->guard('vendor')->user()->hashed_id]) }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.profile') }}
|
||||
</a>
|
||||
|
||||
<a href="{{ route('vendor.logout') }}"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.logout') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,45 @@
|
||||
<div
|
||||
class="main_layout h-screen flex overflow-hidden bg-gray-100"
|
||||
x-data="{ sidebarOpen: false }"
|
||||
@keydown.window.escape="sidebarOpen = false"
|
||||
id="main-sidebar">
|
||||
|
||||
@if($settings && $settings->enable_client_portal)
|
||||
<!-- Off-canvas menu for mobile -->
|
||||
@include('portal.ninja2020.components.general.sidebar.vendor_mobile', ['sidebar' => $sidebar])
|
||||
|
||||
<!-- Static sidebar for desktop -->
|
||||
@unless(request()->query('sidebar') === 'hidden')
|
||||
@include('portal.ninja2020.components.general.sidebar.vendor_desktop', ['sidebar' => $sidebar])
|
||||
@endunless
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
@if($settings && $settings->enable_client_portal)
|
||||
@include('portal.ninja2020.components.general.sidebar.vendor_header', ['sidebar' => $sidebar])
|
||||
@endif
|
||||
|
||||
<main
|
||||
class="flex-1 relative z-0 overflow-y-auto pt-6 focus:outline-none"
|
||||
tabindex="0" x-data
|
||||
x-init="$el.focus()">
|
||||
|
||||
<div class="mx-auto px-4 sm:px-6 md:px-8">
|
||||
@yield('header')
|
||||
</div>
|
||||
|
||||
<div class="mx-auto px-4 sm:px-6 md:px-8">
|
||||
<div class="pt-4 py-6">
|
||||
@includeWhen(session()->has('success'), 'portal.ninja2020.components.general.messages.success')
|
||||
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@include('portal.ninja2020.components.general.vendor_footer')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
@ -0,0 +1,65 @@
|
||||
<div class="md:hidden">
|
||||
<div @click="sidebarOpen = false" class="fixed inset-0 z-30 bg-gray-600 opacity-0 pointer-events-none transition-opacity ease-linear duration-300" :class="{'opacity-75 pointer-events-auto': sidebarOpen, 'opacity-0 pointer-events-none': !sidebarOpen}"></div>
|
||||
<div class="fixed inset-y-0 left-0 flex flex-col z-40 max-w-xs w-full pt-5 pb-4 bg-white transform ease-in-out duration-300 -translate-x-full" :class="{'translate-x-0': sidebarOpen, '-translate-x-full': !sidebarOpen}">
|
||||
<div class="absolute top-0 right-0 -mr-14 p-1">
|
||||
<button x-show="sidebarOpen" @click="sidebarOpen = false" class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600">
|
||||
<svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex items-center px-4">
|
||||
<img class="h-8 w-auto" src="{!! auth()->guard('vendor')->user()->company->present()->logo($settings) !!}" alt="{{ auth()->guard('vendor')->user()->company->present()->name() }} logo" />
|
||||
</div>
|
||||
<div class="mt-5 flex-1 h-0 overflow-y-auto">
|
||||
<nav class="flex-1 pb-4 pt-0 bg-white">
|
||||
@foreach($sidebar as $row)
|
||||
<a class="group flex items-center p-4 text-sm leading-5 font-medium hover:font-semibold focus:outline-none focus:font-semibold transition ease-in-out duration-150 {{ isActive($row['url'], true) ? 'bg-primary text-white' : 'text-gray-900' }}" href="{{ route($row['url']) }}">
|
||||
@if(isActive($row['url'], true))
|
||||
<img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}"
|
||||
class="w-5 h-5 fill-current mr-3" alt=""/>
|
||||
@else
|
||||
<img src="{{ asset('images/svg/dark/' . $row['icon'] . '.svg') }}"
|
||||
class="w-5 h-5 fill-current mr-3" alt=""/>
|
||||
@endif
|
||||
|
||||
<span>{{ $row['title'] }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
@if(!auth()->guard('vendor')->user()->user->account->isPaid())
|
||||
<div class="flex-shrink-0 flex bg-white p-4 justify-center">
|
||||
<div class="flex items-center">
|
||||
<a target="_blank" href="https://www.facebook.com/invoiceninja/">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/invoiceninja">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://github.com/invoiceninja/invoiceninja">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||
</svg> </a>
|
||||
<a target="_blank" href="https://www.invoiceninja.com/contact">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA">
|
||||
<svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex-shrink-0 w-14"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,48 @@
|
||||
<footer class="bg-white px-4 py-5 shadow px-4 sm:px-6 md:px-8 flex justify-center border-t border-gray-200 justify-between items-center" x-data="{ privacy: false, tos: false }">
|
||||
<section>
|
||||
@if(auth()->guard('vendor')->user() && auth()->guard('vendor')->user()->user->account->isPaid())
|
||||
<span class="text-xs md:text-sm text-gray-700">{{ ctrans('texts.footer_label', ['company' => auth()->guard('vendor')->user()->vendor->company->present()->name(), 'year' => date('Y')]) }}</span>
|
||||
@else
|
||||
<span href="https://invoiceninja.com" target="_blank" class="text-xs md:text-sm text-gray-700">
|
||||
{{ ctrans('texts.copyright') }} © {{ date('Y') }}
|
||||
<a class="text-primary hover:underline" href="https://invoiceninja.com" target="_blank">Invoice Ninja</a>.
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<div class="flex items-center">
|
||||
@if(strlen($settings->client_portal_privacy_policy) > 1)
|
||||
<a x-on:click="privacy = true; tos = false" href="#" class="hover:underline text-sm primary-color flex items-center mr-2">{{ __('texts.privacy_policy')}}</a>
|
||||
@endif
|
||||
|
||||
@if(strlen($settings->client_portal_privacy_policy) > 1 && strlen($settings->client_portal_terms) > 1)
|
||||
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus">
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg> Long dash between items. -->
|
||||
@endif
|
||||
|
||||
@if(strlen($settings->client_portal_terms) > 1)
|
||||
<a x-on:click="privacy = false; tos = true" href="#" class="hover:underline text-sm primary-color flex items-center mr-2">{{ __('texts.terms')}}</a>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if(auth()->guard('vendor')->user()->user && !auth()->guard('vendor')->user()->user->account->isPaid())
|
||||
<a href="https://invoiceninja.com" target="_blank">
|
||||
<img class="h-8" src="{{ asset('images/invoiceninja-black-logo-2.png') }}" alt="Invoice Ninja Logo">
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if(strlen($settings->client_portal_privacy_policy) > 1)
|
||||
@component('portal.ninja2020.components.general.pop-up', ['title' => __('texts.privacy_policy') ,'show_property' => 'privacy'])
|
||||
{!! $settings->client_portal_privacy_policy !!}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@if(strlen($settings->client_portal_terms) > 1)
|
||||
@component('portal.ninja2020.components.general.pop-up', ['title' => __('texts.terms') ,'show_property' => 'tos'])
|
||||
{!! $settings->client_portal_terms !!}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
<div class="bg-gray-200 hidden"></div>
|
||||
</footer>
|
@ -0,0 +1,121 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span class="hidden mr-2 text-sm md:block">{{ ctrans('texts.per_page') }}</span>
|
||||
<select wire:model="per_page" class="py-1 text-sm form-select">
|
||||
<option>5</option>
|
||||
<option selected>10</option>
|
||||
<option>15</option>
|
||||
<option>20</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="sent" type="checkbox" class="cursor-pointer form-checkbox" id="paid-checkbox">
|
||||
<label for="paid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_sent') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="accepted" type="checkbox" class="cursor-pointer form-checkbox" id="unpaid-checkbox">
|
||||
<label for="unpaid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.accepted') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="inline-block min-w-full overflow-hidden align-middle rounded">
|
||||
<table class="min-w-full mt-4 border border-gray-200 rounded shadow invoices-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<label>
|
||||
<input type="checkbox" class="form-check form-check-parent">
|
||||
</label>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.purchase_order_number_short') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||
{{ ctrans('texts.purchase_order_date') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('balance')" class="cursor-pointer">
|
||||
{{ ctrans('texts.balance') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('due_date')" class="cursor-pointer">
|
||||
{{ ctrans('texts.due_date') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('status_id')" class="cursor-pointer">
|
||||
{{ ctrans('texts.status') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-white-3 border-b border-gray-200 bg-primary"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($purchase_orders as $purchase_order)
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-nowrap">
|
||||
<label>
|
||||
<input type="checkbox" class="form-check form-check-child" data-value="{{ $purchase_order->hashed_id }}">
|
||||
</label>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{{ $purchase_order->number }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{{ $purchase_order->translateDate($purchase_order->date, $purchase_order->company->date_format(), $purchase_order->company->locale()) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{{ App\Utils\Number::formatMoney($purchase_order->amount, $purchase_order->company) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{{ App\Utils\Number::formatMoney($purchase_order->balance, $purchase_order->company) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{{ $purchase_order->translateDate($purchase_order->due_date, $purchase_order->company->date_format(), $purchase_order->company->locale()) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap">
|
||||
{!! App\Models\PurchaseOrder::badgeForStatus($purchase_order->status_id) !!}
|
||||
</td>
|
||||
<td class="flex items-center justify-end px-6 py-4 text-sm font-medium leading-5 whitespace-nowrap">
|
||||
<a href="{{ route('vendor.purchase_order.show', $purchase_order->hashed_id) }}" class="button-link text-primary">
|
||||
{{ ctrans('texts.view') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap" colspan="100%">
|
||||
{{ ctrans('texts.no_results') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center mt-6 mb-6 md:justify-between">
|
||||
@if($purchase_orders && $purchase_orders->total() > 0)
|
||||
<span class="hidden text-sm text-gray-700 md:block mr-2">
|
||||
{{ ctrans('texts.showing_x_of', ['first' => $purchase_orders->firstItem(), 'last' => $purchase_orders->lastItem(), 'total' => $purchase_orders->total()]) }}
|
||||
</span>
|
||||
@endif
|
||||
{{ $purchase_orders->links('portal/ninja2020/vendor/pagination') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('footer')
|
||||
<script src="{{ asset('js/clients/purchase_orders/action-selectors.js') }}"></script>
|
||||
@endpush
|
168
resources/views/portal/ninja2020/layout/vendor_app.blade.php
Normal file
168
resources/views/portal/ninja2020/layout/vendor_app.blade.php
Normal file
@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<!-- Error: {{ session('error') }} -->
|
||||
|
||||
@if (config('services.analytics.tracking_id'))
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-122229484-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{ config('services.analytics.tracking_id') }}', {'anonymize_ip': true});
|
||||
|
||||
function trackEvent(category, action) {
|
||||
ga('send', 'event', category, action, this.src);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
Vue.config.devtools = true;
|
||||
</script>
|
||||
@else
|
||||
<script>
|
||||
function gtag() {
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
|
||||
<!-- Title -->
|
||||
@if(isset($company->account) && !$company->account->isPaid())
|
||||
<title>@yield('meta_title', '') — Invoice Ninja</title>
|
||||
@elseif(isset($company) && !is_null($company))
|
||||
<title>@yield('meta_title', '') — {{ $company->present()->name() }}</title>
|
||||
@else
|
||||
<title>@yield('meta_title', '')</title>
|
||||
@endif
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="@yield('meta_description')"/>
|
||||
|
||||
<!-- CSRF Token -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ mix('js/app.js') }}" defer></script>
|
||||
<script src="{{ asset('vendor/alpinejs@2.8.2/alpine.js') }}" defer></script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
|
||||
|
||||
@if(auth()->guard('vendor')->user() && !auth()->guard('vendor')->user()->user->account->isPaid())
|
||||
<link href="{{ asset('favicon.png') }}" rel="shortcut icon" type="image/png">
|
||||
@endif
|
||||
|
||||
<link rel="canonical" href="{{ config('ninja.site_url') }}/{{ request()->path() }}"/>
|
||||
|
||||
@if((bool) \App\Utils\Ninja::isSelfHost())
|
||||
<style>
|
||||
{!! $settings->portal_custom_css !!}
|
||||
</style>
|
||||
@endif
|
||||
|
||||
@livewireStyles
|
||||
|
||||
{{-- Feel free to push anything to header using @push('header') --}}
|
||||
@stack('head')
|
||||
|
||||
@if((isset($company) && $company->account->isPaid() && !empty($settings->portal_custom_head)) || ((bool) \App\Utils\Ninja::isSelfHost() && !empty($settings->portal_custom_head)))
|
||||
<div class="py-1 text-sm text-center text-white bg-primary">
|
||||
{!! $settings->portal_custom_head !!}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('vendor/cookieconsent@3/cookieconsent.min.css') }}" />
|
||||
</head>
|
||||
|
||||
@include('portal.ninja2020.components.primary-color')
|
||||
|
||||
<body class="antialiased">
|
||||
@if(session()->has('message'))
|
||||
<div class="py-1 text-sm text-center text-white bg-primary disposable-alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@component('portal.ninja2020.components.general.sidebar.vendor_main', ['settings' => $settings, 'sidebar' => $sidebar])
|
||||
@yield('body')
|
||||
@endcomponent
|
||||
|
||||
@livewireScripts
|
||||
|
||||
<script src="{{ asset('vendor/cookieconsent@3/cookieconsent.min.js') }}" data-cfasync="false"></script>
|
||||
<script>
|
||||
window.addEventListener("load", function(){
|
||||
if (! window.cookieconsent) {
|
||||
return;
|
||||
}
|
||||
window.cookieconsent.initialise({
|
||||
"palette": {
|
||||
"popup": {
|
||||
"background": "#000"
|
||||
},
|
||||
"button": {
|
||||
"background": "#f1d600"
|
||||
},
|
||||
},
|
||||
"content": {
|
||||
"href": "{{ config('ninja.privacy_policy_url.hosted') }}",
|
||||
"message": "This website uses cookies to ensure you get the best experience on our website.",
|
||||
"dismiss": "Got it!",
|
||||
"link": "Learn more",
|
||||
}
|
||||
})}
|
||||
);
|
||||
</script>
|
||||
|
||||
@if($company && $company->google_analytics_key)
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '{{ $company->google_analytics_key }}', 'auto');
|
||||
ga('set', 'anonymizeIp', true);
|
||||
ga('send', 'pageview');
|
||||
|
||||
function trackEvent(category, action) {
|
||||
ga('send', 'event', category, action, this.src);
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
|
||||
</body>
|
||||
|
||||
<footer>
|
||||
@yield('footer')
|
||||
@stack('footer')
|
||||
|
||||
@if((bool) \App\Utils\Ninja::isSelfHost() && !empty($settings->portal_custom_footer))
|
||||
<div class="py-1 text-sm text-center text-white bg-primary">
|
||||
{!! $settings->portal_custom_footer !!}
|
||||
</div>
|
||||
@endif
|
||||
</footer>
|
||||
|
||||
@if((bool) \App\Utils\Ninja::isSelfHost())
|
||||
<script>
|
||||
{!! $settings->portal_custom_js !!}
|
||||
</script>
|
||||
@endif
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
@extends('portal.ninja2020.layout.clean')
|
||||
@section('meta_title', ctrans('texts.vendor'))
|
||||
|
||||
@component('portal.ninja2020.components.test')
|
||||
@endcomponent
|
||||
|
||||
@section('body')
|
||||
<div class="flex justify-center items-center h-screen">
|
||||
<h1>Vendor Portal</h1>
|
||||
</div>
|
||||
@endsection
|
@ -0,0 +1,40 @@
|
||||
<form action="{{ route('vendor.purchase_orders.bulk') }}" method="post" id="approve-form" />
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="action" value="accept">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="purchase_orders[]" value="{{ $purchase_order->hashed_id }}">
|
||||
<input type="hidden" name="signature">
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.approve') }}
|
||||
</h3>
|
||||
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!">
|
||||
<div class="flex text-sm leading-6 font-medium text-gray-500">
|
||||
<p class="mr-2">{{url("vendor/purchase_order/{$key}")}}</p>
|
||||
<p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
@yield('quote-not-approved-right-side')
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="button"
|
||||
class="button button-primary bg-primary"
|
||||
id="approve-button">{{ ctrans('texts.accept') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
@ -0,0 +1,25 @@
|
||||
@extends('portal.ninja2020.layout.vendor_app')
|
||||
@section('meta_title', ctrans('texts.purchase_orders'))
|
||||
|
||||
@section('header')
|
||||
@if($errors->any())
|
||||
<div class="alert alert-failure mb-4">
|
||||
@foreach($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
<div class="flex items-center">
|
||||
<form action="{{ route('vendor.purchase_orders.bulk') }}" method="post" id="bulkActions">
|
||||
@csrf
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" class="button button-primary bg-primary" name="action" value="download">{{ ctrans('texts.download') }}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex flex-col mt-4">
|
||||
@livewire('purchase-orders-table', ['company' => $company])
|
||||
</div>
|
||||
@endsection
|
@ -0,0 +1,59 @@
|
||||
@extends('portal.ninja2020.layout.vendor_app')
|
||||
@section('meta_title', ctrans('texts.view_purchase_order'))
|
||||
|
||||
@push('head')
|
||||
<meta name="show-purchase_order-terms" content="false">
|
||||
<meta name="require-purchase_order-signature" content="{{ $purchase_order->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_purchase_order_signature }}">
|
||||
@include('portal.ninja2020.components.no-cache')
|
||||
|
||||
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
|
||||
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
|
||||
@if(in_array($purchase_order->status_id, [\App\Models\PurchaseOrder::STATUS_SENT, \App\Models\PurchaseOrder::STATUS_DRAFT]))
|
||||
<div class="mb-4">
|
||||
@include('portal.ninja2020.purchase_orders.includes.actions', ['purchase_order' => $purchase_order])
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.purchase_order_number_placeholder', ['purchase_order' => $purchase_order->number])}}
|
||||
- {{ \App\Models\PurchaseOrder::stringStatus($purchase_order->status_id) }}
|
||||
</h3>
|
||||
|
||||
@if($key)
|
||||
<div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!">
|
||||
<div class="flex text-sm leading-6 font-medium text-gray-500">
|
||||
<p class="pr-10">{{url("vendor/purchase_order/{$key}")}}</p>
|
||||
<p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@include('portal.ninja2020.components.entity-documents', ['entity' => $purchase_order])
|
||||
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $purchase_order])
|
||||
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')])
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
<script src="{{ asset('js/clients/purchase_orders/accept.js') }}"></script>
|
||||
<script src="{{ asset('vendor/clipboard.min.js') }}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var clipboard = new ClipboardJS('.btn');
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
177
resources/views/portal/ninja2020/vendor_profile/edit.blade.php
Normal file
177
resources/views/portal/ninja2020/vendor_profile/edit.blade.php
Normal file
@ -0,0 +1,177 @@
|
||||
@extends('portal.ninja2020.layout.vendor_app')
|
||||
|
||||
@section('meta_title', ctrans('texts.vendor_information'))
|
||||
|
||||
@section('header')
|
||||
<p class="leading-5 text-gray-500">{{ ctrans('texts.update_your_personal_info') }}</p>
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
@if(session()->has('missing_required_fields'))
|
||||
<div class="validation validation-fail">
|
||||
<p class="mb-3 font-semibold">{{ ctrans('texts.before_proceeding_with_payment_warning') }}:</p>
|
||||
|
||||
<ul>
|
||||
@foreach(session()->get('missing_required_fields') as $field)
|
||||
<li class="block">— {{ ctrans("texts.{$field}") }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<button onclick="window.history.back();" class="block mt-3 button button-link pl-0 ml-0 underline">{{ ctrans('texts.after_completing_go_back_to_previous_page') }}</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-2 sm:mt-6">
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.contact_details') }}</h3>
|
||||
</div>
|
||||
</div> <!-- End of left-side -->
|
||||
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<form action="{{ route('vendor.profile.update', ['vendor_contact' => $contact->hashed_id]) }}" method="post" id="saveVendor">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="shadow overflow-hidden rounded">
|
||||
<div class="px-4 py-5 bg-white sm:p-6">
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="first_name" class="input-label">@lang('texts.first_name')</label>
|
||||
<input id="contact_first_name"
|
||||
class="input w-full {{ in_array('contact_first_name', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
|
||||
name="first_name" value="{{ $contact->first_name }}"/>
|
||||
@error('first_name')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="last_name" class="input-label">@lang('texts.last_name')</label>
|
||||
<input id="contact_last_name"
|
||||
class="input w-full {{ in_array('contact_last_name', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
|
||||
name="last_name" value="{{ $contact->last_name}}"/>
|
||||
@error('last_name')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<label for="email_address" class="input-label">@lang('texts.email_address')</label>
|
||||
<input id="contact_email_address"
|
||||
class="input w-full {{ in_array('contact_email', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
|
||||
type="email" name="email" value="{{ $contact->email }}"/>
|
||||
@error('email')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<label for="contact_phone" class="input-label">@lang('texts.phone')</label>
|
||||
<input id="contact_phone" class="input w-full" name="phone"
|
||||
value="{{ $contact->phone}}"/>
|
||||
@error('phone')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div> <!-- End of main form -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 sm:mt-6">
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.billing_address') }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<div class="px-4 py-5 bg-white sm:p-6">
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<label for="address1" class="input-label">{{ ctrans('texts.address1') }}</label>
|
||||
<input id="address1" class="input w-full {{ in_array('billing_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="address1" value="{{ $vendor->address1 }}" />
|
||||
@error('address1')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="address2" class="input-label">{{ ctrans('texts.address2') }}</label>
|
||||
<input id="address2" class="input w-full {{ in_array('billing_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="address2" value="{{ $vendor->address2 }}" />
|
||||
@error('address2')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="city" class="input-label">{{ ctrans('texts.city') }}</label>
|
||||
<input id="city" class="input w-full {{ in_array('billing_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="city" value="{{ $vendor->city }}" />
|
||||
@error('city')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-2">
|
||||
<label for="state" class="input-label">{{ ctrans('texts.state') }}</label>
|
||||
<input id="state" class="input w-full {{ in_array('billing_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="state" value="{{ $vendor->state }}" />
|
||||
@error('state')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-2">
|
||||
<label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label>
|
||||
<input id="postal_code" class="input w-full {{ in_array('billing_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="postal_code" value="{{ $vendor->postal_code }}" />
|
||||
@error('postal_code')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-2">
|
||||
<label for="country" class="input-label">@lang('texts.country')</label>
|
||||
<select id="country" class="input w-full bg-white form-select {{ in_array('billing_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" value="{{ $vendor->country_id }}" name="country_id">
|
||||
<option value="none"></option>
|
||||
@foreach($countries as $country)
|
||||
<option value="{{ $country->id }}" @if($vendor->country_id == $country->id) selected @endif>
|
||||
{{ $country->iso_3166_2 }} ({{ $country->name }})
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('country')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||
<button type="submit" class="button button-primary bg-primary">{{ ctrans('texts.save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@endsection
|
@ -17,4 +17,15 @@
|
||||
|
||||
</body>
|
||||
|
||||
<!--
|
||||
|
||||
If you are reading this, there is a fair change that the react application has not loaded for you. There are a couple of solutions:
|
||||
|
||||
1. Download the release file from https://github.com/invoiceninja/invoiceninja and overwrite your current installation.
|
||||
2. Switch back to the Flutter application by editing the database, you can do this with the following SQL
|
||||
|
||||
UPDATE accounts SET
|
||||
set_react_as_default_ap = 0;
|
||||
|
||||
-->
|
||||
</html>
|
||||
|
@ -24,6 +24,7 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']],
|
||||
});
|
||||
|
||||
Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () {
|
||||
Route::put('accounts/{account}', 'AccountController@update')->name('account.update');
|
||||
Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain');
|
||||
Route::get('ping', 'PingController@index')->name('ping');
|
||||
Route::get('health_check', 'PingController@health')->name('health_check');
|
||||
|
@ -9,5 +9,35 @@
|
||||
| is assigned the "api" middleware group. Enjoy building your API!
|
||||
|
|
||||
*/
|
||||
use App\Http\Controllers\Auth\VendorContactLoginController;
|
||||
use App\Http\Controllers\VendorPortal\InvitationController;
|
||||
use App\Http\Controllers\VendorPortal\PurchaseOrderController;
|
||||
use App\Http\Controllers\VendorPortal\VendorContactController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('vendors', [VendorContactLoginController::class, 'catch'])->name('vendor.catchall')->middleware(['domain_db', 'contact_account','vendor_locale']); //catch all
|
||||
|
||||
Route::group(['middleware' => ['invite_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () {
|
||||
/*Invitation catches*/
|
||||
Route::get('purchase_order/{invitation_key}', [InvitationController::class, 'purchaseOrder']);
|
||||
// Route::get('purchase_order/{invitation_key}/download_pdf', 'PurchaseOrderController@downloadPdf')->name('recurring_invoice.download_invitation_key');
|
||||
// Route::get('purchase_order/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload');
|
||||
|
||||
});
|
||||
|
||||
|
||||
Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () {
|
||||
|
||||
Route::get('dashboard', [PurchaseOrderController::class, 'index'])->name('dashboard');
|
||||
Route::get('purchase_orders', [PurchaseOrderController::class, 'index'])->name('purchase_orders.index');
|
||||
Route::get('purchase_orders/{purchase_order}', [PurchaseOrderController::class, 'show'])->name('purchase_order.show');
|
||||
|
||||
Route::get('profile/{vendor_contact}/edit', [VendorContactController::class, 'edit'])->name('profile.edit');
|
||||
Route::put('profile/{vendor_contact}/edit', [VendorContactController::class, 'update'])->name('profile.update');
|
||||
|
||||
Route::post('purchase_orders/bulk', [PurchaseOrderController::class, 'bulk'])->name('purchase_orders.bulk');
|
||||
Route::get('logout', [VendorContactLoginController::class, 'logout'])->name('logout');
|
||||
|
||||
});
|
||||
|
||||
Route::fallback('BaseController@notFoundVendor');
|
@ -85,27 +85,27 @@ class InventoryManagementTest extends TestCase
|
||||
$this->assertEquals(90, $product->in_stock_quantity);
|
||||
|
||||
|
||||
$arr = $response->json();
|
||||
$invoice_hashed_id = $arr['data']['id'];
|
||||
// $arr = $response->json();
|
||||
// $invoice_hashed_id = $arr['data']['id'];
|
||||
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item->type_id = 1;
|
||||
$invoice_item->product_key = $product->product_key;
|
||||
$invoice_item->notes = $product->notes;
|
||||
$invoice_item->quantity = 5;
|
||||
$invoice_item->cost = 100;
|
||||
// $invoice_item = new InvoiceItem;
|
||||
// $invoice_item->type_id = 1;
|
||||
// $invoice_item->product_key = $product->product_key;
|
||||
// $invoice_item->notes = $product->notes;
|
||||
// $invoice_item->quantity = 5;
|
||||
// $invoice_item->cost = 100;
|
||||
|
||||
$line_items2[] = $invoice_item;
|
||||
$invoice->line_items = $line_items2;
|
||||
// $line_items2[] = $invoice_item;
|
||||
// $invoice->line_items = $line_items2;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/invoices/'.$invoice_hashed_id, $invoice->toArray())
|
||||
->assertStatus(200);
|
||||
// $response = $this->withHeaders([
|
||||
// 'X-API-SECRET' => config('ninja.api_secret'),
|
||||
// 'X-API-TOKEN' => $this->token,
|
||||
// ])->put('/api/v1/invoices/'.$invoice_hashed_id, $invoice->toArray())
|
||||
// ->assertStatus(200);
|
||||
|
||||
$product = $product->refresh();
|
||||
// $product = $product->refresh();
|
||||
|
||||
$this->assertEquals(95, $product->in_stock_quantity);
|
||||
// $this->assertEquals(95, $product->in_stock_quantity);
|
||||
}
|
||||
}
|
||||
|
8
webpack.mix.js
vendored
8
webpack.mix.js
vendored
@ -18,6 +18,14 @@ mix.js("resources/js/app.js", "public/js")
|
||||
"resources/js/clients/invoices/action-selectors.js",
|
||||
"public/js/clients/invoices/action-selectors.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/purchase_orders/action-selectors.js",
|
||||
"public/js/clients/purchase_orders/action-selectors.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/purchase_orders/accept.js",
|
||||
"public/js/clients/purchase_orders/accept.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/invoices/payment.js",
|
||||
"public/js/clients/invoices/payment.js"
|
||||
|
Loading…
x
Reference in New Issue
Block a user