mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 18:27:16 -04:00
commit
13b24cc03e
@ -1 +1 @@
|
|||||||
5.10.24
|
5.10.25
|
@ -516,9 +516,10 @@ class CompanySettings extends BaseSettings
|
|||||||
public $quote_late_fee_amount1 = 0;
|
public $quote_late_fee_amount1 = 0;
|
||||||
public $quote_late_fee_percent1 = 0;
|
public $quote_late_fee_percent1 = 0;
|
||||||
|
|
||||||
|
public string $payment_flow = 'default'; //smooth
|
||||||
|
|
||||||
public static $casts = [
|
public static $casts = [
|
||||||
|
'payment_flow' => 'string',
|
||||||
'enable_quote_reminder1' => 'bool',
|
'enable_quote_reminder1' => 'bool',
|
||||||
'quote_num_days_reminder1' => 'int',
|
'quote_num_days_reminder1' => 'int',
|
||||||
'quote_schedule_reminder1' => 'string',
|
'quote_schedule_reminder1' => 'string',
|
||||||
|
@ -131,7 +131,8 @@ class BaseRule implements RuleInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldCalcTax(): bool {
|
public function shouldCalcTax(): bool
|
||||||
|
{
|
||||||
return $this->should_calc_tax && $this->checkIfInvoiceLocked();
|
return $this->should_calc_tax && $this->checkIfInvoiceLocked();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -404,8 +405,9 @@ class BaseRule implements RuleInterface
|
|||||||
{
|
{
|
||||||
$lock_invoices = $this->client->getSetting('lock_invoices');
|
$lock_invoices = $this->client->getSetting('lock_invoices');
|
||||||
|
|
||||||
if($this->invoice instanceof RecurringInvoice)
|
if($this->invoice instanceof RecurringInvoice) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($lock_invoices) {
|
switch ($lock_invoices) {
|
||||||
case 'off':
|
case 'off':
|
||||||
|
@ -251,8 +251,7 @@ class Rule extends BaseRule implements RuleInterface
|
|||||||
$this->reduced_tax_rate = 0;
|
$this->reduced_tax_rate = 0;
|
||||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||||
$this->defaultForeign();
|
$this->defaultForeign();
|
||||||
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || $this->client->has_valid_vat_number)) { //eu country / no valid vat
|
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || !$this->client->has_valid_vat_number)) { //eu country / no valid vat
|
||||||
// if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) {
|
|
||||||
if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
||||||
// nlog("eu zone with sales above threshold");
|
// nlog("eu zone with sales above threshold");
|
||||||
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
|
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
|
||||||
|
@ -48,8 +48,7 @@ class TaxModel
|
|||||||
public function migrate(): self
|
public function migrate(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if($this->version == 'alpha')
|
if($this->version == 'alpha') {
|
||||||
{
|
|
||||||
$this->regions->EU->subregions->PL = new \stdClass();
|
$this->regions->EU->subregions->PL = new \stdClass();
|
||||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace App\DataProviders;
|
namespace App\DataProviders;
|
||||||
|
|
||||||
final class CAProvinces {
|
final class CAProvinces
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* The provinces and territories of Canada
|
* The provinces and territories of Canada
|
||||||
*
|
*
|
||||||
@ -39,7 +40,8 @@ final class CAProvinces {
|
|||||||
* @param string $abbreviation
|
* @param string $abbreviation
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getName($abbreviation) {
|
public static function getName($abbreviation)
|
||||||
|
{
|
||||||
return self::$provinces[$abbreviation];
|
return self::$provinces[$abbreviation];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,8 @@ final class CAProvinces {
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function get() {
|
public static function get()
|
||||||
|
{
|
||||||
return self::$provinces;
|
return self::$provinces;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +61,8 @@ final class CAProvinces {
|
|||||||
* @param string $name
|
* @param string $name
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getAbbreviation($name) {
|
public static function getAbbreviation($name)
|
||||||
|
{
|
||||||
return array_search(ucwords($name), self::$provinces);
|
return array_search(ucwords($name), self::$provinces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,8 +269,7 @@ class ExpenseExport extends BaseExport
|
|||||||
|
|
||||||
if($expense->uses_inclusive_taxes) {
|
if($expense->uses_inclusive_taxes) {
|
||||||
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
|
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$entity['expense.net_amount'] = round($expense->amount, $precision);
|
$entity['expense.net_amount'] = round($expense->amount, $precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +162,9 @@ class ExpenseFilters extends QueryFilters
|
|||||||
{
|
{
|
||||||
$categories_exploded = explode(",", $categories);
|
$categories_exploded = explode(",", $categories);
|
||||||
|
|
||||||
if(empty($categories) || count(array_filter($categories_exploded)) == 0)
|
if(empty($categories) || count(array_filter($categories_exploded)) == 0) {
|
||||||
return $this->builder;
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
$categories_keys = $this->transformKeys($categories_exploded);
|
$categories_keys = $this->transformKeys($categories_exploded);
|
||||||
|
|
||||||
|
@ -254,17 +254,20 @@ class ActivityController extends BaseController
|
|||||||
$activity->client_id = $entity->client_id;
|
$activity->client_id = $entity->client_id;
|
||||||
$activity->project_id = $entity->project_id;
|
$activity->project_id = $entity->project_id;
|
||||||
$activity->vendor_id = $entity->vendor_id;
|
$activity->vendor_id = $entity->vendor_id;
|
||||||
|
// no break
|
||||||
case Task::class:
|
case Task::class:
|
||||||
$activity->task_id = $entity->id;
|
$activity->task_id = $entity->id;
|
||||||
$activity->expense_id = $entity->id;
|
$activity->expense_id = $entity->id;
|
||||||
$activity->client_id = $entity->client_id;
|
$activity->client_id = $entity->client_id;
|
||||||
$activity->project_id = $entity->project_id;
|
$activity->project_id = $entity->project_id;
|
||||||
$activity->vendor_id = $entity->vendor_id;
|
$activity->vendor_id = $entity->vendor_id;
|
||||||
|
// no break
|
||||||
case Payment::class:
|
case Payment::class:
|
||||||
$activity->payment_id = $entity->id;
|
$activity->payment_id = $entity->id;
|
||||||
$activity->expense_id = $entity->id;
|
$activity->expense_id = $entity->id;
|
||||||
$activity->client_id = $entity->client_id;
|
$activity->client_id = $entity->client_id;
|
||||||
$activity->project_id = $entity->project_id;
|
$activity->project_id = $entity->project_id;
|
||||||
|
// no break
|
||||||
default:
|
default:
|
||||||
# code...
|
# code...
|
||||||
break;
|
break;
|
||||||
|
@ -41,8 +41,9 @@ class ContactLoginController extends Controller
|
|||||||
$company = false;
|
$company = false;
|
||||||
$account = false;
|
$account = false;
|
||||||
|
|
||||||
if($request->query('intended'))
|
if($request->query('intended')) {
|
||||||
$request->session()->put('url.intended', $request->query('intended'));
|
$request->session()->put('url.intended', $request->query('intended'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->session()->has('company_key')) {
|
if ($request->session()->has('company_key')) {
|
||||||
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
|
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
|
||||||
@ -142,8 +143,9 @@ class ContactLoginController extends Controller
|
|||||||
|
|
||||||
$this->setRedirectPath();
|
$this->setRedirectPath();
|
||||||
|
|
||||||
if($intended)
|
if($intended) {
|
||||||
$this->redirectTo = $intended;
|
$this->redirectTo = $intended;
|
||||||
|
}
|
||||||
|
|
||||||
return $request->wantsJson()
|
return $request->wantsJson()
|
||||||
? new JsonResponse([], 204)
|
? new JsonResponse([], 204)
|
||||||
|
@ -19,7 +19,6 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
class BrevoController extends BaseController
|
class BrevoController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ class InvoiceController extends Controller
|
|||||||
|
|
||||||
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
|
if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
|
||||||
$invitation->markViewed();
|
$invitation->markViewed();
|
||||||
|
|
||||||
@ -77,13 +78,17 @@ class InvoiceController extends Controller
|
|||||||
'key' => $invitation ? $invitation->key : false,
|
'key' => $invitation ? $invitation->key : false,
|
||||||
'hash' => $hash,
|
'hash' => $hash,
|
||||||
'variables' => $variables,
|
'variables' => $variables,
|
||||||
|
'invoices' => [$invoice->hashed_id],
|
||||||
|
'db' => $invoice->company->db,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($request->query('mode') === 'fullscreen') {
|
if ($request->query('mode') === 'fullscreen') {
|
||||||
return render('invoices.show-fullscreen', $data);
|
return render('invoices.show-fullscreen', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('invoices.show', $data);
|
return auth()->guard('contact')->user()->client->getSetting('payment_flow') == 'default' ? $this->render('invoices.show', $data) : $this->render('invoices.show_smooth', $data);
|
||||||
|
|
||||||
|
// return $this->render('invoices.show_smooth', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showBlob($hash)
|
public function showBlob($hash)
|
||||||
@ -235,9 +240,12 @@ class InvoiceController extends Controller
|
|||||||
'hashed_ids' => $invoices->pluck('hashed_id'),
|
'hashed_ids' => $invoices->pluck('hashed_id'),
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
'variables' => $variables,
|
'variables' => $variables,
|
||||||
|
'invitation' => $invitation,
|
||||||
|
'db' => $invitation->company->db,
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->render('invoices.payment', $data);
|
// return $this->render('invoices.payment', $data);
|
||||||
|
return auth()->guard('contact')->user()->client->getSetting('payment_flow') === 'default' ? $this->render('invoices.payment', $data) : $this->render('invoices.show_smooth_multi', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
|
use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
|
||||||
use \Closure;
|
use Closure;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
@ -19,7 +19,6 @@ use App\Services\Import\Quickbooks\QuickbooksService;
|
|||||||
|
|
||||||
class ImportQuickbooksController extends BaseController
|
class ImportQuickbooksController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
private array $import_entities = [
|
private array $import_entities = [
|
||||||
'client' => 'Customer',
|
'client' => 'Customer',
|
||||||
'invoice' => 'Invoice',
|
'invoice' => 'Invoice',
|
||||||
@ -46,7 +45,6 @@ class ImportQuickbooksController extends BaseController
|
|||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*
|
*
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
|
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
|
||||||
{
|
{
|
||||||
@ -74,15 +72,17 @@ class ImportQuickbooksController extends BaseController
|
|||||||
$this->getData($data);
|
$this->getData($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getData($data) {
|
protected function getData($data)
|
||||||
|
{
|
||||||
|
|
||||||
$entity = $this->import_entities[$data['type']];
|
$entity = $this->import_entities[$data['type']];
|
||||||
$cache_name = "{$data['hash']}-{$data['type']}";
|
$cache_name = "{$data['hash']}-{$data['type']}";
|
||||||
// TODO: Get or put cache or DB?
|
// TODO: Get or put cache or DB?
|
||||||
if(! Cache::has($cache_name) )
|
if(! Cache::has($cache_name)) {
|
||||||
{
|
|
||||||
$contents = call_user_func([$this->service, "fetch{$entity}s"]);
|
$contents = call_user_func([$this->service, "fetch{$entity}s"]);
|
||||||
if($contents->isEmpty()) return;
|
if($contents->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cache::put($cache_name, base64_encode($contents->toJson()), 600);
|
Cache::put($cache_name, base64_encode($contents->toJson()), 600);
|
||||||
}
|
}
|
||||||
@ -117,20 +117,19 @@ class ImportQuickbooksController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function import(Request $request)
|
public function import(Request $request)
|
||||||
{
|
{
|
||||||
$hash = Str::random(32);
|
// $hash = Str::random(32);
|
||||||
foreach($request->input('import_types') as $type)
|
// foreach($request->input('import_types') as $type) {
|
||||||
{
|
// $this->preimport($type, $hash);
|
||||||
$this->preimport($type, $hash);
|
// }
|
||||||
}
|
// /** @var \App\Models\User $user */
|
||||||
/** @var \App\Models\User $user */
|
// // $user = auth()->user() ?? Auth::loginUsingId(60);
|
||||||
// $user = auth()->user() ?? Auth::loginUsingId(60);
|
// $data = ['import_types' => $request->input('import_types') ] + compact('hash');
|
||||||
$data = ['import_types' => $request->input('import_types') ] + compact('hash');
|
// if (Ninja::isHosted()) {
|
||||||
if (Ninja::isHosted()) {
|
// QuickbooksIngest::dispatch($data, $user->company());
|
||||||
QuickbooksIngest::dispatch( $data , $user->company() );
|
// } else {
|
||||||
} else {
|
// QuickbooksIngest::dispatch($data, $user->company());
|
||||||
QuickbooksIngest::dispatch($data, $user->company() );
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Processing'], 200);
|
// return response()->json(['message' => 'Processing'], 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
class MailgunWebhookController extends BaseController
|
class MailgunWebhookController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ use Illuminate\Support\Str;
|
|||||||
|
|
||||||
class OneTimeTokenController extends BaseController
|
class OneTimeTokenController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
@ -19,7 +19,6 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
class PostMarkController extends BaseController
|
class PostMarkController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -297,8 +297,7 @@ class PreviewController extends BaseController
|
|||||||
->setTemplate($design_object)
|
->setTemplate($design_object)
|
||||||
->mock();
|
->mock();
|
||||||
} catch(SyntaxError $e) {
|
} catch(SyntaxError $e) {
|
||||||
}
|
} catch(\Exception $e) {
|
||||||
catch(\Exception $e) {
|
|
||||||
return response()->json(['message' => 'invalid data access', 'errors' => ['design.design.body' => $e->getMessage()]], 422);
|
return response()->json(['message' => 'invalid data access', 'errors' => ['design.design.body' => $e->getMessage()]], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +181,9 @@ class SelfUpdateController extends BaseController
|
|||||||
|
|
||||||
public function checkVersion()
|
public function checkVersion()
|
||||||
{
|
{
|
||||||
if(Ninja::isHosted())
|
if(Ninja::isHosted()) {
|
||||||
return '5.10.SaaS';
|
return '5.10.SaaS';
|
||||||
|
}
|
||||||
|
|
||||||
return trim(file_get_contents(config('ninja.version_url')));
|
return trim(file_get_contents(config('ninja.version_url')));
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,10 @@ class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\Throttle
|
|||||||
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
|
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
|
||||||
{
|
{
|
||||||
$limiter = new DurationLimiter(
|
$limiter = new DurationLimiter(
|
||||||
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
|
$this->getRedisConnection(),
|
||||||
|
$key,
|
||||||
|
$maxAttempts,
|
||||||
|
$decaySeconds
|
||||||
);
|
);
|
||||||
|
|
||||||
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
|
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
|
||||||
|
@ -68,8 +68,9 @@ class StoreNoteRequest extends Request
|
|||||||
|
|
||||||
public function getEntity()
|
public function getEntity()
|
||||||
{
|
{
|
||||||
if(!$this->entity)
|
if(!$this->entity) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's')));
|
$class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's')));
|
||||||
return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
|
return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
|
||||||
|
@ -66,8 +66,7 @@ class ShowCalculatedFieldRequest extends Request
|
|||||||
$input['end_date'] = now()->format('Y-m-d');
|
$input['end_date'] = now()->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($input['period']) && $input['period'] == 'previous')
|
if(isset($input['period']) && $input['period'] == 'previous') {
|
||||||
{
|
|
||||||
$dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
|
$dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
|
||||||
$input['start_date'] = $dates[0];
|
$input['start_date'] = $dates[0];
|
||||||
$input['end_date'] = $dates[1];
|
$input['end_date'] = $dates[1];
|
||||||
|
@ -40,8 +40,9 @@ class BulkInvoiceRequest extends Request
|
|||||||
/** @var \App\Models\User $user */
|
/** @var \App\Models\User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key))
|
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key)) {
|
||||||
throw new DuplicatePaymentException('Duplicate request.', 429);
|
throw new DuplicatePaymentException('Duplicate request.', 429);
|
||||||
|
}
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key), true, 1);
|
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key), true, 1);
|
||||||
|
|
||||||
|
@ -80,8 +80,9 @@ class StorePaymentRequest extends Request
|
|||||||
/** @var \App\Models\User $user */
|
/** @var \App\Models\User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key))
|
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key)) {
|
||||||
throw new DuplicatePaymentException('Duplicate request.', 429);
|
throw new DuplicatePaymentException('Duplicate request.', 429);
|
||||||
|
}
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1);
|
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1);
|
||||||
|
|
||||||
|
@ -64,6 +64,6 @@ class AuthQuickbooksRequest extends FormRequest
|
|||||||
|
|
||||||
public function getCompany(): ?Company
|
public function getCompany(): ?Company
|
||||||
{
|
{
|
||||||
return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
|
return Company::query()->where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ use Illuminate\Auth\Access\AuthorizationException;
|
|||||||
|
|
||||||
class GenericReportRequest extends Request
|
class GenericReportRequest extends Request
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*
|
*
|
||||||
|
@ -15,7 +15,6 @@ use App\Http\Requests\Request;
|
|||||||
|
|
||||||
class DisconnectUserMailerRequest extends Request
|
class DisconnectUserMailerRequest extends Request
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*
|
*
|
||||||
|
@ -77,8 +77,7 @@ class UpdateUserRequest extends Request
|
|||||||
unset($input['oauth_user_token']);
|
unset($input['oauth_user_token']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($input['password']) && is_string($input['password']))
|
if(isset($input['password']) && is_string($input['password'])) {
|
||||||
{
|
|
||||||
$input['password'] = trim($input['password']);
|
$input['password'] = trim($input['password']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,7 @@ class ValidClientScheme implements ValidationRule, ValidatorAwareRule
|
|||||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
{
|
{
|
||||||
|
|
||||||
if(isset($value['Invoice']))
|
if(isset($value['Invoice'])) {
|
||||||
{
|
|
||||||
$r = new EInvoice();
|
$r = new EInvoice();
|
||||||
$errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
|
$errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ use Illuminate\Contracts\Validation\ValidatorAwareRule;
|
|||||||
*/
|
*/
|
||||||
class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The validator instance.
|
* The validator instance.
|
||||||
*
|
*
|
||||||
@ -35,8 +34,7 @@ class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
|||||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
{
|
{
|
||||||
|
|
||||||
if(isset($value['Invoice']))
|
if(isset($value['Invoice'])) {
|
||||||
{
|
|
||||||
$r = new EInvoice();
|
$r = new EInvoice();
|
||||||
$errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
|
$errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ class AccountComponent extends Component
|
|||||||
"authorization_type" => 'Online'
|
"authorization_type" => 'Online'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(public array $account) {
|
public function __construct(public array $account)
|
||||||
|
{
|
||||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields));
|
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,16 +34,20 @@ class AddressComponent extends Component
|
|||||||
'country' => 'US'
|
'country' => 'US'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(public array $address) {
|
public function __construct(public array $address)
|
||||||
|
{
|
||||||
if(strlen($this->address['state']) > 2) {
|
if(strlen($this->address['state']) > 2) {
|
||||||
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
|
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->attributes = $this->newAttributeBag(
|
$this->attributes = $this->newAttributeBag(
|
||||||
Arr::only(Arr::mapWithKeys($this->address, function ($item, $key) {
|
Arr::only(
|
||||||
|
Arr::mapWithKeys($this->address, function ($item, $key) {
|
||||||
return in_array($key, ['address1','address2','state']) ? [ (['address1' => 'address_1','address2' => 'address_2','state' => 'province_code'])[$key] => $item ] : [ $key => $item ];
|
return in_array($key, ['address1','address2','state']) ? [ (['address1' => 'address_1','address2' => 'address_2','state' => 'province_code'])[$key] => $item ] : [ $key => $item ];
|
||||||
}),
|
}),
|
||||||
$this->fields) );
|
$this->fields
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,12 +18,11 @@ use App\Models\ClientContact;
|
|||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
|
||||||
// Contact Component
|
// Contact Component
|
||||||
class ContactComponent extends Component
|
class ContactComponent extends Component
|
||||||
{
|
{
|
||||||
|
public function __construct(ClientContact $contact)
|
||||||
public function __construct(ClientContact $contact) {
|
{
|
||||||
|
|
||||||
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||||
'home_phone' => $contact->client->phone,
|
'home_phone' => $contact->client->phone,
|
||||||
|
@ -12,23 +12,24 @@
|
|||||||
namespace App\Import\Providers;
|
namespace App\Import\Providers;
|
||||||
|
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Factory\ProductFactory;
|
|
||||||
use App\Factory\ClientFactory;
|
use App\Factory\ClientFactory;
|
||||||
use App\Factory\InvoiceFactory;
|
use App\Factory\InvoiceFactory;
|
||||||
use App\Factory\PaymentFactory;
|
use App\Factory\PaymentFactory;
|
||||||
|
use App\Factory\ProductFactory;
|
||||||
|
use App\Import\ImportException;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use App\Repositories\ClientRepository;
|
use App\Repositories\ClientRepository;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
use App\Repositories\ProductRepository;
|
|
||||||
use App\Repositories\PaymentRepository;
|
use App\Repositories\PaymentRepository;
|
||||||
|
use App\Repositories\ProductRepository;
|
||||||
use App\Http\Requests\Client\StoreClientRequest;
|
use App\Http\Requests\Client\StoreClientRequest;
|
||||||
use App\Http\Requests\Product\StoreProductRequest;
|
|
||||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||||
use App\Http\Requests\Payment\StorePaymentRequest;
|
use App\Http\Requests\Payment\StorePaymentRequest;
|
||||||
|
use App\Http\Requests\Product\StoreProductRequest;
|
||||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
|
use App\Import\Transformer\Quickbooks\ClientTransformer;
|
||||||
use App\Import\Transformer\Quickbooks\InvoiceTransformer;
|
use App\Import\Transformer\Quickbooks\InvoiceTransformer;
|
||||||
use App\Import\Transformer\Quickbooks\ProductTransformer;
|
|
||||||
use App\Import\Transformer\Quickbooks\PaymentTransformer;
|
use App\Import\Transformer\Quickbooks\PaymentTransformer;
|
||||||
|
use App\Import\Transformer\Quickbooks\ProductTransformer;
|
||||||
|
|
||||||
class Quickbooks extends BaseImport
|
class Quickbooks extends BaseImport
|
||||||
{
|
{
|
||||||
@ -94,10 +95,11 @@ class Quickbooks extends BaseImport
|
|||||||
$this->entity_count['products'] = $count;
|
$this->entity_count['products'] = $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getData($type) {
|
public function getData($type)
|
||||||
|
{
|
||||||
|
|
||||||
// get the data from cache? file? or api ?
|
// get the data from cache? file? or api ?
|
||||||
return json_decode(base64_decode(Cache::get("{$this->hash}-$type")), 1);
|
return json_decode(base64_decode(Cache::get("{$this->hash}-{$type}")), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function payment()
|
public function payment()
|
||||||
@ -198,8 +200,7 @@ class Quickbooks extends BaseImport
|
|||||||
'error' => $validator->errors()->all(),
|
'error' => $validator->errors()->all(),
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
if(!Invoice::where('number',$invoice_data['number'])->get()->first())
|
if(!Invoice::where('number', $invoice_data['number'])->first()) {
|
||||||
{
|
|
||||||
$invoice = InvoiceFactory::create(
|
$invoice = InvoiceFactory::create(
|
||||||
$this->company->id,
|
$this->company->id,
|
||||||
$this->company->owner()->id
|
$this->company->owner()->id
|
||||||
|
@ -244,8 +244,7 @@ class Wave extends BaseImport implements ImportInterface
|
|||||||
if (empty($expense_data['vendor_id'])) {
|
if (empty($expense_data['vendor_id'])) {
|
||||||
$vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
|
$vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
|
||||||
|
|
||||||
if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor']))
|
if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor'])) {
|
||||||
{
|
|
||||||
$vendor_repository->save(
|
$vendor_repository->save(
|
||||||
['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
|
['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
|
||||||
$vendor = VendorFactory::create(
|
$vendor = VendorFactory::create(
|
||||||
|
@ -119,8 +119,9 @@ class BaseTransformer
|
|||||||
{
|
{
|
||||||
$code = array_key_exists($key, $data) ? $data[$key] : false;
|
$code = array_key_exists($key, $data) ? $data[$key] : false;
|
||||||
|
|
||||||
if(!$code)
|
if(!$code) {
|
||||||
return $this->company->settings->currency_id;
|
return $this->company->settings->currency_id;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||||
$currencies = app('currencies');
|
$currencies = app('currencies');
|
||||||
|
@ -24,7 +24,6 @@ use Illuminate\Support\Str;
|
|||||||
*/
|
*/
|
||||||
class ClientTransformer extends BaseTransformer
|
class ClientTransformer extends BaseTransformer
|
||||||
{
|
{
|
||||||
|
|
||||||
use CommonTrait {
|
use CommonTrait {
|
||||||
transform as preTransform;
|
transform as preTransform;
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@ class ClientTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
parent::__construct($company);
|
parent::__construct($company);
|
||||||
|
|
||||||
$this->model = new Model;
|
$this->model = new Model();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +72,8 @@ class ClientTransformer extends BaseTransformer
|
|||||||
return $transformed_data;
|
return $transformed_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getContacts($data) {
|
protected function getContacts($data)
|
||||||
|
{
|
||||||
return (new ClientContact())->fill([
|
return (new ClientContact())->fill([
|
||||||
'first_name' => $this->getString($data, 'GivenName'),
|
'first_name' => $this->getString($data, 'GivenName'),
|
||||||
'last_name' => $this->getString($data, 'FamilyName'),
|
'last_name' => $this->getString($data, 'FamilyName'),
|
||||||
@ -84,11 +84,13 @@ class ClientTransformer extends BaseTransformer
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getShipAddrCountry($data,$field) {
|
public function getShipAddrCountry($data, $field)
|
||||||
|
{
|
||||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBillAddrCountry($data,$field) {
|
public function getBillAddrCountry($data, $field)
|
||||||
|
{
|
||||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ trait CommonTrait
|
|||||||
{
|
{
|
||||||
protected $model;
|
protected $model;
|
||||||
|
|
||||||
public function getString($data,$field) {
|
public function getString($data, $field)
|
||||||
|
{
|
||||||
return Arr::get($data, $field);
|
return Arr::get($data, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@
|
|||||||
|
|
||||||
namespace App\Import\Transformer\Quickbooks;
|
namespace App\Import\Transformer\Quickbooks;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use App\Models\Invoice;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use App\Import\ImportException;
|
use Illuminate\Support\Str;
|
||||||
use App\DataMapper\InvoiceItem;
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Import\ImportException;
|
||||||
use App\Models\Invoice as Model;
|
use App\Models\Invoice as Model;
|
||||||
use App\Import\Transformer\BaseTransformer;
|
use App\Import\Transformer\BaseTransformer;
|
||||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||||
@ -48,7 +49,7 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
parent::__construct($company);
|
parent::__construct($company);
|
||||||
|
|
||||||
$this->model = new Model;
|
$this->model = new Model();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInvoiceStatus($data)
|
public function getInvoiceStatus($data)
|
||||||
@ -73,18 +74,19 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
'description' => $this->getString($item, 'Description'),
|
'description' => $this->getString($item, 'Description'),
|
||||||
'product_key' => $this->getString($item, 'Description'),
|
'product_key' => $this->getString($item, 'Description'),
|
||||||
'quantity' => (int) $this->getString($item, 'SalesItemLineDetail.Qty'),
|
'quantity' => (int) $this->getString($item, 'SalesItemLineDetail.Qty'),
|
||||||
'unit_price' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
'unit_price' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||||
'line_total' => (double) $this->getString($item,'Amount'),
|
'line_total' => (float) $this->getString($item, 'Amount'),
|
||||||
'cost' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
'cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||||
'product_cost' => (double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
'product_cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||||
'tax_amount' => (double) $this->getString($item,'TxnTaxDetail.TotalTax'),
|
'tax_amount' => (float) $this->getString($item, 'TxnTaxDetail.TotalTax'),
|
||||||
];
|
];
|
||||||
}, array_filter($this->getString($data, 'Line'), function ($item) {
|
}, array_filter($this->getString($data, 'Line'), function ($item) {
|
||||||
return $this->getString($item, 'DetailType') !== 'SubTotalLineDetail';
|
return $this->getString($item, 'DetailType') !== 'SubTotalLineDetail';
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInvoiceClient($data, $field = null) {
|
public function getInvoiceClient($data, $field = null)
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* "CustomerRef": {
|
* "CustomerRef": {
|
||||||
"value": "23",
|
"value": "23",
|
||||||
@ -145,8 +147,7 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
"BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address, 3, 'N/A')) + ['Line1' => $has_company ? $bill_address->Line3 : $bill_address->Line2 ],
|
"BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address, 3, 'N/A')) + ['Line1' => $has_company ? $bill_address->Line3 : $bill_address->Line2 ],
|
||||||
"ShipAddr" => $ship_address
|
"ShipAddr" => $ship_address
|
||||||
] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
|
] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
|
||||||
if($this->hasClient($client['CompanyName']))
|
if($this->hasClient($client['CompanyName'])) {
|
||||||
{
|
|
||||||
$client_id = $this->getClient($client['CompanyName'], $this->getString($client, 'PrimaryEmailAddr.Address'));
|
$client_id = $this->getClient($client['CompanyName'], $this->getString($client, 'PrimaryEmailAddr.Address'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,12 +162,12 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
|
|
||||||
public function getDeposit($data)
|
public function getDeposit($data)
|
||||||
{
|
{
|
||||||
return (double) $this->getString($data,'Deposit');
|
return (float) $this->getString($data, 'Deposit');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBalance($data)
|
public function getBalance($data)
|
||||||
{
|
{
|
||||||
return (double) $this->getString($data,'Balance');
|
return (float) $this->getString($data, 'Balance');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCustomerMemo($data)
|
public function getCustomerMemo($data)
|
||||||
@ -176,7 +177,8 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
|
|
||||||
public function getDocNumber($data, $field = null)
|
public function getDocNumber($data, $field = null)
|
||||||
{
|
{
|
||||||
return sprintf("%s-%s",
|
return sprintf(
|
||||||
|
"%s-%s",
|
||||||
$this->getString($data, 'DocNumber'),
|
$this->getString($data, 'DocNumber'),
|
||||||
$this->getString($data, 'Id.value')
|
$this->getString($data, 'Id.value')
|
||||||
);
|
);
|
||||||
@ -185,7 +187,9 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
public function getLinkedTxn($data)
|
public function getLinkedTxn($data)
|
||||||
{
|
{
|
||||||
$payments = $this->getString($data, 'LinkedTxn');
|
$payments = $this->getString($data, 'LinkedTxn');
|
||||||
if(empty($payments)) return [];
|
if(empty($payments)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [[
|
return [[
|
||||||
'amount' => $this->getTotalAmt($data),
|
'amount' => $this->getTotalAmt($data),
|
||||||
|
@ -44,10 +44,11 @@ class PaymentTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
parent::__construct($company);
|
parent::__construct($company);
|
||||||
|
|
||||||
$this->model = new Model;
|
$this->model = new Model();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotalAmt($data, $field = null) {
|
public function getTotalAmt($data, $field = null)
|
||||||
|
{
|
||||||
return (float) $this->getString($data, $field);
|
return (float) $this->getString($data, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +71,12 @@ class PaymentTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
$invoices = [];
|
$invoices = [];
|
||||||
$invoice = $this->getString($data, 'Line.LinkedTxn.TxnType');
|
$invoice = $this->getString($data, 'Line.LinkedTxn.TxnType');
|
||||||
if(is_null($invoice) || $invoice !== 'Invoice') return $invoices;
|
if(is_null($invoice) || $invoice !== 'Invoice') {
|
||||||
if( is_null( ($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value')))) ) return $invoices;
|
return $invoices;
|
||||||
|
}
|
||||||
|
if(is_null(($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value'))))) {
|
||||||
|
return $invoices;
|
||||||
|
}
|
||||||
|
|
||||||
return [[
|
return [[
|
||||||
'amount' => (float) $this->getString($data, 'Line.Amount'),
|
'amount' => (float) $this->getString($data, 'Line.Amount'),
|
||||||
@ -88,7 +93,9 @@ class PaymentTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
$invoice = Invoice::query()->where('company_id', $this->company->id)
|
$invoice = Invoice::query()->where('company_id', $this->company->id)
|
||||||
->where('is_deleted', false)
|
->where('is_deleted', false)
|
||||||
->where("number", "LIKE",
|
->where(
|
||||||
|
"number",
|
||||||
|
"LIKE",
|
||||||
"%-$invoice_number%",
|
"%-$invoice_number%",
|
||||||
)
|
)
|
||||||
->first();
|
->first();
|
||||||
|
@ -22,7 +22,6 @@ use App\Import\ImportException;
|
|||||||
*/
|
*/
|
||||||
class ProductTransformer extends BaseTransformer
|
class ProductTransformer extends BaseTransformer
|
||||||
{
|
{
|
||||||
|
|
||||||
use CommonTrait;
|
use CommonTrait;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@ -41,19 +40,22 @@ class ProductTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
parent::__construct($company);
|
parent::__construct($company);
|
||||||
|
|
||||||
$this->model = new Model;
|
$this->model = new Model();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQtyOnHand($data, $field = null) {
|
public function getQtyOnHand($data, $field = null)
|
||||||
|
{
|
||||||
return (int) $this->getString($data, $field);
|
return (int) $this->getString($data, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPurchaseCost($data, $field = null) {
|
public function getPurchaseCost($data, $field = null)
|
||||||
return (double) $this->getString($data, $field);
|
{
|
||||||
|
return (float) $this->getString($data, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getUnitPrice($data, $field = null) {
|
public function getUnitPrice($data, $field = null)
|
||||||
|
{
|
||||||
return (float) $this->getString($data, $field);
|
return (float) $this->getString($data, $field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,13 @@ class ExpenseTransformer extends BaseTransformer
|
|||||||
|
|
||||||
$tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0;
|
$tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0;
|
||||||
|
|
||||||
if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1)
|
if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1) {
|
||||||
$public_notes = $data['Notes / Memo'];
|
$public_notes = $data['Notes / Memo'];
|
||||||
elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1)
|
} elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1) {
|
||||||
$public_notes = $data['Transaction Description'];
|
$public_notes = $data['Transaction Description'];
|
||||||
else
|
} else {
|
||||||
$public_notes = '';
|
$public_notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$transformed = [
|
$transformed = [
|
||||||
|
@ -114,8 +114,7 @@ class RecurringExpensesCron
|
|||||||
$exchange_rate = new CurrencyApi();
|
$exchange_rate = new CurrencyApi();
|
||||||
|
|
||||||
$expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date));
|
$expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$expense->exchange_rate = 1;
|
$expense->exchange_rate = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,10 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
|
|
||||||
class QuickbooksIngest implements ShouldQueue
|
class QuickbooksIngest implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
protected $engine;
|
protected $engine;
|
||||||
protected $request;
|
protected $request;
|
||||||
|
@ -48,8 +48,7 @@ class TaskAssigned implements ShouldQueue
|
|||||||
|
|
||||||
$company_user = $this->task->assignedCompanyUser();
|
$company_user = $this->task->assignedCompanyUser();
|
||||||
|
|
||||||
if(($company_user instanceof CompanyUser) && $this->findEntityAssignedNotification($company_user, 'task'))
|
if(($company_user instanceof CompanyUser) && $this->findEntityAssignedNotification($company_user, 'task')) {
|
||||||
{
|
|
||||||
$mo = new EmailObject();
|
$mo = new EmailObject();
|
||||||
$mo->subject = ctrans('texts.task_assigned_subject', ['task' => $this->task->number, 'date' => now()->setTimeZone($this->task->company->timezone()->name)->format($this->task->company->date_format()) ]);
|
$mo->subject = ctrans('texts.task_assigned_subject', ['task' => $this->task->number, 'date' => now()->setTimeZone($this->task->company->timezone()->name)->format($this->task->company->date_format()) ]);
|
||||||
$mo->body = ctrans('texts.task_assigned_body', ['task' => $this->task->number, 'description' => $this->task->description ?? '', 'client' => $this->task->client ? $this->task->client->present()->name() : ' ']);
|
$mo->body = ctrans('texts.task_assigned_body', ['task' => $this->task->number, 'description' => $this->task->description ?? '', 'client' => $this->task->client ? $this->task->client->present()->name() : ' ']);
|
||||||
|
@ -59,7 +59,6 @@ class Register extends Component
|
|||||||
|
|
||||||
public function register(array $data)
|
public function register(array $data)
|
||||||
{
|
{
|
||||||
|
|
||||||
$service = new ClientRegisterService(
|
$service = new ClientRegisterService(
|
||||||
company: $this->subscription->company,
|
company: $this->subscription->company,
|
||||||
additional: $this->additional_fields,
|
additional: $this->additional_fields,
|
||||||
|
@ -363,10 +363,11 @@ class BillingPortalPurchase extends Component
|
|||||||
$method_values = array_column($this->methods, 'is_paypal');
|
$method_values = array_column($this->methods, 'is_paypal');
|
||||||
$is_paypal = in_array('1', $method_values);
|
$is_paypal = in_array('1', $method_values);
|
||||||
|
|
||||||
if($is_paypal && !$this->steps['check_rff'])
|
if($is_paypal && !$this->steps['check_rff']) {
|
||||||
$this->rff();
|
$this->rff();
|
||||||
elseif(!$this->steps['check_rff'])
|
} elseif(!$this->steps['check_rff']) {
|
||||||
$this->steps['fetched_payment_methods'] = true;
|
$this->steps['fetched_payment_methods'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$this->heading_text = ctrans('texts.payment_methods');
|
$this->heading_text = ctrans('texts.payment_methods');
|
||||||
|
|
||||||
|
@ -515,8 +515,7 @@ class BillingPortalPurchasev2 extends Component
|
|||||||
strlen($this->contact_email ?? '') == 0 ||
|
strlen($this->contact_email ?? '') == 0 ||
|
||||||
strlen($this->client_city ?? '') == 0 ||
|
strlen($this->client_city ?? '') == 0 ||
|
||||||
strlen($this->client_postal_code ?? '') == 0
|
strlen($this->client_postal_code ?? '') == 0
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
$this->check_rff = true;
|
$this->check_rff = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
287
app/Livewire/Flow2/InvoicePay.php
Normal file
287
app/Livewire/Flow2/InvoicePay.php
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Utils\Number;
|
||||||
|
use App\Utils\Traits\MakesDates;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class InvoicePay extends Component
|
||||||
|
{
|
||||||
|
use MakesDates;
|
||||||
|
use MakesHash;
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
private $mappings = [
|
||||||
|
'client_name' => 'name',
|
||||||
|
'client_website' => 'website',
|
||||||
|
'client_phone' => 'phone',
|
||||||
|
|
||||||
|
'client_address_line_1' => 'address1',
|
||||||
|
'client_address_line_2' => 'address2',
|
||||||
|
'client_city' => 'city',
|
||||||
|
'client_state' => 'state',
|
||||||
|
'client_postal_code' => 'postal_code',
|
||||||
|
'client_country_id' => 'country_id',
|
||||||
|
|
||||||
|
'client_shipping_address_line_1' => 'shipping_address1',
|
||||||
|
'client_shipping_address_line_2' => 'shipping_address2',
|
||||||
|
'client_shipping_city' => 'shipping_city',
|
||||||
|
'client_shipping_state' => 'shipping_state',
|
||||||
|
'client_shipping_postal_code' => 'shipping_postal_code',
|
||||||
|
'client_shipping_country_id' => 'shipping_country_id',
|
||||||
|
|
||||||
|
'client_custom_value1' => 'custom_value1',
|
||||||
|
'client_custom_value2' => 'custom_value2',
|
||||||
|
'client_custom_value3' => 'custom_value3',
|
||||||
|
'client_custom_value4' => 'custom_value4',
|
||||||
|
|
||||||
|
'contact_first_name' => 'first_name',
|
||||||
|
'contact_last_name' => 'last_name',
|
||||||
|
'contact_email' => 'email',
|
||||||
|
// 'contact_phone' => 'phone',
|
||||||
|
];
|
||||||
|
|
||||||
|
public $client_address_array = [
|
||||||
|
'address1',
|
||||||
|
'address2',
|
||||||
|
'city',
|
||||||
|
'state',
|
||||||
|
'postal_code',
|
||||||
|
'country_id',
|
||||||
|
'shipping_address1',
|
||||||
|
'shipping_address2',
|
||||||
|
'shipping_city',
|
||||||
|
'shipping_state',
|
||||||
|
'shipping_postal_code',
|
||||||
|
'shipping_country_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public $invitation_id;
|
||||||
|
|
||||||
|
public $invoices;
|
||||||
|
|
||||||
|
public $variables;
|
||||||
|
|
||||||
|
public $db;
|
||||||
|
|
||||||
|
public $settings;
|
||||||
|
|
||||||
|
public $terms_accepted = false;
|
||||||
|
|
||||||
|
public $signature_accepted = false;
|
||||||
|
|
||||||
|
public $payment_method_accepted = false;
|
||||||
|
|
||||||
|
public $under_over_payment = false;
|
||||||
|
|
||||||
|
public $required_fields = false;
|
||||||
|
|
||||||
|
#[On('update.context')]
|
||||||
|
public function handleContext(string $property, $value): self
|
||||||
|
{
|
||||||
|
$this->setContext(property: $property, value: $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('terms-accepted')]
|
||||||
|
public function termsAccepted()
|
||||||
|
{
|
||||||
|
nlog("Terms accepted");
|
||||||
|
// $this->invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id)->withoutRelations();
|
||||||
|
$this->terms_accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('signature-captured')]
|
||||||
|
public function signatureCaptured($base64)
|
||||||
|
{
|
||||||
|
nlog("signature captured");
|
||||||
|
|
||||||
|
$this->signature_accepted = true;
|
||||||
|
$invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id);
|
||||||
|
$invite->signature_base64 = $base64;
|
||||||
|
$invite->signature_date = now()->addSeconds($invite->contact->client->timezone_offset());
|
||||||
|
$this->setContext('signature', $base64); // $this->context['signature'] = $base64;
|
||||||
|
$invite->save();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('payable-amount')]
|
||||||
|
public function payableAmount($payable_amount)
|
||||||
|
{
|
||||||
|
// $this->setContext('payable_invoices.0.amount', Number::parseFloat($payable_amount)); // $this->context['payable_invoices'][0]['amount'] = Number::parseFloat($payable_amount); //TODO DB: check parseFloat()
|
||||||
|
$this->under_over_payment = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('payment-method-selected')]
|
||||||
|
public function paymentMethodSelected($company_gateway_id, $gateway_type_id, $amount)
|
||||||
|
{
|
||||||
|
$this->setContext('company_gateway_id', $company_gateway_id);
|
||||||
|
$this->setContext('gateway_type_id', $gateway_type_id);
|
||||||
|
$this->setContext('amount', $amount);
|
||||||
|
$this->setContext('pre_payment', false);
|
||||||
|
$this->setContext('is_recurring', false);
|
||||||
|
$this->setContext('invitation_id', $this->invitation_id);
|
||||||
|
|
||||||
|
$this->payment_method_accepted = true;
|
||||||
|
|
||||||
|
$company_gateway = CompanyGateway::query()->find($company_gateway_id);
|
||||||
|
|
||||||
|
$this->checkRequiredFields($company_gateway);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('required-fields')]
|
||||||
|
public function requiredFieldsFilled()
|
||||||
|
{
|
||||||
|
$this->required_fields = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkRequiredFields(CompanyGateway $company_gateway)
|
||||||
|
{
|
||||||
|
|
||||||
|
$fields = $company_gateway->driver()->getClientRequiredFields();
|
||||||
|
|
||||||
|
$this->setContext('fields', $fields); // $this->context['fields'] = $fields;
|
||||||
|
|
||||||
|
if ($company_gateway->always_show_required_fields) {
|
||||||
|
return $this->required_fields = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contact = $this->getContext()['contact'];
|
||||||
|
|
||||||
|
foreach ($fields as $index => $field) {
|
||||||
|
$_field = $this->mappings[$field['name']];
|
||||||
|
|
||||||
|
if (\Illuminate\Support\Str::startsWith($field['name'], 'client_')) {
|
||||||
|
if (
|
||||||
|
empty($contact->client->{$_field})
|
||||||
|
|| is_null($contact->client->{$_field})
|
||||||
|
) {
|
||||||
|
|
||||||
|
return $this->required_fields = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\Illuminate\Support\Str::startsWith($field['name'], 'contact_')) {
|
||||||
|
if (empty($contact->{$_field}) || is_null($contact->{$_field}) || str_contains($contact->{$_field}, '@example.com')) {
|
||||||
|
return $this->required_fields = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->required_fields = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function component(): string
|
||||||
|
{
|
||||||
|
if (!$this->terms_accepted) {
|
||||||
|
return Terms::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->signature_accepted) {
|
||||||
|
return Signature::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->under_over_payment) {
|
||||||
|
return UnderOverPayment::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->payment_method_accepted) {
|
||||||
|
return PaymentMethod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->required_fields) {
|
||||||
|
return RequiredFields::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessPayment::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function componentUniqueId(): string
|
||||||
|
{
|
||||||
|
return "purchase-" . md5(microtime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->resetContext();
|
||||||
|
|
||||||
|
MultiDB::setDb($this->db);
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$invite = \App\Models\InvoiceInvitation::with('contact.client', 'company')->withTrashed()->find($this->invitation_id);
|
||||||
|
$client = $invite->contact->client;
|
||||||
|
$settings = $client->getMergedSettings();
|
||||||
|
$this->setContext('contact', $invite->contact); // $this->context['contact'] = $invite->contact;
|
||||||
|
$this->setContext('settings', $settings); // $this->context['settings'] = $settings;
|
||||||
|
$this->setContext('db', $this->db); // $this->context['db'] = $this->db;
|
||||||
|
|
||||||
|
nlog($this->invoices);
|
||||||
|
|
||||||
|
if(is_array($this->invoices)) {
|
||||||
|
$this->invoices = Invoice::find($this->transformKeys($this->invoices));
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoices = $this->invoices->filter(function ($i) {
|
||||||
|
$i = $i->service()
|
||||||
|
->markSent()
|
||||||
|
->removeUnpaidGatewayFees()
|
||||||
|
->save();
|
||||||
|
|
||||||
|
return $i->isPayable();
|
||||||
|
});
|
||||||
|
|
||||||
|
//under-over / payment
|
||||||
|
|
||||||
|
//required fields
|
||||||
|
$this->terms_accepted = !$settings->show_accept_invoice_terms;
|
||||||
|
$this->signature_accepted = !$settings->require_invoice_signature;
|
||||||
|
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
|
||||||
|
$this->required_fields = false;
|
||||||
|
|
||||||
|
$this->setContext('variables', $this->variables); // $this->context['variables'] = $this->variables;
|
||||||
|
$this->setContext('invoices', $invoices); // $this->context['invoices'] = $invoices;
|
||||||
|
$this->setContext('settings', $settings); // $this->context['settings'] = $settings;
|
||||||
|
$this->setContext('invitation', $invite); // $this->context['invitation'] = $invite;
|
||||||
|
|
||||||
|
$payable_invoices = $invoices->map(function ($i) {
|
||||||
|
/** @var \App\Models\Invoice $i */
|
||||||
|
return [
|
||||||
|
'invoice_id' => $i->hashed_id,
|
||||||
|
'amount' => $i->partial > 0 ? $i->partial : $i->balance,
|
||||||
|
'formatted_amount' => Number::formatValue($i->partial > 0 ? $i->partial : $i->balance, $i->client->currency()),
|
||||||
|
'number' => $i->number,
|
||||||
|
'date' => $i->translateDate($i->date, $i->client->date_format(), $i->client->locale())
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$this->setContext('payable_invoices', $payable_invoices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
return render('flow2.invoice-pay');
|
||||||
|
}
|
||||||
|
}
|
47
app/Livewire/Flow2/InvoiceSummary.php
Normal file
47
app/Livewire/Flow2/InvoiceSummary.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class InvoiceSummary extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
public $invoices;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
//@TODO for a single invoice - show all details, for multi-invoices, only show the summaries
|
||||||
|
$this->invoices = $this->getContext()['invoices']; // $this->context['invitation']->invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On(self::CONTEXT_UPDATE)]
|
||||||
|
public function onContextUpdate(): void
|
||||||
|
{
|
||||||
|
// refactor logic for updating the price for eg if it changes with under/over pay
|
||||||
|
|
||||||
|
$this->invoices = $this->getContext()['invoices'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
return render('flow2.invoices-summary', [
|
||||||
|
'invoice' => $this->invoices,
|
||||||
|
'client' => $this->invoices->first()->client,
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
78
app/Livewire/Flow2/PaymentMethod.php
Normal file
78
app/Livewire/Flow2/PaymentMethod.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
|
||||||
|
class PaymentMethod extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
public $invoice;
|
||||||
|
|
||||||
|
public $variables;
|
||||||
|
|
||||||
|
public $methods = [];
|
||||||
|
|
||||||
|
public $isLoading = true;
|
||||||
|
|
||||||
|
public $amount = 0;
|
||||||
|
|
||||||
|
public function placeholder()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div class="flex items-center justify-center min-h-screen">
|
||||||
|
<svg class="animate-spin h-10 w-10 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSelect(string $company_gateway_id, string $gateway_type_id, string $amount)
|
||||||
|
{
|
||||||
|
$this->isLoading = true;
|
||||||
|
|
||||||
|
$this->dispatch(
|
||||||
|
event: 'payment-method-selected',
|
||||||
|
company_gateway_id: $company_gateway_id,
|
||||||
|
gateway_type_id: $gateway_type_id,
|
||||||
|
amount: $amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->variables = $this->getContext()['variables'];
|
||||||
|
$this->amount = array_sum(array_column($this->getContext()['payable_invoices'], 'amount'));
|
||||||
|
|
||||||
|
MultiDB::setDb($this->getContext()['db']);
|
||||||
|
|
||||||
|
$this->methods = $this->getContext()['invitation']->contact->client->service()->getPaymentMethods($this->amount);
|
||||||
|
|
||||||
|
if (count($this->methods) == 1) {
|
||||||
|
$this->dispatch('singlePaymentMethodFound', company_gateway_id: $this->methods[0]['company_gateway_id'], gateway_type_id: $this->methods[0]['gateway_type_id'], amount: $this->amount);
|
||||||
|
} else {
|
||||||
|
$this->isLoading = false;
|
||||||
|
$this->dispatch('loadingCompleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
return render('flow2.payment-method', ['methods' => $this->methods]);
|
||||||
|
}
|
||||||
|
}
|
86
app/Livewire/Flow2/ProcessPayment.php
Normal file
86
app/Livewire/Flow2/ProcessPayment.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Services\ClientPortal\LivewireInstantPayment;
|
||||||
|
|
||||||
|
class ProcessPayment extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
private ?string $payment_view;
|
||||||
|
|
||||||
|
private array $payment_data_payload = [];
|
||||||
|
|
||||||
|
public $isLoading = true;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
|
||||||
|
MultiDB::setDb($this->getContext()['db']);
|
||||||
|
|
||||||
|
$invitation = InvoiceInvitation::find($this->getContext()['invitation_id']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'company_gateway_id' => $this->getContext()['company_gateway_id'],
|
||||||
|
'payment_method_id' => $this->getContext()['gateway_type_id'],
|
||||||
|
'payable_invoices' => $this->getContext()['payable_invoices'],
|
||||||
|
'signature' => isset($this->getContext()['signature']) ? $this->getContext()['signature'] : false,
|
||||||
|
'signature_ip' => isset($this->getContext()['signature_ip']) ? $this->getContext()['signature_ip'] : false,
|
||||||
|
'pre_payment' => false,
|
||||||
|
'frequency_id' => false,
|
||||||
|
'remaining_cycles' => false,
|
||||||
|
'is_recurring' => false,
|
||||||
|
// 'hash' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$responder_data = (new LivewireInstantPayment($data))->run();
|
||||||
|
|
||||||
|
$company_gateway = CompanyGateway::find($this->getContext()['company_gateway_id']);
|
||||||
|
|
||||||
|
if (!$responder_data['success']) {
|
||||||
|
throw new PaymentFailed($responder_data['error'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$driver = $company_gateway
|
||||||
|
->driver($invitation->contact->client)
|
||||||
|
->setPaymentMethod($data['payment_method_id'])
|
||||||
|
->setPaymentHash($responder_data['payload']['ph']);
|
||||||
|
|
||||||
|
$this->payment_data_payload = $driver->processPaymentViewData($responder_data['payload']);
|
||||||
|
|
||||||
|
$this->payment_view = $driver->livewirePaymentView(
|
||||||
|
$this->payment_data_payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->isLoading = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|string|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
if ($this->isLoading) {
|
||||||
|
return <<<'HTML'
|
||||||
|
<template></template>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
return render($this->payment_view, $this->payment_data_payload);
|
||||||
|
}
|
||||||
|
}
|
136
app/Livewire/Flow2/RequiredFields.php
Normal file
136
app/Livewire/Flow2/RequiredFields.php
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Services\Client\RFFService;
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class RequiredFields extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
public ?CompanyGateway $company_gateway;
|
||||||
|
|
||||||
|
public ?string $client_name;
|
||||||
|
public ?string $contact_first_name;
|
||||||
|
public ?string $contact_last_name;
|
||||||
|
public ?string $contact_email;
|
||||||
|
public ?string $client_phone;
|
||||||
|
public ?string $client_address_line_1;
|
||||||
|
public ?string $client_city;
|
||||||
|
public ?string $client_state;
|
||||||
|
public ?int $client_country_id;
|
||||||
|
public ?string $client_postal_code;
|
||||||
|
public ?string $client_shipping_address_line_1;
|
||||||
|
public ?string $client_shipping_city;
|
||||||
|
public ?string $client_shipping_state;
|
||||||
|
public ?string $client_shipping_postal_code;
|
||||||
|
public ?int $client_shipping_country_id;
|
||||||
|
public ?string $client_custom_value1;
|
||||||
|
public ?string $client_custom_value2;
|
||||||
|
public ?string $client_custom_value3;
|
||||||
|
public ?string $client_custom_value4;
|
||||||
|
|
||||||
|
/** @var array<int, string> */
|
||||||
|
public array $fields = [];
|
||||||
|
|
||||||
|
public bool $is_loading = true;
|
||||||
|
|
||||||
|
public array $errors = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
MultiDB::setDB(
|
||||||
|
$this->getContext()['db'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->fields = $this->getContext()['fields'];
|
||||||
|
|
||||||
|
$this->company_gateway = CompanyGateway::withTrashed()
|
||||||
|
->with('company')
|
||||||
|
->find($this->getContext()['company_gateway_id']);
|
||||||
|
|
||||||
|
$contact = auth()->user();
|
||||||
|
|
||||||
|
$this->client_name = $contact->client->name;
|
||||||
|
$this->contact_first_name = $contact->first_name;
|
||||||
|
$this->contact_last_name = $contact->last_name;
|
||||||
|
$this->contact_email = $contact->email;
|
||||||
|
$this->client_phone = $contact->client->phone;
|
||||||
|
$this->client_address_line_1 = $contact->client->address1;
|
||||||
|
$this->client_city = $contact->client->city;
|
||||||
|
$this->client_state = $contact->client->state;
|
||||||
|
$this->client_country_id = $contact->client->country_id;
|
||||||
|
$this->client_postal_code = $contact->client->postal_code;
|
||||||
|
$this->client_shipping_address_line_1 = $contact->client->shipping_address1;
|
||||||
|
$this->client_shipping_city = $contact->client->shipping_city;
|
||||||
|
$this->client_shipping_state = $contact->client->shipping_state;
|
||||||
|
$this->client_shipping_postal_code = $contact->client->shipping_postal_code;
|
||||||
|
$this->client_shipping_country_id = $contact->client->shipping_country_id;
|
||||||
|
$this->client_custom_value1 = $contact->client->custom_value1;
|
||||||
|
$this->client_custom_value2 = $contact->client->custom_value2;
|
||||||
|
$this->client_custom_value3 = $contact->client->custom_value3;
|
||||||
|
$this->client_custom_value4 = $contact->client->custom_value4;
|
||||||
|
|
||||||
|
$rff = new RFFService(
|
||||||
|
fields: $this->getContext()['fields'],
|
||||||
|
database: $this->getContext()['db'],
|
||||||
|
company_gateway_id: $this->company_gateway->id,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var \App\Models\ClientContact $contact */
|
||||||
|
$rff->check($contact);
|
||||||
|
|
||||||
|
if ($rff->unfilled_fields === 0) {
|
||||||
|
$this->dispatch('required-fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rff->unfilled_fields > 0) {
|
||||||
|
$this->is_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSubmit(array $data)
|
||||||
|
{
|
||||||
|
$this->errors = [];
|
||||||
|
$this->is_loading = true;
|
||||||
|
|
||||||
|
$rff = new RFFService(
|
||||||
|
fields: $this->fields,
|
||||||
|
database: $this->getContext()['db'],
|
||||||
|
company_gateway_id: $this->company_gateway->id,
|
||||||
|
);
|
||||||
|
|
||||||
|
$contact = auth()->user();
|
||||||
|
|
||||||
|
/** @var \App\Models\ClientContact $contact */
|
||||||
|
$errors = $rff->handleSubmit($data, $contact, return_errors: true, callback: function () {
|
||||||
|
$this->dispatch('required-fields');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is_array($errors) && count($errors)) {
|
||||||
|
$this->errors = $errors;
|
||||||
|
$this->is_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
return render('flow2.required-fields', [
|
||||||
|
'contact' => $this->getContext()['contact'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
23
app/Livewire/Flow2/Signature.php
Normal file
23
app/Livewire/Flow2/Signature.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Signature extends Component
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return render('components.livewire.signature');
|
||||||
|
}
|
||||||
|
}
|
36
app/Livewire/Flow2/Terms.php
Normal file
36
app/Livewire/Flow2/Terms.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Terms extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
public $invoice;
|
||||||
|
|
||||||
|
public $variables;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->invoice = $this->getContext()['invoices']->first();
|
||||||
|
$this->variables = $this->getContext()['variables'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return render('components.livewire.terms');
|
||||||
|
}
|
||||||
|
}
|
76
app/Livewire/Flow2/UnderOverPayment.php
Normal file
76
app/Livewire/Flow2/UnderOverPayment.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Livewire\Flow2;
|
||||||
|
|
||||||
|
use App\Utils\Number;
|
||||||
|
use App\Utils\Traits\WithSecureContext;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class UnderOverPayment extends Component
|
||||||
|
{
|
||||||
|
use WithSecureContext;
|
||||||
|
|
||||||
|
public $payableAmount;
|
||||||
|
|
||||||
|
public $currency;
|
||||||
|
|
||||||
|
public $invoice_amount;
|
||||||
|
|
||||||
|
public $errors = '';
|
||||||
|
|
||||||
|
public $payableInvoices = [];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->invoice_amount = array_sum(array_column($this->getContext()['payable_invoices'], 'amount'));
|
||||||
|
$this->currency = $this->getContext()['invitation']->contact->client->currency();
|
||||||
|
$this->payableInvoices = $this->getContext()['payable_invoices'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkValue(array $payableInvoices)
|
||||||
|
{
|
||||||
|
$this->errors = '';
|
||||||
|
|
||||||
|
$settings = $this->getContext()['settings'];
|
||||||
|
|
||||||
|
foreach($payableInvoices as $key => $invoice) {
|
||||||
|
$payableInvoices[$key]['amount'] = Number::parseFloat($invoice['formatted_amount']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$input_amount = collect($payableInvoices)->sum('amount');
|
||||||
|
|
||||||
|
if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0) {
|
||||||
|
if($input_amount <= $settings->client_portal_under_payment_minimum) {
|
||||||
|
// return error message under payment too low.
|
||||||
|
$this->errors = ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]);
|
||||||
|
$this->dispatch('errorMessageUpdate', errors: $this->errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$settings->client_portal_allow_over_payment && ($input_amount > $this->invoice_amount)) {
|
||||||
|
$this->errors = ctrans('texts.over_payments_disabled');
|
||||||
|
$this->dispatch('errorMessageUpdate', errors: $this->errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->errors) {
|
||||||
|
$this->setContext('payable_invoices', $payableInvoices);
|
||||||
|
$this->dispatch('payable-amount', payable_amount: $input_amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
{
|
||||||
|
return render('flow2.under-over-payments');
|
||||||
|
}
|
||||||
|
}
|
@ -449,8 +449,9 @@ class Activity extends StaticModel
|
|||||||
$replacements['created_at'] = $this->created_at ?? '';
|
$replacements['created_at'] = $this->created_at ?? '';
|
||||||
$replacements['ip'] = $this->ip ?? '';
|
$replacements['ip'] = $this->ip ?? '';
|
||||||
|
|
||||||
if($this->activity_type_id == 141)
|
if($this->activity_type_id == 141) {
|
||||||
$replacements = $this->harvestNoteEntities($replacements);
|
$replacements = $this->harvestNoteEntities($replacements);
|
||||||
|
}
|
||||||
|
|
||||||
return $replacements;
|
return $replacements;
|
||||||
|
|
||||||
@ -472,12 +473,12 @@ class Activity extends StaticModel
|
|||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach($entities as $entity)
|
foreach($entities as $entity) {
|
||||||
{
|
|
||||||
$entity_key = substr($entity, 1);
|
$entity_key = substr($entity, 1);
|
||||||
|
|
||||||
if($this?->{$entity_key})
|
if($this?->{$entity_key}) {
|
||||||
$replacements = array_merge($replacements, $this->matchVar($entity));
|
$replacements = array_merge($replacements, $this->matchVar($entity));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,8 +376,7 @@ class BaseModel extends Model
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$pdf = (new PdfMerge($files->flatten()->toArray()))->run();
|
$pdf = (new PdfMerge($files->flatten()->toArray()))->run();
|
||||||
}
|
} catch(\Exception $e) {
|
||||||
catch(\Exception $e){
|
|
||||||
nlog("Exception:: BaseModel:: PdfMerge::" . $e->getMessage());
|
nlog("Exception:: BaseModel:: PdfMerge::" . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ use Laracasts\Presenter\PresentableTrait;
|
|||||||
* @property string|null $smtp_port
|
* @property string|null $smtp_port
|
||||||
* @property string|null $smtp_encryption
|
* @property string|null $smtp_encryption
|
||||||
* @property string|null $smtp_local_domain
|
* @property string|null $smtp_local_domain
|
||||||
* @property string|null $quickbooks
|
* @property object|null $quickbooks
|
||||||
* @property boolean $smtp_verify_peer
|
* @property boolean $smtp_verify_peer
|
||||||
* @property-read \App\Models\Account $account
|
* @property-read \App\Models\Account $account
|
||||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
||||||
|
@ -431,9 +431,9 @@ class Invoice extends BaseModel
|
|||||||
|
|
||||||
public function isPayable(): bool
|
public function isPayable(): bool
|
||||||
{
|
{
|
||||||
if($this->is_deleted || $this->status_id == self::STATUS_PAID)
|
if($this->is_deleted || $this->status_id == self::STATUS_PAID) {
|
||||||
return false;
|
return false;
|
||||||
elseif ($this->status_id == self::STATUS_DRAFT && $this->is_deleted == false) {
|
} elseif ($this->status_id == self::STATUS_DRAFT && $this->is_deleted == false) {
|
||||||
return true;
|
return true;
|
||||||
} elseif ($this->status_id == self::STATUS_SENT && $this->is_deleted == false) {
|
} elseif ($this->status_id == self::STATUS_SENT && $this->is_deleted == false) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -435,8 +435,9 @@ class Quote extends BaseModel
|
|||||||
*/
|
*/
|
||||||
public function canRemind(): bool
|
public function canRemind(): bool
|
||||||
{
|
{
|
||||||
if (in_array($this->status_id, [self::STATUS_DRAFT, self::STATUS_APPROVED, self::STATUS_CONVERTED]) || $this->is_deleted)
|
if (in_array($this->status_id, [self::STATUS_DRAFT, self::STATUS_APPROVED, self::STATUS_CONVERTED]) || $this->is_deleted) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -296,8 +296,7 @@ class Task extends BaseModel
|
|||||||
$client_currency = $this->client->getSetting('currency_id');
|
$client_currency = $this->client->getSetting('currency_id');
|
||||||
$company_currency = $this->company->getSetting('currency_id');
|
$company_currency = $this->company->getSetting('currency_id');
|
||||||
|
|
||||||
if($client_currency != $company_currency)
|
if($client_currency != $company_currency) {
|
||||||
{
|
|
||||||
$converter = new CurrencyApi();
|
$converter = new CurrencyApi();
|
||||||
return $converter->convert($this->taskValue(), $client_currency, $company_currency);
|
return $converter->convert($this->taskValue(), $client_currency, $company_currency);
|
||||||
}
|
}
|
||||||
@ -374,8 +373,9 @@ class Task extends BaseModel
|
|||||||
|
|
||||||
public function assignedCompanyUser()
|
public function assignedCompanyUser()
|
||||||
{
|
{
|
||||||
if(!$this->assigned_user_id)
|
if(!$this->assigned_user_id) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return CompanyUser::where('company_id', $this->company_id)->where('user_id', $this->assigned_user_id)->first();
|
return CompanyUser::where('company_id', $this->company_id)->where('user_id', $this->assigned_user_id)->first();
|
||||||
}
|
}
|
||||||
|
30
app/PaymentDrivers/Common/LivewireMethodInterface.php
Normal file
30
app/PaymentDrivers/Common/LivewireMethodInterface.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers\Common;
|
||||||
|
|
||||||
|
interface LivewireMethodInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Payment page for the gateway method.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
public function livewirePaymentView(array $data): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment data for the gateway method.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function paymentData(array $data): array;
|
||||||
|
}
|
261
app/PaymentDrivers/PayPalExpressPaymentDriver.php
Normal file
261
app/PaymentDrivers/PayPalExpressPaymentDriver.php
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Omnipay\Common\Item;
|
||||||
|
use Omnipay\Omnipay;
|
||||||
|
|
||||||
|
class PayPalExpressPaymentDriver extends BaseDriver
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public $token_billing = false;
|
||||||
|
|
||||||
|
public $can_authorise_credit_card = false;
|
||||||
|
|
||||||
|
private $omnipay_gateway;
|
||||||
|
|
||||||
|
private float $fee = 0;
|
||||||
|
|
||||||
|
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
|
||||||
|
|
||||||
|
public function gatewayTypes()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
GatewayType::PAYPAL,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Omnipay PayPal_Express gateway.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function initializeOmnipayGateway(): void
|
||||||
|
{
|
||||||
|
$this->omnipay_gateway = Omnipay::create(
|
||||||
|
$this->company_gateway->gateway->provider
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPaymentMethod($payment_method_id)
|
||||||
|
{
|
||||||
|
// PayPal doesn't have multiple ways of paying.
|
||||||
|
// There's just one, off-site redirect.
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeView($payment_method)
|
||||||
|
{
|
||||||
|
// PayPal doesn't support direct authorization.
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeResponse($request)
|
||||||
|
{
|
||||||
|
// PayPal doesn't support direct authorization.
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentView($data)
|
||||||
|
{
|
||||||
|
$this->initializeOmnipayGateway();
|
||||||
|
|
||||||
|
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
|
||||||
|
$this->payment_hash->save();
|
||||||
|
|
||||||
|
$response = $this->omnipay_gateway
|
||||||
|
->purchase($this->generatePaymentDetails($data))
|
||||||
|
->setItems($this->generatePaymentItems($data))
|
||||||
|
->send();
|
||||||
|
|
||||||
|
if ($response->isRedirect()) {
|
||||||
|
return redirect($response->getRedirectUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->sendFailureMail($response->getMessage() ?: '');
|
||||||
|
|
||||||
|
$message = [
|
||||||
|
'server_response' => $response->getMessage(),
|
||||||
|
'data' => $this->payment_hash->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
$message,
|
||||||
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
|
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||||
|
SystemLog::TYPE_PAYPAL,
|
||||||
|
$this->client,
|
||||||
|
$this->client->company,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new PaymentFailed($response->getMessage(), $response->getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentResponse($request)
|
||||||
|
{
|
||||||
|
$this->initializeOmnipayGateway();
|
||||||
|
|
||||||
|
$response = $this->omnipay_gateway
|
||||||
|
->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
|
||||||
|
->send();
|
||||||
|
|
||||||
|
if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) {
|
||||||
|
return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled'));
|
||||||
|
} elseif($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) {
|
||||||
|
redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response->isSuccessful()) {
|
||||||
|
$data = [
|
||||||
|
'payment_method' => $response->getData()['TOKEN'],
|
||||||
|
'payment_type' => PaymentType::PAYPAL,
|
||||||
|
'amount' => $this->payment_hash->data->amount,
|
||||||
|
'transaction_reference' => $response->getTransactionReference(),
|
||||||
|
'gateway_type_id' => GatewayType::PAYPAL,
|
||||||
|
];
|
||||||
|
|
||||||
|
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
['response' => (array) $response->getData(), 'data' => $data],
|
||||||
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
|
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||||
|
SystemLog::TYPE_PAYPAL,
|
||||||
|
$this->client,
|
||||||
|
$this->client->company,
|
||||||
|
);
|
||||||
|
|
||||||
|
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $response->isSuccessful()) {
|
||||||
|
$data = $response->getData();
|
||||||
|
|
||||||
|
$this->sendFailureMail($response->getMessage() ?: '');
|
||||||
|
|
||||||
|
$message = [
|
||||||
|
'server_response' => $data['L_LONGMESSAGE0'],
|
||||||
|
'data' => $this->payment_hash->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
$message,
|
||||||
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
|
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||||
|
SystemLog::TYPE_PAYPAL,
|
||||||
|
$this->client,
|
||||||
|
$this->client->company,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new PaymentFailed($response->getMessage(), $response->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePaymentDetails(array $data)
|
||||||
|
{
|
||||||
|
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||||
|
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||||
|
|
||||||
|
// $this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'currency' => $this->client->getCurrencyCode(),
|
||||||
|
'transactionType' => 'Purchase',
|
||||||
|
'clientIp' => request()->getClientIp(),
|
||||||
|
// 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2),
|
||||||
|
'amount' => round($data['total']['amount_with_fee'], 2),
|
||||||
|
'returnUrl' => route('client.payments.response', [
|
||||||
|
'company_gateway_id' => $this->company_gateway->id,
|
||||||
|
'payment_hash' => $this->payment_hash->hash,
|
||||||
|
'payment_method_id' => GatewayType::PAYPAL,
|
||||||
|
]),
|
||||||
|
'cancelUrl' => $this->client->company->domain()."/client/invoices/{$invoice->hashed_id}",
|
||||||
|
'description' => implode(',', collect($this->payment_hash->data->invoices)
|
||||||
|
->map(function ($invoice) {
|
||||||
|
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
|
||||||
|
})->toArray()),
|
||||||
|
'transactionId' => $this->payment_hash->hash.'-'.time(),
|
||||||
|
'ButtonSource' => 'InvoiceNinja_SP',
|
||||||
|
'solutionType' => 'Sole',
|
||||||
|
'no_shipping' => $this->company_gateway->require_shipping_address ? 0 : 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePaymentItems(array $data)
|
||||||
|
{
|
||||||
|
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||||
|
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
|
||||||
|
$items[] = new Item([
|
||||||
|
'name' => ' ',
|
||||||
|
'description' => ctrans('texts.invoice_number').'# '.$invoice->number,
|
||||||
|
'price' => $data['total']['amount_with_fee'],
|
||||||
|
'quantity' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function feeCalc($invoice, $invoice_total)
|
||||||
|
{
|
||||||
|
$invoice->service()->removeUnpaidGatewayFees();
|
||||||
|
$invoice = $invoice->fresh();
|
||||||
|
|
||||||
|
$balance = floatval($invoice->balance);
|
||||||
|
|
||||||
|
$_updated_invoice = $invoice->service()->addGatewayFee($this->company_gateway, GatewayType::PAYPAL, $invoice_total)->save();
|
||||||
|
|
||||||
|
if (floatval($_updated_invoice->balance) > $balance) {
|
||||||
|
$fee = floatval($_updated_invoice->balance) - $balance;
|
||||||
|
|
||||||
|
$this->payment_hash->fee_total = $fee;
|
||||||
|
$this->payment_hash->save();
|
||||||
|
|
||||||
|
return $fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function livewirePaymentView(array $data): string
|
||||||
|
{
|
||||||
|
$this->processPaymentView($data);
|
||||||
|
|
||||||
|
return ''; // Gateway is offsite.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentViewData(array $data): array
|
||||||
|
{
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,9 @@ namespace App\Repositories\Import\Quickbooks\Contracts;
|
|||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
interface RepositoryInterface {
|
interface RepositoryInterface
|
||||||
|
{
|
||||||
function get(int $max = 100): Collection;
|
public function get(int $max = 100): Collection;
|
||||||
function all(): Collection;
|
public function all(): Collection;
|
||||||
function count(): int;
|
public function count(): int;
|
||||||
}
|
}
|
@ -9,7 +9,6 @@ use App\Repositories\Import\Quickbooks\Transformers\Transformer as QuickbooksTra
|
|||||||
|
|
||||||
abstract class Repository implements RepositoryInterface
|
abstract class Repository implements RepositoryInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
protected string $entity;
|
protected string $entity;
|
||||||
protected QuickbooksInterface $db;
|
protected QuickbooksInterface $db;
|
||||||
protected QuickbooksTransformer $transfomer;
|
protected QuickbooksTransformer $transfomer;
|
||||||
@ -20,7 +19,8 @@ abstract class Repository implements RepositoryInterface
|
|||||||
$this->transformer = $transfomer;
|
$this->transformer = $transfomer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function count() : int {
|
public function count(): int
|
||||||
|
{
|
||||||
return $this->db->totalRecords($this->entity);
|
return $this->db->totalRecords($this->entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ class Transformer
|
|||||||
{
|
{
|
||||||
public function transform(array $items, string $type): Collection
|
public function transform(array $items, string $type): Collection
|
||||||
{
|
{
|
||||||
if(!method_exists($this, ($method = "transform{$type}s"))) throw new \InvalidArgumentException("Unknown type: $type");
|
if(!method_exists($this, ($method = "transform{$type}s"))) {
|
||||||
|
throw new \InvalidArgumentException("Unknown type: $type");
|
||||||
|
}
|
||||||
|
|
||||||
return call_user_func([$this, $method], $items);
|
return call_user_func([$this, $method], $items);
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,9 @@ class TaskRepository extends BaseRepository
|
|||||||
$this->new_task = false;
|
$this->new_task = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!is_numeric($task->rate) && !isset($data['rate']))
|
if(!is_numeric($task->rate) && !isset($data['rate'])) {
|
||||||
$data['rate'] = 0;
|
$data['rate'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$task->fill($data);
|
$task->fill($data);
|
||||||
$task->saveQuietly();
|
$task->saveQuietly();
|
||||||
@ -118,13 +119,15 @@ class TaskRepository extends BaseRepository
|
|||||||
|
|
||||||
$key_values = array_column($time_log, 0);
|
$key_values = array_column($time_log, 0);
|
||||||
|
|
||||||
if(count($key_values) > 0)
|
if(count($key_values) > 0) {
|
||||||
array_multisort($key_values, SORT_ASC, $time_log);
|
array_multisort($key_values, SORT_ASC, $time_log);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($time_log as $key => $value) {
|
foreach($time_log as $key => $value) {
|
||||||
|
|
||||||
if(is_array($time_log[$key]) && count($time_log[$key]) >=2)
|
if(is_array($time_log[$key]) && count($time_log[$key]) >= 2) {
|
||||||
$time_log[$key][1] = $this->roundTimeLog($time_log[$key][0], $time_log[$key][1]);
|
$time_log[$key][1] = $this->roundTimeLog($time_log[$key][0], $time_log[$key][1]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,19 +72,20 @@ class ProcessBankRules extends AbstractService
|
|||||||
// $client.custom4
|
// $client.custom4
|
||||||
private function matchCredit()
|
private function matchCredit()
|
||||||
{
|
{
|
||||||
|
$match_set = [];
|
||||||
|
|
||||||
$this->credit_rules = $this->bank_transaction->company->credit_rules();
|
$this->credit_rules = $this->bank_transaction->company->credit_rules();
|
||||||
|
|
||||||
foreach ($this->credit_rules as $bank_transaction_rule)
|
foreach ($this->credit_rules as $bank_transaction_rule) {
|
||||||
{
|
$match_set = [];
|
||||||
|
|
||||||
if (!is_array($bank_transaction_rule['rules'])) {
|
if (!is_array($bank_transaction_rule['rules'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($bank_transaction_rule['rules'] as $rule) {
|
|
||||||
$rule_count = count($bank_transaction_rule['rules']);
|
$rule_count = count($bank_transaction_rule['rules']);
|
||||||
|
|
||||||
|
foreach ($bank_transaction_rule['rules'] as $rule) {
|
||||||
|
|
||||||
$payments = Payment::query()
|
$payments = Payment::query()
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
@ -93,41 +94,74 @@ class ProcessBankRules extends AbstractService
|
|||||||
->whereNull('transaction_id')
|
->whereNull('transaction_id')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
match($rule['search_key']){
|
$invoices = Invoice::query()
|
||||||
'$payment.amount' => $results = $this->searchPaymentResource('amount', $rule),
|
|
||||||
'$payment.transaction_reference' => $results = $this->searchPaymentResource('transaction_reference', $rule),
|
|
||||||
'$payment.custom1' => $results = $this->searchPaymentResource('custom1', $rule),
|
|
||||||
'$payment.custom2' => $results = $this->searchPaymentResource('custom2', $rule),
|
|
||||||
'$payment.custom3' => $results = $this->searchPaymentResource('custom3', $rule),
|
|
||||||
'$payment.custom4' => $results = $this->searchPaymentResource('custom4', $rule),
|
|
||||||
'$invoice.amount' => $results = $this->searchInvoiceResource('amount', $rule),
|
|
||||||
'$invoice.number' => $results = $this->searchInvoiceResource('number', $rule),
|
|
||||||
'$invoice.po_number' => $results = $this->searchInvoiceResource('po_number', $rule),
|
|
||||||
'$invoice.custom1' => $results = $this->searchInvoiceResource('custom1', $rule),
|
|
||||||
'$invoice.custom2' => $results = $this->searchInvoiceResource('custom2', $rule),
|
|
||||||
'$invoice.custom3' => $results = $this->searchInvoiceResource('custom3', $rule),
|
|
||||||
'$invoice.custom4' => $results = $this->searchInvoiceResource('custom4', $rule),
|
|
||||||
'$client.id_number' => $results = $this->searchClientResource('id_number', $rule),
|
|
||||||
'$client.email' => $results = $this->searchClientResource('email', $rule),
|
|
||||||
'$client.custom1' => $results = $this->searchClientResource('custom1', $rule),
|
|
||||||
'$client.custom2' => $results = $this->searchClientResource('custom2', $rule),
|
|
||||||
'$client.custom3' => $results = $this->searchClientResource('custom3', $rule),
|
|
||||||
'$client.custom4' => $results = $this->searchClientResource('custom4', $rule),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function searchInvoiceResource(string $column, array $rule)
|
|
||||||
{
|
|
||||||
|
|
||||||
return Invoice::query()
|
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
->where('company_id', $this->bank_transaction->company_id)
|
->where('company_id', $this->bank_transaction->company_id)
|
||||||
->whereIn('status_id', [1,2,3])
|
->whereIn('status_id', [1,2,3])
|
||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->when($rule['search_key'] == 'description', function ($q) use ($rule, $column){
|
->get();
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
match($rule['search_key']) {
|
||||||
|
'$payment.amount' => $results = [Payment::class, $this->searchPaymentResource('amount', $rule, $payments)],
|
||||||
|
'$payment.transaction_reference' => $results = [Payment::class, $this->searchPaymentResource('transaction_reference', $rule, $payments)],
|
||||||
|
'$payment.custom1' => $results = [Payment::class, $this->searchPaymentResource('custom1', $rule, $payments)],
|
||||||
|
'$payment.custom2' => $results = [Payment::class, $this->searchPaymentResource('custom2', $rule, $payments)],
|
||||||
|
'$payment.custom3' => $results = [Payment::class, $this->searchPaymentResource('custom3', $rule, $payments)],
|
||||||
|
'$payment.custom4' => $results = [Payment::class, $this->searchPaymentResource('custom4', $rule, $payments)],
|
||||||
|
'$invoice.amount' => $results = [Invoice::class, $this->searchInvoiceResource('amount', $rule, $invoices)],
|
||||||
|
'$invoice.number' => $results = [Invoice::class, $this->searchInvoiceResource('number', $rule, $invoices)],
|
||||||
|
'$invoice.po_number' => $results = [Invoice::class, $this->searchInvoiceResource('po_number', $rule, $invoices)],
|
||||||
|
'$invoice.custom1' => $results = [Invoice::class, $this->searchInvoiceResource('custom1', $rule, $invoices)],
|
||||||
|
'$invoice.custom2' => $results = [Invoice::class, $this->searchInvoiceResource('custom2', $rule, $invoices)],
|
||||||
|
'$invoice.custom3' => $results = [Invoice::class, $this->searchInvoiceResource('custom3', $rule, $invoices)],
|
||||||
|
'$invoice.custom4' => $results = [Invoice::class, $this->searchInvoiceResource('custom4', $rule, $invoices)],
|
||||||
|
'$client.id_number' => $results = [Client::class, $this->searchClientResource('id_number', $rule, $invoices, $payments)],
|
||||||
|
'$client.email' => $results = [Client::class, $this->searchClientResource('email', $rule, $invoices, $payments)],
|
||||||
|
'$client.custom1' => $results = [Client::class, $this->searchClientResource('custom1', $rule, $invoices, $payments)],
|
||||||
|
'$client.custom2' => $results = [Client::class, $this->searchClientResource('custom2', $rule, $invoices, $payments)],
|
||||||
|
'$client.custom3' => $results = [Client::class, $this->searchClientResource('custom3', $rule, $invoices, $payments)],
|
||||||
|
'$client.custom4' => $results = [Client::class, $this->searchClientResource('custom4', $rule, $invoices, $payments)],
|
||||||
|
default => $results = [Client::class, [collect([]), Invoice::class]],
|
||||||
|
};
|
||||||
|
|
||||||
|
if($results[0] == 'App\Models\Client') {
|
||||||
|
$set = $results[1];
|
||||||
|
$result_set = $set[0];
|
||||||
|
$entity = $set[1];
|
||||||
|
|
||||||
|
if($result_set->count() > 0) {
|
||||||
|
$match_set[] = [$entity, $result_set->pluck('id')];
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif($results[1]->count() > 0) {
|
||||||
|
$match_set[] = $results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($bank_transaction_rule['matches_on_all'] && (count($match_set) == $rule_count)) || (!$bank_transaction_rule['matches_on_all'] && count($match_set) > 0)) {
|
||||||
|
|
||||||
|
$this->bank_transaction->vendor_id = $bank_transaction_rule->vendor_id;
|
||||||
|
$this->bank_transaction->ninja_category_id = $bank_transaction_rule->category_id;
|
||||||
|
$this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED;
|
||||||
|
$this->bank_transaction->bank_transaction_rule_id = $bank_transaction_rule->id;
|
||||||
|
$this->bank_transaction->save();
|
||||||
|
|
||||||
|
|
||||||
|
//auto-convert
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function searchInvoiceResource(string $column, array $rule, $invoices)
|
||||||
|
{
|
||||||
|
|
||||||
|
return $invoices->when($rule['search_key'] == 'description', function ($q) use ($rule, $column) {
|
||||||
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
||||||
return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
||||||
});
|
});
|
||||||
@ -140,14 +174,68 @@ class ProcessBankRules extends AbstractService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function searchPaymentResource()
|
private function searchPaymentResource(string $column, array $rule, $payments)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
return $payments->when($rule['search_key'] == 'description', function ($q) use ($rule, $column) {
|
||||||
|
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
||||||
|
return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($rule['search_key'] == 'amount', function ($q) use ($rule, $column) {
|
||||||
|
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
||||||
|
return $this->matchNumberOperator($this->bank_transaction->amount, $record->{$column}, $rule['operator']);
|
||||||
|
});
|
||||||
|
})->pluck("id");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function searchClientResource()
|
private function searchClientResource(string $column, array $rule, $invoices, $payments)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
$invoice_matches = Client::query()
|
||||||
|
->whereIn('id', $invoices->pluck('client_id'))
|
||||||
|
->when($column == 'email', function ($q) {
|
||||||
|
return $q->whereHas('contacts', function ($qc) {
|
||||||
|
$qc->where('email', $this->bank_transaction->description);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($column != 'email', function ($q) use ($rule, $column) {
|
||||||
|
|
||||||
|
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
||||||
|
return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
||||||
|
});
|
||||||
|
})->pluck('id');
|
||||||
|
|
||||||
|
|
||||||
|
$intersection = $invoices->whereIn('client_id', $invoice_matches);
|
||||||
|
|
||||||
|
if($intersection->count() > 0) {
|
||||||
|
return [$intersection, Invoice::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
$payments_matches = Client::query()
|
||||||
|
->whereIn('id', $payments->pluck('client_id'))
|
||||||
|
->when($column == 'email', function ($q) {
|
||||||
|
return $q->whereHas('contacts', function ($qc) {
|
||||||
|
$qc->where('email', $this->bank_transaction->description);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($column != 'email', function ($q) use ($rule, $column) {
|
||||||
|
|
||||||
|
return $q->cursor()->filter(function ($record) use ($rule, $column) {
|
||||||
|
return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
||||||
|
});
|
||||||
|
})->pluck('id');
|
||||||
|
|
||||||
|
$intersection = $payments->whereIn('client_id', $payments_matches);
|
||||||
|
|
||||||
|
if($intersection->count() > 0) {
|
||||||
|
return [$intersection, Payment::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Client::class, collect([])];
|
||||||
|
|
||||||
}
|
}
|
||||||
// $payment.amount => "Payment Amount", float
|
// $payment.amount => "Payment Amount", float
|
||||||
// $payment.transaction_reference => "Payment Transaction Reference", string
|
// $payment.transaction_reference => "Payment Transaction Reference", string
|
||||||
|
@ -23,7 +23,6 @@ use Illuminate\Contracts\Database\Eloquent\Builder;
|
|||||||
*/
|
*/
|
||||||
trait ChartCalculations
|
trait ChartCalculations
|
||||||
{
|
{
|
||||||
|
|
||||||
public function getActiveInvoices($data): int|float
|
public function getActiveInvoices($data): int|float
|
||||||
{
|
{
|
||||||
$result = 0;
|
$result = 0;
|
||||||
@ -34,8 +33,9 @@ trait ChartCalculations
|
|||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->whereIn('status_id', [2,3,4]);
|
->whereIn('status_id', [2,3,4]);
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('amount'),
|
'sum' => $result = $q->sum('amount'),
|
||||||
@ -58,8 +58,9 @@ trait ChartCalculations
|
|||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->whereIn('status_id', [2,3]);
|
->whereIn('status_id', [2,3]);
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('balance'),
|
'sum' => $result = $q->sum('balance'),
|
||||||
@ -82,8 +83,9 @@ trait ChartCalculations
|
|||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->where('status_id', 4);
|
->where('status_id', 4);
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('amount'),
|
'sum' => $result = $q->sum('amount'),
|
||||||
@ -106,8 +108,9 @@ trait ChartCalculations
|
|||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
->whereIn('status_id', [5,6]);
|
->whereIn('status_id', [5,6]);
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('refunded'),
|
'sum' => $result = $q->sum('refunded'),
|
||||||
@ -133,8 +136,9 @@ trait ChartCalculations
|
|||||||
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
|
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
|
||||||
});
|
});
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('refunded'),
|
'sum' => $result = $q->sum('refunded'),
|
||||||
@ -160,8 +164,9 @@ trait ChartCalculations
|
|||||||
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
|
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
|
||||||
});
|
});
|
||||||
|
|
||||||
if(in_array($data['period'],['current,previous']))
|
if(in_array($data['period'], ['current,previous'])) {
|
||||||
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||||
|
}
|
||||||
|
|
||||||
match ($data['calculation']) {
|
match ($data['calculation']) {
|
||||||
'sum' => $result = $q->sum('refunded'),
|
'sum' => $result = $q->sum('refunded'),
|
||||||
|
@ -36,14 +36,24 @@ class PaymentMethod
|
|||||||
{
|
{
|
||||||
$this->getGateways()
|
$this->getGateways()
|
||||||
->getMethods();
|
->getMethods();
|
||||||
// ->buildUrls();
|
|
||||||
|
|
||||||
return $this->getPaymentUrls();
|
return $this->getPaymentUrls();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPaymentUrls()
|
public function getPaymentUrls()
|
||||||
{
|
{
|
||||||
|
$pu = collect($this->payment_urls);
|
||||||
|
$keys = $pu->pluck('gateway_type_id');
|
||||||
|
$contains_both = $keys->contains('1') && $keys->contains('29'); //handle the case where PayPal Advanced cards + regular CC is present
|
||||||
|
|
||||||
|
$this->payment_urls = $pu->when($contains_both, function ($methods) {
|
||||||
|
return $methods->reject(function ($item) {
|
||||||
|
return $item['gateway_type_id'] == '29';
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
return $this->payment_urls;
|
return $this->payment_urls;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPaymentMethods()
|
public function getPaymentMethods()
|
||||||
@ -148,10 +158,8 @@ class PaymentMethod
|
|||||||
$this->payment_methods = $payment_methods_collections->intersectByKeys($payment_methods_collections->flatten(1)->unique());
|
$this->payment_methods = $payment_methods_collections->intersectByKeys($payment_methods_collections->flatten(1)->unique());
|
||||||
|
|
||||||
//@15-06-2024
|
//@15-06-2024
|
||||||
foreach($this->payment_methods as $key => $type)
|
foreach($this->payment_methods as $key => $type) {
|
||||||
{
|
foreach ($type as $gateway_id => $gateway_type_id) {
|
||||||
foreach ($type as $gateway_id => $gateway_type_id)
|
|
||||||
{
|
|
||||||
$gate = $this->gateways->where('id', $gateway_id)->first();
|
$gate = $this->gateways->where('id', $gateway_id)->first();
|
||||||
$this->buildUrl($gate, $gateway_type_id);
|
$this->buildUrl($gate, $gateway_type_id);
|
||||||
}
|
}
|
||||||
@ -168,13 +176,9 @@ class PaymentMethod
|
|||||||
foreach ($gateway->driver($this->client)->gatewayTypes() as $type) {
|
foreach ($gateway->driver($this->client)->gatewayTypes() as $type) {
|
||||||
if (isset($gateway->fees_and_limits) && is_object($gateway->fees_and_limits) && property_exists($gateway->fees_and_limits, GatewayType::CREDIT_CARD)) { //@phpstan-ignore-line
|
if (isset($gateway->fees_and_limits) && is_object($gateway->fees_and_limits) && property_exists($gateway->fees_and_limits, GatewayType::CREDIT_CARD)) { //@phpstan-ignore-line
|
||||||
if ($this->validGatewayForAmount($gateway->fees_and_limits->{GatewayType::CREDIT_CARD}, $this->amount)) {
|
if ($this->validGatewayForAmount($gateway->fees_and_limits->{GatewayType::CREDIT_CARD}, $this->amount)) {
|
||||||
// $this->payment_methods[] = [$gateway->id => $type];
|
|
||||||
// @15-06-2024
|
|
||||||
$this->buildUrl($gateway, $type);
|
$this->buildUrl($gateway, $type);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// $this->payment_methods[] = [$gateway->id => null];
|
|
||||||
//@15-06-2024
|
|
||||||
$this->buildUrl($gateway, null);
|
$this->buildUrl($gateway, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,52 +229,6 @@ class PaymentMethod
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
//@deprecated as buildUrl() supercedes
|
|
||||||
private function buildUrls()
|
|
||||||
{
|
|
||||||
foreach ($this->payment_methods as $key => $child_array) {
|
|
||||||
foreach ($child_array as $gateway_id => $gateway_type_id) {
|
|
||||||
$gateway = CompanyGateway::query()->find($gateway_id);
|
|
||||||
|
|
||||||
$fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client, $gateway_type_id);
|
|
||||||
|
|
||||||
if (! $gateway_type_id || (GatewayType::CUSTOM == $gateway_type_id)) {
|
|
||||||
$this->payment_urls[] = [
|
|
||||||
'label' => $gateway->getConfigField('name').$fee_label,
|
|
||||||
'company_gateway_id' => $gateway_id,
|
|
||||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
|
||||||
'is_paypal' => $gateway->isPayPal(),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$this->payment_urls[] = [
|
|
||||||
'label' => $gateway->getTypeAlias($gateway_type_id).$fee_label,
|
|
||||||
'company_gateway_id' => $gateway_id,
|
|
||||||
'gateway_type_id' => $gateway_type_id,
|
|
||||||
'is_paypal' => $gateway->isPayPal(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($this->client->getSetting('use_credits_payment') == 'option' || $this->client->getSetting('use_credits_payment') == 'always') && $this->client->service()->getCreditBalance() > 0) {
|
|
||||||
// Show credits as only payment option if both statements are true.
|
|
||||||
if (
|
|
||||||
$this->client->service()->getCreditBalance() > $this->amount
|
|
||||||
&& $this->client->getSetting('use_credits_payment') == 'always') {
|
|
||||||
$payment_urls = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->payment_urls[] = [
|
|
||||||
'label' => ctrans('texts.apply_credit'),
|
|
||||||
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,
|
|
||||||
'gateway_type_id' => GatewayType::CREDIT,
|
|
||||||
'is_paypal' => false,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validGatewayForAmount($fees_and_limits_for_payment_type): bool
|
private function validGatewayForAmount($fees_and_limits_for_payment_type): bool
|
||||||
{
|
{
|
||||||
if (isset($fees_and_limits_for_payment_type)) {
|
if (isset($fees_and_limits_for_payment_type)) {
|
||||||
|
189
app/Services/Client/RFFService.php
Normal file
189
app/Services/Client/RFFService.php
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Client;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
class RFFService
|
||||||
|
{
|
||||||
|
public array $mappings = [
|
||||||
|
'client_name' => 'name',
|
||||||
|
'client_website' => 'website',
|
||||||
|
'client_phone' => 'phone',
|
||||||
|
|
||||||
|
'client_address_line_1' => 'address1',
|
||||||
|
'client_address_line_2' => 'address2',
|
||||||
|
'client_city' => 'city',
|
||||||
|
'client_state' => 'state',
|
||||||
|
'client_postal_code' => 'postal_code',
|
||||||
|
'client_country_id' => 'country_id',
|
||||||
|
|
||||||
|
'client_shipping_address_line_1' => 'shipping_address1',
|
||||||
|
'client_shipping_address_line_2' => 'shipping_address2',
|
||||||
|
'client_shipping_city' => 'shipping_city',
|
||||||
|
'client_shipping_state' => 'shipping_state',
|
||||||
|
'client_shipping_postal_code' => 'shipping_postal_code',
|
||||||
|
'client_shipping_country_id' => 'shipping_country_id',
|
||||||
|
|
||||||
|
'client_custom_value1' => 'custom_value1',
|
||||||
|
'client_custom_value2' => 'custom_value2',
|
||||||
|
'client_custom_value3' => 'custom_value3',
|
||||||
|
'client_custom_value4' => 'custom_value4',
|
||||||
|
|
||||||
|
'contact_first_name' => 'first_name',
|
||||||
|
'contact_last_name' => 'last_name',
|
||||||
|
'contact_email' => 'email',
|
||||||
|
// 'contact_phone' => 'phone',
|
||||||
|
];
|
||||||
|
|
||||||
|
public int $unfilled_fields = 0;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public array $fields,
|
||||||
|
public string $database,
|
||||||
|
public string $company_gateway_id,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check(ClientContact $contact): void
|
||||||
|
{
|
||||||
|
$_contact = $contact;
|
||||||
|
|
||||||
|
foreach ($this->fields as $index => $field) {
|
||||||
|
$_field = $this->mappings[$field['name']];
|
||||||
|
|
||||||
|
if (Str::startsWith($field['name'], 'client_')) {
|
||||||
|
if (
|
||||||
|
empty($_contact->client->{$_field})
|
||||||
|
|| is_null($_contact->client->{$_field})
|
||||||
|
) {
|
||||||
|
// $this->show_form = true;
|
||||||
|
$this->unfilled_fields++;
|
||||||
|
} else {
|
||||||
|
$this->fields[$index]['filled'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::startsWith($field['name'], 'contact_')) {
|
||||||
|
if (empty($_contact->{$_field}) || is_null($_contact->{$_field}) || str_contains($_contact->{$_field}, '@example.com')) {
|
||||||
|
$this->unfilled_fields++;
|
||||||
|
} else {
|
||||||
|
$this->fields[$index]['filled'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSubmit(array $data, ClientContact $contact, callable $callback, bool $return_errors = false): bool|array
|
||||||
|
{
|
||||||
|
MultiDB::setDb($this->database);
|
||||||
|
|
||||||
|
$rules = [];
|
||||||
|
|
||||||
|
collect($this->fields)->map(function ($field) use (&$rules) {
|
||||||
|
if (!array_key_exists('filled', $field)) {
|
||||||
|
$rules[$field['name']] = array_key_exists('validation_rules', $field)
|
||||||
|
? $field['validation_rules']
|
||||||
|
: 'required';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$validator = Validator::make($data, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
if ($return_errors) {
|
||||||
|
return $validator->getMessageBag()->getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
session()->flash('validation_errors', $validator->getMessageBag()->getMessages());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->update($data, $contact)) {
|
||||||
|
$callback();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(array $data, ClientContact $_contact): bool
|
||||||
|
{
|
||||||
|
$client = [];
|
||||||
|
$contact = [];
|
||||||
|
|
||||||
|
MultiDB::setDb($this->database);
|
||||||
|
|
||||||
|
foreach ($data as $field => $value) {
|
||||||
|
if (Str::startsWith($field, 'client_')) {
|
||||||
|
$client[$this->mappings[$field]] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::startsWith($field, 'contact_')) {
|
||||||
|
$contact[$this->mappings[$field]] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $_contact->first_name = $data['contact_first_name'] ?? '';
|
||||||
|
// $_contact->last_name = $data['contact_last_name'] ?? '';
|
||||||
|
// $_contact->client->name = $data['client_name'] ?? '';
|
||||||
|
// $_contact->email = $data['contact_email'] ?? '';
|
||||||
|
// $_contact->client->phone = $data['client_phone'] ?? '';
|
||||||
|
// $_contact->client->address1 = $data['client_address_line_1'] ?? '';
|
||||||
|
// $_contact->client->city = $data['client_city'] ?? '';
|
||||||
|
// $_contact->client->state = $data['client_state'] ?? '';
|
||||||
|
// $_contact->client->country_id = $data['client_country_id'] ?? '';
|
||||||
|
// $_contact->client->postal_code = $data['client_postal_code'] ?? '';
|
||||||
|
// $_contact->client->shipping_address1 = $data['client_shipping_address_line_1'] ?? '';
|
||||||
|
// $_contact->client->shipping_city = $data['client_shipping_city'] ?? '';
|
||||||
|
// $_contact->client->shipping_state = $data['client_shipping_state'] ?? '';
|
||||||
|
// $_contact->client->shipping_postal_code = $data['client_shipping_postal_code'] ?? '';
|
||||||
|
// $_contact->client->shipping_country_id = $data['client_shipping_country_id'] ?? '';
|
||||||
|
// $_contact->client->custom_value1 = $data['client_custom_value1'] ?? '';
|
||||||
|
// $_contact->client->custom_value2 = $data['client_custom_value2'] ?? '';
|
||||||
|
// $_contact->client->custom_value3 = $data['client_custom_value3'] ?? '';
|
||||||
|
// $_contact->client->custom_value4 = $data['client_custom_value4'] ?? '';
|
||||||
|
// $_contact->push();
|
||||||
|
|
||||||
|
|
||||||
|
$_contact
|
||||||
|
->fill($contact)
|
||||||
|
->push();
|
||||||
|
|
||||||
|
$_contact->client
|
||||||
|
->fill($client)
|
||||||
|
->push();
|
||||||
|
|
||||||
|
/** @var \App\Models\CompanyGateway $cg */
|
||||||
|
$cg = CompanyGateway::find(
|
||||||
|
$this->company_gateway_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
//@phpstan-ignore-next-line
|
||||||
|
if ($cg && $cg->update_details) {
|
||||||
|
$payment_gateway = $cg->driver($_contact->client)->init();
|
||||||
|
|
||||||
|
if (method_exists($payment_gateway, "updateCustomer")) {
|
||||||
|
$payment_gateway->updateCustomer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
263
app/Services/ClientPortal/LivewireInstantPayment.php
Normal file
263
app/Services/ClientPortal/LivewireInstantPayment.php
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\ClientPortal;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
use App\Jobs\Invoice\CheckGatewayFee;
|
||||||
|
use App\Jobs\Invoice\InjectSignature;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use App\Utils\Number;
|
||||||
|
use App\Utils\Traits\MakesDates;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LivewireInstantPayment
|
||||||
|
*
|
||||||
|
* New entry point for livewire component
|
||||||
|
* payments.
|
||||||
|
*/
|
||||||
|
class LivewireInstantPayment
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
use MakesDates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (bool) success
|
||||||
|
* (string) error - "displayed back to the user, either in error div, or in with() on redirect"
|
||||||
|
* (string) redirect - ie client.invoices.index
|
||||||
|
* (array) payload - the data needed to complete the payment
|
||||||
|
* (string) component - the payment component to be displayed
|
||||||
|
*
|
||||||
|
* @var array $responder
|
||||||
|
*/
|
||||||
|
private array $responder = [
|
||||||
|
'success' => true,
|
||||||
|
'error' => '',
|
||||||
|
'redirect' => '',
|
||||||
|
'payload' => [],
|
||||||
|
'component' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is_credit_payment
|
||||||
|
*
|
||||||
|
* Indicates whether this is a credit payment
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $is_credit_payment = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __construct
|
||||||
|
*
|
||||||
|
* contact() guard
|
||||||
|
* company_gateway_id
|
||||||
|
* payable_invoices[] ['invoice_id' => '', 'amount' => 0]
|
||||||
|
* ?signature
|
||||||
|
* ?signature_ip
|
||||||
|
* payment_method_id
|
||||||
|
* ?pre_payment
|
||||||
|
* ?frequency_id
|
||||||
|
* ?remaining_cycles
|
||||||
|
* ?is_recurring
|
||||||
|
* ?hash
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(public array $data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
nlog($this->data);
|
||||||
|
|
||||||
|
$company_gateway = CompanyGateway::query()->find($this->data['company_gateway_id']);
|
||||||
|
|
||||||
|
if ($this->data['company_gateway_id'] == CompanyGateway::GATEWAY_CREDIT) {
|
||||||
|
$this->is_credit_payment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payable_invoices = collect($this->data['payable_invoices']);
|
||||||
|
|
||||||
|
$tokens = [];
|
||||||
|
|
||||||
|
$invoices = Invoice::query()
|
||||||
|
->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))
|
||||||
|
->withTrashed()
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$client = $invoices->first()->client;
|
||||||
|
|
||||||
|
/* pop non payable invoice from the $payable_invoices array */
|
||||||
|
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
||||||
|
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first();
|
||||||
|
});
|
||||||
|
|
||||||
|
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
||||||
|
$payable_invoice_collection = collect();
|
||||||
|
|
||||||
|
foreach ($payable_invoices as $payable_invoice) {
|
||||||
|
$payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||||
|
|
||||||
|
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||||
|
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), $client->currency()->precision);
|
||||||
|
$invoice_balance = Number::roundValue($invoice->balance, $client->currency()->precision);
|
||||||
|
|
||||||
|
$payable_invoice['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||||
|
$payable_invoice['invoice_number'] = $invoice->number;
|
||||||
|
|
||||||
|
if (isset($invoice->po_number)) {
|
||||||
|
$additional_info = $invoice->po_number;
|
||||||
|
} elseif (isset($invoice->public_notes)) {
|
||||||
|
$additional_info = $invoice->public_notes;
|
||||||
|
} else {
|
||||||
|
$additional_info = $invoice->date;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payable_invoice['additional_info'] = $additional_info;
|
||||||
|
|
||||||
|
$payable_invoice_collection->push($payable_invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->data['signature']) && $this->data['signature']) {
|
||||||
|
|
||||||
|
$contact_id = auth()->guard('contact')->user() ? auth()->guard('contact')->user()->id : null;
|
||||||
|
|
||||||
|
$invoices->each(function ($invoice) use ($contact_id) {
|
||||||
|
InjectSignature::dispatch($invoice, $contact_id, $this->data['signature'], $this->data['signature_ip']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$payable_invoices = $payable_invoice_collection;
|
||||||
|
|
||||||
|
$payment_method_id = $this->data['payment_method_id'];
|
||||||
|
$invoice_totals = $payable_invoices->sum('amount');
|
||||||
|
$first_invoice = $invoices->first();
|
||||||
|
$credit_totals = in_array($first_invoice->client->getSetting('use_credits_payment'), ['always', 'option']) ? $first_invoice->client->service()->getCreditBalance() : 0;
|
||||||
|
$starting_invoice_amount = $first_invoice->balance;
|
||||||
|
|
||||||
|
if ($company_gateway) {
|
||||||
|
$first_invoice->service()->addGatewayFee($company_gateway, $payment_method_id, $invoice_totals)->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gateway fee is calculated
|
||||||
|
* by adding it as a line item, and then subtract
|
||||||
|
* the starting and finishing amounts of the invoice.
|
||||||
|
*/
|
||||||
|
$fee_totals = $first_invoice->balance - $starting_invoice_amount;
|
||||||
|
|
||||||
|
if ($company_gateway) {
|
||||||
|
$tokens = $client->gateway_tokens()
|
||||||
|
->whereCompanyGatewayId($company_gateway->id)
|
||||||
|
->whereGatewayTypeId($payment_method_id)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->is_credit_payment) {
|
||||||
|
$credit_totals = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** $hash_data = mixed[] */
|
||||||
|
$hash_data = [
|
||||||
|
'invoices' => $payable_invoices->toArray(),
|
||||||
|
'credits' => $credit_totals,
|
||||||
|
'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals)),
|
||||||
|
'pre_payment' => $this->data['pre_payment'],
|
||||||
|
'frequency_id' => $this->data['frequency_id'],
|
||||||
|
'remaining_cycles' => $this->data['remaining_cycles'],
|
||||||
|
'is_recurring' => $this->data['is_recurring'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($this->data['hash'])) {
|
||||||
|
$hash_data['billing_context'] = Cache::get($this->data['hash']);
|
||||||
|
} elseif ($old_hash = PaymentHash::query()->where('fee_invoice_id', $first_invoice->id)->whereNull('payment_id')->orderBy('id', 'desc')->first()) {
|
||||||
|
if (isset($old_hash->data->billing_context)) {
|
||||||
|
$hash_data['billing_context'] = $old_hash->data->billing_context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment_hash = new PaymentHash();
|
||||||
|
$payment_hash->hash = Str::random(32);
|
||||||
|
$payment_hash->data = $hash_data;
|
||||||
|
$payment_hash->fee_total = $fee_totals;
|
||||||
|
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||||
|
|
||||||
|
$payment_hash->save();
|
||||||
|
|
||||||
|
if ($this->is_credit_payment) {
|
||||||
|
$amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals));
|
||||||
|
} else {
|
||||||
|
$credit_totals = 0;
|
||||||
|
$amount_with_fee = max(0, $invoice_totals + $fee_totals);
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = [
|
||||||
|
'credit_totals' => $credit_totals,
|
||||||
|
'invoice_totals' => $invoice_totals,
|
||||||
|
'fee_total' => $fee_totals,
|
||||||
|
'amount_with_fee' => $amount_with_fee,
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'ph' => $payment_hash,
|
||||||
|
'payment_hash' => $payment_hash->hash,
|
||||||
|
'total' => $totals,
|
||||||
|
'invoices' => $payable_invoices,
|
||||||
|
'tokens' => $tokens,
|
||||||
|
'payment_method_id' => $payment_method_id,
|
||||||
|
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||||
|
'client' => $client,
|
||||||
|
'pre_payment' => $this->data['pre_payment'],
|
||||||
|
'is_recurring' => $this->data['is_recurring'],
|
||||||
|
'company_gateway' => $company_gateway,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->is_credit_payment) {
|
||||||
|
|
||||||
|
$this->mergeResponder(['success' => true, 'component' => 'CreditPaymentComponent', 'payload' => $data]);
|
||||||
|
return $this->getResponder();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mergeResponder(['success' => true, 'payload' => $data]);
|
||||||
|
|
||||||
|
return $this->getResponder();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getResponder(): array
|
||||||
|
{
|
||||||
|
return $this->responder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mergeResponder(array $data): self
|
||||||
|
{
|
||||||
|
$this->responder = array_merge($this->responder, $data);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -23,8 +23,8 @@ enum HttpVerb: string
|
|||||||
case DELETE = 'delete';
|
case DELETE = 'delete';
|
||||||
}
|
}
|
||||||
|
|
||||||
class Storecove {
|
class Storecove
|
||||||
|
{
|
||||||
private string $base_url = 'https://api.storecove.com/api/v2/';
|
private string $base_url = 'https://api.storecove.com/api/v2/';
|
||||||
|
|
||||||
private array $peppol_discovery = [
|
private array $peppol_discovery = [
|
||||||
@ -44,7 +44,9 @@ class Storecove {
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
public function __construct(){}
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//config('ninja.storecove_api_key');
|
//config('ninja.storecove_api_key');
|
||||||
|
|
||||||
@ -170,8 +172,9 @@ class Storecove {
|
|||||||
nlog($r->body());
|
nlog($r->body());
|
||||||
nlog($r->json());
|
nlog($r->json());
|
||||||
|
|
||||||
if($r->successful())
|
if($r->successful()) {
|
||||||
return $r->json()['guid'];
|
return $r->json()['guid'];
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -265,8 +268,9 @@ class Storecove {
|
|||||||
|
|
||||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload);
|
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload);
|
||||||
|
|
||||||
if($r->successful())
|
if($r->successful()) {
|
||||||
return $r->json();
|
return $r->json();
|
||||||
|
}
|
||||||
|
|
||||||
return $r;
|
return $r;
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ use horstoeko\zugferd\ZugferdDocumentReader;
|
|||||||
use horstoeko\zugferdvisualizer\ZugferdVisualizer;
|
use horstoeko\zugferdvisualizer\ZugferdVisualizer;
|
||||||
use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer;
|
use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer;
|
||||||
|
|
||||||
class ZugferdEDocument extends AbstractService {
|
class ZugferdEDocument extends AbstractService
|
||||||
|
{
|
||||||
public ZugferdDocumentReader|string $document;
|
public ZugferdDocumentReader|string $document;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,7 +76,7 @@ class ZugferdEDocument extends AbstractService {
|
|||||||
if ($taxCurrency && $taxCurrency != $invoiceCurrency) {
|
if ($taxCurrency && $taxCurrency != $invoiceCurrency) {
|
||||||
$expense->private_notes = ctrans("texts.tax_currency_mismatch");
|
$expense->private_notes = ctrans("texts.tax_currency_mismatch");
|
||||||
}
|
}
|
||||||
$expense->uses_inclusive_taxes = True;
|
$expense->uses_inclusive_taxes = true;
|
||||||
$expense->amount = $grandTotalAmount;
|
$expense->amount = $grandTotalAmount;
|
||||||
$counter = 1;
|
$counter = 1;
|
||||||
if ($this->document->firstDocumentTax()) {
|
if ($this->document->firstDocumentTax()) {
|
||||||
@ -117,8 +118,7 @@ class ZugferdEDocument extends AbstractService {
|
|||||||
$expense->vendor_id = $vendor->id;
|
$expense->vendor_id = $vendor->id;
|
||||||
}
|
}
|
||||||
$expense->transaction_reference = $documentno;
|
$expense->transaction_reference = $documentno;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// The document exists as an expense
|
// The document exists as an expense
|
||||||
// Handle accordingly
|
// Handle accordingly
|
||||||
nlog("Document already exists");
|
nlog("Document already exists");
|
||||||
@ -128,4 +128,3 @@ class ZugferdEDocument extends AbstractService {
|
|||||||
return $expense;
|
return $expense;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,8 +341,9 @@ class Peppol extends AbstractService
|
|||||||
$this->p_invoice->ID = $this->invoice->number;
|
$this->p_invoice->ID = $this->invoice->number;
|
||||||
$this->p_invoice->IssueDate = new \DateTime($this->invoice->date);
|
$this->p_invoice->IssueDate = new \DateTime($this->invoice->date);
|
||||||
|
|
||||||
if($this->invoice->due_date)
|
if($this->invoice->due_date) {
|
||||||
$this->p_invoice->DueDate = new \DateTime($this->invoice->due_date);
|
$this->p_invoice->DueDate = new \DateTime($this->invoice->due_date);
|
||||||
|
}
|
||||||
|
|
||||||
$this->p_invoice->InvoiceTypeCode = 380; //
|
$this->p_invoice->InvoiceTypeCode = 380; //
|
||||||
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
|
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
|
||||||
@ -390,10 +391,11 @@ class Peppol extends AbstractService
|
|||||||
|
|
||||||
private function getTotalTaxAmount(): float
|
private function getTotalTaxAmount(): float
|
||||||
{
|
{
|
||||||
if(!$this->invoice->total_taxes)
|
if(!$this->invoice->total_taxes) {
|
||||||
return 0;
|
return 0;
|
||||||
elseif($this->invoice->uses_inclusive_taxes)
|
} elseif($this->invoice->uses_inclusive_taxes) {
|
||||||
return $this->invoice->total_taxes;
|
return $this->invoice->total_taxes;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->calcAmountLineTax($this->invoice->tax_rate1, $this->invoice->amount) ?? 0;
|
return $this->calcAmountLineTax($this->invoice->tax_rate1, $this->invoice->amount) ?? 0;
|
||||||
}
|
}
|
||||||
@ -746,11 +748,11 @@ class Peppol extends AbstractService
|
|||||||
};
|
};
|
||||||
|
|
||||||
//single array
|
//single array
|
||||||
if(is_array($rules) && !is_array($rules[0]))
|
if(is_array($rules) && !is_array($rules[0])) {
|
||||||
return $rules[2];
|
return $rules[2];
|
||||||
|
}
|
||||||
|
|
||||||
foreach($rules as $rule)
|
foreach($rules as $rule) {
|
||||||
{
|
|
||||||
if(stripos($rule[0], $code) !== false) {
|
if(stripos($rule[0], $code) !== false) {
|
||||||
return $rule[2];
|
return $rule[2];
|
||||||
}
|
}
|
||||||
@ -768,12 +770,13 @@ class Peppol extends AbstractService
|
|||||||
|
|
||||||
if(strlen($this->invoice->client->vat_number ?? '') > 1) {
|
if(strlen($this->invoice->client->vat_number ?? '') > 1) {
|
||||||
|
|
||||||
$pi = new PartyIdentification;
|
$pi = new PartyIdentification();
|
||||||
|
|
||||||
$vatID = new ID;
|
$vatID = new ID();
|
||||||
|
|
||||||
if($scheme = $this->resolveTaxScheme())
|
if($scheme = $this->resolveTaxScheme()) {
|
||||||
$vatID->schemeID = $scheme;
|
$vatID->schemeID = $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
$vatID->value = $this->invoice->client->vat_number;
|
$vatID->value = $this->invoice->client->vat_number;
|
||||||
$pi->ID = $vatID;
|
$pi->ID = $vatID;
|
||||||
@ -804,7 +807,8 @@ class Peppol extends AbstractService
|
|||||||
$physical_location = new PhysicalLocation();
|
$physical_location = new PhysicalLocation();
|
||||||
$physical_location->Address = $address;
|
$physical_location->Address = $address;
|
||||||
|
|
||||||
$party->PhysicalLocation = $physical_location;;
|
$party->PhysicalLocation = $physical_location;
|
||||||
|
;
|
||||||
|
|
||||||
$contact = new Contact();
|
$contact = new Contact();
|
||||||
$contact->ElectronicMail = $this->invoice->client->present()->email();
|
$contact->ElectronicMail = $this->invoice->client->present()->email();
|
||||||
@ -871,17 +875,15 @@ class Peppol extends AbstractService
|
|||||||
|
|
||||||
if(count($receiver_identifiers) > 1) {
|
if(count($receiver_identifiers) > 1) {
|
||||||
|
|
||||||
foreach($receiver_identifiers as $ident)
|
foreach($receiver_identifiers as $ident) {
|
||||||
{
|
if(str_contains($ident[0], $client_classification)) {
|
||||||
if(str_contains($ident[0], $client_classification))
|
|
||||||
{
|
|
||||||
return $ident[3];
|
return $ident[3];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} elseif(count($receiver_identifiers) == 1) {
|
||||||
elseif(count($receiver_identifiers) == 1)
|
|
||||||
return $receiver_identifiers[3];
|
return $receiver_identifiers[3];
|
||||||
|
}
|
||||||
|
|
||||||
throw new \Exception("e-invoice generation halted:: Could not resolve the Tax Code for this client? {$this->invoice->client->hashed_id}");
|
throw new \Exception("e-invoice generation halted:: Could not resolve the Tax Code for this client? {$this->invoice->client->hashed_id}");
|
||||||
|
|
||||||
@ -914,8 +916,9 @@ class Peppol extends AbstractService
|
|||||||
//only scans for top level props
|
//only scans for top level props
|
||||||
foreach($settings as $prop => $visibility) {
|
foreach($settings as $prop => $visibility) {
|
||||||
|
|
||||||
if($prop_value = $this->getSetting($prop))
|
if($prop_value = $this->getSetting($prop)) {
|
||||||
$this->p_invoice->{$prop} = $prop_value;
|
$this->p_invoice->{$prop} = $prop_value;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -965,8 +968,9 @@ class Peppol extends AbstractService
|
|||||||
private function senderSpecificLevelMutators(): self
|
private function senderSpecificLevelMutators(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if(method_exists($this, $this->invoice->company->country()->iso_3166_2))
|
if(method_exists($this, $this->invoice->company->country()->iso_3166_2)) {
|
||||||
$this->{$this->invoice->company->country()->iso_3166_2}();
|
$this->{$this->invoice->company->country()->iso_3166_2}();
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -982,8 +986,9 @@ class Peppol extends AbstractService
|
|||||||
private function receiverSpecificLevelMutators(): self
|
private function receiverSpecificLevelMutators(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if(method_exists($this, "client_{$this->invoice->company->country()->iso_3166_2}"))
|
if(method_exists($this, "client_{$this->invoice->company->country()->iso_3166_2}")) {
|
||||||
$this->{"client_{$this->invoice->company->country()->iso_3166_2}"}();
|
$this->{"client_{$this->invoice->company->country()->iso_3166_2}"}();
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -999,9 +1004,9 @@ class Peppol extends AbstractService
|
|||||||
private function setPaymentMeans(bool $required = false): self
|
private function setPaymentMeans(bool $required = false): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if(isset($this->p_invoice->PaymentMeans))
|
if(isset($this->p_invoice->PaymentMeans)) {
|
||||||
return $this;
|
return $this;
|
||||||
elseif($paymentMeans = $this->getSetting('Invoice.PaymentMeans')){
|
} elseif($paymentMeans = $this->getSetting('Invoice.PaymentMeans')) {
|
||||||
$this->p_invoice->PaymentMeans = is_array($paymentMeans) ? $paymentMeans : [$paymentMeans];
|
$this->p_invoice->PaymentMeans = is_array($paymentMeans) ? $paymentMeans : [$paymentMeans];
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -1022,8 +1027,7 @@ class Peppol extends AbstractService
|
|||||||
{
|
{
|
||||||
$this->p_invoice->BuyerReference = $this->invoice->po_number ?? '';
|
$this->p_invoice->BuyerReference = $this->invoice->po_number ?? '';
|
||||||
|
|
||||||
if(strlen($this->invoice->po_number ?? '') > 1)
|
if(strlen($this->invoice->po_number ?? '') > 1) {
|
||||||
{
|
|
||||||
$order_reference = new OrderReference();
|
$order_reference = new OrderReference();
|
||||||
$id = new ID();
|
$id = new ID();
|
||||||
$id->value = $this->invoice->po_number;
|
$id->value = $this->invoice->po_number;
|
||||||
@ -1064,13 +1068,11 @@ class Peppol extends AbstractService
|
|||||||
//@phpstan-ignore-next-line
|
//@phpstan-ignore-next-line
|
||||||
if(isset($this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID)) {
|
if(isset($this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID)) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
} elseif($customer_assigned_account_id = $this->getSetting('Invoice.AccountingCustomerParty.CustomerAssignedAccountID')) {
|
||||||
elseif($customer_assigned_account_id = $this->getSetting('Invoice.AccountingCustomerParty.CustomerAssignedAccountID')){
|
|
||||||
|
|
||||||
$this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID = $customer_assigned_account_id;
|
$this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID = $customer_assigned_account_id;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
} elseif(strlen($this->invoice->client->id_number ?? '') > 1) {
|
||||||
elseif(strlen($this->invoice->client->id_number ?? '') > 1){
|
|
||||||
|
|
||||||
$customer_assigned_account_id = new CustomerAssignedAccountID();
|
$customer_assigned_account_id = new CustomerAssignedAccountID();
|
||||||
$customer_assigned_account_id->value = $this->invoice->client->id_number;
|
$customer_assigned_account_id->value = $this->invoice->client->id_number;
|
||||||
@ -1130,8 +1132,7 @@ class Peppol extends AbstractService
|
|||||||
$emails = $meta['routing']['emails'];
|
$emails = $meta['routing']['emails'];
|
||||||
array_push($emails, $email);
|
array_push($emails, $email);
|
||||||
$meta['routing']['emails'] = $emails;
|
$meta['routing']['emails'] = $emails;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$meta['routing']['emails'] = [$email];
|
$meta['routing']['emails'] = [$email];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1246,8 +1247,9 @@ class Peppol extends AbstractService
|
|||||||
private function ES(): self
|
private function ES(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if(!isset($this->invoice->due_date))
|
if(!isset($this->invoice->due_date)) {
|
||||||
$this->p_invoice->DueDate = new \DateTime($this->invoice->date);
|
$this->p_invoice->DueDate = new \DateTime($this->invoice->date);
|
||||||
|
}
|
||||||
|
|
||||||
if($this->invoice->client->classification == 'business' && $this->invoice->company->getSetting('classification') == 'business') {
|
if($this->invoice->client->classification == 'business' && $this->invoice->company->getSetting('classification') == 'business') {
|
||||||
//must have a paymentmeans as credit_transfer
|
//must have a paymentmeans as credit_transfer
|
||||||
@ -1323,8 +1325,7 @@ class Peppol extends AbstractService
|
|||||||
|
|
||||||
// ["scheme" => 'FR:SIRET', "id" => "0002:{$this->invoice->client->id_number}"]
|
// ["scheme" => 'FR:SIRET', "id" => "0002:{$this->invoice->client->id_number}"]
|
||||||
]));
|
]));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
//SIRET
|
//SIRET
|
||||||
$this->setStorecoveMeta($this->buildRouting([
|
$this->setStorecoveMeta($this->buildRouting([
|
||||||
["scheme" => 'FR:SIRET', "id" => "{$this->invoice->client->id_number}"]
|
["scheme" => 'FR:SIRET', "id" => "{$this->invoice->client->id_number}"]
|
||||||
|
@ -117,15 +117,18 @@ class RO
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
public function __construct(protected Invoice $invoice){}
|
public function __construct(protected Invoice $invoice)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function getStateCode(?string $state_code): string
|
public function getStateCode(?string $state_code): string
|
||||||
{
|
{
|
||||||
$state_code = strlen($state_code ?? '') > 1 ? $state_code : $this->invoice->client->state;
|
$state_code = strlen($state_code ?? '') > 1 ? $state_code : $this->invoice->client->state;
|
||||||
|
|
||||||
//codes are configured by default
|
//codes are configured by default
|
||||||
if(isset($this->countrySubEntity[$state_code]))
|
if(isset($this->countrySubEntity[$state_code])) {
|
||||||
return $state_code;
|
return $state_code;
|
||||||
|
}
|
||||||
|
|
||||||
$key = array_search($state_code, $this->countrySubEntity);
|
$key = array_search($state_code, $this->countrySubEntity);
|
||||||
|
|
||||||
@ -140,8 +143,9 @@ class RO
|
|||||||
{
|
{
|
||||||
$client_sector_code = $client_city ?? $this->invoice->client->city;
|
$client_sector_code = $client_city ?? $this->invoice->client->city;
|
||||||
|
|
||||||
if(in_array($this->getStateCode($this->invoice->client->state), ['BUCHAREST', 'RO-B']))
|
if(in_array($this->getStateCode($this->invoice->client->state), ['BUCHAREST', 'RO-B'])) {
|
||||||
return in_array(strtoupper($this->invoice->client->city), array_keys($this->sectorList)) ? strtoupper($this->invoice->client->city) : 'SECTOR1';
|
return in_array(strtoupper($this->invoice->client->city), array_keys($this->sectorList)) ? strtoupper($this->invoice->client->city) : 'SECTOR1';
|
||||||
|
}
|
||||||
|
|
||||||
return $client_sector_code;
|
return $client_sector_code;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ class PropertyResolver
|
|||||||
return self::traverse($object, $pathSegments);
|
return self::traverse($object, $pathSegments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function traverse($object, array $pathSegments) {
|
private static function traverse($object, array $pathSegments)
|
||||||
|
{
|
||||||
if (empty($pathSegments)) {
|
if (empty($pathSegments)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@ namespace App\Services\Import\Quickbooks\Contracts;
|
|||||||
|
|
||||||
interface SdkInterface
|
interface SdkInterface
|
||||||
{
|
{
|
||||||
function getAuthorizationUrl(): string;
|
public function getAuthorizationUrl(): string;
|
||||||
function accessToken(string $code, string $realm): array;
|
public function accessToken(string $code, string $realm): array;
|
||||||
function refreshToken(): array;
|
public function refreshToken(): array;
|
||||||
function getAccessToken(): array;
|
public function getAccessToken();
|
||||||
function getRefreshToken(): array;
|
public function getRefreshToken(): array;
|
||||||
function totalRecords(string $entity): int;
|
public function totalRecords(string $entity): int;
|
||||||
function fetchRecords(string $entity, int $max): array;
|
public function fetchRecords(string $entity, int $max): array;
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,20 @@ use App\Libraries\MultiDB;
|
|||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class CompanyTokensRepository {
|
class CompanyTokensRepository
|
||||||
|
{
|
||||||
|
|
||||||
private $company_key;
|
private $company_key;
|
||||||
private $store_key = "quickbooks-token";
|
private $store_key = "quickbooks-token";
|
||||||
|
|
||||||
public function __construct(string $key = null) {
|
public function __construct(string $key = null)
|
||||||
|
{
|
||||||
$this->company_key = $key ?? auth()->user->company()->company_key ?? null;
|
$this->company_key = $key ?? auth()->user->company()->company_key ?? null;
|
||||||
$this->store_key .= $key;
|
$this->store_key .= $key;
|
||||||
$this->setCompanyDbByKey();
|
$this->setCompanyDbByKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(array $tokens) {
|
public function save(array $tokens)
|
||||||
|
{
|
||||||
$this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']);
|
$this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']);
|
||||||
$this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']);
|
$this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']);
|
||||||
}
|
}
|
||||||
@ -35,7 +36,8 @@ class CompanyTokensRepository {
|
|||||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get() {
|
public function get()
|
||||||
|
{
|
||||||
return $this->getAccessToken() + $this->getRefreshToken();
|
return $this->getAccessToken() + $this->getRefreshToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface
|
|||||||
|
|
||||||
final class SdkWrapper implements QuickbooksInterface
|
final class SdkWrapper implements QuickbooksInterface
|
||||||
{
|
{
|
||||||
|
public const MAXRESULTS = 10000;
|
||||||
const MAXRESULTS = 10000;
|
|
||||||
|
|
||||||
private $sdk;
|
private $sdk;
|
||||||
private $entities = ['Customer','Invoice','Payment','Item'];
|
private $entities = ['Customer','Invoice','Payment','Item'];
|
||||||
@ -33,7 +32,8 @@ final class SdkWrapper implements QuickbooksInterface
|
|||||||
return $this->getTokens();
|
return $this->getTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRefreshToken(): array{
|
public function getRefreshToken(): array
|
||||||
|
{
|
||||||
return $this->getTokens();
|
return $this->getTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,11 +66,13 @@ final class SdkWrapper implements QuickbooksInterface
|
|||||||
return $this->getTokens();
|
return $this->getTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleCallbacks(array $data): void {
|
public function handleCallbacks(array $data): void
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function totalRecords(string $entity) : int {
|
public function totalRecords(string $entity): int
|
||||||
|
{
|
||||||
return $this->sdk->Query("select count(*) from $entity");
|
return $this->sdk->Query("select count(*) from $entity");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,9 +81,12 @@ final class SdkWrapper implements QuickbooksInterface
|
|||||||
return (array) $this->sdk->Query($query, $start, $limit);
|
return (array) $this->sdk->Query($query, $start, $limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchRecords( string $entity, int $max = 1000): array {
|
public function fetchRecords(string $entity, int $max = 1000): array
|
||||||
|
{
|
||||||
|
|
||||||
if(!in_array($entity, $this->entities)) return [];
|
if(!in_array($entity, $this->entities)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$records = [];
|
$records = [];
|
||||||
$start = 0;
|
$start = 0;
|
||||||
@ -94,12 +99,16 @@ final class SdkWrapper implements QuickbooksInterface
|
|||||||
do {
|
do {
|
||||||
$limit = min(self::MAXRESULTS, $total - $start);
|
$limit = min(self::MAXRESULTS, $total - $start);
|
||||||
$recordsChunk = $this->queryData("select * from $entity", $start, $limit);
|
$recordsChunk = $this->queryData("select * from $entity", $start, $limit);
|
||||||
if(empty($recordsChunk)) break;
|
if(empty($recordsChunk)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$records = array_merge($records, $recordsChunk);
|
$records = array_merge($records, $recordsChunk);
|
||||||
$start += $limit;
|
$start += $limit;
|
||||||
} while ($start < $total);
|
} while ($start < $total);
|
||||||
if(empty($records)) throw new \Exceptions("No records retrieved!");
|
if(empty($records)) {
|
||||||
|
throw new \Exception("No records retrieved!");
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
|
nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Services\Import\Quickbooks;
|
namespace App\Services\Import\Quickbooks;
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use App\Services\Import\Quickbooks\Auth;
|
use App\Services\Import\Quickbooks\Auth;
|
||||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
|
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
|
||||||
use App\Services\Import\QuickBooks\Contracts\SdkInterface as QuickbooksInterface;
|
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||||
|
|
||||||
final class Service
|
final class Service
|
||||||
{
|
{
|
||||||
private QuickbooksInterface $sdk;
|
private QuickbooksInterface $sdk;
|
||||||
|
|
||||||
public function __construct(QuickbooksInterface $quickbooks) {
|
public function __construct(QuickbooksInterface $quickbooks)
|
||||||
|
{
|
||||||
$this->sdk = $quickbooks;
|
$this->sdk = $quickbooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +62,8 @@ final class Service
|
|||||||
return $this->fetchRecords('Item', $max) ;
|
return $this->fetchRecords('Item', $max) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function fetchRecords(string $entity, $max = 100) : Collection {
|
protected function fetchRecords(string $entity, $max = 100): Collection
|
||||||
|
{
|
||||||
return (self::RepositoryFactory($entity))->get($max);
|
return (self::RepositoryFactory($entity))->get($max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +42,9 @@ class CreateInvitations extends AbstractService
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
|
|
||||||
if(!$this->purchase_order->vendor)
|
if(!$this->purchase_order->vendor) {
|
||||||
return $this->purchase_order;
|
return $this->purchase_order;
|
||||||
|
}
|
||||||
|
|
||||||
$contacts = $this->purchase_order->vendor->contacts()->get();
|
$contacts = $this->purchase_order->vendor->contacts()->get();
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ use App\Models\PurchaseOrder;
|
|||||||
|
|
||||||
class MarkSent
|
class MarkSent
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(public Vendor $vendor, public PurchaseOrder $purchase_order)
|
public function __construct(public Vendor $vendor, public PurchaseOrder $purchase_order)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,13 @@ class ProfitLoss
|
|||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
MultiDB::setDb($this->company->db);
|
||||||
|
App::forgetInstance('translator');
|
||||||
|
App::setLocale($this->company->locale());
|
||||||
|
$t = app('translator');
|
||||||
|
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||||
|
|
||||||
return $this->build()->getCsv();
|
return $this->build()->getCsv();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,12 +363,6 @@ class ProfitLoss
|
|||||||
nlog($this->income_taxes);
|
nlog($this->income_taxes);
|
||||||
nlog(array_sum(array_column($this->expense_break_down, 'total')));
|
nlog(array_sum(array_column($this->expense_break_down, 'total')));
|
||||||
|
|
||||||
MultiDB::setDb($this->company->db);
|
|
||||||
App::forgetInstance('translator');
|
|
||||||
App::setLocale($this->company->locale());
|
|
||||||
$t = app('translator');
|
|
||||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
|
||||||
|
|
||||||
$csv = Writer::createFromString();
|
$csv = Writer::createFromString();
|
||||||
|
|
||||||
$csv->insertOne([ctrans('texts.profit_and_loss')]);
|
$csv->insertOne([ctrans('texts.profit_and_loss')]);
|
||||||
|
1
app/Services/Vendor/VendorService.php
vendored
1
app/Services/Vendor/VendorService.php
vendored
@ -17,7 +17,6 @@ use Illuminate\Database\QueryException;
|
|||||||
|
|
||||||
class VendorService
|
class VendorService
|
||||||
{
|
{
|
||||||
|
|
||||||
use GeneratesCounter;
|
use GeneratesCounter;
|
||||||
|
|
||||||
private bool $completed = true;
|
private bool $completed = true;
|
||||||
|
@ -40,7 +40,7 @@ class UserTransformer extends EntityTransformer
|
|||||||
|
|
||||||
public function transform(User $user)
|
public function transform(User $user)
|
||||||
{
|
{
|
||||||
$ref = new \stdClass;
|
$ref = new \stdClass();
|
||||||
$ref->free = 0;
|
$ref->free = 0;
|
||||||
$ref->pro = 0;
|
$ref->pro = 0;
|
||||||
$ref->enterprise = 0;
|
$ref->enterprise = 0;
|
||||||
|
@ -13,6 +13,7 @@ namespace App\Utils;
|
|||||||
|
|
||||||
use Illuminate\Http\File;
|
use Illuminate\Http\File;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
class TempFile
|
class TempFile
|
||||||
{
|
{
|
||||||
public static function path($url): string
|
public static function path($url): string
|
||||||
|
@ -84,8 +84,9 @@ trait MakesReminders
|
|||||||
{
|
{
|
||||||
$interval = $this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id);
|
$interval = $this->addTimeInterval($last_sent_date, $endless_reminder_frequency_id);
|
||||||
|
|
||||||
if(is_null($interval))
|
if(is_null($interval)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (Carbon::now()->startOfDay()->eq($interval)) {
|
if (Carbon::now()->startOfDay()->eq($interval)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -29,8 +29,8 @@ class PDF extends FPDI
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$trans = mb_convert_encoding($trans, 'ISO-8859-1', 'UTF-8');
|
$trans = mb_convert_encoding($trans, 'ISO-8859-1', 'UTF-8');
|
||||||
|
} catch(\Exception $e) {
|
||||||
}
|
}
|
||||||
catch(\Exception $e){}
|
|
||||||
|
|
||||||
$this->Cell(0, 5, $trans, 0, 0, $this->text_alignment);
|
$this->Cell(0, 5, $trans, 0, 0, $this->text_alignment);
|
||||||
}
|
}
|
||||||
|
47
app/Utils/Traits/WithSecureContext.php
Normal file
47
app/Utils/Traits/WithSecureContext.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Utils\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
trait WithSecureContext
|
||||||
|
{
|
||||||
|
public const CONTEXT_UPDATE = 'secureContext.updated';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Psr\Container\ContainerExceptionInterface
|
||||||
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
|
public function getContext(): mixed
|
||||||
|
{
|
||||||
|
return session()->get('secureContext.invoice-pay');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContext(string $property, $value): array
|
||||||
|
{
|
||||||
|
$clone = session()->pull('secureContext.invoice-pay', default: []);
|
||||||
|
|
||||||
|
data_set($clone, $property, $value);
|
||||||
|
|
||||||
|
session()->put('secureContext.invoice-pay', $clone);
|
||||||
|
|
||||||
|
$this->dispatch(self::CONTEXT_UPDATE);
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetContext(): void
|
||||||
|
{
|
||||||
|
session()->forget('secureContext.invoice-pay');
|
||||||
|
}
|
||||||
|
}
|
24
composer.lock
generated
24
composer.lock
generated
@ -535,16 +535,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
"version": "3.320.4",
|
"version": "3.320.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||||
"reference": "e6af3e760864d43a30d8b7deb4f9dc6a49a5f66a"
|
"reference": "afda5aefd59da90208d2f59427ce81e91535b1f2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e6af3e760864d43a30d8b7deb4f9dc6a49a5f66a",
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/afda5aefd59da90208d2f59427ce81e91535b1f2",
|
||||||
"reference": "e6af3e760864d43a30d8b7deb4f9dc6a49a5f66a",
|
"reference": "afda5aefd59da90208d2f59427ce81e91535b1f2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -627,9 +627,9 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.320.4"
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.320.5"
|
||||||
},
|
},
|
||||||
"time": "2024-08-20T18:20:32+00:00"
|
"time": "2024-08-21T18:14:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@ -8912,16 +8912,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/log",
|
"name": "psr/log",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/php-fig/log.git",
|
"url": "https://github.com/php-fig/log.git",
|
||||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
|
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
|
||||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8956,9 +8956,9 @@
|
|||||||
"psr-3"
|
"psr-3"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/php-fig/log/tree/3.0.0"
|
"source": "https://github.com/php-fig/log/tree/3.0.1"
|
||||||
},
|
},
|
||||||
"time": "2021-07-14T16:46:02+00:00"
|
"time": "2024-08-21T13:31:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/simple-cache",
|
"name": "psr/simple-cache",
|
||||||
|
@ -17,8 +17,8 @@ return [
|
|||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||||
'app_version' => env('APP_VERSION', '5.10.24'),
|
'app_version' => env('APP_VERSION', '5.10.25'),
|
||||||
'app_tag' => env('APP_TAG', '5.10.24'),
|
'app_tag' => env('APP_TAG', '5.10.25'),
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', false),
|
'api_secret' => env('API_SECRET', false),
|
||||||
|
1215
package-lock.json
generated
1215
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
|||||||
"vue-template-compiler": "^2.6.14"
|
"vue-template-compiler": "^2.6.14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@invoiceninja/simple-card": "^0.0.2",
|
||||||
"axios": "^0.25",
|
"axios": "^0.25",
|
||||||
"card-js": "^1.0.13",
|
"card-js": "^1.0.13",
|
||||||
"card-validator": "^8.1.1",
|
"card-validator": "^8.1.1",
|
||||||
@ -35,7 +36,8 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"resolve-url-loader": "^4.0.0",
|
"resolve-url-loader": "^4.0.0",
|
||||||
"sass": "^1.43.4",
|
"sass": "^1.43.4",
|
||||||
"sass-loader": "^12.3.0"
|
"sass-loader": "^12.3.0",
|
||||||
|
"signature_pad": "^5.0.2"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
1
public/build/assets/app-06521fee.css
vendored
1
public/build/assets/app-06521fee.css
vendored
File diff suppressed because one or more lines are too long
1
public/build/assets/app-fee1da41.css
vendored
Normal file
1
public/build/assets/app-fee1da41.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
public/build/assets/forte-credit-card-payment-7bb15431.js
vendored
Normal file
9
public/build/assets/forte-credit-card-payment-7bb15431.js
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
var o=Object.defineProperty;var l=(n,e,t)=>e in n?o(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var d=(n,e,t)=>(l(n,typeof e!="symbol"?e+"":e,t),t);/**
|
||||||
|
* 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
|
||||||
|
*/class c{constructor(e){d(this,"handleAuthorization",()=>{var e=$("#my-card"),t={api_login_id:this.apiLoginId,card_number:e.CardJs("cardNumber").replace(/[^\d]/g,""),expire_year:e.CardJs("expiryYear").replace(/[^\d]/g,""),expire_month:e.CardJs("expiryMonth").replace(/[^\d]/g,""),cvv:document.getElementById("cvv").value.replace(/[^\d]/g,"")};return document.getElementById("pay-now")&&(document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden")),forte.createToken(t).success(this.successResponseHandler).error(this.failedResponseHandler),!1});d(this,"successResponseHandler",e=>{document.getElementById("payment_token").value=e.onetime_token,document.getElementById("card_brand").value=e.card_brand,document.getElementById("expire_year").value=e.expire_year,document.getElementById("expire_month").value=e.expire_month,document.getElementById("last_4").value=e.last_4;let t=document.querySelector("input[name=token-billing-checkbox]:checked");return t&&(document.getElementById("store_card").value=t.value),document.getElementById("server_response").submit(),!1});d(this,"failedResponseHandler",e=>{var t='<div class="alert alert-failure mb-4"><ul><li>'+e.response_description+"</li></ul></div>";return document.getElementById("forte_errors").innerHTML=t,document.getElementById("pay-now").disabled=!1,document.querySelector("#pay-now > svg").classList.add("hidden"),document.querySelector("#pay-now > span").classList.remove("hidden"),!1});d(this,"handle",()=>{Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(r=>r.addEventListener("click",a=>{document.getElementById("save-card--container").style.display="none",document.getElementById("forte--credit-card-container").style.display="none",document.getElementById("token").value=a.target.dataset.token}));let e=document.getElementById("toggle-payment-with-credit-card");e&&e.addEventListener("click",()=>{document.getElementById("save-card--container").style.display="grid",document.getElementById("forte--credit-card-container").style.display="flex",document.getElementById("token").value=null});let t=document.getElementById("pay-now");return t&&t.addEventListener("click",r=>{let a=document.getElementById("token");a.value?this.handlePayNowAction(a.value):this.handleAuthorization()}),this});this.apiLoginId=e,this.cardHolderName=document.getElementById("cardholder_name")}handlePayNowAction(e){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),document.getElementById("token").value=e,document.getElementById("server_response").submit()}}const i=document.querySelector('meta[name="forte-api-login-id"]').content;new c(i).handle();
|
@ -240,7 +240,7 @@
|
|||||||
"src": "resources/js/setup/setup.js"
|
"src": "resources/js/setup/setup.js"
|
||||||
},
|
},
|
||||||
"resources/sass/app.scss": {
|
"resources/sass/app.scss": {
|
||||||
"file": "assets/app-06521fee.css",
|
"file": "assets/app-fee1da41.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/sass/app.scss"
|
"src": "resources/sass/app.scss"
|
||||||
}
|
}
|
||||||
|
633
public/vendor/signature_pad@5/signature_pad.js
vendored
Normal file
633
public/vendor/signature_pad@5/signature_pad.js
vendored
Normal file
@ -0,0 +1,633 @@
|
|||||||
|
/*!
|
||||||
|
* Signature Pad v5.0.2 | https://github.com/szimek/signature_pad
|
||||||
|
* (c) 2024 Szymon Nowak | Released under the MIT license
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Point {
|
||||||
|
constructor(x, y, pressure, time) {
|
||||||
|
if (isNaN(x) || isNaN(y)) {
|
||||||
|
throw new Error(`Point is invalid: (${x}, ${y})`);
|
||||||
|
}
|
||||||
|
this.x = +x;
|
||||||
|
this.y = +y;
|
||||||
|
this.pressure = pressure || 0;
|
||||||
|
this.time = time || Date.now();
|
||||||
|
}
|
||||||
|
distanceTo(start) {
|
||||||
|
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
|
||||||
|
}
|
||||||
|
equals(other) {
|
||||||
|
return (this.x === other.x &&
|
||||||
|
this.y === other.y &&
|
||||||
|
this.pressure === other.pressure &&
|
||||||
|
this.time === other.time);
|
||||||
|
}
|
||||||
|
velocityFrom(start) {
|
||||||
|
return this.time !== start.time
|
||||||
|
? this.distanceTo(start) / (this.time - start.time)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bezier {
|
||||||
|
static fromPoints(points, widths) {
|
||||||
|
const c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
|
||||||
|
const c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
|
||||||
|
return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
|
||||||
|
}
|
||||||
|
static calculateControlPoints(s1, s2, s3) {
|
||||||
|
const dx1 = s1.x - s2.x;
|
||||||
|
const dy1 = s1.y - s2.y;
|
||||||
|
const dx2 = s2.x - s3.x;
|
||||||
|
const dy2 = s2.y - s3.y;
|
||||||
|
const m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
|
||||||
|
const m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
|
||||||
|
const l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
||||||
|
const l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
||||||
|
const dxm = m1.x - m2.x;
|
||||||
|
const dym = m1.y - m2.y;
|
||||||
|
const k = l2 / (l1 + l2);
|
||||||
|
const cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
|
||||||
|
const tx = s2.x - cm.x;
|
||||||
|
const ty = s2.y - cm.y;
|
||||||
|
return {
|
||||||
|
c1: new Point(m1.x + tx, m1.y + ty),
|
||||||
|
c2: new Point(m2.x + tx, m2.y + ty),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
constructor(startPoint, control2, control1, endPoint, startWidth, endWidth) {
|
||||||
|
this.startPoint = startPoint;
|
||||||
|
this.control2 = control2;
|
||||||
|
this.control1 = control1;
|
||||||
|
this.endPoint = endPoint;
|
||||||
|
this.startWidth = startWidth;
|
||||||
|
this.endWidth = endWidth;
|
||||||
|
}
|
||||||
|
length() {
|
||||||
|
const steps = 10;
|
||||||
|
let length = 0;
|
||||||
|
let px;
|
||||||
|
let py;
|
||||||
|
for (let i = 0; i <= steps; i += 1) {
|
||||||
|
const t = i / steps;
|
||||||
|
const cx = this.point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
|
||||||
|
const cy = this.point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
|
||||||
|
if (i > 0) {
|
||||||
|
const xdiff = cx - px;
|
||||||
|
const ydiff = cy - py;
|
||||||
|
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||||
|
}
|
||||||
|
px = cx;
|
||||||
|
py = cy;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
point(t, start, c1, c2, end) {
|
||||||
|
return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
|
||||||
|
+ (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
|
||||||
|
+ (3.0 * c2 * (1.0 - t) * t * t)
|
||||||
|
+ (end * t * t * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignatureEventTarget {
|
||||||
|
constructor() {
|
||||||
|
try {
|
||||||
|
this._et = new EventTarget();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
this._et = document;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEventListener(type, listener, options) {
|
||||||
|
this._et.addEventListener(type, listener, options);
|
||||||
|
}
|
||||||
|
dispatchEvent(event) {
|
||||||
|
return this._et.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
removeEventListener(type, callback, options) {
|
||||||
|
this._et.removeEventListener(type, callback, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function throttle(fn, wait = 250) {
|
||||||
|
let previous = 0;
|
||||||
|
let timeout = null;
|
||||||
|
let result;
|
||||||
|
let storedContext;
|
||||||
|
let storedArgs;
|
||||||
|
const later = () => {
|
||||||
|
previous = Date.now();
|
||||||
|
timeout = null;
|
||||||
|
result = fn.apply(storedContext, storedArgs);
|
||||||
|
if (!timeout) {
|
||||||
|
storedContext = null;
|
||||||
|
storedArgs = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return function wrapper(...args) {
|
||||||
|
const now = Date.now();
|
||||||
|
const remaining = wait - (now - previous);
|
||||||
|
storedContext = this;
|
||||||
|
storedArgs = args;
|
||||||
|
if (remaining <= 0 || remaining > wait) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
previous = now;
|
||||||
|
result = fn.apply(storedContext, storedArgs);
|
||||||
|
if (!timeout) {
|
||||||
|
storedContext = null;
|
||||||
|
storedArgs = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!timeout) {
|
||||||
|
timeout = window.setTimeout(later, remaining);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignaturePad extends SignatureEventTarget {
|
||||||
|
constructor(canvas, options = {}) {
|
||||||
|
var _a, _b, _c;
|
||||||
|
super();
|
||||||
|
this.canvas = canvas;
|
||||||
|
this._drawingStroke = false;
|
||||||
|
this._isEmpty = true;
|
||||||
|
this._lastPoints = [];
|
||||||
|
this._data = [];
|
||||||
|
this._lastVelocity = 0;
|
||||||
|
this._lastWidth = 0;
|
||||||
|
this._handleMouseDown = (event) => {
|
||||||
|
if (!this._isLeftButtonPressed(event, true) || this._drawingStroke) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._strokeBegin(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handleMouseMove = (event) => {
|
||||||
|
if (!this._isLeftButtonPressed(event, true) || !this._drawingStroke) {
|
||||||
|
this._strokeEnd(this._pointerEventToSignatureEvent(event), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._strokeMoveUpdate(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handleMouseUp = (event) => {
|
||||||
|
if (this._isLeftButtonPressed(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._strokeEnd(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handleTouchStart = (event) => {
|
||||||
|
if (event.targetTouches.length !== 1 || this._drawingStroke) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.cancelable) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this._strokeBegin(this._touchEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handleTouchMove = (event) => {
|
||||||
|
if (event.targetTouches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.cancelable) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (!this._drawingStroke) {
|
||||||
|
this._strokeEnd(this._touchEventToSignatureEvent(event), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._strokeMoveUpdate(this._touchEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handleTouchEnd = (event) => {
|
||||||
|
if (event.targetTouches.length !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.cancelable) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.canvas.removeEventListener('touchmove', this._handleTouchMove);
|
||||||
|
this._strokeEnd(this._touchEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handlePointerDown = (event) => {
|
||||||
|
if (!this._isLeftButtonPressed(event) || this._drawingStroke) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this._strokeBegin(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handlePointerMove = (event) => {
|
||||||
|
if (!this._isLeftButtonPressed(event, true) || !this._drawingStroke) {
|
||||||
|
this._strokeEnd(this._pointerEventToSignatureEvent(event), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this._strokeMoveUpdate(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this._handlePointerUp = (event) => {
|
||||||
|
if (this._isLeftButtonPressed(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this._strokeEnd(this._pointerEventToSignatureEvent(event));
|
||||||
|
};
|
||||||
|
this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
|
||||||
|
this.minWidth = options.minWidth || 0.5;
|
||||||
|
this.maxWidth = options.maxWidth || 2.5;
|
||||||
|
this.throttle = (_a = options.throttle) !== null && _a !== void 0 ? _a : 16;
|
||||||
|
this.minDistance = (_b = options.minDistance) !== null && _b !== void 0 ? _b : 5;
|
||||||
|
this.dotSize = options.dotSize || 0;
|
||||||
|
this.penColor = options.penColor || 'black';
|
||||||
|
this.backgroundColor = options.backgroundColor || 'rgba(0,0,0,0)';
|
||||||
|
this.compositeOperation = options.compositeOperation || 'source-over';
|
||||||
|
this.canvasContextOptions = (_c = options.canvasContextOptions) !== null && _c !== void 0 ? _c : {};
|
||||||
|
this._strokeMoveUpdate = this.throttle
|
||||||
|
? throttle(SignaturePad.prototype._strokeUpdate, this.throttle)
|
||||||
|
: SignaturePad.prototype._strokeUpdate;
|
||||||
|
this._ctx = canvas.getContext('2d', this.canvasContextOptions);
|
||||||
|
this.clear();
|
||||||
|
this.on();
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
const { _ctx: ctx, canvas } = this;
|
||||||
|
ctx.fillStyle = this.backgroundColor;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
this._data = [];
|
||||||
|
this._reset(this._getPointGroupOptions());
|
||||||
|
this._isEmpty = true;
|
||||||
|
}
|
||||||
|
fromDataURL(dataUrl, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const image = new Image();
|
||||||
|
const ratio = options.ratio || window.devicePixelRatio || 1;
|
||||||
|
const width = options.width || this.canvas.width / ratio;
|
||||||
|
const height = options.height || this.canvas.height / ratio;
|
||||||
|
const xOffset = options.xOffset || 0;
|
||||||
|
const yOffset = options.yOffset || 0;
|
||||||
|
this._reset(this._getPointGroupOptions());
|
||||||
|
image.onload = () => {
|
||||||
|
this._ctx.drawImage(image, xOffset, yOffset, width, height);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
image.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
image.crossOrigin = 'anonymous';
|
||||||
|
image.src = dataUrl;
|
||||||
|
this._isEmpty = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toDataURL(type = 'image/png', encoderOptions) {
|
||||||
|
switch (type) {
|
||||||
|
case 'image/svg+xml':
|
||||||
|
if (typeof encoderOptions !== 'object') {
|
||||||
|
encoderOptions = undefined;
|
||||||
|
}
|
||||||
|
return `data:image/svg+xml;base64,${btoa(this.toSVG(encoderOptions))}`;
|
||||||
|
default:
|
||||||
|
if (typeof encoderOptions !== 'number') {
|
||||||
|
encoderOptions = undefined;
|
||||||
|
}
|
||||||
|
return this.canvas.toDataURL(type, encoderOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on() {
|
||||||
|
this.canvas.style.touchAction = 'none';
|
||||||
|
this.canvas.style.msTouchAction = 'none';
|
||||||
|
this.canvas.style.userSelect = 'none';
|
||||||
|
const isIOS = /Macintosh/.test(navigator.userAgent) && 'ontouchstart' in document;
|
||||||
|
if (window.PointerEvent && !isIOS) {
|
||||||
|
this._handlePointerEvents();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._handleMouseEvents();
|
||||||
|
if ('ontouchstart' in window) {
|
||||||
|
this._handleTouchEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
off() {
|
||||||
|
this.canvas.style.touchAction = 'auto';
|
||||||
|
this.canvas.style.msTouchAction = 'auto';
|
||||||
|
this.canvas.style.userSelect = 'auto';
|
||||||
|
this.canvas.removeEventListener('pointerdown', this._handlePointerDown);
|
||||||
|
this.canvas.removeEventListener('mousedown', this._handleMouseDown);
|
||||||
|
this.canvas.removeEventListener('touchstart', this._handleTouchStart);
|
||||||
|
this._removeMoveUpEventListeners();
|
||||||
|
}
|
||||||
|
_getListenerFunctions() {
|
||||||
|
var _a;
|
||||||
|
const canvasWindow = window.document === this.canvas.ownerDocument
|
||||||
|
? window
|
||||||
|
: (_a = this.canvas.ownerDocument.defaultView) !== null && _a !== void 0 ? _a : this.canvas.ownerDocument;
|
||||||
|
return {
|
||||||
|
addEventListener: canvasWindow.addEventListener.bind(canvasWindow),
|
||||||
|
removeEventListener: canvasWindow.removeEventListener.bind(canvasWindow),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_removeMoveUpEventListeners() {
|
||||||
|
const { removeEventListener } = this._getListenerFunctions();
|
||||||
|
removeEventListener('pointermove', this._handlePointerMove);
|
||||||
|
removeEventListener('pointerup', this._handlePointerUp);
|
||||||
|
removeEventListener('mousemove', this._handleMouseMove);
|
||||||
|
removeEventListener('mouseup', this._handleMouseUp);
|
||||||
|
removeEventListener('touchmove', this._handleTouchMove);
|
||||||
|
removeEventListener('touchend', this._handleTouchEnd);
|
||||||
|
}
|
||||||
|
isEmpty() {
|
||||||
|
return this._isEmpty;
|
||||||
|
}
|
||||||
|
fromData(pointGroups, { clear = true } = {}) {
|
||||||
|
if (clear) {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
this._fromData(pointGroups, this._drawCurve.bind(this), this._drawDot.bind(this));
|
||||||
|
this._data = this._data.concat(pointGroups);
|
||||||
|
}
|
||||||
|
toData() {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
_isLeftButtonPressed(event, only) {
|
||||||
|
if (only) {
|
||||||
|
return event.buttons === 1;
|
||||||
|
}
|
||||||
|
return (event.buttons & 1) === 1;
|
||||||
|
}
|
||||||
|
_pointerEventToSignatureEvent(event) {
|
||||||
|
return {
|
||||||
|
event: event,
|
||||||
|
type: event.type,
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
pressure: 'pressure' in event ? event.pressure : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_touchEventToSignatureEvent(event) {
|
||||||
|
const touch = event.changedTouches[0];
|
||||||
|
return {
|
||||||
|
event: event,
|
||||||
|
type: event.type,
|
||||||
|
x: touch.clientX,
|
||||||
|
y: touch.clientY,
|
||||||
|
pressure: touch.force,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_getPointGroupOptions(group) {
|
||||||
|
return {
|
||||||
|
penColor: group && 'penColor' in group ? group.penColor : this.penColor,
|
||||||
|
dotSize: group && 'dotSize' in group ? group.dotSize : this.dotSize,
|
||||||
|
minWidth: group && 'minWidth' in group ? group.minWidth : this.minWidth,
|
||||||
|
maxWidth: group && 'maxWidth' in group ? group.maxWidth : this.maxWidth,
|
||||||
|
velocityFilterWeight: group && 'velocityFilterWeight' in group
|
||||||
|
? group.velocityFilterWeight
|
||||||
|
: this.velocityFilterWeight,
|
||||||
|
compositeOperation: group && 'compositeOperation' in group
|
||||||
|
? group.compositeOperation
|
||||||
|
: this.compositeOperation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_strokeBegin(event) {
|
||||||
|
const cancelled = !this.dispatchEvent(new CustomEvent('beginStroke', { detail: event, cancelable: true }));
|
||||||
|
if (cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { addEventListener } = this._getListenerFunctions();
|
||||||
|
switch (event.event.type) {
|
||||||
|
case 'mousedown':
|
||||||
|
addEventListener('mousemove', this._handleMouseMove);
|
||||||
|
addEventListener('mouseup', this._handleMouseUp);
|
||||||
|
break;
|
||||||
|
case 'touchstart':
|
||||||
|
addEventListener('touchmove', this._handleTouchMove);
|
||||||
|
addEventListener('touchend', this._handleTouchEnd);
|
||||||
|
break;
|
||||||
|
case 'pointerdown':
|
||||||
|
addEventListener('pointermove', this._handlePointerMove);
|
||||||
|
addEventListener('pointerup', this._handlePointerUp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this._drawingStroke = true;
|
||||||
|
const pointGroupOptions = this._getPointGroupOptions();
|
||||||
|
const newPointGroup = Object.assign(Object.assign({}, pointGroupOptions), { points: [] });
|
||||||
|
this._data.push(newPointGroup);
|
||||||
|
this._reset(pointGroupOptions);
|
||||||
|
this._strokeUpdate(event);
|
||||||
|
}
|
||||||
|
_strokeUpdate(event) {
|
||||||
|
if (!this._drawingStroke) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._data.length === 0) {
|
||||||
|
this._strokeBegin(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dispatchEvent(new CustomEvent('beforeUpdateStroke', { detail: event }));
|
||||||
|
const point = this._createPoint(event.x, event.y, event.pressure);
|
||||||
|
const lastPointGroup = this._data[this._data.length - 1];
|
||||||
|
const lastPoints = lastPointGroup.points;
|
||||||
|
const lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
|
||||||
|
const isLastPointTooClose = lastPoint
|
||||||
|
? point.distanceTo(lastPoint) <= this.minDistance
|
||||||
|
: false;
|
||||||
|
const pointGroupOptions = this._getPointGroupOptions(lastPointGroup);
|
||||||
|
if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
|
||||||
|
const curve = this._addPoint(point, pointGroupOptions);
|
||||||
|
if (!lastPoint) {
|
||||||
|
this._drawDot(point, pointGroupOptions);
|
||||||
|
}
|
||||||
|
else if (curve) {
|
||||||
|
this._drawCurve(curve, pointGroupOptions);
|
||||||
|
}
|
||||||
|
lastPoints.push({
|
||||||
|
time: point.time,
|
||||||
|
x: point.x,
|
||||||
|
y: point.y,
|
||||||
|
pressure: point.pressure,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.dispatchEvent(new CustomEvent('afterUpdateStroke', { detail: event }));
|
||||||
|
}
|
||||||
|
_strokeEnd(event, shouldUpdate = true) {
|
||||||
|
this._removeMoveUpEventListeners();
|
||||||
|
if (!this._drawingStroke) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shouldUpdate) {
|
||||||
|
this._strokeUpdate(event);
|
||||||
|
}
|
||||||
|
this._drawingStroke = false;
|
||||||
|
this.dispatchEvent(new CustomEvent('endStroke', { detail: event }));
|
||||||
|
}
|
||||||
|
_handlePointerEvents() {
|
||||||
|
this._drawingStroke = false;
|
||||||
|
this.canvas.addEventListener('pointerdown', this._handlePointerDown);
|
||||||
|
}
|
||||||
|
_handleMouseEvents() {
|
||||||
|
this._drawingStroke = false;
|
||||||
|
this.canvas.addEventListener('mousedown', this._handleMouseDown);
|
||||||
|
}
|
||||||
|
_handleTouchEvents() {
|
||||||
|
this.canvas.addEventListener('touchstart', this._handleTouchStart);
|
||||||
|
}
|
||||||
|
_reset(options) {
|
||||||
|
this._lastPoints = [];
|
||||||
|
this._lastVelocity = 0;
|
||||||
|
this._lastWidth = (options.minWidth + options.maxWidth) / 2;
|
||||||
|
this._ctx.fillStyle = options.penColor;
|
||||||
|
this._ctx.globalCompositeOperation = options.compositeOperation;
|
||||||
|
}
|
||||||
|
_createPoint(x, y, pressure) {
|
||||||
|
const rect = this.canvas.getBoundingClientRect();
|
||||||
|
return new Point(x - rect.left, y - rect.top, pressure, new Date().getTime());
|
||||||
|
}
|
||||||
|
_addPoint(point, options) {
|
||||||
|
const { _lastPoints } = this;
|
||||||
|
_lastPoints.push(point);
|
||||||
|
if (_lastPoints.length > 2) {
|
||||||
|
if (_lastPoints.length === 3) {
|
||||||
|
_lastPoints.unshift(_lastPoints[0]);
|
||||||
|
}
|
||||||
|
const widths = this._calculateCurveWidths(_lastPoints[1], _lastPoints[2], options);
|
||||||
|
const curve = Bezier.fromPoints(_lastPoints, widths);
|
||||||
|
_lastPoints.shift();
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_calculateCurveWidths(startPoint, endPoint, options) {
|
||||||
|
const velocity = options.velocityFilterWeight * endPoint.velocityFrom(startPoint) +
|
||||||
|
(1 - options.velocityFilterWeight) * this._lastVelocity;
|
||||||
|
const newWidth = this._strokeWidth(velocity, options);
|
||||||
|
const widths = {
|
||||||
|
end: newWidth,
|
||||||
|
start: this._lastWidth,
|
||||||
|
};
|
||||||
|
this._lastVelocity = velocity;
|
||||||
|
this._lastWidth = newWidth;
|
||||||
|
return widths;
|
||||||
|
}
|
||||||
|
_strokeWidth(velocity, options) {
|
||||||
|
return Math.max(options.maxWidth / (velocity + 1), options.minWidth);
|
||||||
|
}
|
||||||
|
_drawCurveSegment(x, y, width) {
|
||||||
|
const ctx = this._ctx;
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
ctx.arc(x, y, width, 0, 2 * Math.PI, false);
|
||||||
|
this._isEmpty = false;
|
||||||
|
}
|
||||||
|
_drawCurve(curve, options) {
|
||||||
|
const ctx = this._ctx;
|
||||||
|
const widthDelta = curve.endWidth - curve.startWidth;
|
||||||
|
const drawSteps = Math.ceil(curve.length()) * 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = options.penColor;
|
||||||
|
for (let i = 0; i < drawSteps; i += 1) {
|
||||||
|
const t = i / drawSteps;
|
||||||
|
const tt = t * t;
|
||||||
|
const ttt = tt * t;
|
||||||
|
const u = 1 - t;
|
||||||
|
const uu = u * u;
|
||||||
|
const uuu = uu * u;
|
||||||
|
let x = uuu * curve.startPoint.x;
|
||||||
|
x += 3 * uu * t * curve.control1.x;
|
||||||
|
x += 3 * u * tt * curve.control2.x;
|
||||||
|
x += ttt * curve.endPoint.x;
|
||||||
|
let y = uuu * curve.startPoint.y;
|
||||||
|
y += 3 * uu * t * curve.control1.y;
|
||||||
|
y += 3 * u * tt * curve.control2.y;
|
||||||
|
y += ttt * curve.endPoint.y;
|
||||||
|
const width = Math.min(curve.startWidth + ttt * widthDelta, options.maxWidth);
|
||||||
|
this._drawCurveSegment(x, y, width);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
_drawDot(point, options) {
|
||||||
|
const ctx = this._ctx;
|
||||||
|
const width = options.dotSize > 0
|
||||||
|
? options.dotSize
|
||||||
|
: (options.minWidth + options.maxWidth) / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
this._drawCurveSegment(point.x, point.y, width);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = options.penColor;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
_fromData(pointGroups, drawCurve, drawDot) {
|
||||||
|
for (const group of pointGroups) {
|
||||||
|
const { points } = group;
|
||||||
|
const pointGroupOptions = this._getPointGroupOptions(group);
|
||||||
|
if (points.length > 1) {
|
||||||
|
for (let j = 0; j < points.length; j += 1) {
|
||||||
|
const basicPoint = points[j];
|
||||||
|
const point = new Point(basicPoint.x, basicPoint.y, basicPoint.pressure, basicPoint.time);
|
||||||
|
if (j === 0) {
|
||||||
|
this._reset(pointGroupOptions);
|
||||||
|
}
|
||||||
|
const curve = this._addPoint(point, pointGroupOptions);
|
||||||
|
if (curve) {
|
||||||
|
drawCurve(curve, pointGroupOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._reset(pointGroupOptions);
|
||||||
|
drawDot(points[0], pointGroupOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toSVG({ includeBackgroundColor = false } = {}) {
|
||||||
|
const pointGroups = this._data;
|
||||||
|
const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||||
|
const minX = 0;
|
||||||
|
const minY = 0;
|
||||||
|
const maxX = this.canvas.width / ratio;
|
||||||
|
const maxY = this.canvas.height / ratio;
|
||||||
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
|
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||||
|
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
||||||
|
svg.setAttribute('viewBox', `${minX} ${minY} ${maxX} ${maxY}`);
|
||||||
|
svg.setAttribute('width', maxX.toString());
|
||||||
|
svg.setAttribute('height', maxY.toString());
|
||||||
|
if (includeBackgroundColor && this.backgroundColor) {
|
||||||
|
const rect = document.createElement('rect');
|
||||||
|
rect.setAttribute('width', '100%');
|
||||||
|
rect.setAttribute('height', '100%');
|
||||||
|
rect.setAttribute('fill', this.backgroundColor);
|
||||||
|
svg.appendChild(rect);
|
||||||
|
}
|
||||||
|
this._fromData(pointGroups, (curve, { penColor }) => {
|
||||||
|
const path = document.createElement('path');
|
||||||
|
if (!isNaN(curve.control1.x) &&
|
||||||
|
!isNaN(curve.control1.y) &&
|
||||||
|
!isNaN(curve.control2.x) &&
|
||||||
|
!isNaN(curve.control2.y)) {
|
||||||
|
const attr = `M ${curve.startPoint.x.toFixed(3)},${curve.startPoint.y.toFixed(3)} ` +
|
||||||
|
`C ${curve.control1.x.toFixed(3)},${curve.control1.y.toFixed(3)} ` +
|
||||||
|
`${curve.control2.x.toFixed(3)},${curve.control2.y.toFixed(3)} ` +
|
||||||
|
`${curve.endPoint.x.toFixed(3)},${curve.endPoint.y.toFixed(3)}`;
|
||||||
|
path.setAttribute('d', attr);
|
||||||
|
path.setAttribute('stroke-width', (curve.endWidth * 2.25).toFixed(3));
|
||||||
|
path.setAttribute('stroke', penColor);
|
||||||
|
path.setAttribute('fill', 'none');
|
||||||
|
path.setAttribute('stroke-linecap', 'round');
|
||||||
|
svg.appendChild(path);
|
||||||
|
}
|
||||||
|
}, (point, { penColor, dotSize, minWidth, maxWidth }) => {
|
||||||
|
const circle = document.createElement('circle');
|
||||||
|
const size = dotSize > 0 ? dotSize : (minWidth + maxWidth) / 2;
|
||||||
|
circle.setAttribute('r', size.toString());
|
||||||
|
circle.setAttribute('cx', point.x.toString());
|
||||||
|
circle.setAttribute('cy', point.y.toString());
|
||||||
|
circle.setAttribute('fill', penColor);
|
||||||
|
svg.appendChild(circle);
|
||||||
|
});
|
||||||
|
return svg.outerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SignaturePad as default };
|
||||||
|
//# sourceMappingURL=signature_pad.js.map
|
1
public/vendor/signature_pad@5/signature_pad.js.map
vendored
Normal file
1
public/vendor/signature_pad@5/signature_pad.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user