mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Post Merge v5-develop
This commit is contained in:
commit
071f2ee102
@ -1 +1 @@
|
||||
5.4.4
|
||||
5.4.10
|
@ -25,6 +25,9 @@ class CompanySettings extends BaseSettings
|
||||
/*Invoice*/
|
||||
public $auto_archive_invoice = false; // @implemented
|
||||
|
||||
public $qr_iban = ''; //@implemented
|
||||
public $besr_id = ''; //@implemented
|
||||
|
||||
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
|
||||
|
||||
public $enable_client_portal_tasks = false; //@ben to implement
|
||||
@ -433,7 +436,12 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $auto_archive_invoice_cancelled = false;
|
||||
|
||||
public $vendor_portal_enable_uploads=false;
|
||||
|
||||
public static $casts = [
|
||||
'vendor_portal_enable_uploads' => 'bool',
|
||||
'besr_id' => 'string',
|
||||
'qr_iban' => 'string',
|
||||
'email_subject_purchase_order' => 'string',
|
||||
'email_template_purchase_order' => 'string',
|
||||
'require_purchase_order_signature' => 'bool',
|
||||
|
@ -13,7 +13,8 @@ class PaymentFailed extends Exception
|
||||
|
||||
public function render($request)
|
||||
{
|
||||
if (auth()->user() || ($request->has('cko-session-id') && $request->query('cko-session-id'))) {
|
||||
if (auth()->guard('contact')->user() || ($request->has('cko-session-id') && $request->query('cko-session-id') )) {
|
||||
|
||||
return render('gateways.unsuccessful', [
|
||||
'message' => $this->getMessage(),
|
||||
'code' => $this->getCode(),
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
|
||||
class UserFactory
|
||||
|
@ -68,7 +68,7 @@ class InvoiceFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function number(string $number) :Builder
|
||||
public function number(string $number = '') :Builder
|
||||
{
|
||||
return $this->builder->where('number', $number);
|
||||
}
|
||||
@ -138,6 +138,14 @@ class InvoiceFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
public function without_deleted_clients()
|
||||
{
|
||||
|
||||
return $this->builder->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted',0);
|
||||
});
|
||||
}
|
||||
|
||||
public function upcoming()
|
||||
{
|
||||
return $this->builder
|
||||
@ -213,7 +221,7 @@ class InvoiceFilters extends QueryFilters
|
||||
{
|
||||
if (auth()->guard('contact')->user()) {
|
||||
return $this->contactViewFilter();
|
||||
} else {
|
||||
} else {
|
||||
return $this->builder->company()->with(['invitations.company'], ['documents.company']);
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,9 @@ class QuoteFilters extends QueryFilters
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
if($sort_col[0] == 'valid_until')
|
||||
$sort_col[0] = 'due_date';
|
||||
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
|
@ -102,16 +102,12 @@ class InvoiceSum
|
||||
private function calculateCustomValues()
|
||||
{
|
||||
|
||||
// $this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
|
||||
|
||||
// $this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
|
||||
|
||||
// $this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
|
||||
|
||||
// $this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
|
||||
|
||||
$this->total += $this->total_custom_values;
|
||||
@ -155,7 +151,7 @@ class InvoiceSum
|
||||
*/
|
||||
private function calculateBalance()
|
||||
{
|
||||
//$this->invoice->balance = $this->balance($this->getTotal(), $this->invoice);
|
||||
|
||||
$this->setCalculatedAttributes();
|
||||
|
||||
return $this;
|
||||
@ -174,22 +170,6 @@ class InvoiceSum
|
||||
{
|
||||
$this->total += $this->total_taxes;
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value1)) {
|
||||
// $this->total += $this->invoice->custom_value1;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value2)) {
|
||||
// $this->total += $this->invoice->custom_value2;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value3)) {
|
||||
// $this->total += $this->invoice->custom_value3;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value4)) {
|
||||
// $this->total += $this->invoice->custom_value4;
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
151
app/Helpers/SwissQr/SwissQrGenerator.php
Normal file
151
app/Helpers/SwissQr/SwissQrGenerator.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?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\Helpers\SwissQr;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Sprain\SwissQrBill as QrBill;
|
||||
|
||||
/**
|
||||
* SwissQrGenerator.
|
||||
*/
|
||||
class SwissQrGenerator
|
||||
{
|
||||
|
||||
protected Company $company;
|
||||
|
||||
protected $invoice;
|
||||
|
||||
protected Client $client;
|
||||
|
||||
public function __construct($invoice, Company $company)
|
||||
{
|
||||
$this->company = $company;
|
||||
|
||||
$this->invoice = $invoice;
|
||||
|
||||
$this->client = $invoice->client;
|
||||
}
|
||||
|
||||
private function calcDueAmount()
|
||||
{
|
||||
if($this->invoice->partial > 0)
|
||||
return $this->invoice->partial;
|
||||
|
||||
if($this->invoice->status_id == Invoice::STATUS_DRAFT)
|
||||
return $this->invoice->amount;
|
||||
|
||||
return $this->invoice->balance;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
// This is an example how to create a typical qr bill:
|
||||
// - with reference number
|
||||
// - with known debtor
|
||||
// - with specified amount
|
||||
// - with human-readable additional information
|
||||
// - using your QR-IBAN
|
||||
//
|
||||
// Likely the most common use-case in the business world.
|
||||
|
||||
// Create a new instance of QrBill, containing default headers with fixed values
|
||||
$qrBill = QrBill\QrBill::create();
|
||||
|
||||
|
||||
// Add creditor information
|
||||
// Who will receive the payment and to which bank account?
|
||||
$qrBill->setCreditor(
|
||||
QrBill\DataGroup\Element\CombinedAddress::create(
|
||||
$this->company->present()->name(),
|
||||
$this->company->present()->address1(),
|
||||
$this->company->present()->getCompanyCityState(),
|
||||
'CH'
|
||||
));
|
||||
|
||||
$qrBill->setCreditorInformation(
|
||||
QrBill\DataGroup\Element\CreditorInformation::create(
|
||||
$this->company->present()->qr_iban() ?: '' // This is a special QR-IBAN. Classic IBANs will not be valid here.
|
||||
));
|
||||
|
||||
// Add debtor information
|
||||
// Who has to pay the invoice? This part is optional.
|
||||
//
|
||||
// Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress
|
||||
// They are interchangeable for creditor as well as debtor.
|
||||
$qrBill->setUltimateDebtor(
|
||||
QrBill\DataGroup\Element\StructuredAddress::createWithStreet(
|
||||
substr($this->client->present()->name(), 0 , 70),
|
||||
$this->client->address1 ? substr($this->client->address1, 0 , 70) : '',
|
||||
$this->client->address2 ? substr($this->client->address2, 0 , 16) : '',
|
||||
$this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '',
|
||||
$this->client->city ? substr($this->client->postal_code, 0, 35) : '',
|
||||
'CH'
|
||||
));
|
||||
|
||||
// Add payment amount information
|
||||
// What amount is to be paid?
|
||||
$qrBill->setPaymentAmountInformation(
|
||||
QrBill\DataGroup\Element\PaymentAmountInformation::create(
|
||||
'CHF',
|
||||
$this->calcDueAmount()
|
||||
));
|
||||
|
||||
// Add payment reference
|
||||
// This is what you will need to identify incoming payments.
|
||||
$referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate(
|
||||
$this->company->present()->besr_id() ?: '', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL.
|
||||
$this->invoice->number// A number to match the payment with your internal data, e.g. an invoice number
|
||||
);
|
||||
|
||||
$qrBill->setPaymentReference(
|
||||
QrBill\DataGroup\Element\PaymentReference::create(
|
||||
QrBill\DataGroup\Element\PaymentReference::TYPE_QR,
|
||||
$referenceNumber
|
||||
));
|
||||
|
||||
// Optionally, add some human-readable information about what the bill is for.
|
||||
$qrBill->setAdditionalInformation(
|
||||
QrBill\DataGroup\Element\AdditionalInformation::create(
|
||||
$this->invoice->public_notes ?: ''
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// Now get the QR code image and save it as a file.
|
||||
try {
|
||||
|
||||
$output = new QrBill\PaymentPart\Output\HtmlOutput\HtmlOutput($qrBill, 'en');
|
||||
|
||||
$html = $output
|
||||
->setPrintable(false)
|
||||
->getPaymentPart();
|
||||
|
||||
return $html;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
foreach($qrBill->getViolations() as $key => $violation) {
|
||||
nlog("qr");
|
||||
nlog($violation);
|
||||
}
|
||||
|
||||
return '';
|
||||
// return $e->getMessage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -46,6 +46,7 @@ use Laravel\Socialite\Facades\Socialite;
|
||||
use Microsoft\Graph\Model;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
@ -326,18 +327,14 @@ class LoginController extends BaseController
|
||||
if (request()->input('provider') == 'google') {
|
||||
return $this->handleGoogleOauth();
|
||||
} elseif (request()->input('provider') == 'microsoft') {
|
||||
// if (request()->has('token')) {
|
||||
// return $this->handleSocialiteLogin('microsoft', request()->get('token'));
|
||||
// } else {
|
||||
// $message = 'Bearer token missing for the microsoft login';
|
||||
// }
|
||||
return $this->handleMicrosoftOauth();
|
||||
} elseif (request()->input('provider') == 'apple') {
|
||||
// if (request()->has('token')) {
|
||||
// return $this->handleSocialiteLogin('apple', request()->get('token'));
|
||||
// } else {
|
||||
// $message = 'Token is missing for the apple login';
|
||||
// }
|
||||
if (request()->has('id_token')) {
|
||||
$token = request()->input('id_token');
|
||||
return $this->handleSocialiteLogin('apple', $token);
|
||||
} else {
|
||||
$message = 'Token is missing for the apple login';
|
||||
}
|
||||
}
|
||||
|
||||
return response()
|
||||
@ -354,6 +351,7 @@ class LoginController extends BaseController
|
||||
private function handleSocialiteLogin($provider, $token)
|
||||
{
|
||||
$user = $this->getSocialiteUser($provider, $token);
|
||||
nlog($user);
|
||||
if ($user) {
|
||||
return $this->loginOrCreateFromSocialite($user, $provider);
|
||||
}
|
||||
@ -490,9 +488,11 @@ class LoginController extends BaseController
|
||||
{
|
||||
if (request()->has('accessToken')) {
|
||||
$accessToken = request()->input('accessToken');
|
||||
} else {
|
||||
return response()->json(['message' => 'Invalid response from oauth server'], 400);
|
||||
}
|
||||
elseif(request()->has('access_token'))
|
||||
$accessToken = request()->input('access_token');
|
||||
else
|
||||
return response()->json(['message' => 'Invalid response from oauth server, no access token in response.'], 400);
|
||||
|
||||
|
||||
$graph = new \Microsoft\Graph\Graph();
|
||||
$graph->setAccessToken($accessToken);
|
||||
@ -503,6 +503,7 @@ class LoginController extends BaseController
|
||||
|
||||
if ($user) {
|
||||
$account = request()->input('account');
|
||||
|
||||
$email = $user->getMail() ?: $user->getUserPrincipalName();
|
||||
|
||||
$query = [
|
||||
@ -541,6 +542,10 @@ class LoginController extends BaseController
|
||||
|
||||
return $this->createNewAccount($new_account);
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Unable to authenticate this user'], 400);
|
||||
|
||||
}
|
||||
|
||||
private function existingOauthUser($existing_user)
|
||||
@ -685,9 +690,9 @@ class LoginController extends BaseController
|
||||
$parameters = ['access_type' => 'offline', 'prompt' => 'consent select_account', 'redirect_uri' => config('ninja.app_url') . '/auth/google'];
|
||||
}
|
||||
|
||||
if ($provider == 'microsoft') {
|
||||
$scopes = ['email', 'Mail.ReadWrite', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid'];
|
||||
$parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url') . '/auth/microsoft'];
|
||||
if($provider == 'microsoft'){
|
||||
$scopes = ['email', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid'];
|
||||
$parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url')."/auth/microsoft"];
|
||||
}
|
||||
|
||||
if (request()->has('code')) {
|
||||
@ -751,7 +756,10 @@ class LoginController extends BaseController
|
||||
|
||||
$oauth_user_token = $socialite_user->accessTokenResponseBody['access_token'];
|
||||
|
||||
if ($user = OAuth::handleAuth($socialite_user, $provider)) {
|
||||
$oauth_expiry = now()->addSeconds($socialite_user->accessTokenResponseBody['expires_in']) ?: now()->addSeconds(300);
|
||||
|
||||
if($user = OAuth::handleAuth($socialite_user, $provider))
|
||||
{
|
||||
nlog('found user and updating their user record');
|
||||
$name = OAuth::splitName($socialite_user->getName());
|
||||
|
||||
@ -763,6 +771,7 @@ class LoginController extends BaseController
|
||||
'oauth_provider_id' => $provider,
|
||||
'oauth_user_token' => $oauth_user_token,
|
||||
'oauth_user_refresh_token' => $socialite_user->accessTokenResponseBody['refresh_token'],
|
||||
'oauth_user_token_expiry' => $oauth_expiry,
|
||||
];
|
||||
|
||||
$user->update($update_user);
|
||||
|
@ -60,51 +60,52 @@ class BaseController extends Controller
|
||||
protected $manager;
|
||||
|
||||
private $first_load = [
|
||||
'account',
|
||||
'user.company_user',
|
||||
'token.company_user',
|
||||
'company.activities',
|
||||
'company.designs.company',
|
||||
'company.task_statuses',
|
||||
'company.expense_categories',
|
||||
'company.documents',
|
||||
'company.users.company_user',
|
||||
'company.clients.contacts.company',
|
||||
'company.clients.gateway_tokens',
|
||||
'company.clients.documents',
|
||||
'company.company_gateways.gateway',
|
||||
'company.credits.invitations.contact',
|
||||
'company.credits.invitations.company',
|
||||
'company.credits.documents',
|
||||
'company.expenses.documents',
|
||||
'company.groups.documents',
|
||||
'company.invoices.invitations.contact',
|
||||
'company.invoices.invitations.company',
|
||||
'company.invoices.documents',
|
||||
'company.products',
|
||||
'company.products.documents',
|
||||
'company.payments.paymentables',
|
||||
'company.payments.documents',
|
||||
'company.purchase_orders.documents',
|
||||
'company.payment_terms.company',
|
||||
'company.projects.documents',
|
||||
'company.recurring_expenses',
|
||||
'company.recurring_invoices',
|
||||
'company.recurring_invoices.invitations.contact',
|
||||
'company.recurring_invoices.invitations.company',
|
||||
'company.recurring_invoices.documents',
|
||||
'company.quotes.invitations.contact',
|
||||
'company.quotes.invitations.company',
|
||||
'company.quotes.documents',
|
||||
'company.tasks.documents',
|
||||
'company.subscriptions',
|
||||
'company.tax_rates',
|
||||
'company.tokens_hashed',
|
||||
'company.vendors.contacts.company',
|
||||
'company.vendors.documents',
|
||||
'company.webhooks',
|
||||
'company.system_logs',
|
||||
];
|
||||
'account',
|
||||
'user.company_user',
|
||||
'token.company_user',
|
||||
'company.activities',
|
||||
'company.designs.company',
|
||||
'company.task_statuses',
|
||||
'company.expense_categories',
|
||||
'company.documents',
|
||||
'company.users.company_user',
|
||||
'company.clients.contacts.company',
|
||||
'company.clients.gateway_tokens',
|
||||
'company.clients.documents',
|
||||
'company.company_gateways.gateway',
|
||||
'company.credits.invitations.contact',
|
||||
'company.credits.invitations.company',
|
||||
'company.credits.documents',
|
||||
'company.expenses.documents',
|
||||
'company.groups.documents',
|
||||
'company.invoices.invitations.contact',
|
||||
'company.invoices.invitations.company',
|
||||
'company.purchase_orders.invitations',
|
||||
'company.invoices.documents',
|
||||
'company.products',
|
||||
'company.products.documents',
|
||||
'company.payments.paymentables',
|
||||
'company.payments.documents',
|
||||
'company.purchase_orders.documents',
|
||||
'company.payment_terms.company',
|
||||
'company.projects.documents',
|
||||
'company.recurring_expenses',
|
||||
'company.recurring_invoices',
|
||||
'company.recurring_invoices.invitations.contact',
|
||||
'company.recurring_invoices.invitations.company',
|
||||
'company.recurring_invoices.documents',
|
||||
'company.quotes.invitations.contact',
|
||||
'company.quotes.invitations.company',
|
||||
'company.quotes.documents',
|
||||
'company.tasks.documents',
|
||||
'company.subscriptions',
|
||||
'company.tax_rates',
|
||||
'company.tokens_hashed',
|
||||
'company.vendors.contacts.company',
|
||||
'company.vendors.documents',
|
||||
'company.webhooks',
|
||||
'company.system_logs',
|
||||
];
|
||||
|
||||
private $mini_load = [
|
||||
'account',
|
||||
@ -767,6 +768,10 @@ class BaseController extends Controller
|
||||
return redirect('/')->with(['login' => 'true']);
|
||||
}
|
||||
|
||||
if (request()->has('signup') && request()->input('signup') == 'true') {
|
||||
return redirect('/')->with(['signup' => 'true']);
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
//pass report errors bool to front end
|
||||
@ -776,11 +781,16 @@ class BaseController extends Controller
|
||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
||||
$data['login'] = request()->has('login') ? request()->input('login') : 'false';
|
||||
$data['signup'] = request()->has('signup') ? request()->input('signup') : 'false';
|
||||
|
||||
if (request()->session()->has('login')) {
|
||||
$data['login'] = 'true';
|
||||
}
|
||||
|
||||
if(request()->session()->has('signup')){
|
||||
$data['signup'] = 'true';
|
||||
}
|
||||
|
||||
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||
|
||||
$data['path'] = $this->setBuild();
|
||||
|
@ -650,4 +650,85 @@ class ClientController extends BaseController
|
||||
|
||||
//todo add an event here using the client name as reference for purge event
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param PurgeClientRequest $request
|
||||
* @param Client $client
|
||||
* @param string $mergeable client hashed_id
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/clients/{id}/{mergaeble_client_hashed_id}/merge",
|
||||
* operationId="mergeClient",
|
||||
* tags={"clients"},
|
||||
* summary="Merges two clients",
|
||||
* description="Handles merging 2 clients",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Client Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="mergeable_client_hashedid",
|
||||
* in="path",
|
||||
* description="The Mergeable Client Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the client object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function merge(PurgeClientRequest $request, Client $client, string $mergeable_client)
|
||||
{
|
||||
|
||||
$m_client = Client::withTrashed()
|
||||
->where('id', $this->decodePrimaryKey($mergeable_client))
|
||||
->where('company_id', auth()->user()->company()->id)
|
||||
->first();
|
||||
|
||||
if(!$m_client)
|
||||
return response()->json(['message' => "Client not found"]);
|
||||
|
||||
$merged_client = $client->service()->merge($m_client)->save();
|
||||
|
||||
return $this->itemResponse($merged_client);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -235,8 +235,11 @@ class InvitationController extends Controller
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
if($invitation->contact->trashed())
|
||||
$invitation->contact->restore();
|
||||
|
||||
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
|
||||
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if ($invoice->partial > 0) {
|
||||
|
@ -25,6 +25,7 @@ use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Subscription;
|
||||
use App\Notifications\Ninja\NewAccountNotification;
|
||||
use App\Repositories\SubscriptionRepository;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -156,6 +157,9 @@ class NinjaPlanController extends Controller
|
||||
->increment()
|
||||
->queue();
|
||||
|
||||
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
|
||||
$ninja_company->notification(new NewAccountNotification($account, $client))->ninja();
|
||||
|
||||
return $this->render('plan.trial_confirmed', $data);
|
||||
}
|
||||
|
||||
|
@ -90,13 +90,17 @@ class PaymentController extends Controller
|
||||
|
||||
public function response(PaymentResponseRequest $request)
|
||||
{
|
||||
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
|
||||
$payment_hash = PaymentHash::where('hash', $request->payment_hash)->first();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice ? $invoice->client : auth()->user()->client;
|
||||
|
||||
return $gateway
|
||||
// ->driver(auth()->user()->client)
|
||||
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
|
||||
$payment_hash = PaymentHash::where('hash', $request->payment_hash)->firstOrFail();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice ? $invoice->client : auth()->guard('contact')->user()->client;
|
||||
|
||||
// 09-07-2022 catch duplicate responses for invoices that already paid here.
|
||||
if($invoice && $invoice->status_id == Invoice::STATUS_PAID)
|
||||
abort(400, 'Invoice paid. Duplicate submission');
|
||||
|
||||
return $gateway
|
||||
->driver($client)
|
||||
->setPaymentMethod($request->input('payment_method_id'))
|
||||
->setPaymentHash($payment_hash)
|
||||
|
@ -180,7 +180,7 @@ class QuoteController extends Controller
|
||||
if ($process) {
|
||||
foreach ($quotes as $quote) {
|
||||
$quote->service()->approve(auth()->user())->save();
|
||||
event(new QuoteWasApproved(auth()->guard('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
|
||||
// event(new QuoteWasApproved(auth()->guard('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
|
||||
|
||||
if (request()->has('signature') && ! is_null(request()->signature) && ! empty(request()->signature)) {
|
||||
InjectSignature::dispatch($quote, request()->signature);
|
||||
|
@ -22,6 +22,7 @@ use Google_Client;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Microsoft\Graph\Model;
|
||||
|
||||
class ConnectedAccountController extends BaseController
|
||||
{
|
||||
@ -81,12 +82,61 @@ class ConnectedAccountController extends BaseController
|
||||
return $this->handleGoogleOauth();
|
||||
}
|
||||
|
||||
if ($request->input('provider') == 'microsoft') {
|
||||
return $this->handleMicrosoftOauth($request);
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(['message' => 'Provider not supported'], 400)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
private function handleMicrosoftOauth($request)
|
||||
{
|
||||
nlog($request->all());
|
||||
|
||||
if(!$request->has('access_token'))
|
||||
return response()->json(['message' => 'No access_token parameter found!'], 400);
|
||||
|
||||
$graph = new \Microsoft\Graph\Graph();
|
||||
$graph->setAccessToken($request->input('access_token'));
|
||||
|
||||
$user = $graph->createRequest("GET", "/me")
|
||||
->setReturnType(Model\User::class)
|
||||
->execute();
|
||||
|
||||
if($user){
|
||||
|
||||
$email = $user->getMail() ?: $user->getUserPrincipalName();
|
||||
|
||||
if(auth()->user()->email != $email && MultiDB::checkUserEmailExists($email))
|
||||
return response()->json(['message' => ctrans('texts.email_already_register')], 400);
|
||||
|
||||
$connected_account = [
|
||||
'email' => $email,
|
||||
'oauth_user_id' => $user->getId(),
|
||||
'oauth_provider_id' => 'microsoft',
|
||||
'email_verified_at' =>now()
|
||||
];
|
||||
|
||||
auth()->user()->update($connected_account);
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
$this->setLoginCache(auth()->user());
|
||||
|
||||
return $this->itemResponse(auth()->user());
|
||||
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
|
||||
}
|
||||
|
||||
private function handleGoogleOauth()
|
||||
{
|
||||
$user = false;
|
||||
|
@ -602,7 +602,18 @@ class CreditController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
// EmailCredit::dispatch($credit, $credit->company);
|
||||
|
||||
$credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
|
||||
EmailEntity::dispatch($invitation, $credit->company, 'credit');
|
||||
});
|
||||
|
||||
|
||||
if (! $bulk) {
|
||||
return response()->json(['message'=>'email sent'], 200);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'send_email':
|
||||
|
||||
$credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
|
||||
EmailEntity::dispatch($invitation, $credit->company, 'credit');
|
||||
|
@ -17,12 +17,15 @@ use App\Http\Middleware\UserVerified;
|
||||
use App\Http\Requests\Email\SendEmailRequest;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\Mail\EntitySentMailer;
|
||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Transformers\QuoteTransformer;
|
||||
use App\Transformers\RecurringInvoiceTransformer;
|
||||
use App\Utils\Ninja;
|
||||
@ -125,6 +128,10 @@ class EmailController extends BaseController
|
||||
'body' => $body,
|
||||
];
|
||||
|
||||
if($entity == 'purchaseOrder' || $template == 'purchase_order'){
|
||||
return $this->sendPurchaseOrder($entity_obj, $data);
|
||||
}
|
||||
|
||||
$entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) {
|
||||
if (! $invitation->contact->trashed() && $invitation->contact->email) {
|
||||
$entity_obj->service()->markSent()->save();
|
||||
@ -172,4 +179,17 @@ class EmailController extends BaseController
|
||||
|
||||
return $this->itemResponse($entity_obj->fresh());
|
||||
}
|
||||
|
||||
private function sendPurchaseOrder($entity_obj, $data)
|
||||
{
|
||||
|
||||
$this->entity_type = PurchaseOrder::class;
|
||||
|
||||
$this->entity_transformer = PurchaseOrderTransformer::class;
|
||||
|
||||
PurchaseOrderEmail::dispatch($entity_obj, $entity_obj->company, $data);
|
||||
|
||||
return $this->itemResponse($entity_obj);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -744,13 +744,7 @@ class InvoiceController extends BaseController
|
||||
$this->itemResponse($invoice);
|
||||
}
|
||||
break;
|
||||
// case 'reverse':
|
||||
// $invoice = $invoice->service()->handleReversal()->deletePdf()->save();
|
||||
|
||||
// if (! $bulk) {
|
||||
// $this->itemResponse($invoice);
|
||||
// }
|
||||
// break;
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
@ -767,6 +761,24 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
break;
|
||||
|
||||
case 'send_email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
|
||||
if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) {
|
||||
$this->reminder_template = $invoice->client->getSetting(request()->input('email_type'));
|
||||
} else {
|
||||
$this->reminder_template = $invoice->calculateTemplate('invoice');
|
||||
}
|
||||
|
||||
BulkInvoiceJob::dispatch($invoice, $this->reminder_template);
|
||||
|
||||
if (! $bulk) {
|
||||
return response()->json(['message' => 'email sent'], 200);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
|
||||
break;
|
||||
|
@ -272,19 +272,25 @@ class PreviewController extends BaseController
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
|
470
app/Http/Controllers/PreviewPurchaseOrderController.php
Normal file
470
app/Http/Controllers/PreviewPurchaseOrderController.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?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;
|
||||
|
||||
use App\DataMapper\Analytics\LivePreview;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewPurchaseOrderRequest;
|
||||
use App\Jobs\Util\PreviewPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
class PreviewPurchaseOrderController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
use MakesInvoiceHtml;
|
||||
use PageNumbering;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template filled with entity variables.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/preview/purchase_order",
|
||||
* operationId="getPreviewPurchaseOrder",
|
||||
* tags={"preview"},
|
||||
* summary="Returns a pdf preview for purchase order",
|
||||
* description="Returns a pdf preview for purchase order.",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The pdf response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
if (request()->has('entity') &&
|
||||
request()->has('entity_id') &&
|
||||
! empty(request()->input('entity')) &&
|
||||
! empty(request()->input('entity_id')) &&
|
||||
request()->has('body')) {
|
||||
|
||||
$design_object = json_decode(json_encode(request()->input('design')));
|
||||
|
||||
if (! is_object($design_object)) {
|
||||
return response()->json(['message' => ctrans('texts.invalid_design_object')], 400);
|
||||
}
|
||||
|
||||
$entity_obj = PurchaseOrder::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first();
|
||||
|
||||
if (! $entity_obj) {
|
||||
return $this->blankEntity();
|
||||
}
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($entity_obj->company->locale());
|
||||
$t->replace(Ninja::transformTranslations($entity_obj->company->settings));
|
||||
|
||||
$html = new VendorHtmlEngine($entity_obj->invitations()->first());
|
||||
|
||||
$design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name'];
|
||||
|
||||
$design_class = new $design_namespace();
|
||||
|
||||
$state = [
|
||||
'template' => $design_class->elements([
|
||||
'client' => null,
|
||||
'vendor' => $entity_obj->vendor,
|
||||
'entity' => $entity_obj,
|
||||
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $entity_obj->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$design = new Design(request()->design['name']);
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
return $pdf;
|
||||
|
||||
}
|
||||
|
||||
//else
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
return $this->blankEntity();
|
||||
}
|
||||
|
||||
public function live(PreviewPurchaseOrderRequest $request)
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
MultiDB::setDb($company->db);
|
||||
|
||||
$repo = new PurchaseOrderRepository();
|
||||
$entity_obj = PurchaseOrderFactory::create($company->id, auth()->user()->id);
|
||||
$class = PurchaseOrder::class;
|
||||
|
||||
try {
|
||||
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
if($request->has('entity_id')){
|
||||
|
||||
$entity_obj = $class::on(config('database.default'))
|
||||
->with('vendor.company')
|
||||
->where('id', $this->decodePrimaryKey($request->input('entity_id')))
|
||||
->where('company_id', $company->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
}
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
|
||||
if(!$request->has('entity_id'))
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($entity_obj->company->locale());
|
||||
$t->replace(Ninja::transformTranslations($entity_obj->company->settings));
|
||||
|
||||
$html = new VendorHtmlEngine($entity_obj->invitations()->first());
|
||||
|
||||
$design = \App\Models\Design::find($entity_obj->design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if(!$design)
|
||||
$design = \App\Models\Design::find(2);
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true)
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => null,
|
||||
'vendor' => $entity_obj->vendor,
|
||||
'entity' => $entity_obj,
|
||||
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'$product' => $design->design->product,
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $entity_obj->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), $company);
|
||||
|
||||
|
||||
if(Ninja::isHosted())
|
||||
{
|
||||
LightLogs::create(new LivePreview())
|
||||
->increment()
|
||||
->queue();
|
||||
}
|
||||
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
private function blankEntity()
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('company_id', auth()->user()->company()->id)->orderBy('id', 'desc')->first();
|
||||
|
||||
/* If we don't have a valid invitation in the system - create a mock using transactions */
|
||||
if(!$invitation)
|
||||
return $this->mockEntity();
|
||||
|
||||
$design_object = json_decode(json_encode(request()->input('design')));
|
||||
|
||||
if (! is_object($design_object)) {
|
||||
return response()->json(['message' => 'Invalid custom design object'], 400);
|
||||
}
|
||||
|
||||
$html = new VendorHtmlEngine($invitation);
|
||||
|
||||
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
|
||||
|
||||
$state = [
|
||||
'template' => $design->elements([
|
||||
'client' => null,
|
||||
'vendor' => $invitation->purchase_order->vendor,
|
||||
'entity' => $invitation->purchase_order,
|
||||
'pdf_variables' => (array) $invitation->company->settings->pdf_variables,
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $invitation->company->markdown_enabled,
|
||||
];
|
||||
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
private function mockEntity()
|
||||
{
|
||||
|
||||
DB::connection(auth()->user()->company()->db)->beginTransaction();
|
||||
|
||||
$vendor = Vendor::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
]);
|
||||
|
||||
$contact = VendorContact::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
$purchase_order = PurchaseOrder::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
'terms' => 'Sample Terms',
|
||||
'footer' => 'Sample Footer',
|
||||
'public_notes' => 'Sample Public Notes',
|
||||
]);
|
||||
|
||||
$invitation = PurchaseOrderInvitation::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'purchase_order_id' => $purchase_order->id,
|
||||
'vendor_contact_id' => $contact->id,
|
||||
]);
|
||||
|
||||
$purchase_order->setRelation('invitations', $invitation);
|
||||
$purchase_order->setRelation('vendor', $vendor);
|
||||
$purchase_order->setRelation('company', auth()->user()->company());
|
||||
$purchase_order->load('vendor.company');
|
||||
|
||||
$design_object = json_decode(json_encode(request()->input('design')));
|
||||
|
||||
if (! is_object($design_object)) {
|
||||
return response()->json(['message' => 'Invalid custom design object'], 400);
|
||||
}
|
||||
|
||||
$html = new VendorHtmlEngine($purchase_order->invitations()->first());
|
||||
|
||||
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
|
||||
|
||||
$state = [
|
||||
'template' => $design->elements([
|
||||
'client' => null,
|
||||
'vendor' => $purchase_order->vendor,
|
||||
'entity' => $purchase_order,
|
||||
'pdf_variables' => (array) $purchase_order->company->settings->pdf_variables,
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $purchase_order->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
DB::connection(auth()->user()->company()->db)->rollBack();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
@ -22,26 +23,30 @@ use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\UploadPurchaseOrderRequest;
|
||||
use App\Jobs\Invoice\ZipInvoices;
|
||||
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
|
||||
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Expense;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
use App\Transformers\ExpenseTransformer;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PurchaseOrderController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
use SavesDocuments;
|
||||
|
||||
protected $entity_type = PurchaseOrder::class;
|
||||
|
||||
protected $entity_transformer = PurchaseOrderTransformer::class;
|
||||
|
||||
protected $purchase_order_repository;
|
||||
|
||||
public function __construct(PurchaseOrderRepository $purchase_order_repository)
|
||||
@ -50,7 +55,6 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
$this->purchase_order_repository = $purchase_order_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the list of Purchase Orders.
|
||||
*
|
||||
@ -97,7 +101,6 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
return $this->listResponse($purchase_orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
@ -143,7 +146,6 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
@ -185,6 +187,7 @@ class PurchaseOrderController extends BaseController
|
||||
*/
|
||||
public function store(StorePurchaseOrderRequest $request)
|
||||
{
|
||||
|
||||
$purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$purchase_order = $purchase_order->service()
|
||||
@ -196,7 +199,6 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
@ -252,7 +254,6 @@ class PurchaseOrderController extends BaseController
|
||||
{
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
@ -307,7 +308,6 @@ class PurchaseOrderController extends BaseController
|
||||
{
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
@ -367,11 +367,14 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
$purchase_order = $this->purchase_order_repository->save($request->all(), $purchase_order);
|
||||
|
||||
$purchase_order = $purchase_order->service()
|
||||
->triggeredActions($request)
|
||||
->save();
|
||||
|
||||
event(new PurchaseOrderWasUpdated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
@ -481,6 +484,7 @@ class PurchaseOrderController extends BaseController
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
@ -498,8 +502,7 @@ class PurchaseOrderController extends BaseController
|
||||
if ($action == 'bulk_download' && $purchase_orders->count() >= 1) {
|
||||
$purchase_orders->each(function ($purchase_order) {
|
||||
if (auth()->user()->cannot('view', $purchase_order)) {
|
||||
nlog('access denied');
|
||||
|
||||
nlog("access denied");
|
||||
return response()->json(['message' => ctrans('text.access_denied')]);
|
||||
}
|
||||
});
|
||||
@ -594,7 +597,7 @@ class PurchaseOrderController extends BaseController
|
||||
}
|
||||
|
||||
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
|
||||
{
|
||||
{
|
||||
/*If we are using bulk actions, we don't want to return anything */
|
||||
switch ($action) {
|
||||
case 'mark_sent':
|
||||
@ -608,9 +611,9 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
$file = $purchase_order->service()->getPurchaseOrderPdf();
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
@ -635,7 +638,7 @@ class PurchaseOrderController extends BaseController
|
||||
return $this->listResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
|
||||
@ -643,10 +646,113 @@ class PurchaseOrderController extends BaseController
|
||||
if (! $bulk) {
|
||||
return response()->json(['message' => 'email sent'], 200);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'send_email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
|
||||
|
||||
if (! $bulk) {
|
||||
return response()->json(['message' => 'email sent'], 200);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'add_to_inventory':
|
||||
|
||||
$purchase_order->service()->add_to_inventory();
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
|
||||
case 'expense':
|
||||
|
||||
if($purchase_order->expense()->exists())
|
||||
return response()->json(['message' => ctrans('texts.purchase_order_already_expensed')], 400);
|
||||
|
||||
$expense = $purchase_order->service()->expense();
|
||||
|
||||
return $this->itemResponse($purchase_order);
|
||||
|
||||
case 'cancel':
|
||||
|
||||
if($purchase_order->status_id <= PurchaseOrder::STATUS_SENT)
|
||||
{
|
||||
$purchase_order->status_id = PurchaseOrder::STATUS_CANCELLED;
|
||||
$purchase_order->save();
|
||||
}
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->listResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UploadPurchaseOrderRequest $request
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/purchase_orders/{id}/upload",
|
||||
* operationId="uploadPurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Uploads a document to a purchase_orders",
|
||||
* description="Handles the uploading of a document to a purchase_order",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Purchase Order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the Purchase Order object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Vendor"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function upload(UploadPurchaseOrderRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
|
||||
if(!$this->checkFeature(Account::FEATURE_DOCUMENTS))
|
||||
return $this->featureFailure();
|
||||
|
||||
if ($request->has('documents'))
|
||||
$this->saveDocuments($request->file('documents'), $purchase_order);
|
||||
|
||||
return $this->itemResponse($purchase_order->fresh());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -723,6 +723,13 @@ class QuoteController extends BaseController
|
||||
|
||||
return response()->json(['message'=> ctrans('texts.sent_message')], 200);
|
||||
break;
|
||||
|
||||
case 'send_email':
|
||||
$quote->service()->sendEmail();
|
||||
|
||||
return response()->json(['message'=> ctrans('texts.sent_message')], 200);
|
||||
break;
|
||||
|
||||
case 'mark_sent':
|
||||
$quote->service()->markSent()->save();
|
||||
|
||||
|
@ -204,10 +204,6 @@ class RecurringInvoiceController extends BaseController
|
||||
{
|
||||
$recurring_invoice = $this->recurring_invoice_repo->save($request->all(), RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
// $offset = $recurring_invoice->client->timezone_offset();
|
||||
// $recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
|
||||
// $recurring_invoice->saveQuietly();
|
||||
|
||||
$recurring_invoice->service()
|
||||
->triggeredActions($request)
|
||||
->save();
|
||||
@ -700,6 +696,15 @@ class RecurringInvoiceController extends BaseController
|
||||
$this->itemResponse($recurring_invoice);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'send_now':
|
||||
$recurring_invoice = $recurring_invoice->service()->sendNow();
|
||||
|
||||
if (! $bulk) {
|
||||
$this->itemResponse($recurring_invoice);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
// code...
|
||||
|
@ -21,6 +21,7 @@ use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\PaymentDrivers\Stripe\Connect\Account;
|
||||
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
@ -114,6 +115,8 @@ class StripeConnectController extends BaseController
|
||||
$company_gateway->setConfig($payload);
|
||||
$company_gateway->save();
|
||||
|
||||
StripeWebhook::dispatch($company->company_key, $company_gateway->id);
|
||||
|
||||
//response here
|
||||
return view('auth.connect.completed');
|
||||
}
|
||||
|
@ -64,9 +64,11 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function __construct(UserRepository $user_repo)
|
||||
{
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->user_repo = $user_repo;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +158,7 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function create(CreateUserRequest $request)
|
||||
{
|
||||
$user = UserFactory::create(auth()->user()->account->id);
|
||||
$user = UserFactory::create(auth()->user()->account_id);
|
||||
|
||||
return $this->itemResponse($user);
|
||||
}
|
||||
@ -208,13 +210,13 @@ class UserController extends BaseController
|
||||
|
||||
$user_agent = request()->input('token_name') ?: request()->server('HTTP_USER_AGENT');
|
||||
|
||||
$ct = (new CreateCompanyToken($company, $user, $user_agent))->handle();
|
||||
$ct = CreateCompanyToken::dispatchNow($company, $user, $user_agent);
|
||||
|
||||
event(new UserWasCreated($user, auth()->user(), $company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
$user->setCompany($company);
|
||||
$user->company_id = $company->id;
|
||||
|
||||
|
||||
return $this->itemResponse($user);
|
||||
}
|
||||
|
||||
@ -394,7 +396,7 @@ class UserController extends BaseController
|
||||
UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company());
|
||||
}
|
||||
|
||||
$user->company_users()->update(['permissions_updated_at' => now()]);
|
||||
// $user->company_users()->update(["permissions_updated_at" => now()]);
|
||||
|
||||
event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
@ -463,9 +465,8 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function destroy(DestroyUserRequest $request, User $user)
|
||||
{
|
||||
if ($user->isOwner()) {
|
||||
return response()->json(['message', 'Cannot detach owner.'], 400);
|
||||
}
|
||||
if($user->isOwner())
|
||||
return response()->json(['message', 'Cannot detach owner.'],400);
|
||||
|
||||
/* If the user passes the company user we archive the company user */
|
||||
$user = $this->user_repo->delete($request->all(), $user);
|
||||
@ -605,6 +606,7 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function detach(DetachCompanyUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
if ($request->entityIsDeleted($user)) {
|
||||
return $request->disallowUpdate();
|
||||
}
|
||||
@ -614,9 +616,8 @@ class UserController extends BaseController
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
if ($company_user->is_owner) {
|
||||
if($company_user->is_owner)
|
||||
return response()->json(['message', 'Cannot detach owner.'], 401);
|
||||
}
|
||||
|
||||
$token = $company_user->token->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first();
|
||||
|
||||
@ -680,11 +681,14 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function invite(ReconfirmUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
$user->service()->invite($user->company());
|
||||
|
||||
return response()->json(['message' => ctrans('texts.confirmation_resent')], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invite an existing user to a company.
|
||||
*
|
||||
@ -734,8 +738,10 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function reconfirm(ReconfirmUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
$user->service()->invite($user->company());
|
||||
|
||||
return response()->json(['message' => ctrans('texts.confirmation_resent')], 200);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,13 @@ use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Events\Quote\QuoteWasViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Services\ClientPortal\InstantPayment;
|
||||
@ -43,37 +45,35 @@ class InvitationController extends Controller
|
||||
|
||||
public function purchaseOrder(string $invitation_key)
|
||||
{
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$invitation = PurchaseOrderInvitation::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->whereHas('purchase_order', function ($query) {
|
||||
$query->where('is_deleted', 0);
|
||||
$query->where('is_deleted',0);
|
||||
})
|
||||
->with('contact.vendor')
|
||||
->first();
|
||||
|
||||
if (! $invitation) {
|
||||
return abort(404, 'The resource is no longer available.');
|
||||
}
|
||||
if(!$invitation)
|
||||
return abort(404,'The resource is no longer available.');
|
||||
|
||||
if ($invitation->contact->trashed()) {
|
||||
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(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');
|
||||
nlog("else - default - login contact");
|
||||
request()->session()->invalidate();
|
||||
auth()->guard('vendor')->loginUsingId($vendor_contact->id, true);
|
||||
}
|
||||
@ -81,55 +81,49 @@ class InvitationController extends Controller
|
||||
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 {
|
||||
|
||||
}
|
||||
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)
|
||||
// {
|
||||
public function download(string $invitation_key)
|
||||
{
|
||||
$invitation = PurchaseOrderInvitation::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->with('contact.vendor')
|
||||
->firstOrFail();
|
||||
|
||||
// set_time_limit(45);
|
||||
if(!$invitation)
|
||||
return response()->json(["message" => "no record found"], 400);
|
||||
|
||||
// if(Ninja::isHosted())
|
||||
// return $this->returnRawPdf($entity, $invitation_key);
|
||||
$file_name = $invitation->purchase_order->numberFormatter().'.pdf';
|
||||
|
||||
// return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf');
|
||||
// }
|
||||
// $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
|
||||
|
||||
// private function returnRawPdf(string $entity, string $invitation_key)
|
||||
// {
|
||||
$file = (new CreatePurchaseOrderPdf($invitation))->rawPdf();
|
||||
|
||||
// if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
|
||||
// return response()->json(['message' => 'Invalid resource request']);
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
// $key = $entity.'_id';
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
// $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
}
|
||||
|
||||
// $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);
|
||||
|
||||
// }
|
||||
}
|
||||
|
41
app/Http/Controllers/VendorPortal/UploadController.php
Normal file
41
app/Http/Controllers/VendorPortal/UploadController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?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\Http\Requests\VendorPortal\Uploads\StoreUploadRequest;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class UploadController extends Controller
|
||||
{
|
||||
use SavesDocuments;
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Main logic behind uploading the files.
|
||||
*
|
||||
* @param StoreUploadRequest $request
|
||||
* @return Response|ResponseFactory
|
||||
*/
|
||||
public function upload(StoreUploadRequest $request, PurchaseOrder $purchase_order)
|
||||
{
|
||||
|
||||
$this->saveDocuments($request->getFile(), $purchase_order, true);
|
||||
|
||||
return response([], 200);
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ use App\Http\Middleware\UrlSetDb;
|
||||
use App\Http\Middleware\UserVerified;
|
||||
use App\Http\Middleware\VendorLocale;
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
use App\Http\Middleware\VerifyHash;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Illuminate\Auth\Middleware\Authorize;
|
||||
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
|
||||
@ -161,6 +162,7 @@ class Kernel extends HttpKernel
|
||||
'locale' => Locale::class,
|
||||
'vendor_locale' => VendorLocale::class,
|
||||
'contact_register' => ContactRegister::class,
|
||||
'verify_hash' => VerifyHash::class,
|
||||
'shop_token_auth' => ShopTokenAuth::class,
|
||||
'phantom_secret' => PhantomSecret::class,
|
||||
'contact_key_login' => ContactKeyLogin::class,
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
@ -28,6 +27,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class BillingPortalPurchase extends Component
|
||||
@ -46,6 +46,7 @@ class BillingPortalPurchase extends Component
|
||||
*/
|
||||
public $heading_text;
|
||||
|
||||
|
||||
/**
|
||||
* E-mail address model for user input.
|
||||
*
|
||||
@ -86,14 +87,14 @@ class BillingPortalPurchase extends Component
|
||||
/**
|
||||
* Id for CompanyGateway record.
|
||||
*
|
||||
* @var string|int
|
||||
* @var string|integer
|
||||
*/
|
||||
public $company_gateway_id;
|
||||
|
||||
/**
|
||||
* Id for GatewayType.
|
||||
*
|
||||
* @var string|int
|
||||
* @var string|integer
|
||||
*/
|
||||
public $payment_method_id;
|
||||
|
||||
@ -143,7 +144,7 @@ class BillingPortalPurchase extends Component
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $quantity = 1;
|
||||
public $quantity;
|
||||
|
||||
/**
|
||||
* First-hit request data (queries, locales...).
|
||||
@ -182,12 +183,15 @@ class BillingPortalPurchase extends Component
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->quantity = 1;
|
||||
|
||||
$this->price = $this->subscription->price;
|
||||
|
||||
if (request()->query('coupon')) {
|
||||
$this->coupon = request()->query('coupon');
|
||||
$this->handleCoupon();
|
||||
} elseif (strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
}
|
||||
}
|
||||
@ -237,7 +241,7 @@ class BillingPortalPurchase extends Component
|
||||
$company = $this->subscription->company;
|
||||
$user = $this->subscription->user;
|
||||
$user->setCompany($company);
|
||||
|
||||
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
$data = [
|
||||
@ -267,7 +271,7 @@ class BillingPortalPurchase extends Component
|
||||
})->first();
|
||||
|
||||
if ($record) {
|
||||
$data['settings']['language_id'] = (string) $record->id;
|
||||
$data['settings']['language_id'] = (string)$record->id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,11 +299,10 @@ class BillingPortalPurchase extends Component
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ((int) $this->price == 0) {
|
||||
if ((int)$this->price == 0)
|
||||
$this->steps['payment_required'] = false;
|
||||
} else {
|
||||
else
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
}
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
|
||||
|
||||
@ -357,7 +360,7 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
$this->invoice = $this->subscription
|
||||
->service()
|
||||
->createInvoice($data)
|
||||
->createInvoice($data, $this->quantity)
|
||||
->service()
|
||||
->markSent()
|
||||
->fillDefaults()
|
||||
@ -393,8 +396,9 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
public function handlePaymentNotRequired()
|
||||
{
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
if ($is_eligible['status_code'] != 200) {
|
||||
$this->steps['not_eligible'] = true;
|
||||
$this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
@ -403,6 +407,7 @@ class BillingPortalPurchase extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
return $this->subscription->service()->handleNoPaymentRequired([
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'quantity' => $this->quantity,
|
||||
@ -430,14 +435,14 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
if ($option == 'increment') {
|
||||
$this->quantity++;
|
||||
|
||||
return $this->price = (int) $this->price + $this->subscription->product->price;
|
||||
$this->price = $this->subscription->promo_price * $this->quantity;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
$this->quantity--;
|
||||
$this->price = (int) $this->price - $this->subscription->product->price;
|
||||
$this->quantity--;
|
||||
$this->price = $this->subscription->promo_price * $this->quantity;
|
||||
|
||||
return 0;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function handleCoupon()
|
||||
@ -458,12 +463,12 @@ class BillingPortalPurchase extends Component
|
||||
->first();
|
||||
|
||||
$mailer = new NinjaMailerObject();
|
||||
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string) route('client.subscription.purchase', $this->subscription->hashed_id).'?coupon='.$this->coupon);
|
||||
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
|
||||
$mailer->company = $this->subscription->company;
|
||||
$mailer->settings = $this->subscription->company->settings;
|
||||
$mailer->to_user = $contact;
|
||||
|
||||
NinjaMailerJob::dispatchSync($mailer);
|
||||
NinjaMailerJob::dispatchNow($mailer);
|
||||
|
||||
$this->steps['passwordless_login_sent'] = true;
|
||||
$this->passwordless_login_btn = false;
|
||||
|
@ -72,6 +72,12 @@ class RequiredClientInfo extends Component
|
||||
'state',
|
||||
'postal_code',
|
||||
'country_id',
|
||||
'shipping_address1',
|
||||
'shipping_address2',
|
||||
'shipping_city',
|
||||
'shipping_state',
|
||||
'shipping_postal_code',
|
||||
'shipping_country_id',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
|
@ -31,6 +31,7 @@ class PasswordProtection
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
$error = [
|
||||
'message' => 'Invalid Password',
|
||||
'errors' => new stdClass,
|
||||
@ -38,61 +39,95 @@ class PasswordProtection
|
||||
|
||||
$timeout = auth()->user()->company()->default_password_timeout;
|
||||
|
||||
if ($timeout == 0) {
|
||||
$timeout = 30 * 60 * 1000 * 1000;
|
||||
} else {
|
||||
$timeout = $timeout / 1000;
|
||||
}
|
||||
if($timeout == 0)
|
||||
$timeout = 30*60*1000*1000;
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
//test if password if base64 encoded
|
||||
$x_api_password = $request->header('X-API-PASSWORD');
|
||||
|
||||
if ($request->header('X-API-PASSWORD-BASE64')) {
|
||||
if($request->header('X-API-PASSWORD-BASE64'))
|
||||
{
|
||||
$x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64'));
|
||||
}
|
||||
|
||||
// If no password supplied - then we just check if their authentication is in cache //
|
||||
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in') && ! $x_api_password) {
|
||||
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in') && !$x_api_password) {
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
return $next($request);
|
||||
} elseif ($request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >= 1) {
|
||||
|
||||
}elseif( $request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >=1){
|
||||
|
||||
//user is attempting to reauth with OAuth - check the token value
|
||||
//todo expand this to include all OAuth providers
|
||||
$user = false;
|
||||
$google = new Google();
|
||||
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
|
||||
if(auth()->user()->oauth_provider_id == 'google')
|
||||
{
|
||||
$user = false;
|
||||
$google = new Google();
|
||||
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
|
||||
|
||||
if (is_array($user)) {
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
];
|
||||
if (is_array($user)) {
|
||||
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google'
|
||||
];
|
||||
|
||||
//If OAuth and user also has a password set - check both
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
|
||||
nlog('existing user with password');
|
||||
//If OAuth and user also has a password set - check both
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
|
||||
|
||||
nlog("existing user with password");
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->company()->oauth_password_required){
|
||||
|
||||
nlog("existing user without password");
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
elseif(auth()->user()->oauth_provider_id == 'microsoft')
|
||||
{
|
||||
try{
|
||||
$payload = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', request()->header('X-API-OAUTH-PASSWORD'))[1]))));
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("could not decode microsoft response");
|
||||
return response()->json(['message' => 'Could not decode the response from Microsoft'], 412);
|
||||
}
|
||||
|
||||
if($payload->preferred_username == auth()->user()->email){
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
return $next($request);
|
||||
} elseif ($existing_user = MultiDB::hasUser($query) && ! auth()->user()->company()->oauth_password_required) {
|
||||
nlog('existing user without password');
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return response()->json($error, 412);
|
||||
} elseif ($x_api_password && Hash::check($x_api_password, auth()->user()->password)) {
|
||||
|
||||
|
||||
}elseif ($x_api_password && Hash::check($x_api_password, auth()->user()->password)) {
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
return $next($request);
|
||||
|
||||
} else {
|
||||
|
||||
return response()->json($error, 412);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
37
app/Http/Middleware/VerifyHash.php
Normal file
37
app/Http/Middleware/VerifyHash.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Ninja;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VerifyHash
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
if($request->has('payment_hash')){
|
||||
|
||||
$ph = PaymentHash::with('fee_invoice')->where('hash', $request->payment_hash)->first();
|
||||
|
||||
if($ph)
|
||||
auth()->guard('contact')->loginUsingId($ph->fee_invoice->invitations->first()->contact->id, true);
|
||||
|
||||
return $next($request);
|
||||
|
||||
}
|
||||
|
||||
abort(404, 'Unable to verify payment hash');
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ class ShowRecurringInvoiceRequest extends Request
|
||||
{
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->guard('contact')->user()->client->id === $this->recurring_invoice->client_id
|
||||
return auth()->guard('contact')->user()->client->id == $this->recurring_invoice->client_id
|
||||
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,14 @@
|
||||
<?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\ClientPortal\Uploads;
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\Email;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SendEmailRequest extends Request
|
||||
{
|
||||
@ -42,7 +43,7 @@ class SendEmailRequest extends Request
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
@ -56,13 +57,11 @@ class SendEmailRequest extends Request
|
||||
unset($input['template']);
|
||||
}
|
||||
|
||||
if (array_key_exists('entity_id', $input)) {
|
||||
if(array_key_exists('entity_id', $input))
|
||||
$input['entity_id'] = $this->decodePrimaryKey($input['entity_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('entity', $input)) {
|
||||
$input['entity'] = "App\Models\\".ucfirst($input['entity']);
|
||||
}
|
||||
|
||||
if(array_key_exists('entity', $input))
|
||||
$input['entity'] = "App\Models\\".ucfirst(Str::camel($input['entity']));
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace App\Http\Requests\Expense;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Expense\UniqueExpenseNumberRule;
|
||||
use App\Models\Expense;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
|
62
app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php
Normal file
62
app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?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\Preview;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PreviewPurchaseOrderRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', PurchaseOrder::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
$rules['number'] = ['nullable'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = 0;
|
||||
$input['balance'] = 0;
|
||||
$input['number'] = ctrans('texts.live_preview') . " #". rand(0,1000);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -58,6 +58,6 @@ class StoreProjectRequest extends Request
|
||||
|
||||
public function getClient($client_id)
|
||||
{
|
||||
return Client::find($client_id);
|
||||
return Client::withTrashed()->find($client_id);
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,17 @@
|
||||
|
||||
namespace App\Http\Requests\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StorePurchaseOrderRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
@ -29,7 +32,6 @@ class StorePurchaseOrderRequest extends Request
|
||||
{
|
||||
return auth()->user()->can('create', PurchaseOrder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@ -42,20 +44,26 @@ class StorePurchaseOrderRequest extends Request
|
||||
$rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (isset($input['line_items']) && is_array($input['line_items']))
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
$input['amount'] = 0;
|
||||
$input['balance'] = 0;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\PurchaseOrder;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
@ -20,6 +21,7 @@ class UpdatePurchaseOrderRequest extends Request
|
||||
{
|
||||
use ChecksEntityStatus;
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
@ -59,6 +61,10 @@ class UpdatePurchaseOrderRequest extends Request
|
||||
|
||||
$input['id'] = $this->purchase_order->id;
|
||||
|
||||
if (isset($input['line_items']) && is_array($input['line_items'])) {
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Quote Ninja (https://paymentninja.com).
|
||||
*
|
||||
* @link https://github.com/paymentninja/paymentninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://paymentninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\PurchaseOrder;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class UploadPurchaseOrderRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->purchase_order);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
$rules = [];
|
||||
|
||||
if($this->input('documents'))
|
||||
$rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Quote Ninja (https://paymentninja.com).
|
||||
* Invoice Ninja (https://paymentninja.com).
|
||||
*
|
||||
* @link https://github.com/paymentninja/paymentninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Quote Ninja LLC (https://paymentninja.com)
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://paymentninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
@ -23,7 +23,7 @@ class SortTaskRequest extends Request
|
||||
public function authorize() : bool
|
||||
{
|
||||
return true;
|
||||
// return auth()->user()->can('edit', $this->task);
|
||||
|
||||
}
|
||||
|
||||
public function rules()
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?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\Uploads;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreUploadRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return (bool) auth()->guard('vendor')->user()->vendor->company->getSetting('vendor_portal_enable_uploads');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'file' => ['file', 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Since saveDocuments() expects an array of uploaded files,
|
||||
* we need to convert it to an array before uploading.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
if (gettype($this->file) !== 'array') {
|
||||
return [$this->file];
|
||||
}
|
||||
|
||||
return $this->file;
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ class ValidCreditsPresentRule implements Rule
|
||||
{
|
||||
//todo need to ensure the clients credits are here not random ones!
|
||||
|
||||
if (request()->input('credits') && is_array(request()->input('credits'))) {
|
||||
if (request()->input('credits') && is_array(request()->input('credits')) && count(request()->input('credits')) > 0) {
|
||||
$credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id')))
|
||||
->count();
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -37,11 +37,11 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/*Multi Mailer implemented*/
|
||||
|
||||
@ -65,8 +65,10 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public function __construct(NinjaMailerObject $nmo, bool $override = false)
|
||||
{
|
||||
|
||||
$this->nmo = $nmo;
|
||||
$this->override = $override;
|
||||
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -78,29 +80,30 @@ class NinjaMailerJob implements ShouldQueue
|
||||
/* Serializing models from other jobs wipes the primary key */
|
||||
$this->company = Company::where('company_key', $this->nmo->company->company_key)->first();
|
||||
|
||||
if ($this->preFlightChecksFail()) {
|
||||
if($this->preFlightChecksFail())
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the email driver */
|
||||
$this->setMailDriver();
|
||||
|
||||
if (strlen($this->nmo->settings->reply_to_email) > 1) {
|
||||
if (property_exists($this->nmo->settings, 'reply_to_name')) {
|
||||
|
||||
if(property_exists($this->nmo->settings, 'reply_to_name'))
|
||||
$reply_to_name = strlen($this->nmo->settings->reply_to_name) > 3 ? $this->nmo->settings->reply_to_name : $this->nmo->settings->reply_to_email;
|
||||
} else {
|
||||
else
|
||||
$reply_to_name = $this->nmo->settings->reply_to_email;
|
||||
}
|
||||
|
||||
$this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name);
|
||||
} else {
|
||||
|
||||
}
|
||||
else {
|
||||
$this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name());
|
||||
}
|
||||
|
||||
//send email
|
||||
try {
|
||||
nlog("trying to send to {$this->nmo->to_user->email} ".now()->toDateTimeString());
|
||||
nlog('Using mailer => '.$this->mailer);
|
||||
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
|
||||
nlog("Using mailer => ". $this->mailer);
|
||||
|
||||
Mail::mailer($this->mailer)
|
||||
->to($this->nmo->to_user->email)
|
||||
@ -111,7 +114,9 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
/* Count the amount of emails sent across all the users accounts */
|
||||
Cache::increment($this->company->account->key);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
nlog("error failed with {$e->getMessage()}");
|
||||
|
||||
$message = $e->getMessage();
|
||||
@ -121,26 +126,25 @@ class NinjaMailerJob implements ShouldQueue
|
||||
* this merges a text string with a json object
|
||||
* need to harvest the ->Message property using the following
|
||||
*/
|
||||
if ($e instanceof ClientException) { //postmark specific failure
|
||||
if($e instanceof ClientException) { //postmark specific failure
|
||||
|
||||
$response = $e->getResponse();
|
||||
$message_body = json_decode($response->getBody()->getContents());
|
||||
|
||||
if ($message_body && property_exists($message_body, 'Message')) {
|
||||
|
||||
if($message_body && property_exists($message_body, 'Message')){
|
||||
$message = $message_body->Message;
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If the is an entity attached to the message send a failure mailer */
|
||||
if ($this->nmo->entity) {
|
||||
if($this->nmo->entity)
|
||||
$this->entityEmailFailed($message);
|
||||
}
|
||||
|
||||
/* Don't send postmark failures to Sentry */
|
||||
if (Ninja::isHosted() && (! $e instanceof ClientException)) {
|
||||
if(Ninja::isHosted() && (!$e instanceof ClientException))
|
||||
app('sentry')->captureException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,13 +161,12 @@ class NinjaMailerJob implements ShouldQueue
|
||||
event(new PaymentWasEmailedAndFailed($this->nmo->entity, $this->nmo->company, $message, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
break;
|
||||
default:
|
||||
// code...
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->nmo->to_user instanceof ClientContact) {
|
||||
if ($this->nmo->to_user instanceof ClientContact)
|
||||
$this->logMailError($message, $this->nmo->to_user->client);
|
||||
}
|
||||
}
|
||||
|
||||
private function setMailDriver()
|
||||
@ -188,6 +191,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function setOfficeMailer()
|
||||
@ -195,45 +199,65 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$sending_user = $this->nmo->settings->gmail_sending_user_id;
|
||||
|
||||
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||
|
||||
/* Always ensure the user is set on the correct account */
|
||||
if($user->account_id != $this->company->account_id){
|
||||
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
|
||||
}
|
||||
|
||||
nlog("Sending via {$user->name()}");
|
||||
|
||||
$token = $this->refreshOfficeToken($user);
|
||||
|
||||
if ($token) {
|
||||
if($token)
|
||||
{
|
||||
$user->oauth_user_token = $token;
|
||||
$user->save();
|
||||
} else {
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
|
||||
}
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from($user->email, $user->name())
|
||||
->withSymfonyMessage(function ($message) use ($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
->withSwiftMessage(function ($message) use($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
});
|
||||
|
||||
sleep(rand(1, 3));
|
||||
sleep(rand(1,3));
|
||||
}
|
||||
|
||||
private function setGmailMailer()
|
||||
{
|
||||
if (LaravelGmail::check()) {
|
||||
if(LaravelGmail::check())
|
||||
LaravelGmail::logout();
|
||||
}
|
||||
|
||||
$sending_user = $this->nmo->settings->gmail_sending_user_id;
|
||||
|
||||
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||
|
||||
nlog("Gmail sending via {$user->name()}");
|
||||
/* Always ensure the user is set on the correct account */
|
||||
if($user->account_id != $this->company->account_id){
|
||||
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
|
||||
}
|
||||
|
||||
nlog("Sending via {$user->name()}");
|
||||
|
||||
$google = (new Google())->init();
|
||||
|
||||
try {
|
||||
try{
|
||||
|
||||
if ($google->getClient()->isAccessTokenExpired()) {
|
||||
$google->refreshToken($user);
|
||||
$user = $user->fresh();
|
||||
@ -241,21 +265,21 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
|
||||
|
||||
sleep(rand(2, 6));
|
||||
} catch (\Exception $e) {
|
||||
sleep(rand(2,6));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user doesn't have a valid token, notify them
|
||||
*/
|
||||
if (! $user->oauth_user_token) {
|
||||
|
||||
if(!$user->oauth_user_token) {
|
||||
$this->company->account->gmailCredentialNotification();
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
@ -267,53 +291,58 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$token = $user->oauth_user_token->access_token;
|
||||
|
||||
if (! $token) {
|
||||
if(!$token) {
|
||||
$this->company->account->gmailCredentialNotification();
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from($user->email, $user->name())
|
||||
->withSymfonyMessage(function ($message) use ($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
->withSwiftMessage(function ($message) use($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function preFlightChecksFail()
|
||||
{
|
||||
|
||||
/* If we are migrating data we don't want to fire any emails */
|
||||
if ($this->nmo->company->is_disabled && ! $this->override) {
|
||||
if($this->company->is_disabled && !$this->override)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */
|
||||
if (Ninja::isHosted() && $this->nmo->to_user && strpos($this->nmo->to_user->email, '@example.com') !== false) {
|
||||
if(Ninja::isHosted() && $this->nmo->to_user && strpos($this->nmo->to_user->email, '@example.com') !== false)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* GMail users are uncapped */
|
||||
if (Ninja::isHosted() && $this->nmo->settings->email_sending_method == 'gmail') {
|
||||
if(Ninja::isHosted() && ($this->nmo->settings->email_sending_method == 'gmail' || $this->nmo->settings->email_sending_method == 'office365'))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
|
||||
if (Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded()) {
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded())
|
||||
return true;
|
||||
|
||||
/* To handle spam users we drop all emails from flagged accounts */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensure the user has a valid email address */
|
||||
if (! str_contains($this->nmo->to_user->email, '@')) {
|
||||
if(!str_contains($this->nmo->to_user->email, "@"))
|
||||
return true;
|
||||
}
|
||||
|
||||
/* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */
|
||||
if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class))
|
||||
return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function logMailError($errors, $recipient_object)
|
||||
{
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$errors,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
@ -333,27 +362,53 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public function failed($exception = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function refreshOfficeToken($user)
|
||||
{
|
||||
$guzzle = new \GuzzleHttp\Client();
|
||||
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||
$expiry = $user->oauth_user_token_expiry ?: now()->subDay();
|
||||
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id'),
|
||||
'client_secret' => config('ninja.o365.client_secret'),
|
||||
'scope' => 'email Mail.ReadWrite Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token,
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
if($expiry->lt(now()))
|
||||
{
|
||||
$guzzle = new \GuzzleHttp\Client();
|
||||
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||
|
||||
if ($token) {
|
||||
return $token->access_token;
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id') ,
|
||||
'client_secret' => config('ninja.o365.client_secret') ,
|
||||
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
|
||||
nlog($token);
|
||||
|
||||
if($token){
|
||||
|
||||
$user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token;
|
||||
$user->oauth_user_token = $token->access_token;
|
||||
$user->oauth_user_token_expiry = now()->addSeconds($token->expires_in);
|
||||
$user->save();
|
||||
|
||||
return $token->access_token;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $user->oauth_user_token;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this the cleanest way to requeue a job?
|
||||
*
|
||||
* $this->delete();
|
||||
*
|
||||
* $job = NinjaMailerJob::dispatch($this->nmo, $this->override)->delay(3600);
|
||||
*/
|
||||
|
||||
}
|
@ -49,7 +49,6 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
private array $request;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -71,19 +70,19 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->request['Tag']);
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($this->request['Tag']);
|
||||
|
||||
$this->invitation = $this->discoverInvitation($this->request['MessageID']);
|
||||
|
||||
if (! $this->invitation) {
|
||||
if(!$this->invitation)
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists('Details', $this->request)) {
|
||||
if(array_key_exists('Details', $this->request))
|
||||
$this->invitation->email_error = $this->request['Details'];
|
||||
}
|
||||
|
||||
switch ($this->request['RecordType']) {
|
||||
|
||||
switch ($this->request['RecordType'])
|
||||
{
|
||||
case 'Delivery':
|
||||
return $this->processDelivery();
|
||||
case 'Bounce':
|
||||
@ -93,32 +92,33 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
case 'Open':
|
||||
return $this->processOpen();
|
||||
default:
|
||||
// code...
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// {
|
||||
// "Metadata": {
|
||||
// {
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// },
|
||||
// "RecordType": "Open",
|
||||
// "FirstOpen": true,
|
||||
// "Client": {
|
||||
// },
|
||||
// "RecordType": "Open",
|
||||
// "FirstOpen": true,
|
||||
// "Client": {
|
||||
// "Name": "Chrome 35.0.1916.153",
|
||||
// "Company": "Google",
|
||||
// "Family": "Chrome"
|
||||
// },
|
||||
// "OS": {
|
||||
// },
|
||||
// "OS": {
|
||||
// "Name": "OS X 10.7 Lion",
|
||||
// "Company": "Apple Computer, Inc.",
|
||||
// "Family": "OS X 10"
|
||||
// },
|
||||
// "Platform": "WebMail",
|
||||
// "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
// "ReadSeconds": 5,
|
||||
// "Geo": {
|
||||
// },
|
||||
// "Platform": "WebMail",
|
||||
// "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
// "ReadSeconds": 5,
|
||||
// "Geo": {
|
||||
// "CountryISOCode": "RS",
|
||||
// "Country": "Serbia",
|
||||
// "RegionISOCode": "VO",
|
||||
@ -127,81 +127,83 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
// "Zip": "21000",
|
||||
// "Coords": "45.2517,19.8369",
|
||||
// "IP": "188.2.95.4"
|
||||
// },
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "MessageStream": "outbound",
|
||||
// "ReceivedAt": "2022-02-06T06:37:48Z",
|
||||
// "Tag": "welcome-email",
|
||||
// "Recipient": "john@example.com"
|
||||
// }
|
||||
// },
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "MessageStream": "outbound",
|
||||
// "ReceivedAt": "2022-02-06T06:37:48Z",
|
||||
// "Tag": "welcome-email",
|
||||
// "Recipient": "john@example.com"
|
||||
// }
|
||||
|
||||
private function processOpen()
|
||||
{
|
||||
|
||||
$this->invitation->opened_date = now();
|
||||
$this->invitation->save();
|
||||
|
||||
SystemLogger::dispatch($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_OPENED,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
SystemLogger::dispatch($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_OPENED,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->invitation->contact->client,
|
||||
$this->invitation->company
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// {
|
||||
// "RecordType": "Delivery",
|
||||
// "ServerID": 23,
|
||||
// "MessageStream": "outbound",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "Recipient": "john@example.com",
|
||||
// "Tag": "welcome-email",
|
||||
// "DeliveredAt": "2021-02-21T16:34:52Z",
|
||||
// "Details": "Test delivery webhook details",
|
||||
// "Metadata": {
|
||||
// {
|
||||
// "RecordType": "Delivery",
|
||||
// "ServerID": 23,
|
||||
// "MessageStream": "outbound",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "Recipient": "john@example.com",
|
||||
// "Tag": "welcome-email",
|
||||
// "DeliveredAt": "2021-02-21T16:34:52Z",
|
||||
// "Details": "Test delivery webhook details",
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
private function processDelivery()
|
||||
{
|
||||
$this->invitation->email_status = 'delivered';
|
||||
$this->invitation->save();
|
||||
|
||||
SystemLogger::dispatch($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_DELIVERY,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
SystemLogger::dispatch($this->request,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_DELIVERY,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->invitation->contact->client,
|
||||
$this->invitation->company
|
||||
);
|
||||
}
|
||||
|
||||
// {
|
||||
// "Metadata": {
|
||||
// {
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// },
|
||||
// "RecordType": "Bounce",
|
||||
// "ID": 42,
|
||||
// "Type": "HardBounce",
|
||||
// "TypeCode": 1,
|
||||
// "Name": "Hard bounce",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).",
|
||||
// "Details": "Test bounce details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": true,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
// },
|
||||
// "RecordType": "Bounce",
|
||||
// "ID": 42,
|
||||
// "Type": "HardBounce",
|
||||
// "TypeCode": 1,
|
||||
// "Name": "Hard bounce",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).",
|
||||
// "Details": "Test bounce details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": true,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
|
||||
private function processBounce()
|
||||
{
|
||||
@ -218,38 +220,39 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
|
||||
SystemLogger::dispatch($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
|
||||
|
||||
if (config('ninja.notification.slack')) {
|
||||
$this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
|
||||
}
|
||||
// if(config('ninja.notification.slack'))
|
||||
// $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
|
||||
|
||||
}
|
||||
|
||||
// {
|
||||
// "Metadata": {
|
||||
// {
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// },
|
||||
// "RecordType": "SpamComplaint",
|
||||
// "ID": 42,
|
||||
// "Type": "SpamComplaint",
|
||||
// "TypeCode": 100001,
|
||||
// "Name": "Spam complaint",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The subscriber explicitly marked this message as spam.",
|
||||
// "Details": "Test spam complaint details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": false,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
// },
|
||||
// "RecordType": "SpamComplaint",
|
||||
// "ID": 42,
|
||||
// "Type": "SpamComplaint",
|
||||
// "TypeCode": 100001,
|
||||
// "Name": "Spam complaint",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The subscriber explicitly marked this message as spam.",
|
||||
// "Details": "Test spam complaint details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": false,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
private function processSpamComplaint()
|
||||
{
|
||||
|
||||
$this->invitation->email_status = 'spam';
|
||||
$this->invitation->save();
|
||||
|
||||
@ -263,25 +266,24 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
|
||||
SystemLogger::dispatch($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
|
||||
|
||||
if (config('ninja.notification.slack')) {
|
||||
if(config('ninja.notification.slack'))
|
||||
$this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function discoverInvitation($message_id)
|
||||
{
|
||||
$invitation = false;
|
||||
|
||||
if ($invitation = InvoiceInvitation::where('message_id', $message_id)->first()) {
|
||||
if($invitation = InvoiceInvitation::where('message_id', $message_id)->first())
|
||||
return $invitation;
|
||||
} elseif ($invitation = QuoteInvitation::where('message_id', $message_id)->first()) {
|
||||
elseif($invitation = QuoteInvitation::where('message_id', $message_id)->first())
|
||||
return $invitation;
|
||||
} elseif ($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first()) {
|
||||
elseif($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first())
|
||||
return $invitation;
|
||||
} elseif ($invitation = CreditInvitation::where('message_id', $message_id)->first()) {
|
||||
elseif($invitation = CreditInvitation::where('message_id', $message_id)->first())
|
||||
return $invitation;
|
||||
} else {
|
||||
else
|
||||
return $invitation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -81,7 +81,6 @@ class SendRecurring implements ShouldQueue
|
||||
$invoice = $invoice->service()
|
||||
->markSent()
|
||||
->applyNumber()
|
||||
//->createInvitations() //need to only link invitations to those in the recurring invoice
|
||||
->fillDefaults()
|
||||
->adjustInventory()
|
||||
->save();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -39,7 +39,7 @@ class VersionCheck implements ShouldQueue
|
||||
|
||||
nlog("latest version = {$version_file}");
|
||||
|
||||
if ($version_file) {
|
||||
if (Ninja::isSelfHost() && $version_file) {
|
||||
Account::whereNotNull('id')->update(['latest_version' => $version_file]);
|
||||
}
|
||||
|
||||
|
86
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
86
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
@ -33,8 +33,8 @@ use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\Traits\Pdf\PDF;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -65,6 +65,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
|
||||
public $vendor;
|
||||
|
||||
private string $path = '';
|
||||
|
||||
private string $file_path = '';
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -74,7 +78,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
$this->company = $invitation->company;
|
||||
|
||||
|
||||
$this->entity = $invitation->purchase_order;
|
||||
$this->entity_string = 'purchase_order';
|
||||
|
||||
@ -82,12 +86,40 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
|
||||
$this->vendor = $invitation->contact->vendor;
|
||||
$this->vendor->load('company');
|
||||
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$pdf = $this->rawPdf();
|
||||
|
||||
if ($pdf) {
|
||||
|
||||
try{
|
||||
|
||||
if(!Storage::disk($this->disk)->exists($this->path))
|
||||
Storage::disk($this->disk)->makeDirectory($this->path, 0775);
|
||||
|
||||
Storage::disk($this->disk)->put($this->file_path, $pdf, 'public');
|
||||
|
||||
}
|
||||
catch(\Exception $e)
|
||||
{
|
||||
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $this->file_path;
|
||||
}
|
||||
|
||||
public function rawPdf()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
/* Forget the singleton*/
|
||||
@ -106,27 +138,26 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
}
|
||||
|
||||
$entity_design_id = '';
|
||||
|
||||
$path = $this->vendor->purchase_order_filepath($this->invitation);
|
||||
|
||||
$this->path = $this->vendor->purchase_order_filepath($this->invitation);
|
||||
$entity_design_id = 'purchase_order_design_id';
|
||||
|
||||
$file_path = $path.$this->entity->numberFormatter().'.pdf';
|
||||
$this->file_path = $this->path.$this->entity->numberFormatter().'.pdf';
|
||||
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if (! $design) {
|
||||
if(!$design)
|
||||
$design = Design::find(2);
|
||||
}
|
||||
|
||||
$html = new VendorHtmlEngine($this->invitation);
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true),
|
||||
];
|
||||
'custom_partials' => json_decode(json_encode($design->design), true)
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
@ -160,23 +191,28 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
|
||||
|
||||
if ($numbered_pdf) {
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
}
|
||||
} else {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
|
||||
|
||||
if ($numbered_pdf) {
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
@ -185,22 +221,14 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
info($maker->getCompiledHTML());
|
||||
}
|
||||
|
||||
if ($pdf) {
|
||||
try {
|
||||
if (! Storage::disk($this->disk)->exists($path)) {
|
||||
Storage::disk($this->disk)->makeDirectory($path, 0775);
|
||||
}
|
||||
return $pdf;
|
||||
|
||||
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
|
||||
} catch (\Exception $e) {
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
public function failed($e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -33,10 +33,9 @@ class Account extends BaseModel
|
||||
use PresentableTrait;
|
||||
use MakesHash;
|
||||
|
||||
private $free_plan_email_quota = 250;
|
||||
private $free_plan_email_quota = 50;
|
||||
|
||||
private $paid_plan_email_quota = 500;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -65,74 +64,53 @@ class Account extends BaseModel
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [
|
||||
'deleted_at',
|
||||
'promo_expires',
|
||||
'discount_expires',
|
||||
// 'trial_started',
|
||||
// 'plan_expires'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'promo_expires' => 'datetime',
|
||||
'discount_expires' => 'datetime',
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'onboarding' => 'object',
|
||||
'set_react_as_default_ap' => 'bool',
|
||||
'set_react_as_default_ap' => 'bool'
|
||||
];
|
||||
|
||||
const PLAN_FREE = 'free';
|
||||
|
||||
const PLAN_PRO = 'pro';
|
||||
|
||||
const PLAN_ENTERPRISE = 'enterprise';
|
||||
|
||||
const PLAN_WHITE_LABEL = 'white_label';
|
||||
|
||||
const PLAN_TERM_MONTHLY = 'month';
|
||||
|
||||
const PLAN_TERM_YEARLY = 'year';
|
||||
|
||||
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';
|
||||
|
||||
const FEATURE_INVOICE_SETTINGS = 'invoice_settings';
|
||||
|
||||
const FEATURE_CUSTOM_EMAILS = 'custom_emails';
|
||||
|
||||
const FEATURE_PDF_ATTACHMENT = 'pdf_attachments';
|
||||
|
||||
const FEATURE_MORE_INVOICE_DESIGNS = 'more_invoice_designs';
|
||||
|
||||
const FEATURE_REPORTS = 'reports';
|
||||
|
||||
const FEATURE_BUY_NOW_BUTTONS = 'buy_now_buttons';
|
||||
|
||||
const FEATURE_API = 'api';
|
||||
|
||||
const FEATURE_CLIENT_PORTAL_PASSWORD = 'client_portal_password';
|
||||
|
||||
const FEATURE_CUSTOM_URL = 'custom_url';
|
||||
|
||||
const FEATURE_MORE_CLIENTS = 'more_clients';
|
||||
|
||||
const FEATURE_WHITE_LABEL = 'white_label';
|
||||
|
||||
const FEATURE_REMOVE_CREATED_BY = 'remove_created_by';
|
||||
|
||||
const FEATURE_USERS = 'users'; // Grandfathered for old Pro users
|
||||
|
||||
const FEATURE_DOCUMENTS = 'documents';
|
||||
|
||||
const FEATURE_USER_PERMISSIONS = 'permissions';
|
||||
|
||||
const RESULT_FAILURE = 'failure';
|
||||
|
||||
const RESULT_SUCCESS = 'success';
|
||||
|
||||
public function getEntityType()
|
||||
@ -172,9 +150,8 @@ class Account extends BaseModel
|
||||
|
||||
public function getPlan()
|
||||
{
|
||||
if (Carbon::parse($this->plan_expires)->lt(now())) {
|
||||
if(Carbon::parse($this->plan_expires)->lt(now()))
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->plan ?: '';
|
||||
}
|
||||
@ -310,15 +287,16 @@ class Account extends BaseModel
|
||||
$trial_active = false;
|
||||
|
||||
//14 day trial
|
||||
$duration = 60 * 60 * 24 * 14;
|
||||
$duration = 60*60*24*14;
|
||||
|
||||
if ($trial_plan && $include_trial) {
|
||||
$trial_started = $this->trial_started;
|
||||
$trial_expires = Carbon::parse($this->trial_started)->addSeconds($duration);
|
||||
|
||||
if ($trial_expires->greaterThan(now())) {
|
||||
if($trial_expires->greaterThan(now())){
|
||||
$trial_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$plan_active = false;
|
||||
@ -338,6 +316,7 @@ class Account extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Should we show plan details or trial details?
|
||||
if (($plan && ! $trial_plan) || ! $include_trial) {
|
||||
$use_plan = true;
|
||||
@ -394,14 +373,20 @@ class Account extends BaseModel
|
||||
|
||||
public function getDailyEmailLimit()
|
||||
{
|
||||
if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) {
|
||||
return 20;
|
||||
}
|
||||
if($this->is_flagged)
|
||||
return 0;
|
||||
|
||||
if ($this->isPaid()) {
|
||||
if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0)
|
||||
return 20;
|
||||
|
||||
if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2 && !$this->payment_id)
|
||||
return 20;
|
||||
|
||||
if($this->isPaid()){
|
||||
$limit = $this->paid_plan_email_quota;
|
||||
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 100;
|
||||
} else {
|
||||
}
|
||||
else{
|
||||
$limit = $this->free_plan_email_quota;
|
||||
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50;
|
||||
}
|
||||
@ -411,22 +396,22 @@ class Account extends BaseModel
|
||||
|
||||
public function emailsSent()
|
||||
{
|
||||
if (is_null(Cache::get($this->key))) {
|
||||
if(is_null(Cache::get($this->key)))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Cache::get($this->key);
|
||||
}
|
||||
}
|
||||
|
||||
public function emailQuotaExceeded() :bool
|
||||
{
|
||||
if (is_null(Cache::get($this->key))) {
|
||||
if(is_null(Cache::get($this->key)))
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Cache::get($this->key) > $this->getDailyEmailLimit()) {
|
||||
if (is_null(Cache::get("throttle_notified:{$this->key}"))) {
|
||||
if(Cache::get($this->key) > $this->getDailyEmailLimit()) {
|
||||
|
||||
if(is_null(Cache::get("throttle_notified:{$this->key}"))) {
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->companies()->first()->settings));
|
||||
@ -440,14 +425,14 @@ class Account extends BaseModel
|
||||
|
||||
Cache::put("throttle_notified:{$this->key}", true, 60 * 24);
|
||||
|
||||
if (config('ninja.notification.slack')) {
|
||||
if(config('ninja.notification.slack'))
|
||||
$this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
catch(\Exception $e){
|
||||
\Sentry\captureMessage("I encountered an error with email quotas for account {$this->key} - defaulting to SEND");
|
||||
}
|
||||
|
||||
@ -456,16 +441,17 @@ class Account extends BaseModel
|
||||
|
||||
public function gmailCredentialNotification() :bool
|
||||
{
|
||||
nlog('checking if gmail credential notification has already been sent');
|
||||
nlog("checking if gmail credential notification has already been sent");
|
||||
|
||||
if (is_null(Cache::get($this->key))) {
|
||||
if(is_null(Cache::get($this->key)))
|
||||
return false;
|
||||
}
|
||||
|
||||
nlog('Sending notification');
|
||||
|
||||
nlog("Sending notification");
|
||||
|
||||
try {
|
||||
if (is_null(Cache::get("gmail_credentials_notified:{$this->key}"))) {
|
||||
|
||||
if(is_null(Cache::get("gmail_credentials_notified:{$this->key}"))) {
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->companies()->first()->settings));
|
||||
@ -479,17 +465,20 @@ class Account extends BaseModel
|
||||
|
||||
Cache::put("gmail_credentials_notified:{$this->key}", true, 60 * 24);
|
||||
|
||||
if (config('ninja.notification.slack')) {
|
||||
if(config('ninja.notification.slack'))
|
||||
$this->companies()->first()->notification(new GmailCredentialNotification($this))->ninja();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
\Sentry\captureMessage("I encountered an error with sending with gmail for account {$this->key}");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function resolveRouteBinding($value, $field = null)
|
||||
@ -501,4 +490,18 @@ class Account extends BaseModel
|
||||
return $this
|
||||
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||
}
|
||||
|
||||
public function getTrialDays()
|
||||
{
|
||||
if($this->payment_id)
|
||||
return 0;
|
||||
|
||||
$plan_expires = Carbon::parse($this->plan_expires);
|
||||
|
||||
if(!$this->payment_id && $plan_expires->gt(now()))
|
||||
return $plan_expires->diffInDays();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -203,7 +203,6 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
//$this->notify(new ClientContactResetPassword($token));
|
||||
}
|
||||
|
||||
public function preferredLocale()
|
||||
|
@ -56,6 +56,7 @@ class Expense extends BaseModel
|
||||
'tax_amount3',
|
||||
'uses_inclusive_taxes',
|
||||
'calculate_tax_by_amount',
|
||||
'purchase_order_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -102,6 +103,11 @@ class Expense extends BaseModel
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function purchase_order()
|
||||
{
|
||||
return $this->hasOne(PurchaseOrder::class);
|
||||
}
|
||||
|
||||
public function translate_entity()
|
||||
{
|
||||
return ctrans('texts.expense');
|
||||
|
@ -63,6 +63,8 @@ class Gateway extends StaticModel
|
||||
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
|
||||
} elseif ($this->id == 20 || $this->id == 56) {
|
||||
$link = 'https://dashboard.stripe.com/account/apikeys';
|
||||
} elseif ($this->id == 59) {
|
||||
$link = 'https://www.forte.net/';
|
||||
}
|
||||
|
||||
return $link;
|
||||
@ -168,6 +170,12 @@ class Gateway extends StaticModel
|
||||
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']], // Razorpay
|
||||
];
|
||||
break;
|
||||
case 59:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], // Forte
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
];
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
break;
|
||||
|
@ -18,78 +18,42 @@ class PaymentType extends StaticModel
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
const CREDIT = 1;
|
||||
|
||||
const CREDIT = 32;
|
||||
const ACH = 4;
|
||||
|
||||
const VISA = 5;
|
||||
|
||||
const MASTERCARD = 6;
|
||||
|
||||
const AMERICAN_EXPRESS = 7;
|
||||
|
||||
const DISCOVER = 8;
|
||||
|
||||
const DINERS = 9;
|
||||
|
||||
const EUROCARD = 10;
|
||||
|
||||
const NOVA = 11;
|
||||
|
||||
const CREDIT_CARD_OTHER = 12;
|
||||
|
||||
const PAYPAL = 13;
|
||||
|
||||
const CHECK = 15;
|
||||
|
||||
const CARTE_BLANCHE = 16;
|
||||
|
||||
const UNIONPAY = 17;
|
||||
|
||||
const JCB = 18;
|
||||
|
||||
const LASER = 19;
|
||||
|
||||
const MAESTRO = 20;
|
||||
|
||||
const SOLO = 21;
|
||||
|
||||
const SWITCH = 22;
|
||||
|
||||
const ALIPAY = 27;
|
||||
|
||||
const SOFORT = 28;
|
||||
|
||||
const SEPA = 29;
|
||||
|
||||
const GOCARDLESS = 30;
|
||||
|
||||
const CRYPTO = 31;
|
||||
|
||||
const MOLLIE_BANK_TRANSFER = 34;
|
||||
|
||||
const KBC = 35;
|
||||
|
||||
const BANCONTACT = 36;
|
||||
|
||||
const IDEAL = 37;
|
||||
|
||||
const HOSTED_PAGE = 38;
|
||||
|
||||
const GIROPAY = 39;
|
||||
|
||||
const PRZELEWY24 = 40;
|
||||
|
||||
const EPS = 41;
|
||||
|
||||
const DIRECT_DEBIT = 42;
|
||||
|
||||
const BECS = 43;
|
||||
|
||||
const ACSS = 44;
|
||||
|
||||
const INSTANT_BANK_PAY = 45;
|
||||
|
||||
const FPX = 46;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
|
@ -125,6 +125,26 @@ class CompanyPresenter extends EntityPresenter
|
||||
}
|
||||
}
|
||||
|
||||
public function address1()
|
||||
{
|
||||
return $this->entity->settings->address1;
|
||||
}
|
||||
|
||||
public function address2()
|
||||
{
|
||||
return $this->entity->settings->address2;
|
||||
}
|
||||
|
||||
public function qr_iban()
|
||||
{
|
||||
return $this->entity->getSetting('qr_iban');
|
||||
}
|
||||
|
||||
public function besr_id()
|
||||
{
|
||||
return $this->entity->getSetting('besr_id');
|
||||
}
|
||||
|
||||
public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban)
|
||||
{
|
||||
$settings = $this->entity->settings;
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
@ -33,7 +34,6 @@ class PurchaseOrder extends BaseModel
|
||||
'discount',
|
||||
'company_id',
|
||||
'status_id',
|
||||
'user_id',
|
||||
'last_sent_date',
|
||||
'is_deleted',
|
||||
'po_number',
|
||||
@ -72,10 +72,6 @@ class PurchaseOrder extends BaseModel
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
// 'custom_surcharge_tax1',
|
||||
// 'custom_surcharge_tax2',
|
||||
// 'custom_surcharge_tax3',
|
||||
// 'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'invoice_id',
|
||||
'assigned_user_id',
|
||||
@ -83,9 +79,8 @@ class PurchaseOrder extends BaseModel
|
||||
'balance',
|
||||
'partial',
|
||||
'paid_to_date',
|
||||
// 'subscription_id',
|
||||
'vendor_id',
|
||||
'last_viewed',
|
||||
'last_viewed'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -99,12 +94,10 @@ class PurchaseOrder extends BaseModel
|
||||
];
|
||||
|
||||
const STATUS_DRAFT = 1;
|
||||
|
||||
const STATUS_SENT = 2;
|
||||
|
||||
const STATUS_ACCEPTED = 3;
|
||||
|
||||
const STATUS_CANCELLED = 4;
|
||||
const STATUS_RECEIVED = 4;
|
||||
const STATUS_CANCELLED = 5;
|
||||
|
||||
public static function stringStatus(int $status)
|
||||
{
|
||||
@ -126,6 +119,7 @@ class PurchaseOrder extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function badgeForStatus(int $status)
|
||||
{
|
||||
switch ($status) {
|
||||
@ -147,6 +141,7 @@ class PurchaseOrder extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function assigned_user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();
|
||||
@ -154,7 +149,7 @@ class PurchaseOrder extends BaseModel
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
return $this->belongsTo(Vendor::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function history()
|
||||
@ -172,6 +167,11 @@ class PurchaseOrder extends BaseModel
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function expense()
|
||||
{
|
||||
return $this->belongsTo(Expense::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
@ -181,7 +181,6 @@ class PurchaseOrder extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Client::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function markInvitationsSent()
|
||||
{
|
||||
$this->invitations->each(function ($invitation) {
|
||||
@ -195,33 +194,33 @@ class PurchaseOrder extends BaseModel
|
||||
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
|
||||
{
|
||||
if (! $invitation) {
|
||||
if ($this->invitations()->exists()) {
|
||||
|
||||
if($this->invitations()->exists())
|
||||
$invitation = $this->invitations()->first();
|
||||
} else {
|
||||
else{
|
||||
$this->service()->createInvitations();
|
||||
$invitation = $this->invitations()->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (! $invitation) {
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
}
|
||||
|
||||
$file_path = $this->vendor->purchase_order_filepath($invitation).$this->numberFormatter().'.pdf';
|
||||
|
||||
if (Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)) {
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
} elseif (Ninja::isHosted() && $portal) {
|
||||
$file_path = (new CreatePurchaseOrderPdf($invitation, config('filesystems.default')))->handle();
|
||||
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if (Storage::disk('public')->exists($file_path)) {
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
$file_path = (new CreatePurchaseOrderPdf($invitation))->handle();
|
||||
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
@ -272,4 +271,5 @@ class PurchaseOrder extends BaseModel
|
||||
|
||||
return $purchase_order_calc->build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'oauth_user_token_expiry' => 'datetime',
|
||||
];
|
||||
|
||||
public function name()
|
||||
|
93
app/Notifications/Ninja/EmailQualityNotification.php
Normal file
93
app/Notifications/Ninja/EmailQualityNotification.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?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\Notifications\Ninja;
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EmailQualityNotification extends Notification
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
protected Company $company;
|
||||
|
||||
protected string $spam_string;
|
||||
|
||||
public function __construct(Company $company, string $spam_string)
|
||||
{
|
||||
$this->company = $company;
|
||||
$this->spam_string = $spam_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
|
||||
$content = "Email Quality notification for Company {$this->company->company_key} \n";
|
||||
|
||||
$owner = $this->company->owner();
|
||||
|
||||
$content .= "Owner {$owner->present()->name() } | {$owner->email} \n";
|
||||
$content .= "Spam trigger: {$this->spam_string}";
|
||||
|
||||
return (new SlackMessage)
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content($content);
|
||||
}
|
||||
}
|
93
app/Notifications/Ninja/NewAccountNotification.php
Normal file
93
app/Notifications/Ninja/NewAccountNotification.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?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\Notifications\Ninja;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class NewAccountNotification extends Notification
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
protected Account $account;
|
||||
|
||||
protected Client $client;
|
||||
|
||||
public function __construct(Account $account, Client $client)
|
||||
{
|
||||
$this->account = $account;
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$content = "New Trial Started\n";
|
||||
$content .= "{$this->client->name}\n";
|
||||
$content .= "Account key: {$this->account->key}\n";
|
||||
$content .= "Users: {$this->account->users()->pluck('email')}\n";
|
||||
$content .= "Contacts: {$this->client->contacts()->pluck('email')}\n";
|
||||
|
||||
|
||||
return (new SlackMessage)
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content($content);
|
||||
}
|
||||
}
|
121
app/Notifications/Ninja/SpamNotification.php
Normal file
121
app/Notifications/Ninja/SpamNotification.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?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\Notifications\Ninja;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SpamNotification extends Notification
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
protected array $spam_list;
|
||||
|
||||
public function __construct($spam_list)
|
||||
{
|
||||
$this->spam_list = $spam_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$content = '';
|
||||
|
||||
foreach($this->spam_list as $spam_list)
|
||||
{
|
||||
|
||||
if(array_key_exists('companies', $spam_list))
|
||||
{
|
||||
$content .= " Companies \n";
|
||||
|
||||
foreach($spam_list['companies'] as $company)
|
||||
{
|
||||
$content .= "{$company['name']} - c_key={$company['company_key']} - a_key={$company['account_key']} - {$company['owner']} \n";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists('templates', $spam_list))
|
||||
{
|
||||
$content .= " Templates \n";
|
||||
|
||||
foreach($spam_list['templates'] as $company)
|
||||
{
|
||||
$content .= "{$company['name']} - c_key={$company['company_key']} - a_key={$company['account_key']} - {$company['owner']} \n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(array_key_exists('users', $spam_list))
|
||||
{
|
||||
|
||||
$content .= ' Users \n';
|
||||
|
||||
foreach($spam_list['users'] as $user)
|
||||
{
|
||||
$content .= "{$user['email']} - a_key={$user['account_key']} - created={$user['created']} \n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (new SlackMessage)
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content($content);
|
||||
}
|
||||
}
|
@ -66,7 +66,19 @@ class AuthorizePaymentDriver extends BaseDriver
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
{
|
||||
return [
|
||||
|
||||
$fields = [];
|
||||
|
||||
if ($this->company_gateway->require_shipping_address) {
|
||||
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
['name' => 'client_name', 'label' => ctrans('texts.name'), 'type' => 'text', 'validation' => 'required|min:2'],
|
||||
['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required|email:rfc'],
|
||||
['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'],
|
||||
@ -75,6 +87,9 @@ class AuthorizePaymentDriver extends BaseDriver
|
||||
['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'],
|
||||
['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'select', 'validation' => 'required'],
|
||||
];
|
||||
|
||||
return array_merge($fields, $data);
|
||||
|
||||
}
|
||||
|
||||
public function authorizeView($payment_method)
|
||||
|
150
app/PaymentDrivers/Forte/ACH.php
Normal file
150
app/PaymentDrivers/Forte/ACH.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Forte;
|
||||
|
||||
use App\Models\Payment;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\PaymentDrivers\FortePaymentDriver;
|
||||
|
||||
class ACH
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $forte;
|
||||
|
||||
private $forte_base_uri="";
|
||||
private $forte_api_access_id="";
|
||||
private $forte_secure_key="";
|
||||
private $forte_auth_organization_id="";
|
||||
private $forte_organization_id="";
|
||||
private $forte_location_id="";
|
||||
|
||||
public function __construct(FortePaymentDriver $forte)
|
||||
{
|
||||
$this->forte = $forte;
|
||||
|
||||
$this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
|
||||
if($this->forte->company_gateway->getConfigField('testMode') == false){
|
||||
$this->forte_base_uri = "https://api.forte.net/v3/";
|
||||
}
|
||||
$this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
|
||||
$this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
|
||||
$this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
|
||||
$this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
|
||||
$this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
|
||||
}
|
||||
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
return render('gateways.forte.ach.authorize', $data);
|
||||
}
|
||||
|
||||
public function authorizeResponse(Request $request)
|
||||
{
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->brand = (string)ctrans('texts.ach');
|
||||
$payment_meta->last4 = (string) $request->last_4;
|
||||
$payment_meta->exp_year = '-';
|
||||
$payment_meta->type = GatewayType::BANK_TRANSFER;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $request->one_time_token,
|
||||
'payment_method_id' => $request->gateway_type_id,
|
||||
];
|
||||
|
||||
$this->forte->storeGatewayToken($data);
|
||||
|
||||
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
|
||||
}
|
||||
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
|
||||
$this->forte->payment_hash->save();
|
||||
|
||||
$data['gateway'] = $this;
|
||||
return render('gateways.forte.ach.pay', $data);
|
||||
}
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
|
||||
|
||||
try {
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 0,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS =>'{
|
||||
"action":"sale",
|
||||
"authorization_amount": '.$payment_hash->data->total->amount_with_fee.',
|
||||
"echeck":{
|
||||
"sec_code":"PPD",
|
||||
},
|
||||
"billing_address":{
|
||||
"first_name": "'.$this->forte->client->name.'",
|
||||
"last_name": "'.$this->forte->client->name.'"
|
||||
},
|
||||
"echeck":{
|
||||
"one_time_token":"'.$request->payment_token.'"
|
||||
}
|
||||
}',
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key),
|
||||
'Cookie: visid_incap_621087=u18+3REYR/iISgzZxOF5s2ODW2IAAAAAQUIPAAAAAADuGqKgECQLS81FcSDrmhGe; nlbi_621087=YHngadhJ2VU+yr7/R1efXgAAAAD3mQyhqmnLls8PRu4iN58G; incap_ses_1136_621087=CVdrXUdhIlm9WJNDieLDD4QVXGIAAAAAvBwvkUcwhM0+OwvdPm2stg=='
|
||||
),
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
$response=json_decode($response);
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
|
||||
if ($httpcode>299) {
|
||||
$error = Validator::make([], []);
|
||||
$error->getMessageBag()->add('gateway_error', $response->response->response_desc);
|
||||
return redirect('client/invoices')->withErrors($error);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_method' => $request->payment_method_id,
|
||||
'payment_type' => PaymentType::ACH,
|
||||
'amount' => $payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $response->transaction_id,
|
||||
'gateway_type_id' => GatewayType::BANK_TRANSFER,
|
||||
];
|
||||
|
||||
$payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
}
|
||||
}
|
160
app/PaymentDrivers/Forte/CreditCard.php
Normal file
160
app/PaymentDrivers/Forte/CreditCard.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Forte;
|
||||
|
||||
use App\Models\Payment;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\PaymentDrivers\FortePaymentDriver;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $forte;
|
||||
|
||||
private $forte_base_uri="";
|
||||
private $forte_api_access_id="";
|
||||
private $forte_secure_key="";
|
||||
private $forte_auth_organization_id="";
|
||||
private $forte_organization_id="";
|
||||
private $forte_location_id="";
|
||||
|
||||
public function __construct(FortePaymentDriver $forte)
|
||||
{
|
||||
$this->forte = $forte;
|
||||
|
||||
$this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
|
||||
if($this->forte->company_gateway->getConfigField('testMode') == false){
|
||||
$this->forte_base_uri = "https://api.forte.net/v3/";
|
||||
}
|
||||
$this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
|
||||
$this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
|
||||
$this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
|
||||
$this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
|
||||
$this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
|
||||
}
|
||||
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
return render('gateways.forte.credit_card.authorize', $data);
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = (string) $request->expire_month;
|
||||
$payment_meta->exp_year = (string) $request->expire_year;
|
||||
$payment_meta->brand = (string) $request->card_type;
|
||||
$payment_meta->last4 = (string) $request->last_4;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $request->one_time_token,
|
||||
'payment_method_id' => $request->payment_method_id,
|
||||
];
|
||||
|
||||
$this->forte->storeGatewayToken($data);
|
||||
|
||||
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
|
||||
}
|
||||
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
|
||||
$this->forte->payment_hash->save();
|
||||
|
||||
$data['gateway'] = $this;
|
||||
return render('gateways.forte.credit_card.pay', $data);
|
||||
}
|
||||
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
|
||||
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
|
||||
$invoice_totals = $payment_hash->data->total->invoice_totals;
|
||||
$fee_total = 0;
|
||||
|
||||
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
|
||||
$calculated_fee = ( 3 * $i) / 100;
|
||||
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
|
||||
if ($calculated_amount_with_fee == $amount_with_fee) {
|
||||
$fee_total = round($calculated_fee / 100,2);
|
||||
$amount_with_fee = $calculated_amount_with_fee;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 0,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS =>'{
|
||||
"action":"sale",
|
||||
"authorization_amount":'.$amount_with_fee.',
|
||||
"service_fee_amount":'.$fee_total.',
|
||||
"billing_address":{
|
||||
"first_name":"'.$this->forte->client->name.'",
|
||||
"last_name":"'.$this->forte->client->name.'"
|
||||
},
|
||||
"card":{
|
||||
"one_time_token":"'.$request->payment_token.'"
|
||||
}
|
||||
}',
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: application/json',
|
||||
'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
|
||||
'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key)
|
||||
),
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
$response=json_decode($response);
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
if ($httpcode>299) {
|
||||
$error = Validator::make([], []);
|
||||
$error->getMessageBag()->add('gateway_error', $response->response->response_desc);
|
||||
return redirect('client/invoices')->withErrors($error);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_method' => $request->payment_method_id,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($request->card_brand)) ?: PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $response->transaction_id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
$payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
}
|
||||
}
|
90
app/PaymentDrivers/FortePaymentDriver.php
Normal file
90
app/PaymentDrivers/FortePaymentDriver.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* 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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GatewayType;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\PaymentDrivers\Forte\ACH;
|
||||
use App\PaymentDrivers\Forte\CreditCard;
|
||||
|
||||
class FortePaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $refundable = true; //does this gateway support refunds?
|
||||
|
||||
public $token_billing = true; //does this gateway support token billing?
|
||||
|
||||
public $can_authorise_credit_card = true; //does this gateway support authorizations?
|
||||
|
||||
public $gateway; //initialized gateway
|
||||
|
||||
public $payment_method; //initialized payment method
|
||||
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||
GatewayType::BANK_TRANSFER => ACH::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the gateway types.
|
||||
*/
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
{
|
||||
$class = self::$methods[$payment_method_id];
|
||||
$this->payment_method = new $class($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
return $this->payment_method->authorizeView($data); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function processPaymentView(array $data)
|
||||
{
|
||||
return $this->payment_method->paymentView($data); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
// public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
// {
|
||||
// return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here
|
||||
// }
|
||||
|
||||
// public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
// {
|
||||
// return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
|
||||
// }
|
||||
}
|
@ -53,39 +53,46 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
foreach ($this->stripe_request as $transaction) {
|
||||
if (array_key_exists('payment_intent', $transaction)) {
|
||||
$payment = Payment::query()
|
||||
foreach ($this->stripe_request as $transaction) {
|
||||
|
||||
if(array_key_exists('payment_intent', $transaction))
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
$query->where('transaction_reference', $transaction['payment_intent'])
|
||||
->orWhere('transaction_reference', $transaction['id']);
|
||||
})
|
||||
})
|
||||
->first();
|
||||
} else {
|
||||
$payment = Payment::query()
|
||||
}
|
||||
else
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
|
||||
$this->payment_completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
|
||||
$this->payment_completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->payment_completed) {
|
||||
if($this->payment_completed)
|
||||
return;
|
||||
}
|
||||
|
||||
if (optional($this->stripe_request['object']['charges']['data'][0])['id']) {
|
||||
|
||||
if(optional($this->stripe_request['object']['charges']['data'][0])['id']){
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
$payment = Payment::query()
|
||||
@ -93,29 +100,30 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
->where('transaction_reference', $this->stripe_request['object']['charges']['data'][0]['id'])
|
||||
->first();
|
||||
|
||||
//return early
|
||||
if ($payment && $payment->status_id == Payment::STATUS_COMPLETED) {
|
||||
nlog(' payment found and status correct - returning ');
|
||||
|
||||
//return early
|
||||
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
|
||||
nlog(" payment found and status correct - returning ");
|
||||
return;
|
||||
} elseif ($payment) {
|
||||
}
|
||||
elseif($payment){
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
|
||||
$hash = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
|
||||
if (! $payment_hash) {
|
||||
if(!$payment_hash)
|
||||
return;
|
||||
}
|
||||
|
||||
nlog('payment intent');
|
||||
nlog("payment intent");
|
||||
nlog($this->stripe_request);
|
||||
|
||||
if (optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types'])) {
|
||||
nlog('hash found');
|
||||
if(array_key_exists('allowed_source_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
|
||||
{
|
||||
nlog("hash found");
|
||||
|
||||
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
|
||||
|
||||
@ -125,7 +133,21 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client);
|
||||
}
|
||||
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['payment_method_types']))
|
||||
{
|
||||
nlog("hash found");
|
||||
|
||||
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function updateCreditCardPayment($payment_hash, $client)
|
||||
@ -145,7 +167,7 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
|
||||
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
@ -156,5 +178,7 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
$client,
|
||||
$client->company,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -29,10 +29,9 @@ class BaseRepository
|
||||
use MakesHash;
|
||||
use SavesDocuments;
|
||||
|
||||
public bool $import_mode = false;
|
||||
public bool $import_mode = false;
|
||||
|
||||
private bool $new_model = false;
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
* @param $type
|
||||
@ -138,10 +137,9 @@ class BaseRepository
|
||||
/* Returns an invoice if defined as a key in the $resource array*/
|
||||
public function getInvitation($invitation, $resource)
|
||||
{
|
||||
if (is_array($invitation) && ! array_key_exists('key', $invitation)) {
|
||||
if (is_array($invitation) && ! array_key_exists('key', $invitation))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$invitation_class = sprintf('App\\Models\\%sInvitation', $resource);
|
||||
|
||||
$invitation = $invitation_class::where('key', $invitation['key'])->first();
|
||||
@ -153,20 +151,20 @@ class BaseRepository
|
||||
private function resolveEntityKey($model)
|
||||
{
|
||||
switch ($model) {
|
||||
case $model instanceof RecurringInvoice:
|
||||
case ($model instanceof RecurringInvoice):
|
||||
return 'recurring_invoice_id';
|
||||
case $model instanceof Invoice:
|
||||
case ($model instanceof Invoice):
|
||||
return 'invoice_id';
|
||||
case $model instanceof Quote:
|
||||
return 'quote_id';
|
||||
case $model instanceof Credit:
|
||||
return 'credit_id';
|
||||
case ($model instanceof Quote):
|
||||
return 'quote_id';
|
||||
case ($model instanceof Credit):
|
||||
return 'credit_id';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative save used for Invoices, Recurring Invoices, Quotes & Credits.
|
||||
*
|
||||
*
|
||||
* @param $data
|
||||
* @param $model
|
||||
* @return mixed
|
||||
@ -175,11 +173,10 @@ class BaseRepository
|
||||
protected function alternativeSave($data, $model)
|
||||
{
|
||||
//forces the client_id if it doesn't exist
|
||||
if (array_key_exists('client_id', $data)) {
|
||||
if(array_key_exists('client_id', $data))
|
||||
$model->client_id = $data['client_id'];
|
||||
}
|
||||
|
||||
$client = Client::where('id', $model->client_id)->withTrashed()->firstOrFail();
|
||||
$client = Client::where('id', $model->client_id)->withTrashed()->firstOrFail();
|
||||
|
||||
$state = [];
|
||||
|
||||
@ -198,14 +195,12 @@ class BaseRepository
|
||||
$tmp_data = $data; //preserves the $data array
|
||||
|
||||
/* We need to unset some variable as we sometimes unguard the model */
|
||||
if (isset($tmp_data['invitations'])) {
|
||||
if (isset($tmp_data['invitations']))
|
||||
unset($tmp_data['invitations']);
|
||||
}
|
||||
|
||||
if (isset($tmp_data['client_contacts'])) {
|
||||
|
||||
if (isset($tmp_data['client_contacts']))
|
||||
unset($tmp_data['client_contacts']);
|
||||
}
|
||||
|
||||
|
||||
$model->fill($tmp_data);
|
||||
|
||||
$model->custom_surcharge_tax1 = $client->company->custom_surcharge_taxes1;
|
||||
@ -213,26 +208,22 @@ class BaseRepository
|
||||
$model->custom_surcharge_tax3 = $client->company->custom_surcharge_taxes3;
|
||||
$model->custom_surcharge_tax4 = $client->company->custom_surcharge_taxes4;
|
||||
|
||||
if (! $model->id) {
|
||||
if(!$model->id)
|
||||
$this->new_model = true;
|
||||
}
|
||||
|
||||
|
||||
$model->saveQuietly();
|
||||
|
||||
/* Model now persisted, now lets do some child tasks */
|
||||
|
||||
if ($model instanceof Invoice) {
|
||||
if($model instanceof Invoice)
|
||||
$model->service()->setReminder()->save();
|
||||
}
|
||||
|
||||
/* Save any documents */
|
||||
if (array_key_exists('documents', $data)) {
|
||||
if (array_key_exists('documents', $data))
|
||||
$this->saveDocuments($data['documents'], $model);
|
||||
}
|
||||
|
||||
if (array_key_exists('file', $data)) {
|
||||
if (array_key_exists('file', $data))
|
||||
$this->saveDocuments($data['file'], $model);
|
||||
}
|
||||
|
||||
/* If invitations are present we need to filter existing invitations with the new ones */
|
||||
if (isset($data['invitations'])) {
|
||||
@ -243,23 +234,24 @@ class BaseRepository
|
||||
$invitation_class = sprintf('App\\Models\\%sInvitation', $resource);
|
||||
$invitation = $invitation_class::where('key', $invitation)->first();
|
||||
|
||||
if ($invitation) {
|
||||
if ($invitation)
|
||||
$invitation->delete();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
foreach ($data['invitations'] as $invitation) {
|
||||
|
||||
//if no invitations are present - create one.
|
||||
if (! $this->getInvitation($invitation, $resource)) {
|
||||
if (isset($invitation['id'])) {
|
||||
|
||||
if (isset($invitation['id']))
|
||||
unset($invitation['id']);
|
||||
}
|
||||
|
||||
//make sure we are creating an invite for a contact who belongs to the client only!
|
||||
$contact = ClientContact::find($invitation['client_contact_id']);
|
||||
|
||||
if ($contact && $model->client_id == $contact->client_id) {
|
||||
|
||||
$invitation_class = sprintf('App\\Models\\%sInvitation', $resource);
|
||||
|
||||
$new_invitation = $invitation_class::withTrashed()
|
||||
@ -268,14 +260,18 @@ class BaseRepository
|
||||
->first();
|
||||
|
||||
if ($new_invitation && $new_invitation->trashed()) {
|
||||
|
||||
$new_invitation->restore();
|
||||
|
||||
} else {
|
||||
|
||||
$invitation_factory_class = sprintf('App\\Factory\\%sInvitationFactory', $resource);
|
||||
$new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id);
|
||||
$new_invitation->{$lcfirst_resource_id} = $model->id;
|
||||
$new_invitation->client_contact_id = $contact->id;
|
||||
$new_invitation->key = $this->createDbHash($model->company->db);
|
||||
$new_invitation->save();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -283,9 +279,8 @@ class BaseRepository
|
||||
}
|
||||
|
||||
/* If no invitations have been created, this is our fail safe to maintain state*/
|
||||
if ($model->invitations()->count() == 0) {
|
||||
if ($model->invitations()->count() == 0)
|
||||
$model->service()->createInvitations();
|
||||
}
|
||||
|
||||
/* Recalculate invoice amounts */
|
||||
$model = $model->calc()->getInvoice();
|
||||
@ -297,81 +292,82 @@ class BaseRepository
|
||||
$model = $model->service()->applyNumber()->save();
|
||||
|
||||
/* Handle attempts where the deposit is greater than the amount/balance of the invoice */
|
||||
if ((int) $model->balance != 0 && $model->partial > $model->amount) {
|
||||
if((int)$model->balance != 0 && $model->partial > $model->amount)
|
||||
$model->partial = min($model->amount, $model->balance);
|
||||
}
|
||||
|
||||
/* Update product details if necessary - if we are inside a transaction - do nothing */
|
||||
if ($model->company->update_products && $model->id && \DB::transactionLevel() == 0) {
|
||||
if ($model->company->update_products && $model->id && \DB::transactionLevel() == 0)
|
||||
UpdateOrCreateProduct::dispatch($model->line_items, $model, $model->company);
|
||||
}
|
||||
|
||||
/* Perform model specific tasks */
|
||||
if ($model instanceof Invoice) {
|
||||
|
||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||
|
||||
//10-07-2022
|
||||
$model->service()->updateStatus()->save();
|
||||
$model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount']), "Update adjustment for invoice {$model->number}");
|
||||
$model->client->service()->updateBalance(($state['finished_amount'] - $state['starting_amount']))->save();
|
||||
$model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount']), "Update adjustment for invoice {$model->number}");
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (! $model->design_id) {
|
||||
if (! $model->design_id)
|
||||
$model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id'));
|
||||
}
|
||||
|
||||
//links tasks and expenses back to the invoice.
|
||||
$model->service()->linkEntities()->save();
|
||||
|
||||
if ($this->new_model) {
|
||||
if($this->new_model)
|
||||
event('eloquent.created: App\Models\Invoice', $model);
|
||||
} else {
|
||||
else
|
||||
event('eloquent.updated: App\Models\Invoice', $model);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($model instanceof Credit) {
|
||||
|
||||
$model = $model->calc()->getCredit();
|
||||
|
||||
if (! $model->design_id) {
|
||||
if (! $model->design_id)
|
||||
$model->design_id = $this->decodePrimaryKey($client->getSetting('credit_design_id'));
|
||||
}
|
||||
|
||||
if (array_key_exists('invoice_id', $data) && $data['invoice_id']) {
|
||||
if(array_key_exists('invoice_id', $data) && $data['invoice_id'])
|
||||
$model->invoice_id = $data['invoice_id'];
|
||||
}
|
||||
|
||||
if ($this->new_model) {
|
||||
event('eloquent.created: App\Models\Credit', $model);
|
||||
} else {
|
||||
if($this->new_model)
|
||||
event('eloquent.created: App\Models\Credit', $model);
|
||||
else
|
||||
event('eloquent.updated: App\Models\Credit', $model);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($model instanceof Quote) {
|
||||
if (! $model->design_id) {
|
||||
|
||||
if (! $model->design_id)
|
||||
$model->design_id = $this->decodePrimaryKey($client->getSetting('quote_design_id'));
|
||||
}
|
||||
|
||||
$model = $model->calc()->getQuote();
|
||||
|
||||
if ($this->new_model) {
|
||||
|
||||
if($this->new_model)
|
||||
event('eloquent.created: App\Models\Quote', $model);
|
||||
} else {
|
||||
else
|
||||
event('eloquent.updated: App\Models\Quote', $model);
|
||||
}
|
||||
}
|
||||
|
||||
if ($model instanceof RecurringInvoice) {
|
||||
if (! $model->design_id) {
|
||||
$model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id'));
|
||||
}
|
||||
|
||||
if (! $model->design_id)
|
||||
$model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id'));
|
||||
|
||||
$model = $model->calc()->getRecurringInvoice();
|
||||
|
||||
if ($this->new_model) {
|
||||
|
||||
if($this->new_model)
|
||||
event('eloquent.created: App\Models\RecurringInvoice', $model);
|
||||
} else {
|
||||
else
|
||||
event('eloquent.updated: App\Models\RecurringInvoice', $model);
|
||||
}
|
||||
}
|
||||
|
||||
$model->save();
|
||||
|
@ -30,22 +30,20 @@ use Illuminate\Support\Carbon;
|
||||
/**
|
||||
* PaymentRepository.
|
||||
*/
|
||||
class PaymentRepository extends BaseRepository
|
||||
{
|
||||
use MakesHash;
|
||||
use SavesDocuments;
|
||||
class PaymentRepository extends BaseRepository {
|
||||
use MakesHash;
|
||||
use SavesDocuments;
|
||||
|
||||
protected $credit_repo;
|
||||
protected $credit_repo;
|
||||
|
||||
public function __construct(CreditRepository $credit_repo)
|
||||
{
|
||||
$this->credit_repo = $credit_repo;
|
||||
}
|
||||
public function __construct( CreditRepository $credit_repo ) {
|
||||
$this->credit_repo = $credit_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves and updates a payment. //todo refactor to handle refunds and payments.
|
||||
*
|
||||
* @param array $data the request object
|
||||
/**
|
||||
* Saves and updates a payment. //todo refactor to handle refunds and payments.
|
||||
*
|
||||
* @param array $data the request object
|
||||
* @param Payment $payment The Payment object
|
||||
* @return Payment|null Payment $payment
|
||||
*/
|
||||
@ -62,6 +60,7 @@ class PaymentRepository extends BaseRepository
|
||||
*/
|
||||
private function applyPayment(array $data, Payment $payment): ?Payment
|
||||
{
|
||||
|
||||
$is_existing_payment = true;
|
||||
$client = false;
|
||||
|
||||
@ -70,9 +69,8 @@ class PaymentRepository extends BaseRepository
|
||||
$payment = $this->processExchangeRates($data, $payment);
|
||||
|
||||
/* This is needed here otherwise the ->fill() overwrites anything that exists*/
|
||||
if ($payment->exchange_rate != 1) {
|
||||
if($payment->exchange_rate != 1)
|
||||
unset($data['exchange_rate']);
|
||||
}
|
||||
|
||||
$is_existing_payment = false;
|
||||
$client = Client::where('id', $data['client_id'])->withTrashed()->first();
|
||||
@ -85,8 +83,8 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
$client->service()->updatePaidToDate($data['amount'])->save();
|
||||
}
|
||||
// elseif($data['amount'] >0){
|
||||
else {
|
||||
|
||||
else{
|
||||
//this fixes an edge case with unapplied payments
|
||||
$client->service()->updatePaidToDate($data['amount'])->save();
|
||||
}
|
||||
@ -95,7 +93,9 @@ class PaymentRepository extends BaseRepository
|
||||
$_credit_totals = array_sum(array_column($data['credits'], 'amount'));
|
||||
|
||||
$client->service()->updatePaidToDate($_credit_totals)->save();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*Fill the payment*/
|
||||
@ -104,11 +104,12 @@ class PaymentRepository extends BaseRepository
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
|
||||
if (! $payment->currency_id && $client) {
|
||||
if (property_exists($client->settings, 'currency_id')) {
|
||||
|
||||
if(property_exists($client->settings, 'currency_id'))
|
||||
$payment->currency_id = $client->settings->currency_id;
|
||||
} else {
|
||||
else
|
||||
$payment->currency_id = $client->company->settings->currency_id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$payment->save();
|
||||
@ -129,6 +130,7 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
/*Iterate through invoices and apply payments*/
|
||||
if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) {
|
||||
|
||||
$invoice_totals = array_sum(array_column($data['invoices'], 'amount'));
|
||||
|
||||
$invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
|
||||
@ -164,20 +166,20 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
if ($credit) {
|
||||
$credit = $credit->service()->markSent()->save();
|
||||
ApplyCreditPayment::dispatchSync($credit, $payment, $paid_credit['amount'], $credit->company);
|
||||
ApplyCreditPayment::dispatchNow($credit, $payment, $paid_credit['amount'], $credit->company);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $is_existing_payment && ! $this->import_mode) {
|
||||
if (array_key_exists('email_receipt', $data) && $data['email_receipt'] == 'true') {
|
||||
$payment->service()->sendEmail();
|
||||
} elseif (! array_key_exists('email_receipt', $data) && $payment->client->getSetting('client_manual_payment_notification')) {
|
||||
$payment->service()->sendEmail();
|
||||
}
|
||||
if ( ! $is_existing_payment && ! $this->import_mode ) {
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
}
|
||||
if (array_key_exists('email_receipt', $data) && $data['email_receipt'] == 'true')
|
||||
$payment->service()->sendEmail();
|
||||
elseif(!array_key_exists('email_receipt', $data) && $payment->client->getSetting('client_manual_payment_notification'))
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) );
|
||||
}
|
||||
|
||||
$payment->applied += ($invoice_totals - $credit_totals); //wont work because - check tests
|
||||
|
||||
@ -205,7 +207,8 @@ class PaymentRepository extends BaseRepository
|
||||
*/
|
||||
public function processExchangeRates($data, $payment)
|
||||
{
|
||||
if (array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1) {
|
||||
|
||||
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1){
|
||||
return $payment;
|
||||
}
|
||||
|
||||
@ -215,6 +218,7 @@ class PaymentRepository extends BaseRepository
|
||||
$company_currency = $client->company->settings->currency_id;
|
||||
|
||||
if ($company_currency != $client_currency) {
|
||||
|
||||
$exchange_rate = new CurrencyApi();
|
||||
|
||||
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
|
||||
@ -223,7 +227,7 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
|
||||
$payment->currency_id = $company_currency;
|
||||
|
||||
return $payment;
|
||||
|
@ -27,6 +27,8 @@ class SubscriptionRepository extends BaseRepository
|
||||
{
|
||||
use CleanLineItems;
|
||||
|
||||
public int $quantity = 1;
|
||||
|
||||
public function save($data, Subscription $subscription): ?Subscription
|
||||
{
|
||||
$subscription->fill($data);
|
||||
@ -119,7 +121,7 @@ class SubscriptionRepository extends BaseRepository
|
||||
private function makeLineItem($product, $multiplier)
|
||||
{
|
||||
$item = new InvoiceItem;
|
||||
$item->quantity = $product->quantity;
|
||||
$item->quantity = $this->quantity;
|
||||
$item->product_key = $product->product_key;
|
||||
$item->notes = $product->notes;
|
||||
$item->cost = $product->price * $multiplier;
|
||||
|
@ -32,7 +32,7 @@ class VendorContactRepository extends BaseRepository
|
||||
}
|
||||
|
||||
/* Get array of IDs which have been removed from the contacts array and soft delete each contact */
|
||||
$vendor->contacts->pluck('id')->diff($contacts->pluck('id'))->each(function ($contact) {
|
||||
$vendor->contacts->pluck('hashed_id')->diff($contacts->pluck('id'))->each(function ($contact) {
|
||||
VendorContact::destroy($contact);
|
||||
});
|
||||
|
||||
|
@ -219,6 +219,7 @@ class Statement
|
||||
protected function getInvoices(): \Illuminate\Support\LazyCollection
|
||||
{
|
||||
return Invoice::withTrashed()
|
||||
->with('payments.type')
|
||||
->where('is_deleted', false)
|
||||
->where('company_id', $this->client->company_id)
|
||||
->where('client_id', $this->client->id)
|
||||
|
@ -248,6 +248,7 @@ class InstantPayment
|
||||
'tokens' => $tokens,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
'client' => $client,
|
||||
];
|
||||
|
||||
if ($is_credit_payment || $totals <= 0) {
|
||||
|
@ -117,7 +117,7 @@ class AddGatewayFee extends AbstractService
|
||||
|
||||
$this->invoice
|
||||
->ledger()
|
||||
->updateInvoiceBalance($adjustment, 'Adjustment for removing gateway fee');
|
||||
->updateInvoiceBalance($adjustment, 'Adjustment for adding gateway fee');
|
||||
}
|
||||
|
||||
return $this->invoice;
|
||||
@ -164,7 +164,7 @@ class AddGatewayFee extends AbstractService
|
||||
|
||||
$this->invoice
|
||||
->ledger()
|
||||
->updateInvoiceBalance($adjustment * -1, 'Adjustment for removing gateway fee');
|
||||
->updateInvoiceBalance($adjustment * -1, 'Adjustment for adding gateway fee');
|
||||
}
|
||||
|
||||
return $this->invoice;
|
||||
|
@ -43,12 +43,10 @@ class ApplyNumber extends AbstractService
|
||||
switch ($this->client->getSetting('counter_number_applied')) {
|
||||
case 'when_saved':
|
||||
$this->trySaving();
|
||||
// $this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice, $this->invoice->recurring_id);
|
||||
break;
|
||||
case 'when_sent':
|
||||
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
|
||||
$this->trySaving();
|
||||
// $this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice, $this->invoice->recurring_id);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -61,21 +59,30 @@ class ApplyNumber extends AbstractService
|
||||
|
||||
private function trySaving()
|
||||
{
|
||||
$x = 1;
|
||||
|
||||
do {
|
||||
try {
|
||||
$x=1;
|
||||
|
||||
do{
|
||||
|
||||
try{
|
||||
|
||||
$this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice, $this->invoice->recurring_id);
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
$this->completed = false;
|
||||
} catch (QueryException $e) {
|
||||
|
||||
|
||||
}
|
||||
catch(QueryException $e){
|
||||
|
||||
$x++;
|
||||
|
||||
if ($x > 10) {
|
||||
if($x>10)
|
||||
$this->completed = false;
|
||||
}
|
||||
}
|
||||
} while ($this->completed);
|
||||
|
||||
}
|
||||
while($this->completed);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace App\Services\Payment;
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Jobs\Invoice\InvoiceWorkflowSettings;
|
||||
use App\Jobs\Ninja\TransactionLog;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
@ -40,23 +41,20 @@ class UpdateInvoicePayment
|
||||
$paid_invoices = $this->payment_hash->invoices();
|
||||
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
|
||||
$client = $this->payment->client;
|
||||
|
||||
if ($client->trashed()) {
|
||||
if($client->trashed())
|
||||
$client->restore();
|
||||
}
|
||||
|
||||
collect($paid_invoices)->each(function ($paid_invoice) use ($invoices, $client) {
|
||||
$client = $client->fresh();
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($paid_invoice) {
|
||||
return $paid_invoice->invoice_id == $inv->hashed_id;
|
||||
});
|
||||
|
||||
if ($invoice->trashed()) {
|
||||
if($invoice->trashed())
|
||||
$invoice->restore();
|
||||
}
|
||||
|
||||
if ($invoice->id == $this->payment_hash->fee_invoice_id) {
|
||||
$paid_amount = $paid_invoice->amount + $this->payment_hash->fee_total;
|
||||
@ -64,14 +62,19 @@ class UpdateInvoicePayment
|
||||
$paid_amount = $paid_invoice->amount;
|
||||
}
|
||||
|
||||
$client->paid_to_date += $paid_amount;
|
||||
$client->balance -= $paid_amount;
|
||||
$client->save();
|
||||
\DB::connection(config('database.default'))->transaction(function () use($client, $paid_amount){
|
||||
|
||||
$update_client = Client::withTrashed()->where('id', $client->id)->lockForUpdate()->first();
|
||||
|
||||
$update_client->paid_to_date += $paid_amount;
|
||||
$update_client->balance -= $paid_amount;
|
||||
$update_client->save();
|
||||
|
||||
}, 1);
|
||||
|
||||
/* Need to determine here is we have an OVER payment - if YES only apply the max invoice amount */
|
||||
if ($paid_amount > $invoice->partial && $paid_amount > $invoice->balance) {
|
||||
if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance)
|
||||
$paid_amount = $invoice->balance;
|
||||
}
|
||||
|
||||
/*Improve performance here - 26-01-2022 - also change the order of events for invoice first*/
|
||||
//caution what if we amount paid was less than partial - we wipe it!
|
||||
@ -79,14 +82,14 @@ class UpdateInvoicePayment
|
||||
$invoice->paid_to_date += $paid_amount;
|
||||
$invoice->save();
|
||||
|
||||
$invoice = $invoice->service()
|
||||
$invoice = $invoice->service()
|
||||
->clearPartial()
|
||||
// ->updateBalance($paid_amount * -1)
|
||||
// ->updatePaidToDate($paid_amount)
|
||||
->updateStatus()
|
||||
->touchPdf()
|
||||
->save();
|
||||
|
||||
|
||||
$invoice->service()
|
||||
->workFlow()
|
||||
->save();
|
||||
@ -115,14 +118,18 @@ class UpdateInvoicePayment
|
||||
];
|
||||
|
||||
TransactionLog::dispatch(TransactionEvent::GATEWAY_PAYMENT_MADE, $transaction, $invoice->company->db);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
/* Remove the event updater from within the loop to prevent race conditions */
|
||||
|
||||
$this->payment->saveQuietly();
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
});
|
||||
|
||||
return $this->payment->fresh();
|
||||
|
@ -60,40 +60,33 @@ class Design extends BaseDesign
|
||||
|
||||
public $company;
|
||||
|
||||
public $client_or_vendor_entity;
|
||||
|
||||
/** @var array */
|
||||
public $aging = [];
|
||||
|
||||
const BOLD = 'bold';
|
||||
|
||||
const BUSINESS = 'business';
|
||||
|
||||
const CLEAN = 'clean';
|
||||
|
||||
const CREATIVE = 'creative';
|
||||
|
||||
const ELEGANT = 'elegant';
|
||||
|
||||
const HIPSTER = 'hipster';
|
||||
|
||||
const MODERN = 'modern';
|
||||
|
||||
const PLAIN = 'plain';
|
||||
|
||||
const PLAYFUL = 'playful';
|
||||
|
||||
const CUSTOM = 'custom';
|
||||
|
||||
const DELIVERY_NOTE = 'delivery_note';
|
||||
|
||||
const STATEMENT = 'statement';
|
||||
|
||||
const PURCHASE_ORDER = 'purchase_order';
|
||||
|
||||
|
||||
public function __construct(string $design = null, array $options = [])
|
||||
{
|
||||
Str::endsWith('.html', $design) ? $this->design = $design : $this->design = "{$design}.html";
|
||||
|
||||
$this->options = $options;
|
||||
|
||||
}
|
||||
|
||||
public function html(): ?string
|
||||
@ -107,7 +100,7 @@ class Design extends BaseDesign
|
||||
$path = $this->options['custom_path'] ?? config('ninja.designs.base_path');
|
||||
|
||||
return file_get_contents(
|
||||
$path.$this->design
|
||||
$path . $this->design
|
||||
);
|
||||
}
|
||||
|
||||
@ -182,9 +175,27 @@ class Design extends BaseDesign
|
||||
$this->sharedFooterElements(),
|
||||
],
|
||||
],
|
||||
// 'swiss-qr' => [
|
||||
// 'id' => 'swiss-qr',
|
||||
// 'elements' => $this->swissQrCodeElement(),
|
||||
// ]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function swissQrCodeElement() :array
|
||||
{
|
||||
if($this->type == self::DELIVERY_NOTE)
|
||||
return [];
|
||||
|
||||
$elements = [];
|
||||
|
||||
if(strlen($this->company->getSetting('qr_iban')) > 5 && strlen($this->company->getSetting('besr_id')) > 1)
|
||||
$elements[] = ['element' => 'qr_code', 'content' => '$swiss_qr', 'show_empty' => false, 'properties' => ['data-ref' => 'swiss-qr-code']];
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function companyDetails(): array
|
||||
{
|
||||
$variables = $this->context['pdf_variables']['company_details'];
|
||||
@ -192,7 +203,7 @@ class Design extends BaseDesign
|
||||
$elements = [];
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-'.substr($variable, 1)]];
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
@ -205,7 +216,7 @@ class Design extends BaseDesign
|
||||
$elements = [];
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-'.substr($variable, 1)]];
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
@ -215,14 +226,13 @@ class Design extends BaseDesign
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
if (! $this->vendor) {
|
||||
if(!$this->vendor)
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$variables = $this->context['pdf_variables']['vendor_details'];
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-'.substr($variable, 1)]];
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
@ -232,9 +242,8 @@ class Design extends BaseDesign
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
if (! $this->client) {
|
||||
if(!$this->client)
|
||||
return $elements;
|
||||
}
|
||||
|
||||
if ($this->type == self::DELIVERY_NOTE) {
|
||||
$elements = [
|
||||
@ -247,10 +256,10 @@ class Design extends BaseDesign
|
||||
['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
|
||||
['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
|
||||
]],
|
||||
['element' => 'p', 'content' => $this->client->shipping_country?->name, 'show_empty' => false],
|
||||
['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
|
||||
];
|
||||
|
||||
if (! is_null($this->context['contact'])) {
|
||||
if (!is_null($this->context['contact'])) {
|
||||
$elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
|
||||
}
|
||||
|
||||
@ -260,7 +269,7 @@ class Design extends BaseDesign
|
||||
$variables = $this->context['pdf_variables']['client_details'];
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-'.substr($variable, 1)]];
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
@ -268,13 +277,16 @@ class Design extends BaseDesign
|
||||
|
||||
public function entityDetails(): array
|
||||
{
|
||||
if ($this->type === 'statement') {
|
||||
$s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
|
||||
|
||||
|
||||
if ($this->type === 'statement') {
|
||||
|
||||
$s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
|
||||
|
||||
return [
|
||||
['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [
|
||||
['element' => 'th', 'properties' => [], 'content' => ''],
|
||||
['element' => 'th', 'properties' => [], 'content' => '<h2>'.ctrans('texts.statement').'</h2>'],
|
||||
['element' => 'th', 'properties' => [], 'content' => ""],
|
||||
['element' => 'th', 'properties' => [], 'content' => "<h2>".ctrans('texts.statement')."</h2>"],
|
||||
]],
|
||||
['element' => 'tr', 'properties' => [], 'elements' => [
|
||||
['element' => 'th', 'properties' => [], 'content' => ctrans('texts.statement_date')],
|
||||
@ -291,7 +303,7 @@ class Design extends BaseDesign
|
||||
|
||||
if ($this->entity instanceof Quote) {
|
||||
$variables = $this->context['pdf_variables']['quote_details'];
|
||||
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$variables[] = '$quote.balance_due';
|
||||
}
|
||||
@ -301,8 +313,10 @@ class Design extends BaseDesign
|
||||
$variables = $this->context['pdf_variables']['credit_details'];
|
||||
}
|
||||
|
||||
if ($this->vendor) {
|
||||
if($this->vendor){
|
||||
|
||||
$variables = $this->context['pdf_variables']['purchase_order_details'];
|
||||
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
@ -310,7 +324,7 @@ class Design extends BaseDesign
|
||||
// We don't want to show account balance or invoice total on PDF.. or any amount with currency.
|
||||
if ($this->type == self::DELIVERY_NOTE) {
|
||||
$variables = array_filter($variables, function ($m) {
|
||||
return ! in_array($m, ['$invoice.balance_due', '$invoice.total']);
|
||||
return !in_array($m, ['$invoice.balance_due', '$invoice.total']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -318,15 +332,18 @@ class Design extends BaseDesign
|
||||
$_variable = explode('.', $variable)[1];
|
||||
$_customs = ['custom1', 'custom2', 'custom3', 'custom4'];
|
||||
|
||||
if (in_array($_variable, $_customs)) {
|
||||
/* 2/7/2022 don't show custom values if they are empty */
|
||||
$var = str_replace("custom", "custom_value", $_variable);
|
||||
|
||||
if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) {
|
||||
$elements[] = ['element' => 'tr', 'elements' => [
|
||||
['element' => 'th', 'content' => $variable.'_label', 'properties' => ['data-ref' => 'entity_details-'.substr($variable, 1).'_label']],
|
||||
['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-'.substr($variable, 1)]],
|
||||
['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
|
||||
['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]],
|
||||
]];
|
||||
} else {
|
||||
$elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [
|
||||
['element' => 'th', 'content' => $variable.'_label', 'properties' => ['data-ref' => 'entity_details-'.substr($variable, 1).'_label']],
|
||||
['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-'.substr($variable, 1)]],
|
||||
['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
|
||||
['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]],
|
||||
]];
|
||||
}
|
||||
}
|
||||
@ -353,7 +370,7 @@ class Design extends BaseDesign
|
||||
|
||||
foreach ($items as $row) {
|
||||
for ($i = 0; $i < count($product_customs); $i++) {
|
||||
if (! empty($row['delivery_note.delivery_note'.($i + 1)])) {
|
||||
if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) {
|
||||
$product_customs[$i] = true;
|
||||
}
|
||||
}
|
||||
@ -361,7 +378,7 @@ class Design extends BaseDesign
|
||||
|
||||
for ($i = 0; $i < count($product_customs); $i++) {
|
||||
if ($product_customs[$i]) {
|
||||
array_push($thead, ['element' => 'th', 'content' => '$product.product'.($i + 1).'_label', 'properties' => ['data-ref' => 'delivery_note-product.product'.($i + 1).'_label']]);
|
||||
array_push($thead, ['element' => 'th', 'content' => '$product.product' . ($i + 1) . '_label', 'properties' => ['data-ref' => 'delivery_note-product.product' . ($i + 1) . '_label']]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,7 +478,7 @@ class Design extends BaseDesign
|
||||
$outstanding = $this->invoices->sum('balance');
|
||||
|
||||
return [
|
||||
['element' => 'p', 'content' => '$outstanding_label: '.Number::formatMoney($outstanding, $this->client)],
|
||||
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->client)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -495,12 +512,13 @@ class Design extends BaseDesign
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
//24-03-2022 show payments per invoice
|
||||
foreach ($this->invoices as $invoice) {
|
||||
foreach ($invoice->payments as $payment) {
|
||||
if ($payment->is_deleted) {
|
||||
|
||||
if($payment->is_deleted)
|
||||
continue;
|
||||
}
|
||||
|
||||
$element = ['element' => 'tr', 'elements' => []];
|
||||
|
||||
@ -510,6 +528,7 @@ class Design extends BaseDesign
|
||||
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->pivot->amount, $this->client) ?: ' '];
|
||||
|
||||
$tbody[] = $element;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,14 +540,14 @@ class Design extends BaseDesign
|
||||
|
||||
public function statementPaymentTableTotals(): array
|
||||
{
|
||||
if (is_null($this->payments) || ! $this->payments->first() || $this->type !== self::STATEMENT) {
|
||||
if (is_null($this->payments) || !$this->payments->first() || $this->type !== self::STATEMENT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (\array_key_exists('show_payments_table', $this->options) && $this->options['show_payments_table'] === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
$payment = $this->payments->first();
|
||||
|
||||
return [
|
||||
@ -583,19 +602,19 @@ class Design extends BaseDesign
|
||||
|
||||
foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) {
|
||||
if (array_key_exists($column, $aliases)) {
|
||||
$elements[] = ['element' => 'th', 'content' => $aliases[$column].'_label', 'properties' => ['data-ref' => "{$type}_table-".substr($aliases[$column], 1).'-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.discount' && ! $this->company->enable_product_discount) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-".substr($column, 1).'-th', 'style' => 'display: none;']];
|
||||
} elseif ($column == '$product.quantity' && ! $this->company->enable_product_quantity) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-".substr($column, 1).'-th', 'style' => 'display: none;']];
|
||||
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.discount' && !$this->company->enable_product_discount) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
|
||||
} elseif ($column == '$product.quantity' && !$this->company->enable_product_quantity) {
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
|
||||
} elseif ($column == '$product.tax_rate1') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.tax_rate2') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} elseif ($column == '$product.tax_rate3') {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
} else {
|
||||
$elements[] = ['element' => 'th', 'content' => $column.'_label', 'properties' => ['data-ref' => "{$type}_table-".substr($column, 1).'-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,7 +644,7 @@ class Design extends BaseDesign
|
||||
|
||||
foreach ($items as $row) {
|
||||
for ($i = 0; $i < count($product_customs); $i++) {
|
||||
if (! empty($row['delivery_note.delivery_note'.($i + 1)])) {
|
||||
if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) {
|
||||
$product_customs[$i] = true;
|
||||
}
|
||||
}
|
||||
@ -640,7 +659,7 @@ class Design extends BaseDesign
|
||||
|
||||
for ($i = 0; $i < count($product_customs); $i++) {
|
||||
if ($product_customs[$i]) {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note'.($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product'.($i + 1).'-td']];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note' . ($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product' . ($i + 1) . '-td']];
|
||||
}
|
||||
}
|
||||
|
||||
@ -655,8 +674,8 @@ class Design extends BaseDesign
|
||||
|
||||
if (
|
||||
array_key_exists($type, $this->context) &&
|
||||
! empty($this->context[$type]) &&
|
||||
! is_null($this->context[$type])
|
||||
!empty($this->context[$type]) &&
|
||||
!is_null($this->context[$type])
|
||||
) {
|
||||
$document = new DOMDocument();
|
||||
$document->loadHTML($this->context[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||
@ -686,9 +705,9 @@ class Design extends BaseDesign
|
||||
|
||||
if ($cell == '$task.rate') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']];
|
||||
} elseif ($cell == '$product.discount' && ! $this->company->enable_product_discount) {
|
||||
} elseif ($cell == '$product.discount' && !$this->company->enable_product_discount) {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']];
|
||||
} elseif ($cell == '$product.quantity' && ! $this->company->enable_product_quantity) {
|
||||
} elseif ($cell == '$product.quantity' && !$this->company->enable_product_quantity) {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']];
|
||||
} elseif ($cell == '$task.hours') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']];
|
||||
@ -698,10 +717,10 @@ class Design extends BaseDesign
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']];
|
||||
} elseif ($cell == '$product.tax_rate3') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']];
|
||||
} elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-".substr($cell, 1).'-td']];
|
||||
} else if ($cell == '$product.unit_cost' || $cell == '$task.rate') {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']];
|
||||
} else {
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-".substr($cell, 1).'-td']];
|
||||
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -730,12 +749,13 @@ class Design extends BaseDesign
|
||||
|
||||
$variables = $this->context['pdf_variables']['total_columns'];
|
||||
|
||||
|
||||
$elements = [
|
||||
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
|
||||
['element' => 'p', 'content' => strtr(str_replace(['labels', 'values'], ['', ''], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']],
|
||||
['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']],
|
||||
['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [
|
||||
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']],
|
||||
['element' => 'span', 'content' => strtr(str_replace('labels', '', $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
|
||||
['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
|
||||
]],
|
||||
['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
|
||||
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [
|
||||
@ -745,6 +765,7 @@ class Design extends BaseDesign
|
||||
['element' => 'div', 'properties' => ['class' => 'totals-table-right-side', 'dir' => '$dir'], 'elements' => []],
|
||||
];
|
||||
|
||||
|
||||
if ($this->type == self::DELIVERY_NOTE) {
|
||||
return $elements;
|
||||
}
|
||||
@ -760,12 +781,20 @@ class Design extends BaseDesign
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->entity instanceof Credit) {
|
||||
// We don't want to show Balanace due on the quotes.
|
||||
if (in_array('$paid_to_date', $variables)) {
|
||||
$variables = \array_diff($variables, ['$paid_to_date']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (['discount'] as $property) {
|
||||
$variable = sprintf('%s%s', '$', $property);
|
||||
|
||||
if (
|
||||
! is_null($this->entity->{$property}) &&
|
||||
! empty($this->entity->{$property}) &&
|
||||
!is_null($this->entity->{$property}) &&
|
||||
!empty($this->entity->{$property}) &&
|
||||
$this->entity->{$property} != 0
|
||||
) {
|
||||
continue;
|
||||
@ -780,56 +809,56 @@ class Design extends BaseDesign
|
||||
if ($variable == '$total_taxes') {
|
||||
$taxes = $this->entity->calc()->getTotalTaxMap();
|
||||
|
||||
if (! $taxes) {
|
||||
if (!$taxes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($taxes as $i => $tax) {
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_'.$i.'-label']],
|
||||
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->context['client']), 'properties' => ['data-ref' => 'totals-table-total_tax_'.$i]],
|
||||
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']],
|
||||
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->client_or_vendor_entity), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]],
|
||||
]];
|
||||
}
|
||||
} elseif ($variable == '$line_taxes') {
|
||||
$taxes = $this->entity->calc()->getTaxMap();
|
||||
|
||||
if (! $taxes) {
|
||||
if (!$taxes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($taxes as $i => $tax) {
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_'.$i.'-label']],
|
||||
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->context['client']), 'properties' => ['data-ref' => 'totals-table-line_tax_'.$i]],
|
||||
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']],
|
||||
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->client_or_vendor_entity), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]],
|
||||
]];
|
||||
}
|
||||
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
|
||||
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
|
||||
|
||||
$visible = (int) $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0' || ! $this->entity->{$_variable};
|
||||
$visible = (int)$this->entity->{$_variable} > 0 || (int)$this->entity->{$_variable} < 0 || !$this->entity->{$_variable};
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable.'_label', 'properties' => ['hidden' => ! $visible, 'data-ref' => 'totals_table-'.substr($variable, 1).'-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => ! $visible, 'data-ref' => 'totals_table-'.substr($variable, 1)]],
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]],
|
||||
]];
|
||||
} elseif (Str::startsWith($variable, '$custom')) {
|
||||
$field = explode('_', $variable);
|
||||
$visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && ! empty($this->company->custom_fields->{$field[1]});
|
||||
$visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]});
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable.'_label', 'properties' => ['hidden' => ! $visible, 'data-ref' => 'totals_table-'.substr($variable, 1).'-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => ! $visible, 'data-ref' => 'totals_table-'.substr($variable, 1)]],
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]],
|
||||
]];
|
||||
} else {
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => $variable.'_label', 'properties' => ['data-ref' => 'totals_table-'.substr($variable, 1).'-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-'.substr($variable, 1)]],
|
||||
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
|
||||
['element' => 'span', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]],
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
|
||||
['element' => 'span', 'content' => ''],
|
||||
['element' => 'span', 'content' => '',],
|
||||
['element' => 'span', 'content' => ''],
|
||||
]];
|
||||
|
||||
|
@ -32,10 +32,12 @@ trait DesignHelpers
|
||||
|
||||
if (isset($this->context['vendor'])) {
|
||||
$this->vendor = $this->context['vendor'];
|
||||
$this->client_or_vendor_entity = $this->context['vendor'];
|
||||
}
|
||||
|
||||
if (isset($this->context['client'])) {
|
||||
$this->client = $this->context['client'];
|
||||
$this->client_or_vendor_entity = $this->context['client'];
|
||||
}
|
||||
|
||||
if (isset($this->context['entity'])) {
|
||||
|
@ -40,7 +40,20 @@ class ApplyNumber extends AbstractService
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
$this->trySaving();
|
||||
switch ($this->vendor->company->getSetting('counter_number_applied')) {
|
||||
case 'when_saved':
|
||||
$this->trySaving();
|
||||
break;
|
||||
case 'when_sent':
|
||||
if ($this->invoice->status_id == PurchaseOrder::STATUS_SENT) {
|
||||
$this->trySaving();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
69
app/Services/PurchaseOrder/PurchaseOrderExpense.php
Normal file
69
app/Services/PurchaseOrder/PurchaseOrderExpense.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
|
||||
class PurchaseOrderExpense
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$expense = ExpenseFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
|
||||
$expense->amount = $this->purchase_order->uses_inclusive_taxes ? $this->purchase_order->amount : ($this->purchase_order->amount - $this->purchase_order->total_taxes);
|
||||
|
||||
$expense->date = now();
|
||||
$expense->vendor_id = $this->purchase_order->vendor_id;
|
||||
$expense->public_notes = $this->purchase_order->public_notes;
|
||||
$expense->uses_inclusive_taxes = $this->purchase_order->uses_inclusive_taxes;
|
||||
$expense->calculate_tax_by_amount = true;
|
||||
$expense->private_notes = ctrans('texts.purchase_order_number_short') . " " . $this->purchase_order->number;
|
||||
|
||||
$line_items = $this->purchase_order->line_items;
|
||||
|
||||
$expense->public_notes = '';
|
||||
|
||||
foreach($line_items as $line_item){
|
||||
$expense->public_notes .= $line_item->quantity . " x " . $line_item->product_key. " [ " .$line_item->notes . " ]\n";
|
||||
}
|
||||
|
||||
$tax_map = $this->purchase_order->calc()->getTaxMap();
|
||||
|
||||
if($this->purchase_order->total_taxes > 0)
|
||||
{
|
||||
$expense->tax_amount1 = $this->purchase_order->total_taxes;
|
||||
$expense->tax_name1 = ctrans("texts.tax");
|
||||
}
|
||||
|
||||
$expense->number = empty($expense->number) ? $this->getNextExpenseNumber($expense) : $expense->number;
|
||||
|
||||
$expense->save();
|
||||
|
||||
$this->purchase_order->expense_id = $expense->id;
|
||||
$this->purchase_order->save();
|
||||
|
||||
return $expense;
|
||||
|
||||
}
|
||||
}
|
57
app/Services/PurchaseOrder/PurchaseOrderInventory.php
Normal file
57
app/Services/PurchaseOrder/PurchaseOrderInventory.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\Services\PurchaseOrder;
|
||||
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\Admin\InventoryNotificationObject;
|
||||
use App\Models\Product;
|
||||
use App\Models\PurchaseOrder;
|
||||
|
||||
class PurchaseOrderInventory
|
||||
{
|
||||
|
||||
private PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$line_items = $this->purchase_order->line_items;
|
||||
|
||||
foreach($line_items as $item)
|
||||
{
|
||||
|
||||
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->purchase_order->company_id)->first();
|
||||
|
||||
if(!$p)
|
||||
continue;
|
||||
|
||||
$p->in_stock_quantity += $item->quantity;
|
||||
$p->saveQuietly();
|
||||
|
||||
}
|
||||
|
||||
$this->purchase_order->status_id = PurchaseOrder::STATUS_RECEIVED;
|
||||
$this->purchase_order->save();
|
||||
|
||||
return $this->purchase_order;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,7 @@ use App\Models\PurchaseOrder;
|
||||
use App\Services\PurchaseOrder\ApplyNumber;
|
||||
use App\Services\PurchaseOrder\CreateInvitations;
|
||||
use App\Services\PurchaseOrder\GetPurchaseOrderPdf;
|
||||
use App\Services\PurchaseOrder\PurchaseOrderExpense;
|
||||
use App\Services\PurchaseOrder\TriggeredActions;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
@ -32,6 +33,7 @@ class PurchaseOrderService
|
||||
|
||||
public function createInvitations()
|
||||
{
|
||||
|
||||
$this->purchase_order = (new CreateInvitations($this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
@ -39,25 +41,34 @@ class PurchaseOrderService
|
||||
|
||||
public function applyNumber()
|
||||
{
|
||||
$this->invoice = (new ApplyNumber($this->purchase_order->vendor, $this->purchase_order))->run();
|
||||
$this->purchase_order = (new ApplyNumber($this->purchase_order->vendor, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
// $settings = $this->purchase_order->client->getMergedSettings();
|
||||
|
||||
// //TODO implement design, footer, terms
|
||||
$settings = $this->purchase_order->company->settings;
|
||||
|
||||
// /* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
|
||||
// if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
|
||||
// $this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
|
||||
if (! $this->purchase_order->design_id)
|
||||
$this->purchase_order->design_id = $this->decodePrimaryKey($settings->invoice_design_id);
|
||||
|
||||
if (!isset($this->invoice->footer) || empty($this->invoice->footer))
|
||||
$this->purchase_order->footer = $settings->purchase_order_footer;
|
||||
|
||||
// if (!isset($this->purchase_order->public_notes))
|
||||
// $this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
|
||||
if (!isset($this->purchase_order->terms) || empty($this->purchase_order->terms))
|
||||
$this->purchase_order->terms = $settings->purchase_order_terms;
|
||||
|
||||
if (!isset($this->purchase_order->public_notes) || empty($this->purchase_order->public_notes))
|
||||
$this->purchase_order->public_notes = $this->purchase_order->vendor->public_notes;
|
||||
|
||||
if($settings->counter_number_applied == 'when_saved'){
|
||||
$this->applyNumber()->save();
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function triggeredActions($request)
|
||||
@ -89,9 +100,11 @@ class PurchaseOrderService
|
||||
public function touchPdf($force = false)
|
||||
{
|
||||
try {
|
||||
if ($force) {
|
||||
|
||||
if($force){
|
||||
|
||||
$this->purchase_order->invitations->each(function ($invitation) {
|
||||
CreatePurchaseOrderPdf::dispatchSync($invitation);
|
||||
CreatePurchaseOrderPdf::dispatchNow($invitation);
|
||||
});
|
||||
|
||||
return $this;
|
||||
@ -100,13 +113,39 @@ class PurchaseOrderService
|
||||
$this->purchase_order->invitations->each(function ($invitation) {
|
||||
CreatePurchaseOrderPdf::dispatch($invitation);
|
||||
});
|
||||
} catch (\Exception $e) {
|
||||
nlog('failed creating purchase orders in Touch PDF');
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
nlog("failed creating purchase orders in Touch PDF");
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add_to_inventory()
|
||||
{
|
||||
if($this->purchase_order->status_id >= PurchaseOrder::STATUS_RECEIVED)
|
||||
return $this->purchase_order;
|
||||
|
||||
$this->purchase_order = (new PurchaseOrderInventory($this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function expense()
|
||||
{
|
||||
$this->markSent();
|
||||
|
||||
if($this->purchase_order->expense()->exists())
|
||||
return $this;
|
||||
|
||||
$expense = (new PurchaseOrderExpense($this->purchase_order))->run();
|
||||
|
||||
return $expense;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the purchase order.
|
||||
* @return \App\Models\PurchaseOrder object
|
||||
@ -117,4 +156,5 @@ class PurchaseOrderService
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Services\Recurring;
|
||||
|
||||
use App\Jobs\RecurringInvoice\SendRecurring;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Services\Recurring\GetInvoicePdf;
|
||||
@ -26,7 +27,7 @@ class RecurringService
|
||||
}
|
||||
|
||||
//set schedules - update next_send_dates
|
||||
|
||||
|
||||
/**
|
||||
* Stops a recurring invoice
|
||||
*
|
||||
@ -34,9 +35,8 @@ class RecurringService
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
if ($this->recurring_entity->status_id < RecurringInvoice::STATUS_PAUSED) {
|
||||
if($this->recurring_entity->status_id < RecurringInvoice::STATUS_PAUSED)
|
||||
$this->recurring_entity->status_id = RecurringInvoice::STATUS_PAUSED;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -50,12 +50,13 @@ class RecurringService
|
||||
|
||||
public function start()
|
||||
{
|
||||
|
||||
if ($this->recurring_entity->remaining_cycles == 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -84,15 +85,20 @@ class RecurringService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
$this->recurring_entity->invitations->each(function ($invitation) {
|
||||
UnlinkFile::dispatchSync(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath($invitation).$this->recurring_entity->numberFormatter().'.pdf');
|
||||
|
||||
$this->recurring_entity->invitations->each(function ($invitation){
|
||||
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf');
|
||||
|
||||
});
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function triggeredActions($request)
|
||||
{
|
||||
|
||||
if ($request->has('start') && $request->input('start') == 'true') {
|
||||
$this->start();
|
||||
}
|
||||
@ -100,8 +106,13 @@ class RecurringService
|
||||
if ($request->has('stop') && $request->input('stop') == 'true') {
|
||||
$this->stop();
|
||||
}
|
||||
|
||||
if ($request->has('send_now') && $request->input('send_now') == 'true' && $this->recurring_entity->invoices()->count() == 0) {
|
||||
$this->sendNow();
|
||||
}
|
||||
|
||||
if (isset($this->recurring_entity->client)) {
|
||||
if(isset($this->recurring_entity->client))
|
||||
{
|
||||
$offset = $this->recurring_entity->client->timezone_offset();
|
||||
$this->recurring_entity->next_send_date = Carbon::parse($this->recurring_entity->next_send_date_client)->startOfDay()->addSeconds($offset);
|
||||
}
|
||||
@ -109,11 +120,24 @@ class RecurringService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
public function sendNow()
|
||||
{
|
||||
return $this;
|
||||
|
||||
if($this->recurring_entity instanceof RecurringInvoice && $this->recurring_entity->status_id == RecurringInvoice::STATUS_DRAFT){
|
||||
$this->start()->save();
|
||||
SendRecurring::dispatchNow($this->recurring_entity, $this->recurring_entity->company->db);
|
||||
}
|
||||
|
||||
return $this->recurring_entity;
|
||||
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->recurring_entity->saveQuietly();
|
||||
|
@ -67,22 +67,24 @@ class SubscriptionService
|
||||
*/
|
||||
public function completePurchase(PaymentHash $payment_hash)
|
||||
{
|
||||
if (! property_exists($payment_hash->data, 'billing_context')) {
|
||||
throw new \Exception('Illegal entrypoint into method, payload must contain billing context');
|
||||
|
||||
if (!property_exists($payment_hash->data, 'billing_context')) {
|
||||
throw new \Exception("Illegal entrypoint into method, payload must contain billing context");
|
||||
}
|
||||
|
||||
if ($payment_hash->data->billing_context->context == 'change_plan') {
|
||||
if($payment_hash->data->billing_context->context == 'change_plan') {
|
||||
return $this->handlePlanChange($payment_hash);
|
||||
}
|
||||
|
||||
// if we have a recurring product - then generate a recurring invoice
|
||||
if (strlen($this->subscription->recurring_product_ids) >= 1) {
|
||||
if(strlen($this->subscription->recurring_product_ids) >=1){
|
||||
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
$recurring_invoice->auto_bill = $this->subscription->auto_bill;
|
||||
|
||||
|
||||
/* Start the recurring service */
|
||||
$recurring_invoice->service()
|
||||
->start()
|
||||
@ -102,7 +104,10 @@ class SubscriptionService
|
||||
$response = $this->triggerWebhook($context);
|
||||
|
||||
$this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||
} else {
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$invoice = Invoice::withTrashed()->find($payment_hash->fee_invoice_id);
|
||||
|
||||
$context = [
|
||||
@ -118,9 +123,9 @@ class SubscriptionService
|
||||
|
||||
/* 06-04-2022 */
|
||||
/* We may not be in a state where the user is present */
|
||||
if (auth()->guard('contact')) {
|
||||
if(auth()->guard('contact'))
|
||||
$this->handleRedirect('/client/invoices/'.$this->encodePrimaryKey($payment_hash->fee_invoice_id));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,9 +156,8 @@ class SubscriptionService
|
||||
// Redirects from here work just fine. Livewire will respect it.
|
||||
$client_contact = ClientContact::find($data['contact_id']);
|
||||
|
||||
if (! $this->subscription->trial_enabled) {
|
||||
return new \Exception('Trials are disabled for this product');
|
||||
}
|
||||
if(!$this->subscription->trial_enabled)
|
||||
return new \Exception("Trials are disabled for this product");
|
||||
|
||||
//create recurring invoice with start date = trial_duration + 1 day
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
@ -163,13 +167,16 @@ class SubscriptionService
|
||||
$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) {
|
||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
} elseif (strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||
{
|
||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
|
||||
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
||||
|
||||
@ -178,13 +185,13 @@ class SubscriptionService
|
||||
->start()
|
||||
->save();
|
||||
|
||||
$context = [
|
||||
'context' => 'trial',
|
||||
'recurring_invoice' => $recurring_invoice->hashed_id,
|
||||
'client' => $recurring_invoice->client->hashed_id,
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'account_key' => $recurring_invoice->client->custom_value2,
|
||||
];
|
||||
$context = [
|
||||
'context' => 'trial',
|
||||
'recurring_invoice' => $recurring_invoice->hashed_id,
|
||||
'client' => $recurring_invoice->client->hashed_id,
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'account_key' => $recurring_invoice->client->custom_value2,
|
||||
];
|
||||
|
||||
//execute any webhooks
|
||||
$response = $this->triggerWebhook($context);
|
||||
@ -223,9 +230,10 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
//sometimes the last document could be a credit if the user is paying for their service with credits.
|
||||
if (! $outstanding_invoice) {
|
||||
$outstanding_invoice = Credit::where('subscription_id', $this->subscription->id)
|
||||
//sometimes the last document could be a credit if the user is paying for their service with credits.
|
||||
if(!$outstanding_invoice){
|
||||
|
||||
$outstanding_invoice = Credit::where('subscription_id', $this->subscription->id)
|
||||
->where('client_id', $recurring_invoice->client_id)
|
||||
->where('is_deleted', 0)
|
||||
->orderBy('id', 'desc')
|
||||
@ -234,21 +242,25 @@ class SubscriptionService
|
||||
|
||||
//need to ensure at this point that a refund is appropriate!!
|
||||
//28-02-2022
|
||||
if ($recurring_invoice->invoices()->count() == 0) {
|
||||
if($recurring_invoice->invoices()->count() == 0){
|
||||
return $target->price;
|
||||
} elseif ($outstanding->count() == 0) {
|
||||
}
|
||||
elseif ($outstanding->count() == 0){
|
||||
//nothing outstanding
|
||||
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||
} elseif ($outstanding->count() == 1) {
|
||||
}
|
||||
elseif ($outstanding->count() == 1){
|
||||
//user has multiple amounts outstanding
|
||||
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||
} elseif ($outstanding->count() > 1) {
|
||||
}
|
||||
elseif ($outstanding->count() > 1) {
|
||||
//user is changing plan mid frequency cycle
|
||||
//we cannot handle this if there are more than one invoice outstanding.
|
||||
return $target->price;
|
||||
}
|
||||
|
||||
return $target->price;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,9 +271,8 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataRefundForSubscription($invoice) :float
|
||||
{
|
||||
if (! $invoice || ! $invoice->date) {
|
||||
if(!$invoice || !$invoice->date)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
|
||||
@ -271,14 +282,15 @@ class SubscriptionService
|
||||
|
||||
$days_in_frequency = $this->getDaysInFrequency();
|
||||
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used) / $days_in_frequency) * $this->subscription->price, 2);
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $this->subscription->price ,2);
|
||||
|
||||
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||
// nlog("invoice amount = {$invoice->amount}");
|
||||
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||
|
||||
return $pro_rata_refund;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We refund unused days left.
|
||||
@ -288,9 +300,8 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataRefund($invoice) :float
|
||||
{
|
||||
if (! $invoice || ! $invoice->date) {
|
||||
if(!$invoice || !$invoice->date)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
|
||||
@ -300,17 +311,17 @@ class SubscriptionService
|
||||
|
||||
$days_in_frequency = $this->getDaysInFrequency();
|
||||
|
||||
if ($days_of_subscription_used >= $days_in_frequency) {
|
||||
if($days_of_subscription_used >= $days_in_frequency)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used) / $days_in_frequency) * $invoice->amount, 2);
|
||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
|
||||
|
||||
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||
// nlog("invoice amount = {$invoice->amount}");
|
||||
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||
|
||||
return $pro_rata_refund;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,9 +334,8 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataRefundItems($invoice, $is_credit = false) :array
|
||||
{
|
||||
if (! $invoice) {
|
||||
if(!$invoice)
|
||||
return [];
|
||||
}
|
||||
|
||||
/* depending on whether we are creating an invoice or a credit*/
|
||||
$multiplier = $is_credit ? 1 : -1;
|
||||
@ -339,21 +349,28 @@ class SubscriptionService
|
||||
// $days_in_frequency = $this->getDaysInFrequency();
|
||||
$days_in_frequency = $invoice->subscription->service()->getDaysInFrequency();
|
||||
|
||||
$ratio = ($days_in_frequency - $days_of_subscription_used) / $days_in_frequency;
|
||||
$ratio = ($days_in_frequency - $days_of_subscription_used)/$days_in_frequency;
|
||||
|
||||
$line_items = [];
|
||||
|
||||
foreach ($invoice->line_items as $item) {
|
||||
if ($item->product_key != ctrans('texts.refund')) {
|
||||
$item->cost = ($item->cost * $ratio * $multiplier);
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
|
||||
if($item->product_key != ctrans('texts.refund'))
|
||||
{
|
||||
|
||||
$item->cost = ($item->cost*$ratio*$multiplier);
|
||||
$item->product_key = ctrans('texts.refund');
|
||||
$item->notes = ctrans('texts.refund').': '.$item->notes;
|
||||
$item->notes = ctrans('texts.refund') . ": ". $item->notes;
|
||||
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $line_items;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -364,6 +381,7 @@ class SubscriptionService
|
||||
*/
|
||||
private function calculateProRataCharge($invoice) :float
|
||||
{
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
|
||||
$current_date = now();
|
||||
@ -374,7 +392,7 @@ class SubscriptionService
|
||||
|
||||
nlog("days to charge = {$days_to_charge} days in frequency = {$days_in_frequency}");
|
||||
|
||||
$pro_rata_charge = round(($days_to_charge / $days_in_frequency) * $invoice->amount, 2);
|
||||
$pro_rata_charge = round(($days_to_charge/$days_in_frequency) * $invoice->amount ,2);
|
||||
|
||||
nlog("pro rata charge = {$pro_rata_charge}");
|
||||
|
||||
@ -404,9 +422,11 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
if ($recurring_invoice->invoices()->count() == 0) {
|
||||
if($recurring_invoice->invoices()->count() == 0){
|
||||
$pro_rata_refund_amount = 0;
|
||||
} elseif (! $last_invoice) {
|
||||
}
|
||||
elseif(!$last_invoice){
|
||||
|
||||
$is_credit = true;
|
||||
|
||||
$last_invoice = Credit::where('subscription_id', $recurring_invoice->subscription_id)
|
||||
@ -414,13 +434,19 @@ class SubscriptionService
|
||||
->where('is_deleted', 0)
|
||||
->withTrashed()
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
->first();
|
||||
|
||||
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription);
|
||||
} elseif ($last_invoice->balance > 0) {
|
||||
|
||||
}
|
||||
|
||||
elseif($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1;
|
||||
nlog("pro rata refund = {$pro_rata_refund_amount}");
|
||||
}
|
||||
@ -432,35 +458,35 @@ class SubscriptionService
|
||||
$credit = false;
|
||||
|
||||
/* Only generate a credit if the previous invoice was paid in full. */
|
||||
if ($last_invoice && $last_invoice->balance == 0) {
|
||||
if($last_invoice && $last_invoice->balance == 0)
|
||||
$credit = $this->createCredit($last_invoice, $target_subscription, $is_credit);
|
||||
}
|
||||
|
||||
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
|
||||
|
||||
$context = [
|
||||
'context' => 'change_plan',
|
||||
'recurring_invoice' => $new_recurring_invoice->hashed_id,
|
||||
'credit' => $credit ? $credit->hashed_id : null,
|
||||
'client' => $new_recurring_invoice->client->hashed_id,
|
||||
'subscription' => $target_subscription->hashed_id,
|
||||
'contact' => auth()->guard('contact')->user()->hashed_id,
|
||||
'account_key' => $new_recurring_invoice->client->custom_value2,
|
||||
];
|
||||
$context = [
|
||||
'context' => 'change_plan',
|
||||
'recurring_invoice' => $new_recurring_invoice->hashed_id,
|
||||
'credit' => $credit ? $credit->hashed_id : null,
|
||||
'client' => $new_recurring_invoice->client->hashed_id,
|
||||
'subscription' => $target_subscription->hashed_id,
|
||||
'contact' => auth()->guard('contact')->user()->hashed_id,
|
||||
'account_key' => $new_recurring_invoice->client->custom_value2,
|
||||
];
|
||||
|
||||
$response = $this->triggerWebhook($context);
|
||||
$response = $this->triggerWebhook($context);
|
||||
|
||||
nlog($response);
|
||||
nlog($response);
|
||||
|
||||
if($credit)
|
||||
return $this->handleRedirect('/client/credits/'.$credit->hashed_id);
|
||||
else
|
||||
return $this->handleRedirect('/client/credits');
|
||||
|
||||
if ($credit) {
|
||||
return $this->handleRedirect('/client/credits/'.$credit->hashed_id);
|
||||
} else {
|
||||
return $this->handleRedirect('/client/credits');
|
||||
}
|
||||
}
|
||||
|
||||
public function changePlanPaymentCheck($data)
|
||||
{
|
||||
|
||||
$recurring_invoice = $data['recurring_invoice'];
|
||||
$old_subscription = $data['subscription'];
|
||||
$target_subscription = $data['target'];
|
||||
@ -474,25 +500,28 @@ class SubscriptionService
|
||||
->withTrashed()
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
if (! $last_invoice) {
|
||||
if(!$last_invoice)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($last_invoice->balance > 0) {
|
||||
if($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
} else {
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1;
|
||||
nlog("pro rata refund = {$pro_rata_refund_amount}");
|
||||
}
|
||||
|
||||
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||
|
||||
if ($total_payable > 0) {
|
||||
if($total_payable > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -503,6 +532,7 @@ class SubscriptionService
|
||||
*/
|
||||
public function createChangePlanInvoice($data)
|
||||
{
|
||||
|
||||
$recurring_invoice = $data['recurring_invoice'];
|
||||
$old_subscription = $data['subscription'];
|
||||
$target_subscription = $data['target'];
|
||||
@ -517,12 +547,16 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
if (! $last_invoice) {
|
||||
if(!$last_invoice){
|
||||
//do nothing
|
||||
} elseif ($last_invoice->balance > 0) {
|
||||
}
|
||||
else if($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1;
|
||||
nlog("pro rata refund = {$pro_rata_refund_amount}");
|
||||
}
|
||||
@ -530,6 +564,7 @@ class SubscriptionService
|
||||
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||
|
||||
return $this->proRataInvoice($last_invoice, $target_subscription, $recurring_invoice->client_id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -540,13 +575,12 @@ class SubscriptionService
|
||||
*/
|
||||
private function handlePlanChange($payment_hash)
|
||||
{
|
||||
nlog('handle plan change');
|
||||
nlog("handle plan change");
|
||||
|
||||
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
||||
|
||||
if (! $old_recurring_invoice) {
|
||||
if(!$old_recurring_invoice)
|
||||
return $this->handleRedirect('/client/recurring_invoices/');
|
||||
}
|
||||
|
||||
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
||||
|
||||
@ -560,11 +594,13 @@ class SubscriptionService
|
||||
'account_key' => $recurring_invoice->client->custom_value2,
|
||||
];
|
||||
|
||||
|
||||
$response = $this->triggerWebhook($context);
|
||||
|
||||
nlog($response);
|
||||
|
||||
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,24 +612,26 @@ class SubscriptionService
|
||||
*/
|
||||
public function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
|
||||
{
|
||||
|
||||
$old_recurring_invoice->service()->stop()->save();
|
||||
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
$recurring_invoice_repo->delete($old_recurring_invoice);
|
||||
|
||||
$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();
|
||||
$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()
|
||||
/* Start the recurring service */
|
||||
$recurring_invoice->service()
|
||||
->start()
|
||||
->save();
|
||||
|
||||
return $recurring_invoice;
|
||||
return $recurring_invoice;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -605,6 +643,7 @@ class SubscriptionService
|
||||
*/
|
||||
private function createCredit($last_invoice, $target, $is_credit = false)
|
||||
{
|
||||
|
||||
$last_invoice_is_credit = $is_credit ? false : true;
|
||||
|
||||
$subscription_repo = new SubscriptionRepository();
|
||||
@ -625,6 +664,7 @@ class SubscriptionService
|
||||
];
|
||||
|
||||
return $credit_repo->save($data, $credit)->service()->markSent()->fillDefaults()->save();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -657,6 +697,7 @@ class SubscriptionService
|
||||
->markSent()
|
||||
->fillDefaults()
|
||||
->save();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -665,24 +706,30 @@ class SubscriptionService
|
||||
* @param array $data
|
||||
* @return Invoice
|
||||
*/
|
||||
public function createInvoice($data): ?Invoice
|
||||
public function createInvoice($data, $quantity = 1): ?\App\Models\Invoice
|
||||
{
|
||||
|
||||
$invoice_repo = new InvoiceRepository();
|
||||
$subscription_repo = new SubscriptionRepository();
|
||||
$subscription_repo->quantity = $quantity;
|
||||
|
||||
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||
$invoice->line_items = $subscription_repo->generateLineItems($this->subscription);
|
||||
$invoice->subscription_id = $this->subscription->id;
|
||||
|
||||
if (strlen($data['coupon']) >= 1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0) {
|
||||
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||
{
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
} elseif (strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
|
||||
return $invoice_repo->save($data, $invoice);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -695,7 +742,7 @@ class SubscriptionService
|
||||
public function convertInvoiceToRecurring($client_id) :RecurringInvoice
|
||||
{
|
||||
MultiDB::setDb($this->subscription->company->db);
|
||||
|
||||
|
||||
$client = Client::withTrashed()->find($client_id);
|
||||
|
||||
$subscription_repo = new SubscriptionRepository();
|
||||
@ -708,13 +755,12 @@ class SubscriptionService
|
||||
$recurring_invoice->date = now();
|
||||
$recurring_invoice->remaining_cycles = -1;
|
||||
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
|
||||
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
|
||||
$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 = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
|
||||
return $recurring_invoice;
|
||||
}
|
||||
|
||||
@ -725,6 +771,7 @@ class SubscriptionService
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -734,13 +781,13 @@ class SubscriptionService
|
||||
*/
|
||||
public function triggerWebhook($context)
|
||||
{
|
||||
nlog('trigger webook');
|
||||
nlog("trigger webook");
|
||||
|
||||
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
|
||||
return ['message' => 'Success', 'status_code' => 200];
|
||||
return ["message" => "Success", "status_code" => 200];
|
||||
}
|
||||
|
||||
nlog('past first if');
|
||||
nlog("past first if");
|
||||
|
||||
$response = false;
|
||||
|
||||
@ -750,18 +797,23 @@ class SubscriptionService
|
||||
|
||||
$response = $this->sendLoad($this->subscription, $body);
|
||||
|
||||
nlog('after response');
|
||||
nlog("after response");
|
||||
|
||||
/* Append the response to the system logger body */
|
||||
if (is_array($response)) {
|
||||
if(is_array($response)){
|
||||
|
||||
$body = $response;
|
||||
} else {
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$body = $response->getStatusCode();
|
||||
|
||||
}
|
||||
|
||||
$client = Client::where('id', $this->decodePrimaryKey($body['client']))->withTrashed()->first();
|
||||
|
||||
SystemLogger::dispatch(
|
||||
SystemLogger::dispatch(
|
||||
$body,
|
||||
SystemLog::CATEGORY_WEBHOOK,
|
||||
SystemLog::EVENT_WEBHOOK_RESPONSE,
|
||||
@ -769,14 +821,14 @@ class SubscriptionService
|
||||
$client,
|
||||
$client->company,
|
||||
);
|
||||
|
||||
nlog("ready to fire back");
|
||||
|
||||
nlog('ready to fire back');
|
||||
if(is_array($body))
|
||||
return $response;
|
||||
else
|
||||
return ['message' => 'There was a problem encountered with the webhook', 'status_code' => 500];
|
||||
|
||||
if (is_array($body)) {
|
||||
return $response;
|
||||
} else {
|
||||
return ['message' => 'There was a problem encountered with the webhook', 'status_code' => 500];
|
||||
}
|
||||
}
|
||||
|
||||
public function fireNotifications()
|
||||
@ -792,17 +844,15 @@ class SubscriptionService
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
if (! $this->subscription->product_ids) {
|
||||
if(!$this->subscription->product_ids)
|
||||
return collect();
|
||||
}
|
||||
|
||||
$keys = $this->transformKeys(explode(',', $this->subscription->product_ids));
|
||||
$keys = $this->transformKeys(explode(",", $this->subscription->product_ids));
|
||||
|
||||
if (is_array($keys)) {
|
||||
if(is_array($keys))
|
||||
return Product::whereIn('id', $keys)->get();
|
||||
} else {
|
||||
else
|
||||
return Product::where('id', $keys)->get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -813,17 +863,18 @@ class SubscriptionService
|
||||
*/
|
||||
public function recurring_products()
|
||||
{
|
||||
if (! $this->subscription->recurring_product_ids) {
|
||||
if(!$this->subscription->recurring_product_ids)
|
||||
return collect();
|
||||
}
|
||||
|
||||
$keys = $this->transformKeys(explode(',', $this->subscription->recurring_product_ids));
|
||||
$keys = $this->transformKeys(explode(",", $this->subscription->recurring_product_ids));
|
||||
|
||||
if (is_array($keys)) {
|
||||
if(is_array($keys)){
|
||||
return Product::whereIn('id', $keys)->get();
|
||||
} else {
|
||||
}
|
||||
else{
|
||||
return Product::where('id', $keys)->get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -844,6 +895,7 @@ class SubscriptionService
|
||||
* Handle the cancellation of a subscription
|
||||
*
|
||||
* @param RecurringInvoice $recurring_invoice
|
||||
*
|
||||
*/
|
||||
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
||||
{
|
||||
@ -864,8 +916,11 @@ class SubscriptionService
|
||||
$recurring_invoice_repo->archive($recurring_invoice);
|
||||
|
||||
/* Refund only if we are in the window - and there is nothing outstanding on the invoice */
|
||||
if ($refund_end_date->greaterThan(now()) && (int) $outstanding_invoice->balance == 0) {
|
||||
if ($outstanding_invoice->payments()->exists()) {
|
||||
if($refund_end_date->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
||||
{
|
||||
|
||||
if($outstanding_invoice->payments()->exists())
|
||||
{
|
||||
$payment = $outstanding_invoice->payments()->first();
|
||||
|
||||
$data = [
|
||||
@ -882,39 +937,47 @@ class SubscriptionService
|
||||
}
|
||||
}
|
||||
|
||||
$context = [
|
||||
'context' => 'cancellation',
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'recurring_invoice' => $recurring_invoice->hashed_id,
|
||||
'client' => $recurring_invoice->client->hashed_id,
|
||||
'contact' => auth()->guard('contact')->user()->hashed_id,
|
||||
'account_key' => $recurring_invoice->client->custom_value2,
|
||||
];
|
||||
$context = [
|
||||
'context' => 'cancellation',
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'recurring_invoice' => $recurring_invoice->hashed_id,
|
||||
'client' => $recurring_invoice->client->hashed_id,
|
||||
'contact' => auth()->guard('contact')->user()->hashed_id,
|
||||
'account_key' => $recurring_invoice->client->custom_value2,
|
||||
];
|
||||
|
||||
$this->triggerWebhook($context);
|
||||
$this->triggerWebhook($context);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->guard('contact')->user()))->build()));
|
||||
$nmo->company = $recurring_invoice->company;
|
||||
$nmo->settings = $recurring_invoice->company->settings;
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->guard('contact')->user()))->build()));
|
||||
$nmo->company = $recurring_invoice->company;
|
||||
$nmo->settings = $recurring_invoice->company->settings;
|
||||
|
||||
$recurring_invoice->company->company_users->each(function ($company_user) use ($nmo){
|
||||
|
||||
$recurring_invoice->company->company_users->each(function ($company_user) use ($nmo) {
|
||||
$methods = $this->findCompanyUserNotificationType($company_user, ['recurring_cancellation', 'all_notifications']);
|
||||
$methods = $this->findCompanyUserNotificationType($company_user, ['recurring_cancellation', 'all_notifications']);
|
||||
|
||||
//if mail is a method type -fire mail!!
|
||||
if (($key = array_search('mail', $methods)) !== false) {
|
||||
unset($methods[$key]);
|
||||
//if mail is a method type -fire mail!!
|
||||
if (($key = array_search('mail', $methods)) !== false) {
|
||||
unset($methods[$key]);
|
||||
|
||||
$nmo->to_user = $company_user->user;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
}
|
||||
});
|
||||
$nmo->to_user = $company_user->user;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
return $this->handleRedirect('client/subscriptions');
|
||||
|
||||
return $this->handleRedirect('client/subscriptions');
|
||||
}
|
||||
|
||||
private function getDaysInFrequency()
|
||||
{
|
||||
|
||||
switch ($this->subscription->frequency_id) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return 1;
|
||||
@ -943,6 +1006,7 @@ class SubscriptionService
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getNextDateForFrequency($date, $frequency)
|
||||
@ -974,25 +1038,27 @@ class SubscriptionService
|
||||
return $date->addYears(3);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 'email' => $this->email ?? $this->contact->email,
|
||||
* 'quantity' => $this->quantity,
|
||||
* 'contact_id' => $this->contact->id,
|
||||
*/
|
||||
* 'email' => $this->email ?? $this->contact->email,
|
||||
* 'quantity' => $this->quantity,
|
||||
* 'contact_id' => $this->contact->id,
|
||||
*/
|
||||
public function handleNoPaymentRequired(array $data)
|
||||
{
|
||||
|
||||
$context = (new ZeroCostProduct($this->subscription, $data))->run();
|
||||
|
||||
// Forward payload to webhook
|
||||
if (array_key_exists('context', $context)) {
|
||||
if(array_key_exists('context', $context))
|
||||
$response = $this->triggerWebhook($context);
|
||||
}
|
||||
|
||||
// Hit the redirect
|
||||
return $this->handleRedirect($context['redirect_url']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1000,36 +1066,36 @@ class SubscriptionService
|
||||
*/
|
||||
private function handleRedirect($default_redirect)
|
||||
{
|
||||
if (array_key_exists('return_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['return_url']) >= 1) {
|
||||
|
||||
if(array_key_exists('return_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['return_url']) >=1)
|
||||
return redirect($this->subscription->webhook_configuration['return_url']);
|
||||
}
|
||||
|
||||
return redirect($default_redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Invoice $invoice
|
||||
* @return true
|
||||
* @throws BindingResolutionException
|
||||
* @param Invoice $invoice
|
||||
* @return true
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function planPaid(Invoice $invoice)
|
||||
{
|
||||
$recurring_invoice_hashed_id = $invoice->recurring_invoice()->exists() ? $invoice->recurring_invoice->hashed_id : null;
|
||||
|
||||
$context = [
|
||||
'context' => 'plan_paid',
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'recurring_invoice' => $recurring_invoice_hashed_id,
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first()->hashed_id : $invoice->client->contacts->first()->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'account_key' => $invoice->client->custom_value2,
|
||||
];
|
||||
$context = [
|
||||
'context' => 'plan_paid',
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'recurring_invoice' => $recurring_invoice_hashed_id,
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first()->hashed_id: $invoice->client->contacts->first()->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'account_key' => $invoice->client->custom_value2,
|
||||
];
|
||||
|
||||
$response = $this->triggerWebhook($context);
|
||||
|
||||
nlog($response);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'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,
|
||||
'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0,
|
||||
];
|
||||
}
|
||||
|
||||
@ -110,6 +111,5 @@ class AccountTransformer extends EntityTransformer
|
||||
|
||||
return $this->includeItem(auth()->user(), $transformer, User::class);
|
||||
|
||||
// return $this->includeItem($account->default_company->owner(), $transformer, User::class);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class ExpenseTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
use SoftDeletes;
|
||||
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'documents',
|
||||
];
|
||||
@ -78,8 +78,7 @@ class ExpenseTransformer extends EntityTransformer
|
||||
'transaction_reference' => (string) $expense->transaction_reference ?: '',
|
||||
'transaction_id' => (string) $expense->transaction_id ?: '',
|
||||
'date' => $expense->date ?: '',
|
||||
//'expense_date' => $expense->date ?: '',
|
||||
'number' => (string) $expense->number ?: '',
|
||||
'number' => (string)$expense->number ?: '',
|
||||
'payment_date' => $expense->payment_date ?: '',
|
||||
'custom_value1' => $expense->custom_value1 ?: '',
|
||||
'custom_value2' => $expense->custom_value2 ?: '',
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Transformers\DocumentTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderTransformer extends EntityTransformer
|
||||
@ -21,6 +23,11 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'invitations',
|
||||
'documents'
|
||||
];
|
||||
|
||||
protected $availableIncludes = [
|
||||
'expense'
|
||||
];
|
||||
|
||||
public function includeInvitations(PurchaseOrder $purchase_order)
|
||||
@ -30,6 +37,21 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
return $this->includeCollection($purchase_order->invitations, $transformer, PurchaseOrderInvitation::class);
|
||||
}
|
||||
|
||||
|
||||
public function includeDocuments(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$transformer = new DocumentTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($purchase_order->documents, $transformer, Document::class);
|
||||
}
|
||||
|
||||
public function includeExpense(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($purchase_order->expense, $transformer, Document::class);
|
||||
}
|
||||
|
||||
public function transform(PurchaseOrder $purchase_order)
|
||||
{
|
||||
return [
|
||||
@ -37,18 +59,18 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
'user_id' => $this->encodePrimaryKey($purchase_order->user_id),
|
||||
'project_id' => $this->encodePrimaryKey($purchase_order->project_id),
|
||||
'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id),
|
||||
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'amount' => (float) $purchase_order->amount,
|
||||
'balance' => (float) $purchase_order->balance,
|
||||
'client_id' => (string) $this->encodePrimaryKey($purchase_order->client_id),
|
||||
'status_id' => (string) ($purchase_order->status_id ?: 1),
|
||||
'design_id' => (string) $this->encodePrimaryKey($purchase_order->design_id),
|
||||
'created_at' => (int) $purchase_order->created_at,
|
||||
'updated_at' => (int) $purchase_order->updated_at,
|
||||
'archived_at' => (int) $purchase_order->deleted_at,
|
||||
'is_deleted' => (bool) $purchase_order->is_deleted,
|
||||
'vendor_id' => (string)$this->encodePrimaryKey($purchase_order->vendor_id),
|
||||
'amount' => (float)$purchase_order->amount,
|
||||
'balance' => (float)$purchase_order->balance,
|
||||
'client_id' => (string)$this->encodePrimaryKey($purchase_order->client_id),
|
||||
'status_id' => (string)($purchase_order->status_id ?: 1),
|
||||
'design_id' => (string)$this->encodePrimaryKey($purchase_order->design_id),
|
||||
'created_at' => (int)$purchase_order->created_at,
|
||||
'updated_at' => (int)$purchase_order->updated_at,
|
||||
'archived_at' => (int)$purchase_order->deleted_at,
|
||||
'is_deleted' => (bool)$purchase_order->is_deleted,
|
||||
'number' => $purchase_order->number ?: '',
|
||||
'discount' => (float) $purchase_order->discount,
|
||||
'discount' => (float)$purchase_order->discount,
|
||||
'po_number' => $purchase_order->po_number ?: '',
|
||||
'date' => $purchase_order->date ?: '',
|
||||
'last_sent_date' => $purchase_order->last_sent_date ?: '',
|
||||
@ -61,37 +83,39 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
'terms' => $purchase_order->terms ?: '',
|
||||
'public_notes' => $purchase_order->public_notes ?: '',
|
||||
'private_notes' => $purchase_order->private_notes ?: '',
|
||||
'uses_inclusive_taxes' => (bool) $purchase_order->uses_inclusive_taxes,
|
||||
'uses_inclusive_taxes' => (bool)$purchase_order->uses_inclusive_taxes,
|
||||
'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '',
|
||||
'tax_rate1' => (float) $purchase_order->tax_rate1,
|
||||
'tax_rate1' => (float)$purchase_order->tax_rate1,
|
||||
'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '',
|
||||
'tax_rate2' => (float) $purchase_order->tax_rate2,
|
||||
'tax_rate2' => (float)$purchase_order->tax_rate2,
|
||||
'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '',
|
||||
'tax_rate3' => (float) $purchase_order->tax_rate3,
|
||||
'total_taxes' => (float) $purchase_order->total_taxes,
|
||||
'is_amount_discount' => (bool) ($purchase_order->is_amount_discount ?: false),
|
||||
'tax_rate3' => (float)$purchase_order->tax_rate3,
|
||||
'total_taxes' => (float)$purchase_order->total_taxes,
|
||||
'is_amount_discount' => (bool)($purchase_order->is_amount_discount ?: false),
|
||||
'footer' => $purchase_order->footer ?: '',
|
||||
'partial' => (float) ($purchase_order->partial ?: 0.0),
|
||||
'partial' => (float)($purchase_order->partial ?: 0.0),
|
||||
'partial_due_date' => $purchase_order->partial_due_date ?: '',
|
||||
'custom_value1' => (string) $purchase_order->custom_value1 ?: '',
|
||||
'custom_value2' => (string) $purchase_order->custom_value2 ?: '',
|
||||
'custom_value3' => (string) $purchase_order->custom_value3 ?: '',
|
||||
'custom_value4' => (string) $purchase_order->custom_value4 ?: '',
|
||||
'has_tasks' => (bool) $purchase_order->has_tasks,
|
||||
'has_expenses' => (bool) $purchase_order->has_expenses,
|
||||
'custom_surcharge1' => (float) $purchase_order->custom_surcharge1,
|
||||
'custom_surcharge2' => (float) $purchase_order->custom_surcharge2,
|
||||
'custom_surcharge3' => (float) $purchase_order->custom_surcharge3,
|
||||
'custom_surcharge4' => (float) $purchase_order->custom_surcharge4,
|
||||
'custom_surcharge_tax1' => (bool) $purchase_order->custom_surcharge_tax1,
|
||||
'custom_surcharge_tax2' => (bool) $purchase_order->custom_surcharge_tax2,
|
||||
'custom_surcharge_tax3' => (bool) $purchase_order->custom_surcharge_tax3,
|
||||
'custom_surcharge_tax4' => (bool) $purchase_order->custom_surcharge_tax4,
|
||||
'line_items' => $purchase_order->line_items ?: (array) [],
|
||||
'entity_type' => 'purchase_order',
|
||||
'exchange_rate' => (float) $purchase_order->exchange_rate,
|
||||
'paid_to_date' => (float) $purchase_order->paid_to_date,
|
||||
'custom_value1' => (string)$purchase_order->custom_value1 ?: '',
|
||||
'custom_value2' => (string)$purchase_order->custom_value2 ?: '',
|
||||
'custom_value3' => (string)$purchase_order->custom_value3 ?: '',
|
||||
'custom_value4' => (string)$purchase_order->custom_value4 ?: '',
|
||||
'has_tasks' => (bool)$purchase_order->has_tasks,
|
||||
'has_expenses' => (bool)$purchase_order->has_expenses,
|
||||
'custom_surcharge1' => (float)$purchase_order->custom_surcharge1,
|
||||
'custom_surcharge2' => (float)$purchase_order->custom_surcharge2,
|
||||
'custom_surcharge3' => (float)$purchase_order->custom_surcharge3,
|
||||
'custom_surcharge4' => (float)$purchase_order->custom_surcharge4,
|
||||
'custom_surcharge_tax1' => (bool)$purchase_order->custom_surcharge_tax1,
|
||||
'custom_surcharge_tax2' => (bool)$purchase_order->custom_surcharge_tax2,
|
||||
'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3,
|
||||
'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4,
|
||||
'line_items' => $purchase_order->line_items ?: (array)[],
|
||||
'entity_type' => 'purchaseOrder',
|
||||
'exchange_rate' => (float)$purchase_order->exchange_rate,
|
||||
'paid_to_date' => (float)$purchase_order->paid_to_date,
|
||||
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),
|
||||
'expense_id' => $this->encodePrimaryKey($purchase_order->expense_id),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,10 @@ namespace App\Transformers;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Transformers\TaskStatusTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
* class TaskTransformer.
|
||||
@ -30,6 +33,8 @@ class TaskTransformer extends EntityTransformer
|
||||
* @var array
|
||||
*/
|
||||
protected $availableIncludes = [
|
||||
'client',
|
||||
'status'
|
||||
];
|
||||
|
||||
public function includeDocuments(Task $task)
|
||||
@ -39,6 +44,27 @@ class TaskTransformer extends EntityTransformer
|
||||
return $this->includeCollection($task->documents, $transformer, Document::class);
|
||||
}
|
||||
|
||||
public function includeClient(Task $task): ?Item
|
||||
{
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
if(!$task->client)
|
||||
return null;
|
||||
|
||||
return $this->includeItem($task->client, $transformer, Client::class);
|
||||
}
|
||||
|
||||
public function includeStatus(Task $task): ?Item
|
||||
{
|
||||
$transformer = new TaskStatusTransformer($this->serializer);
|
||||
|
||||
if(!$task->status)
|
||||
return null;
|
||||
|
||||
return $this->includeItem($task->status, $transformer, TaskStatus::class);
|
||||
}
|
||||
|
||||
|
||||
public function transform(Task $task)
|
||||
{
|
||||
return [
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Helpers\SwissQr\SwissQrGenerator;
|
||||
use App\Models\Country;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\GatewayType;
|
||||
@ -160,6 +161,17 @@ class HtmlEngine
|
||||
if ($this->entity->vendor) {
|
||||
$data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')];
|
||||
}
|
||||
|
||||
if(strlen($this->company->getSetting('qr_iban')) > 5 && strlen($this->company->getSetting('besr_id')) > 1)
|
||||
{
|
||||
try{
|
||||
$data['$swiss_qr'] = ['value' => (new SwissQrGenerator($this->entity, $this->company))->run(), 'label' => ''];
|
||||
}
|
||||
catch(\Exception $e){
|
||||
$data['$swiss_qr'] = ['value' => '', 'label' => ''];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'quote') {
|
||||
@ -275,6 +287,7 @@ class HtmlEngine
|
||||
$data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')];
|
||||
|
||||
$data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
|
||||
|
||||
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
|
@ -16,16 +16,22 @@ use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\MakesTemplateData;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use DB;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Str;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
|
||||
|
||||
@ -79,6 +85,7 @@ class TemplateEngine
|
||||
|
||||
public function build()
|
||||
{
|
||||
|
||||
return $this->setEntity()
|
||||
->setSettingsObject()
|
||||
->setTemplates()
|
||||
@ -89,7 +96,7 @@ class TemplateEngine
|
||||
private function setEntity()
|
||||
{
|
||||
if (strlen($this->entity) > 1 && strlen($this->entity_id) > 1) {
|
||||
$class = 'App\Models\\'.ucfirst($this->entity);
|
||||
$class = 'App\Models\\'.ucfirst(Str::camel($this->entity));
|
||||
$this->entity_obj = $class::withTrashed()->where('id', $this->decodePrimaryKey($this->entity_id))->company()->first();
|
||||
} else {
|
||||
$this->mockEntity();
|
||||
@ -100,7 +107,11 @@ class TemplateEngine
|
||||
|
||||
private function setSettingsObject()
|
||||
{
|
||||
if ($this->entity_obj) {
|
||||
if($this->entity == 'purchaseOrder'){
|
||||
$this->settings_entity = auth()->user()->company();
|
||||
$this->settings = $this->settings_entity->settings;
|
||||
}
|
||||
elseif ($this->entity_obj->client()->exists()) {
|
||||
$this->settings_entity = $this->entity_obj->client;
|
||||
$this->settings = $this->settings_entity->getMergedSettings();
|
||||
} else {
|
||||
@ -144,9 +155,13 @@ class TemplateEngine
|
||||
$this->raw_body = $this->body;
|
||||
$this->raw_subject = $this->subject;
|
||||
|
||||
if ($this->entity_obj) {
|
||||
if($this->entity == 'purchaseOrder'){
|
||||
$this->fakerValues();
|
||||
}
|
||||
elseif ($this->entity_obj->client()->exists()) {
|
||||
$this->entityValues($this->entity_obj->client->primary_contact()->first());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$this->fakerValues();
|
||||
}
|
||||
|
||||
@ -168,16 +183,19 @@ class TemplateEngine
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
$this->body = $converter->convert($this->body);
|
||||
$this->body = $converter->convert($this->body)->getContent();
|
||||
|
||||
}
|
||||
|
||||
private function entityValues($contact)
|
||||
{
|
||||
$this->labels_and_values = (new HtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
|
||||
if($this->entity == 'purchaseOrder')
|
||||
$this->labels_and_values = (new VendorHtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
|
||||
else
|
||||
$this->labels_and_values = (new HtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
|
||||
|
||||
$this->body = strtr($this->body, $this->labels_and_values['labels']);
|
||||
$this->body = strtr($this->body, $this->labels_and_values['values']);
|
||||
// $this->body = str_replace("\n", "<br>", $this->body);
|
||||
|
||||
$this->subject = strtr($this->subject, $this->labels_and_values['labels']);
|
||||
$this->subject = strtr($this->subject, $this->labels_and_values['values']);
|
||||
@ -199,7 +217,18 @@ class TemplateEngine
|
||||
$data['footer'] = '';
|
||||
$data['logo'] = auth()->user()->company()->present()->logo();
|
||||
|
||||
$data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client));
|
||||
if($this->entity_obj->client()->exists())
|
||||
$data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client));
|
||||
else{
|
||||
|
||||
$data['signature'] = $this->settings->email_signature;
|
||||
$data['settings'] = $this->settings;
|
||||
$data['whitelabel'] = $this->entity_obj ? $this->entity_obj->company->account->isPaid() : true;
|
||||
$data['company'] = $this->entity_obj ? $this->entity_obj->company : '';
|
||||
$data['settings'] = $this->settings;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ($email_style == 'custom') {
|
||||
$wrapper = $this->settings_entity->getSetting('email_style_custom');
|
||||
@ -240,8 +269,13 @@ class TemplateEngine
|
||||
|
||||
private function mockEntity()
|
||||
{
|
||||
if(!$this->entity && $this->template && str_contains($this->template, 'purchase_order'))
|
||||
$this->entity = 'purchaseOrder';
|
||||
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$vendor = false;
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
@ -285,12 +319,60 @@ class TemplateEngine
|
||||
]);
|
||||
}
|
||||
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('client', $client);
|
||||
$this->entity_obj->setRelation('company', auth()->user()->company());
|
||||
$this->entity_obj->load('client');
|
||||
$client->setRelation('company', auth()->user()->company());
|
||||
$client->load('company');
|
||||
|
||||
|
||||
if($this->entity == 'purchaseOrder')
|
||||
{
|
||||
|
||||
$vendor = Vendor::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
]);
|
||||
|
||||
$contact = VendorContact::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
|
||||
$this->entity_obj = PurchaseOrder::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'vendor_id' => $vendor->id,
|
||||
]);
|
||||
|
||||
$invitation = PurchaseOrderInvitation::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'purchase_order_id' => $this->entity_obj->id,
|
||||
'vendor_contact_id' => $contact->id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
if($vendor)
|
||||
{
|
||||
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('vendor', $vendor);
|
||||
$this->entity_obj->setRelation('company', auth()->user()->company());
|
||||
$this->entity_obj->load('vendor');
|
||||
$vendor->setRelation('company', auth()->user()->company());
|
||||
$vendor->load('company');
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->entity_obj->setRelation('invitations', $invitation);
|
||||
$this->entity_obj->setRelation('client', $client);
|
||||
$this->entity_obj->setRelation('company', auth()->user()->company());
|
||||
$this->entity_obj->load('client');
|
||||
$client->setRelation('company', auth()->user()->company());
|
||||
$client->load('company');
|
||||
}
|
||||
}
|
||||
|
||||
private function tearDown()
|
||||
|
@ -109,6 +109,10 @@ trait AppSetup
|
||||
'subject' => EmailTemplateDefaults::emailCreditSubject(),
|
||||
'body' => EmailTemplateDefaults::emailCreditTemplate(),
|
||||
],
|
||||
'purchase_order' => [
|
||||
'subject' => EmailTemplateDefaults::emailPurchaseOrderSubject(),
|
||||
'body' => EmailTemplateDefaults::emailPurchaseOrderTemplate(),
|
||||
],
|
||||
];
|
||||
|
||||
Cache::forever($name, $data);
|
||||
|
@ -114,6 +114,9 @@ trait CompanySettingsSaver
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
$value = 'integer';
|
||||
|
||||
if($key == 'besr_id')
|
||||
$value = 'string';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
} elseif (! $this->checkAttribute($value, $settings->{$key})) {
|
||||
@ -182,6 +185,9 @@ trait CompanySettingsSaver
|
||||
$value = 'string';
|
||||
}
|
||||
|
||||
if($key == 'besr_id')
|
||||
$value = 'string';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
} elseif ($this->checkAttribute($value, $settings->{$key})) {
|
||||
|
@ -200,56 +200,33 @@ trait MakesTemplateData
|
||||
$data['$task.tax_name3'] = ['value' => 'CA Sales Tax', 'label' => ctrans('texts.tax')];
|
||||
$data['$task.line_total'] = ['value' => '$100.00', 'label' => ctrans('texts.line_total')];
|
||||
|
||||
//$data['$paid_to_date'] = ;
|
||||
// $data['$your_invoice'] = ;
|
||||
// $data['$quote'] = ;
|
||||
// $data['$your_quote'] = ;
|
||||
// $data['$invoice_issued_to'] = ;
|
||||
// $data['$quote_issued_to'] = ;
|
||||
// $data['$rate'] = ;
|
||||
// $data['$hours'] = ;
|
||||
// $data['$from'] = ;
|
||||
// $data['$to'] = ;
|
||||
// $data['$invoice_to'] = ;
|
||||
// $data['$quote_to'] = ;
|
||||
// $data['$details'] = ;
|
||||
// $data['custom_label1'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label2'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label3'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label4'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
//$data['$blank'] = ;
|
||||
//$data['$surcharge'] = ;
|
||||
/*
|
||||
$data['$tax_invoice'] =
|
||||
$data['$tax_quote'] =
|
||||
$data['$statement'] = ;
|
||||
$data['$statement_date'] = ;
|
||||
$data['$your_statement'] = ;
|
||||
$data['$statement_issued_to'] = ;
|
||||
$data['$statement_to'] = ;
|
||||
$data['$credit_note'] = ;
|
||||
$data['$credit_date'] = ;
|
||||
$data['$credit_issued_to'] = ;
|
||||
$data['$credit_to'] = ;
|
||||
$data['$your_credit'] = ;
|
||||
$data['$phone'] = ;
|
||||
$data['$vendor_name'] = ['value' => 'Joey Diaz Denkins', 'label' => ctrans('texts.vendor_name')];;
|
||||
$data['$vendor.name'] = &$data['$vendor_name'];
|
||||
$data['$vendor'] = &$data['$vendor_name'];
|
||||
|
||||
$data['$outstanding'] = ;
|
||||
$data['$invoice_due_date'] = ;
|
||||
$data['$quote_due_date'] = ;
|
||||
$data['$service'] = ;
|
||||
$data['$product_key'] = ;
|
||||
$data['$unit_cost'] = ;
|
||||
$data['$custom_value1'] = ;
|
||||
$data['$custom_value2'] = ;
|
||||
$data['$delivery_note'] = ;
|
||||
$data['$date'] = ;
|
||||
$data['$method'] = ;
|
||||
$data['$payment_date'] = ;
|
||||
$data['$reference'] = ;
|
||||
$data['$amount'] = ;
|
||||
$data['$amount_paid'] =;
|
||||
*/
|
||||
$data['$vendor.address1'] = &$data['$address1'];
|
||||
$data['$vendor.address2'] = &$data['$address2'];
|
||||
$data['$vendor_address'] = ['value' => '5 Kalamazoo Way\n Jimbuckeroo\n USA 90210', 'label' => ctrans('texts.address')];
|
||||
$data['$vendor.address'] = &$data['$vendor_address'];
|
||||
$data['$vendor.postal_code'] = ['value' => '90210', 'label' => ctrans('texts.postal_code')];
|
||||
$data['$vendor.public_notes'] = $data['$invoice.public_notes'];
|
||||
$data['$vendor.city'] = &$data['$company.city'];
|
||||
$data['$vendor.state'] = &$data['$company.state'];
|
||||
$data['$vendor.id_number'] = &$data['$id_number'];
|
||||
$data['$vendor.vat_number'] = &$data['$vat_number'];
|
||||
$data['$vendor.website'] = &$data['$website'];
|
||||
$data['$vendor.phone'] = &$data['$phone'];
|
||||
$data['$vendor.city_state_postal'] = &$data['$city_state_postal'];
|
||||
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
|
||||
$data['$vendor.country'] = &$data['$country'];
|
||||
$data['$vendor.email'] = &$data['$email'];
|
||||
|
||||
$data['$vendor.billing_address1'] = &$data['$vendor.address1'];
|
||||
$data['$vendor.billing_address2'] = &$data['$vendor.address2'];
|
||||
$data['$vendor.billing_city'] = &$data['$vendor.city'];
|
||||
$data['$vendor.billing_state'] = &$data['$vendor.state'];
|
||||
$data['$vendor.billing_postal_code'] = &$data['$vendor.postal_code'];
|
||||
$data['$vendor.billing_country'] = &$data['$vendor.country'];
|
||||
|
||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||
|
@ -35,9 +35,9 @@ trait SettingsSaver
|
||||
ksort($casts);
|
||||
|
||||
foreach ($casts as $key => $value) {
|
||||
|
||||
|
||||
//try casting floats here
|
||||
if ($value == 'float' && property_exists($settings, $key)) {
|
||||
if($value == 'float' && property_exists($settings, $key)){
|
||||
$settings->{$key} = floatval($settings->{$key});
|
||||
}
|
||||
|
||||
@ -52,12 +52,11 @@ trait SettingsSaver
|
||||
continue;
|
||||
}
|
||||
/*Separate loop if it is a _id field which is an integer cast as a string*/
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && strlen($settings->{$key}) >= 1)) {
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && strlen($settings->{$key}) >= 1)) {
|
||||
$value = 'integer';
|
||||
|
||||
if ($key == 'gmail_sending_user_id') {
|
||||
if($key == 'gmail_sending_user_id' || $key == 'besr_id')
|
||||
$value = 'string';
|
||||
}
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
@ -99,9 +98,9 @@ trait SettingsSaver
|
||||
case 'real':
|
||||
case 'float':
|
||||
case 'double':
|
||||
return ! is_string($value) && (is_float($value) || is_numeric(strval($value)));
|
||||
return !is_string($value) && (is_float($value) || is_numeric(strval($value)));
|
||||
case 'string':
|
||||
return ! is_int($value) || (is_string($value) && method_exists($value, '__toString')) || is_null($value) || is_string($value);
|
||||
return !is_int($value) || ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value);
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
|
@ -60,8 +60,13 @@ class VendorHtmlEngine
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact->load('vendor');
|
||||
|
||||
$this->vendor = $this->contact->vendor->load('company', 'country');
|
||||
|
||||
$this->vendor = $this->contact->vendor->load('company','country');
|
||||
|
||||
if(!$this->vendor->currency_id){
|
||||
$this->vendor->currency_id = $this->company->settings->currency_id;
|
||||
$this->vendor->save();
|
||||
}
|
||||
|
||||
$this->entity->load('vendor');
|
||||
|
||||
@ -72,35 +77,36 @@ class VendorHtmlEngine
|
||||
$this->helpers = new Helpers();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private function resolveEntityString()
|
||||
{
|
||||
switch ($this->invitation) {
|
||||
case $this->invitation instanceof InvoiceInvitation:
|
||||
case ($this->invitation instanceof InvoiceInvitation):
|
||||
return 'invoice';
|
||||
break;
|
||||
case $this->invitation instanceof CreditInvitation:
|
||||
case ($this->invitation instanceof CreditInvitation):
|
||||
return 'credit';
|
||||
break;
|
||||
case $this->invitation instanceof QuoteInvitation:
|
||||
case ($this->invitation instanceof QuoteInvitation):
|
||||
return 'quote';
|
||||
break;
|
||||
case $this->invitation instanceof RecurringInvoiceInvitation:
|
||||
case ($this->invitation instanceof RecurringInvoiceInvitation):
|
||||
return 'recurring_invoice';
|
||||
break;
|
||||
case $this->invitation instanceof PurchaseOrderInvitation:
|
||||
case ($this->invitation instanceof PurchaseOrderInvitation):
|
||||
return 'purchase_order';
|
||||
break;
|
||||
default:
|
||||
// code...
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function buildEntityDataArray() :array
|
||||
{
|
||||
if (! $this->vendor->currency()) {
|
||||
if (! $this->vendor->currency()) {
|
||||
throw new Exception(debug_backtrace()[1]['function'], 1);
|
||||
exit;
|
||||
}
|
||||
@ -125,7 +131,7 @@ class VendorHtmlEngine
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.due_date')];
|
||||
|
||||
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
|
||||
$data['$dueDate'] = &$data['$due_date'];
|
||||
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
@ -157,23 +163,25 @@ class VendorHtmlEngine
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
|
||||
if ($this->entity->uses_inclusive_taxes) {
|
||||
if($this->entity->uses_inclusive_taxes)
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
} else {
|
||||
else
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
}
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
} else {
|
||||
if ($this->entity->status_id == 1) {
|
||||
|
||||
if($this->entity->status_id == 1){
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
} else {
|
||||
}
|
||||
else{
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
@ -201,7 +209,7 @@ class VendorHtmlEngine
|
||||
$data['$created_by_user'] = &$data['$user.name'];
|
||||
$data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')];
|
||||
|
||||
$data['$public_notes'] = ['value' => $this->entity->public_notes, 'label' => ctrans('texts.public_notes')];
|
||||
$data['$public_notes'] = ['value' => $this->entity->public_notes, 'label' => ctrans("texts.public_notes")];
|
||||
$data['$entity.public_notes'] = &$data['$public_notes'];
|
||||
$data['$notes'] = &$data['$public_notes'];
|
||||
|
||||
@ -225,13 +233,12 @@ class VendorHtmlEngine
|
||||
$data['$vat_number'] = ['value' => $this->vendor->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')];
|
||||
$data['$website'] = ['value' => $this->vendor->present()->website() ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$phone'] = ['value' => $this->vendor->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')];
|
||||
$data['$country'] = ['value' => isset($this->vendor->country->name) ? ctrans('texts.country_'.$this->vendor->country->name) : '', 'label' => ctrans('texts.country')];
|
||||
$data['$country'] = ['value' => isset($this->vendor->country->name) ? ctrans('texts.country_' . $this->vendor->country->name) : '', 'label' => ctrans('texts.country')];
|
||||
$data['$country_2'] = ['value' => isset($this->vendor->country) ? $this->vendor->country->iso_3166_2 : '', 'label' => ctrans('texts.country')];
|
||||
$data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')];
|
||||
|
||||
if (str_contains($data['$email']['value'], 'example.com')) {
|
||||
|
||||
if(str_contains($data['$email']['value'], 'example.com'))
|
||||
$data['$email'] = ['value' => '', 'label' => ctrans('texts.email')];
|
||||
}
|
||||
|
||||
$data['$vendor_name'] = ['value' => $this->vendor->present()->name() ?: ' ', 'label' => ctrans('texts.vendor_name')];
|
||||
$data['$vendor.name'] = &$data['$vendor_name'];
|
||||
@ -255,7 +262,7 @@ class VendorHtmlEngine
|
||||
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
|
||||
$data['$vendor.country'] = &$data['$country'];
|
||||
$data['$vendor.email'] = &$data['$email'];
|
||||
|
||||
|
||||
$data['$vendor.billing_address'] = &$data['$vendor_address'];
|
||||
$data['$vendor.billing_address1'] = &$data['$vendor.address1'];
|
||||
$data['$vendor.billing_address2'] = &$data['$vendor.address2'];
|
||||
@ -361,7 +368,7 @@ class VendorHtmlEngine
|
||||
$data['_rate2'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['_rate3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
|
||||
$data['$font_size'] = ['value' => $this->settings->font_size.'px', 'label' => ''];
|
||||
$data['$font_size'] = ['value' => $this->settings->font_size . 'px', 'label' => ''];
|
||||
$data['$font_name'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['name'], 'label' => ''];
|
||||
$data['$font_url'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['url'], 'label' => ''];
|
||||
|
||||
@ -375,7 +382,7 @@ class VendorHtmlEngine
|
||||
|
||||
$data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer), $this->company), 'label' => ''];
|
||||
$data['$footer'] = &$data['$entity_footer'];
|
||||
|
||||
|
||||
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
|
||||
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
|
||||
|
||||
@ -383,8 +390,8 @@ class VendorHtmlEngine
|
||||
$data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => ''];
|
||||
$data['$auto_bill'] = &$data['$autoBill'];
|
||||
|
||||
$data['$dir'] = ['value' => $this->company->language()?->locale === 'ar' ? 'rtl' : 'ltr', 'label' => ''];
|
||||
$data['$dir_text_align'] = ['value' => $this->company->language()?->locale === 'ar' ? 'right' : 'left', 'label' => ''];
|
||||
$data['$dir'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => ''];
|
||||
$data['$dir_text_align'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'right' : 'left', 'label' => ''];
|
||||
|
||||
$data['$payment.date'] = ['value' => ' ', 'label' => ctrans('texts.payment_date')];
|
||||
$data['$method'] = ['value' => ' ', 'label' => ctrans('texts.method')];
|
||||
@ -477,26 +484,25 @@ class VendorHtmlEngine
|
||||
$country = Country::find($this->settings->country_id);
|
||||
|
||||
if ($country) {
|
||||
return ctrans('texts.country_'.$country->name);
|
||||
return ctrans('texts.country_' . $country->name);
|
||||
}
|
||||
|
||||
return ' ';
|
||||
}
|
||||
|
||||
|
||||
private function getCountryCode() :string
|
||||
{
|
||||
$country = Country::find($this->settings->country_id);
|
||||
|
||||
if ($country) {
|
||||
if($country)
|
||||
return $country->iso_3166_2;
|
||||
}
|
||||
// if ($country) {
|
||||
// return ctrans('texts.country_' . $country->iso_3166_2);
|
||||
// }
|
||||
|
||||
return ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the way we are compiling the blade template we
|
||||
* have no ability to iterate, so in the case
|
||||
@ -669,8 +675,8 @@ html {
|
||||
|
||||
/**
|
||||
* Generate markup for HTML images on entity.
|
||||
*
|
||||
* @return string|void
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
protected function generateEntityImagesMarkup()
|
||||
{
|
||||
@ -680,11 +686,11 @@ html {
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$container = $dom->createElement('div');
|
||||
$container = $dom->createElement('div');
|
||||
$container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);');
|
||||
|
||||
foreach ($this->entity->documents as $document) {
|
||||
if (! $document->isImage()) {
|
||||
if (!$document->isImage()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,7 @@
|
||||
"setasign/fpdi": "^2.3",
|
||||
"socialiteproviders/apple": "^5.2",
|
||||
"socialiteproviders/microsoft": "^4.1",
|
||||
"sprain/swiss-qr-bill": "^3.2",
|
||||
"square/square": "13.0.0.20210721",
|
||||
"stripe/stripe-php": "^7.50",
|
||||
"symfony/http-client": "^6.0",
|
||||
|
@ -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.4.4',
|
||||
'app_tag' => '5.4.4',
|
||||
'app_version' => '5.4.10',
|
||||
'app_tag' => '5.4.10',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -194,4 +194,7 @@ return [
|
||||
'ninja_apple_bundle_id' => env('APPLE_BUNDLE_ID', false),
|
||||
'ninja_apple_issuer_id' => env('APPLE_ISSUER_ID', false),
|
||||
'react_app_enabled' => env('REACT_APP_ENABLED', false),
|
||||
'ninja_apple_client_id' => env('APPLE_CLIENT_ID', false),
|
||||
'ninja_apple_client_secret' => env('APPLE_CLIENT_SECRET',false),
|
||||
'ninja_apple_redirect_url' => env('APPLE_REDIRECT_URI',false),
|
||||
];
|
||||
|
51
database/factories/PurchaseOrderFactory.php
Normal file
51
database/factories/PurchaseOrderFactory.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class PurchaseOrderFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = PurchaseOrder::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'number' => $this->faker->ean13(),
|
||||
'discount' => $this->faker->numberBetween(1, 10),
|
||||
'is_amount_discount' => (bool) random_int(0, 1),
|
||||
'tax_name1' => 'GST',
|
||||
'tax_rate1' => 10,
|
||||
'tax_name2' => 'VAT',
|
||||
'tax_rate2' => 17.5,
|
||||
'is_deleted' => false,
|
||||
'po_number' => $this->faker->text(10),
|
||||
'date' => $this->faker->date(),
|
||||
'due_date' => $this->faker->date(),
|
||||
'line_items' => InvoiceItemFactory::generate(5),
|
||||
'terms' => $this->faker->text(500),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Gateway;
|
||||
use App\Models\GatewayType;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FortePaymentGateway extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$fields = new \stdClass;
|
||||
$fields->testMode = false;
|
||||
$fields->apiLoginId = "";
|
||||
$fields->apiAccessId = "";
|
||||
$fields->secureKey = "";
|
||||
$fields->authOrganizationId = "";
|
||||
$fields->organizationId = "";
|
||||
$fields->locationId = "";
|
||||
|
||||
$forte = new Gateway;
|
||||
$forte->id = 59;
|
||||
$forte->name = 'Forte';
|
||||
$forte->key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs';
|
||||
$forte->provider = 'Forte';
|
||||
$forte->is_offsite = true;
|
||||
$forte->fields = \json_encode($fields);
|
||||
$forte->visible = 1;
|
||||
$forte->site_url = 'https://www.forte.net/';
|
||||
$forte->default_gateway_type_id = GatewayType::CREDIT_CARD;
|
||||
$forte->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user