mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 15:34:39 -04:00
commit
13b24cc03e
@ -1 +1 @@
|
||||
5.10.24
|
||||
5.10.25
|
@ -1169,7 +1169,7 @@ class CheckData extends Command
|
||||
->whereNull('exchange_rate')
|
||||
->orWhere('exchange_rate', 0)
|
||||
->cursor()
|
||||
->each(function ($expense){
|
||||
->each(function ($expense) {
|
||||
$expense->exchange_rate = 1;
|
||||
$expense->saveQuietly();
|
||||
|
||||
|
@ -516,9 +516,10 @@ class CompanySettings extends BaseSettings
|
||||
public $quote_late_fee_amount1 = 0;
|
||||
public $quote_late_fee_percent1 = 0;
|
||||
|
||||
|
||||
public string $payment_flow = 'default'; //smooth
|
||||
|
||||
public static $casts = [
|
||||
'payment_flow' => 'string',
|
||||
'enable_quote_reminder1' => 'bool',
|
||||
'quote_num_days_reminder1' => 'int',
|
||||
'quote_schedule_reminder1' => 'string',
|
||||
|
@ -131,7 +131,8 @@ class BaseRule implements RuleInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldCalcTax(): bool {
|
||||
public function shouldCalcTax(): bool
|
||||
{
|
||||
return $this->should_calc_tax && $this->checkIfInvoiceLocked();
|
||||
}
|
||||
/**
|
||||
@ -404,8 +405,9 @@ class BaseRule implements RuleInterface
|
||||
{
|
||||
$lock_invoices = $this->client->getSetting('lock_invoices');
|
||||
|
||||
if($this->invoice instanceof RecurringInvoice)
|
||||
if($this->invoice instanceof RecurringInvoice) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch ($lock_invoices) {
|
||||
case 'off':
|
||||
|
@ -251,8 +251,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
$this->reduced_tax_rate = 0;
|
||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||
$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
|
||||
// if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) {
|
||||
} 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) {
|
||||
// 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;
|
||||
|
@ -48,8 +48,7 @@ class TaxModel
|
||||
public function migrate(): self
|
||||
{
|
||||
|
||||
if($this->version == 'alpha')
|
||||
{
|
||||
if($this->version == 'alpha') {
|
||||
$this->regions->EU->subregions->PL = new \stdClass();
|
||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||
|
@ -11,7 +11,8 @@
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
final class CAProvinces {
|
||||
final class CAProvinces
|
||||
{
|
||||
/**
|
||||
* The provinces and territories of Canada
|
||||
*
|
||||
@ -39,7 +40,8 @@ final class CAProvinces {
|
||||
* @param string $abbreviation
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($abbreviation) {
|
||||
public static function getName($abbreviation)
|
||||
{
|
||||
return self::$provinces[$abbreviation];
|
||||
}
|
||||
|
||||
@ -48,7 +50,8 @@ final class CAProvinces {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get() {
|
||||
public static function get()
|
||||
{
|
||||
return self::$provinces;
|
||||
}
|
||||
|
||||
@ -58,7 +61,8 @@ final class CAProvinces {
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function getAbbreviation($name) {
|
||||
public static function getAbbreviation($name)
|
||||
{
|
||||
return array_search(ucwords($name), self::$provinces);
|
||||
}
|
||||
}
|
||||
|
@ -1041,7 +1041,7 @@ class BaseExport
|
||||
|
||||
$recurring_filters = [];
|
||||
|
||||
if($this->company->getSetting('report_include_drafts')){
|
||||
if($this->company->getSetting('report_include_drafts')) {
|
||||
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ class ClientExport extends BaseExport
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query,' clients');
|
||||
$query = $this->addDateRange($query, ' clients');
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
|
@ -269,8 +269,7 @@ class ExpenseExport extends BaseExport
|
||||
|
||||
if($expense->uses_inclusive_taxes) {
|
||||
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$entity['expense.net_amount'] = round($expense->amount, $precision);
|
||||
}
|
||||
|
||||
|
@ -162,8 +162,9 @@ class ExpenseFilters extends QueryFilters
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
$categories_keys = $this->transformKeys($categories_exploded);
|
||||
|
||||
|
@ -141,7 +141,7 @@ class RecurringInvoiceFilters extends QueryFilters
|
||||
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
|
||||
}
|
||||
|
||||
if($sort_col[0] == 'status_id'){
|
||||
if($sort_col[0] == 'status_id') {
|
||||
return $this->builder->orderBy('status_id', $dir)->orderBy('last_sent_date', $dir);
|
||||
}
|
||||
|
||||
|
@ -254,17 +254,20 @@ class ActivityController extends BaseController
|
||||
$activity->client_id = $entity->client_id;
|
||||
$activity->project_id = $entity->project_id;
|
||||
$activity->vendor_id = $entity->vendor_id;
|
||||
// no break
|
||||
case Task::class:
|
||||
$activity->task_id = $entity->id;
|
||||
$activity->expense_id = $entity->id;
|
||||
$activity->client_id = $entity->client_id;
|
||||
$activity->project_id = $entity->project_id;
|
||||
$activity->vendor_id = $entity->vendor_id;
|
||||
// no break
|
||||
case Payment::class:
|
||||
$activity->payment_id = $entity->id;
|
||||
$activity->expense_id = $entity->id;
|
||||
$activity->client_id = $entity->client_id;
|
||||
$activity->project_id = $entity->project_id;
|
||||
// no break
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
|
@ -41,8 +41,9 @@ class ContactLoginController extends Controller
|
||||
$company = false;
|
||||
$account = false;
|
||||
|
||||
if($request->query('intended'))
|
||||
if($request->query('intended')) {
|
||||
$request->session()->put('url.intended', $request->query('intended'));
|
||||
}
|
||||
|
||||
if ($request->session()->has('company_key')) {
|
||||
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
|
||||
@ -142,8 +143,9 @@ class ContactLoginController extends Controller
|
||||
|
||||
$this->setRedirectPath();
|
||||
|
||||
if($intended)
|
||||
if($intended) {
|
||||
$this->redirectTo = $intended;
|
||||
}
|
||||
|
||||
return $request->wantsJson()
|
||||
? new JsonResponse([], 204)
|
||||
|
@ -934,7 +934,7 @@ class BaseController extends Controller
|
||||
} elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) {
|
||||
// nlog($this->entity_type);
|
||||
} else {
|
||||
$query->where(function ($q) use ($user){ //grouping these together improves query performance significantly)
|
||||
$query->where(function ($q) use ($user) { //grouping these together improves query performance significantly)
|
||||
$q->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id);
|
||||
});
|
||||
}
|
||||
@ -996,7 +996,7 @@ class BaseController extends Controller
|
||||
|
||||
if(request()->has('einvoice')) {
|
||||
|
||||
if(class_exists(Schema::class)){
|
||||
if(class_exists(Schema::class)) {
|
||||
$ro = new Schema();
|
||||
$response_data['einvoice_schema'] = $ro('Peppol');
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ use Illuminate\Http\Request;
|
||||
*/
|
||||
class BrevoController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ class InvitationController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
if(!auth()->guard('contact')->check()){
|
||||
if(!auth()->guard('contact')->check()) {
|
||||
$this->middleware('auth:contact');
|
||||
return redirect()->route('client.login', ['intended' => route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent])]);
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ class InvoiceController extends Controller
|
||||
|
||||
$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) {
|
||||
$invitation->markViewed();
|
||||
|
||||
@ -77,13 +78,17 @@ class InvoiceController extends Controller
|
||||
'key' => $invitation ? $invitation->key : false,
|
||||
'hash' => $hash,
|
||||
'variables' => $variables,
|
||||
'invoices' => [$invoice->hashed_id],
|
||||
'db' => $invoice->company->db,
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
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)
|
||||
@ -235,9 +240,12 @@ class InvoiceController extends Controller
|
||||
'hashed_ids' => $invoices->pluck('hashed_id'),
|
||||
'total' => $total,
|
||||
'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;
|
||||
|
||||
use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
|
||||
use \Closure;
|
||||
use Closure;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
@ -19,7 +19,6 @@ use App\Services\Import\Quickbooks\QuickbooksService;
|
||||
|
||||
class ImportQuickbooksController extends BaseController
|
||||
{
|
||||
|
||||
private array $import_entities = [
|
||||
'client' => 'Customer',
|
||||
'invoice' => 'Invoice',
|
||||
@ -46,7 +45,6 @@ class ImportQuickbooksController extends BaseController
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
|
||||
{
|
||||
@ -74,17 +72,19 @@ class ImportQuickbooksController extends BaseController
|
||||
$this->getData($data);
|
||||
}
|
||||
|
||||
protected function getData($data) {
|
||||
protected function getData($data)
|
||||
{
|
||||
|
||||
$entity = $this->import_entities[$data['type']];
|
||||
$cache_name = "{$data['hash']}-{$data['type']}";
|
||||
// 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"]);
|
||||
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)
|
||||
{
|
||||
$hash = Str::random(32);
|
||||
foreach($request->input('import_types') as $type)
|
||||
{
|
||||
$this->preimport($type, $hash);
|
||||
}
|
||||
/** @var \App\Models\User $user */
|
||||
// $user = auth()->user() ?? Auth::loginUsingId(60);
|
||||
$data = ['import_types' => $request->input('import_types') ] + compact('hash');
|
||||
if (Ninja::isHosted()) {
|
||||
QuickbooksIngest::dispatch( $data , $user->company() );
|
||||
} else {
|
||||
QuickbooksIngest::dispatch($data, $user->company() );
|
||||
}
|
||||
// $hash = Str::random(32);
|
||||
// foreach($request->input('import_types') as $type) {
|
||||
// $this->preimport($type, $hash);
|
||||
// }
|
||||
// /** @var \App\Models\User $user */
|
||||
// // $user = auth()->user() ?? Auth::loginUsingId(60);
|
||||
// $data = ['import_types' => $request->input('import_types') ] + compact('hash');
|
||||
// if (Ninja::isHosted()) {
|
||||
// QuickbooksIngest::dispatch($data, $user->company());
|
||||
// } else {
|
||||
// QuickbooksIngest::dispatch($data, $user->company());
|
||||
// }
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
// return response()->json(['message' => 'Processing'], 200);
|
||||
}
|
||||
}
|
||||
|
@ -503,7 +503,7 @@ class InvoiceController extends BaseController
|
||||
|
||||
$invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
|
||||
|
||||
if ($invoices->count() == 0 ) {
|
||||
if ($invoices->count() == 0) {
|
||||
return response()->json(['message' => 'No Invoices Found']);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ use Illuminate\Http\Request;
|
||||
*/
|
||||
class MailgunWebhookController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -35,7 +34,7 @@ class MailgunWebhookController extends BaseController
|
||||
}
|
||||
|
||||
if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) {
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2,10));
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2, 10));
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Success.'], 200);
|
||||
|
@ -22,7 +22,6 @@ use Illuminate\Support\Str;
|
||||
|
||||
class OneTimeTokenController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
@ -19,7 +19,6 @@ use Illuminate\Http\Request;
|
||||
*/
|
||||
class PostMarkController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
@ -297,8 +297,7 @@ class PreviewController extends BaseController
|
||||
->setTemplate($design_object)
|
||||
->mock();
|
||||
} catch(SyntaxError $e) {
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
} catch(\Exception $e) {
|
||||
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()
|
||||
{
|
||||
if(Ninja::isHosted())
|
||||
if(Ninja::isHosted()) {
|
||||
return '5.10.SaaS';
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$limiter = new DurationLimiter(
|
||||
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
|
||||
$this->getRedisConnection(),
|
||||
$key,
|
||||
$maxAttempts,
|
||||
$decaySeconds
|
||||
);
|
||||
|
||||
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
|
||||
|
@ -68,8 +68,9 @@ class StoreNoteRequest extends Request
|
||||
|
||||
public function getEntity()
|
||||
{
|
||||
if(!$this->entity)
|
||||
if(!$this->entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
@ -66,8 +66,7 @@ class ShowCalculatedFieldRequest extends Request
|
||||
$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());
|
||||
$input['start_date'] = $dates[0];
|
||||
$input['end_date'] = $dates[1];
|
||||
|
@ -40,8 +40,9 @@ class BulkInvoiceRequest extends Request
|
||||
/** @var \App\Models\User $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);
|
||||
}
|
||||
|
||||
\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 */
|
||||
$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);
|
||||
}
|
||||
|
||||
\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
|
||||
{
|
||||
return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
|
||||
return Company::query()->where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class StoreQuoteRequest extends Request
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted',0)];
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
|
@ -17,7 +17,6 @@ use Illuminate\Auth\Access\AuthorizationException;
|
||||
|
||||
class GenericReportRequest extends Request
|
||||
{
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
@ -15,7 +15,6 @@ use App\Http\Requests\Request;
|
||||
|
||||
class DisconnectUserMailerRequest extends Request
|
||||
{
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
@ -77,8 +77,7 @@ class UpdateUserRequest extends Request
|
||||
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']);
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,7 @@ class ValidClientScheme implements ValidationRule, ValidatorAwareRule
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if(isset($value['Invoice']))
|
||||
{
|
||||
if(isset($value['Invoice'])) {
|
||||
$r = new EInvoice();
|
||||
$errors = $r->validateRequest($value['Invoice'], ClientLevel::class);
|
||||
|
||||
|
@ -24,7 +24,6 @@ use Illuminate\Contracts\Validation\ValidatorAwareRule;
|
||||
*/
|
||||
class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
||||
{
|
||||
|
||||
/**
|
||||
* The validator instance.
|
||||
*
|
||||
@ -35,8 +34,7 @@ class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
|
||||
if(isset($value['Invoice']))
|
||||
{
|
||||
if(isset($value['Invoice'])) {
|
||||
$r = new EInvoice();
|
||||
$errors = $r->validateRequest($value['Invoice'], CompanyLevel::class);
|
||||
|
||||
|
@ -89,7 +89,7 @@ class ValidInvoicesRules implements Rule
|
||||
} elseif (floatval($invoice['amount']) > floatval($inv->balance)) {
|
||||
$this->error_msg = ctrans('texts.amount_greater_than_balance_v5');
|
||||
return false;
|
||||
} elseif($inv->is_deleted){
|
||||
} elseif($inv->is_deleted) {
|
||||
$this->error_msg = 'One or more invoices in this request have since been deleted';
|
||||
return false;
|
||||
}
|
||||
|
@ -42,8 +42,9 @@ class AccountComponent extends Component
|
||||
"authorization_type" => 'Online'
|
||||
];
|
||||
|
||||
public function __construct(public array $account) {
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
|
||||
public function __construct(public array $account)
|
||||
{
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields));
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
@ -34,21 +34,25 @@ class AddressComponent extends Component
|
||||
'country' => 'US'
|
||||
];
|
||||
|
||||
public function __construct(public array $address) {
|
||||
if(strlen($this->address['state']) > 2 ) {
|
||||
public function __construct(public array $address)
|
||||
{
|
||||
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->attributes = $this->newAttributeBag(
|
||||
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 ];
|
||||
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 ];
|
||||
}),
|
||||
$this->fields) );
|
||||
$this->fields
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.address', $this->attributes->getAttributes() + $this->defaults );
|
||||
return render('gateways.rotessa.components.address', $this->attributes->getAttributes() + $this->defaults);
|
||||
}
|
||||
}
|
||||
|
@ -18,21 +18,20 @@ use App\Models\ClientContact;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
|
||||
// Contact 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([
|
||||
'home_phone' =>$contact->client->phone,
|
||||
'home_phone' => $contact->client->phone,
|
||||
'custom_identifier' => $contact->client->client_hash,
|
||||
'name' =>$contact->client->name,
|
||||
'name' => $contact->client->name,
|
||||
'id' => null,
|
||||
] )->all();
|
||||
])->all();
|
||||
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields));
|
||||
}
|
||||
|
||||
private $fields = [
|
||||
@ -53,6 +52,6 @@ class ContactComponent extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.contact', $this->attributes->getAttributes() + $this->defaults );
|
||||
return render('gateways.rotessa.components.contact', $this->attributes->getAttributes() + $this->defaults);
|
||||
}
|
||||
}
|
||||
|
@ -12,23 +12,24 @@
|
||||
namespace App\Import\Providers;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Factory\ProductFactory;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Factory\ProductFactory;
|
||||
use App\Import\ImportException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\ProductRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Repositories\ProductRepository;
|
||||
use App\Http\Requests\Client\StoreClientRequest;
|
||||
use App\Http\Requests\Product\StoreProductRequest;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Payment\StorePaymentRequest;
|
||||
use App\Http\Requests\Product\StoreProductRequest;
|
||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
|
||||
use App\Import\Transformer\Quickbooks\InvoiceTransformer;
|
||||
use App\Import\Transformer\Quickbooks\ProductTransformer;
|
||||
use App\Import\Transformer\Quickbooks\PaymentTransformer;
|
||||
use App\Import\Transformer\Quickbooks\ProductTransformer;
|
||||
|
||||
class Quickbooks extends BaseImport
|
||||
{
|
||||
@ -94,10 +95,11 @@ class Quickbooks extends BaseImport
|
||||
$this->entity_count['products'] = $count;
|
||||
}
|
||||
|
||||
public function getData($type) {
|
||||
public function getData($type)
|
||||
{
|
||||
|
||||
// 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()
|
||||
@ -143,7 +145,7 @@ class Quickbooks extends BaseImport
|
||||
$this->repository = app()->make($this->repository_name);
|
||||
$this->repository->import_mode = true;
|
||||
$this->transformer = new InvoiceTransformer($this->company);
|
||||
$invoice_count = $this->ingestInvoices($data,'');
|
||||
$invoice_count = $this->ingestInvoices($data, '');
|
||||
$this->entity_count['invoices'] = $invoice_count;
|
||||
$this->company->update_products = $initial_update_products_value;
|
||||
$this->company->save();
|
||||
@ -198,8 +200,7 @@ class Quickbooks extends BaseImport
|
||||
'error' => $validator->errors()->all(),
|
||||
];
|
||||
} else {
|
||||
if(!Invoice::where('number',$invoice_data['number'])->get()->first())
|
||||
{
|
||||
if(!Invoice::where('number', $invoice_data['number'])->first()) {
|
||||
$invoice = InvoiceFactory::create(
|
||||
$this->company->id,
|
||||
$this->company->owner()->id
|
||||
|
@ -244,8 +244,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
if (empty($expense_data['vendor_id'])) {
|
||||
$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(
|
||||
['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
|
||||
$vendor = VendorFactory::create(
|
||||
|
@ -119,8 +119,9 @@ class BaseTransformer
|
||||
{
|
||||
$code = array_key_exists($key, $data) ? $data[$key] : false;
|
||||
|
||||
if(!$code)
|
||||
if(!$code) {
|
||||
return $this->company->settings->currency_id;
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
$currencies = app('currencies');
|
||||
|
@ -24,7 +24,6 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class ClientTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
use CommonTrait {
|
||||
transform as preTransform;
|
||||
}
|
||||
@ -49,7 +48,7 @@ class ClientTransformer extends BaseTransformer
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model;
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
|
||||
@ -68,12 +67,13 @@ class ClientTransformer extends BaseTransformer
|
||||
}
|
||||
|
||||
$transformed_data = $this->preTransform($data);
|
||||
$transformed_data['contacts'][0] = $this->getContacts($data)->toArray()+['company_id' => $this->company->id ];
|
||||
$transformed_data['contacts'][0] = $this->getContacts($data)->toArray() + ['company_id' => $this->company->id ];
|
||||
|
||||
return $transformed_data;
|
||||
}
|
||||
|
||||
protected function getContacts($data) {
|
||||
protected function getContacts($data)
|
||||
{
|
||||
return (new ClientContact())->fill([
|
||||
'first_name' => $this->getString($data, 'GivenName'),
|
||||
'last_name' => $this->getString($data, 'FamilyName'),
|
||||
@ -84,12 +84,14 @@ class ClientTransformer extends BaseTransformer
|
||||
}
|
||||
|
||||
|
||||
public function getShipAddrCountry($data,$field) {
|
||||
return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c);
|
||||
public function getShipAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
public function getBillAddrCountry($data,$field) {
|
||||
return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c);
|
||||
public function getBillAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ trait CommonTrait
|
||||
{
|
||||
protected $model;
|
||||
|
||||
public function getString($data,$field) {
|
||||
return Arr::get($data,$field);
|
||||
public function getString($data, $field)
|
||||
{
|
||||
return Arr::get($data, $field);
|
||||
}
|
||||
|
||||
public function getCreateTime($data, $field = null)
|
||||
@ -19,7 +20,7 @@ trait CommonTrait
|
||||
|
||||
public function getLastUpdatedTime($data, $field = null)
|
||||
{
|
||||
return $this->parseDateOrNull($data,'MetaData.LastUpdatedTime');
|
||||
return $this->parseDateOrNull($data, 'MetaData.LastUpdatedTime');
|
||||
}
|
||||
|
||||
public function transform($data)
|
||||
@ -27,7 +28,7 @@ trait CommonTrait
|
||||
$transformed = [];
|
||||
|
||||
foreach ($this->fillable as $key => $field) {
|
||||
$transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field));
|
||||
$transformed[$key] = is_null((($v = $this->getString($data, $field)))) ? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field) : $this->getString($data, $field));
|
||||
}
|
||||
|
||||
return $this->model->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + ['company_id' => $this->company->id ] ;
|
||||
|
@ -11,10 +11,11 @@
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Import\ImportException;
|
||||
use Illuminate\Support\Str;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Import\ImportException;
|
||||
use App\Models\Invoice as Model;
|
||||
use App\Import\Transformer\BaseTransformer;
|
||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||
@ -48,7 +49,7 @@ class InvoiceTransformer extends BaseTransformer
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model;
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
public function getInvoiceStatus($data)
|
||||
@ -63,28 +64,29 @@ class InvoiceTransformer extends BaseTransformer
|
||||
|
||||
public function getTotalAmt($data)
|
||||
{
|
||||
return (float) $this->getString($data,'TotalAmt');
|
||||
return (float) $this->getString($data, 'TotalAmt');
|
||||
}
|
||||
|
||||
public function getLine($data)
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'description' => $this->getString($item,'Description'),
|
||||
'product_key' => $this->getString($item,'Description'),
|
||||
'quantity' => (int) $this->getString($item,'SalesItemLineDetail.Qty'),
|
||||
'unit_price' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
||||
'line_total' => (double) $this->getString($item,'Amount'),
|
||||
'cost' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
||||
'product_cost' => (double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
|
||||
'tax_amount' => (double) $this->getString($item,'TxnTaxDetail.TotalTax'),
|
||||
'description' => $this->getString($item, 'Description'),
|
||||
'product_key' => $this->getString($item, 'Description'),
|
||||
'quantity' => (int) $this->getString($item, 'SalesItemLineDetail.Qty'),
|
||||
'unit_price' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'line_total' => (float) $this->getString($item, 'Amount'),
|
||||
'cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'product_cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'tax_amount' => (float) $this->getString($item, 'TxnTaxDetail.TotalTax'),
|
||||
];
|
||||
}, array_filter($this->getString($data,'Line'), function ($item) {
|
||||
return $this->getString($item,'DetailType') !== 'SubTotalLineDetail';
|
||||
}, array_filter($this->getString($data, 'Line'), function ($item) {
|
||||
return $this->getString($item, 'DetailType') !== 'SubTotalLineDetail';
|
||||
}));
|
||||
}
|
||||
|
||||
public function getInvoiceClient($data, $field = null) {
|
||||
public function getInvoiceClient($data, $field = null)
|
||||
{
|
||||
/**
|
||||
* "CustomerRef": {
|
||||
"value": "23",
|
||||
@ -135,23 +137,22 @@ class InvoiceTransformer extends BaseTransformer
|
||||
$customer = explode(" ", $this->getString($data, 'CustomerRef.name'));
|
||||
$customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]];
|
||||
$has_company = property_exists($bill_address, 'Line4');
|
||||
$address = $has_company? $bill_address->Line4 : $bill_address->Line3;
|
||||
$address_1 = substr($address, 0, stripos($address,','));
|
||||
$address =array_filter( [$address_1] + (explode(' ', substr($address, stripos($address,",") + 1 ))));
|
||||
$address = $has_company ? $bill_address->Line4 : $bill_address->Line3;
|
||||
$address_1 = substr($address, 0, stripos($address, ','));
|
||||
$address = array_filter([$address_1] + (explode(' ', substr($address, stripos($address, ",") + 1))));
|
||||
$client_id = null;
|
||||
$client =
|
||||
[
|
||||
"CompanyName" => $has_company? $bill_address->Line2 : $bill_address->Line1,
|
||||
"BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address,3,'N/A') ) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ],
|
||||
"CompanyName" => $has_company ? $bill_address->Line2 : $bill_address->Line1,
|
||||
"BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address, 3, 'N/A')) + ['Line1' => $has_company ? $bill_address->Line3 : $bill_address->Line2 ],
|
||||
"ShipAddr" => $ship_address
|
||||
] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
|
||||
if($this->hasClient($client['CompanyName']))
|
||||
{
|
||||
$client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address'));
|
||||
if($this->hasClient($client['CompanyName'])) {
|
||||
$client_id = $this->getClient($client['CompanyName'], $this->getString($client, 'PrimaryEmailAddr.Address'));
|
||||
}
|
||||
|
||||
|
||||
return ['client'=> (new ClientTransformer($this->company))->transform($client), 'client_id'=> $client_id ];
|
||||
return ['client' => (new ClientTransformer($this->company))->transform($client), 'client_id' => $client_id ];
|
||||
}
|
||||
|
||||
public function getDueDate($data)
|
||||
@ -161,22 +162,23 @@ class InvoiceTransformer extends BaseTransformer
|
||||
|
||||
public function getDeposit($data)
|
||||
{
|
||||
return (double) $this->getString($data,'Deposit');
|
||||
return (float) $this->getString($data, 'Deposit');
|
||||
}
|
||||
|
||||
public function getBalance($data)
|
||||
{
|
||||
return (double) $this->getString($data,'Balance');
|
||||
return (float) $this->getString($data, 'Balance');
|
||||
}
|
||||
|
||||
public function getCustomerMemo($data)
|
||||
{
|
||||
return $this->getString($data,'CustomerMemo.value');
|
||||
return $this->getString($data, 'CustomerMemo.value');
|
||||
}
|
||||
|
||||
public function getDocNumber($data, $field = null)
|
||||
{
|
||||
return sprintf("%s-%s",
|
||||
return sprintf(
|
||||
"%s-%s",
|
||||
$this->getString($data, 'DocNumber'),
|
||||
$this->getString($data, 'Id.value')
|
||||
);
|
||||
@ -184,8 +186,10 @@ class InvoiceTransformer extends BaseTransformer
|
||||
|
||||
public function getLinkedTxn($data)
|
||||
{
|
||||
$payments = $this->getString($data,'LinkedTxn');
|
||||
if(empty($payments)) return [];
|
||||
$payments = $this->getString($data, 'LinkedTxn');
|
||||
if(empty($payments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [[
|
||||
'amount' => $this->getTotalAmt($data),
|
||||
|
@ -44,10 +44,11 @@ class PaymentTransformer extends BaseTransformer
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -56,9 +57,9 @@ class PaymentTransformer extends BaseTransformer
|
||||
return $this->parseDateOrNull($data, $field);
|
||||
}
|
||||
|
||||
public function getCustomerRef($data, $field = null )
|
||||
public function getCustomerRef($data, $field = null)
|
||||
{
|
||||
return $this->getClient($this->getString($data, 'CustomerRef.name'),null);
|
||||
return $this->getClient($this->getString($data, 'CustomerRef.name'), null);
|
||||
}
|
||||
|
||||
public function getCurrencyRef($data, $field = null)
|
||||
@ -69,9 +70,13 @@ class PaymentTransformer extends BaseTransformer
|
||||
public function getLine($data, $field = null)
|
||||
{
|
||||
$invoices = [];
|
||||
$invoice = $this->getString($data,'Line.LinkedTxn.TxnType');
|
||||
if(is_null($invoice) || $invoice !== 'Invoice') return $invoices;
|
||||
if( is_null( ($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value')))) ) return $invoices;
|
||||
$invoice = $this->getString($data, 'Line.LinkedTxn.TxnType');
|
||||
if(is_null($invoice) || $invoice !== 'Invoice') {
|
||||
return $invoices;
|
||||
}
|
||||
if(is_null(($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value'))))) {
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
return [[
|
||||
'amount' => (float) $this->getString($data, 'Line.Amount'),
|
||||
@ -88,7 +93,9 @@ class PaymentTransformer extends BaseTransformer
|
||||
{
|
||||
$invoice = Invoice::query()->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false)
|
||||
->where("number", "LIKE",
|
||||
->where(
|
||||
"number",
|
||||
"LIKE",
|
||||
"%-$invoice_number%",
|
||||
)
|
||||
->first();
|
||||
|
@ -22,7 +22,6 @@ use App\Import\ImportException;
|
||||
*/
|
||||
class ProductTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
use CommonTrait;
|
||||
|
||||
protected $fillable = [
|
||||
@ -41,19 +40,22 @@ class ProductTransformer extends BaseTransformer
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public function getPurchaseCost($data, $field = null) {
|
||||
return (double) $this->getString($data, $field);
|
||||
public function getPurchaseCost($data, $field = null)
|
||||
{
|
||||
return (float) $this->getString($data, $field);
|
||||
}
|
||||
|
||||
|
||||
public function getUnitPrice($data, $field = null) {
|
||||
public function getUnitPrice($data, $field = null)
|
||||
{
|
||||
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;
|
||||
|
||||
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'];
|
||||
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'];
|
||||
else
|
||||
} else {
|
||||
$public_notes = '';
|
||||
}
|
||||
|
||||
|
||||
$transformed = [
|
||||
|
@ -237,7 +237,7 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$amount = $this->bt->amount;
|
||||
|
||||
if ($_invoices->count() >0 && $this->checkPayable($_invoices)) {
|
||||
if ($_invoices->count() > 0 && $this->checkPayable($_invoices)) {
|
||||
$this->createPayment($_invoices, $amount);
|
||||
|
||||
$this->bts->push($this->bt->id);
|
||||
@ -387,7 +387,7 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$hashed_keys = [];
|
||||
|
||||
foreach($this->attachable_invoices as $attachable_invoice){ //@phpstan-ignore-line
|
||||
foreach($this->attachable_invoices as $attachable_invoice) { //@phpstan-ignore-line
|
||||
$hashed_keys[] = $this->encodePrimaryKey($attachable_invoice['id']);
|
||||
}
|
||||
|
||||
|
@ -114,8 +114,7 @@ class RecurringExpensesCron
|
||||
$exchange_rate = new CurrencyApi();
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,10 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class QuickbooksIngest implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
protected $engine;
|
||||
protected $request;
|
||||
@ -35,7 +38,7 @@ class QuickbooksIngest implements ShouldQueue
|
||||
MultiDB::setDb($this->company->db);
|
||||
set_time_limit(0);
|
||||
|
||||
$engine = new Quickbooks(['import_type' => 'client', 'hash'=> $this->request['hash'] ], $this->company);
|
||||
$engine = new Quickbooks(['import_type' => 'client', 'hash' => $this->request['hash'] ], $this->company);
|
||||
foreach ($this->request['import_types'] as $entity) {
|
||||
$engine->import($entity);
|
||||
}
|
||||
|
@ -48,12 +48,11 @@ class TaskAssigned implements ShouldQueue
|
||||
|
||||
$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->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->text_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() : ' ']);
|
||||
$mo->text_body = ctrans('texts.task_assigned_body', ['task' => $this->task->number, 'description' => $this->task->description ?? '', 'client' => $this->task->client ? $this->task->client->present()->name() : ' ']);
|
||||
$mo->company_key = $this->task->company->company_key;
|
||||
$mo->html_template = 'email.template.generic';
|
||||
$mo->to = [new Address($this->task->assigned_user->email, $this->task->assigned_user->present()->name())];
|
||||
|
@ -59,7 +59,6 @@ class Register extends Component
|
||||
|
||||
public function register(array $data)
|
||||
{
|
||||
|
||||
$service = new ClientRegisterService(
|
||||
company: $this->subscription->company,
|
||||
additional: $this->additional_fields,
|
||||
|
@ -363,10 +363,11 @@ class BillingPortalPurchase extends Component
|
||||
$method_values = array_column($this->methods, 'is_paypal');
|
||||
$is_paypal = in_array('1', $method_values);
|
||||
|
||||
if($is_paypal && !$this->steps['check_rff'])
|
||||
if($is_paypal && !$this->steps['check_rff']) {
|
||||
$this->rff();
|
||||
elseif(!$this->steps['check_rff'])
|
||||
} elseif(!$this->steps['check_rff']) {
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
}
|
||||
|
||||
$this->heading_text = ctrans('texts.payment_methods');
|
||||
|
||||
|
@ -515,8 +515,7 @@ class BillingPortalPurchasev2 extends Component
|
||||
strlen($this->contact_email ?? '') == 0 ||
|
||||
strlen($this->client_city ?? '') == 0 ||
|
||||
strlen($this->client_postal_code ?? '') == 0
|
||||
)
|
||||
{
|
||||
) {
|
||||
$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['ip'] = $this->ip ?? '';
|
||||
|
||||
if($this->activity_type_id == 141)
|
||||
if($this->activity_type_id == 141) {
|
||||
$replacements = $this->harvestNoteEntities($replacements);
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
|
||||
@ -472,12 +473,12 @@ class Activity extends StaticModel
|
||||
|
||||
];
|
||||
|
||||
foreach($entities as $entity)
|
||||
{
|
||||
foreach($entities as $entity) {
|
||||
$entity_key = substr($entity, 1);
|
||||
|
||||
if($this?->{$entity_key})
|
||||
if($this?->{$entity_key}) {
|
||||
$replacements = array_merge($replacements, $this->matchVar($entity));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -374,10 +374,9 @@ class BaseModel extends Model
|
||||
|
||||
$files->push($company_docs);
|
||||
|
||||
try{
|
||||
try {
|
||||
$pdf = (new PdfMerge($files->flatten()->toArray()))->run();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
} catch(\Exception $e) {
|
||||
nlog("Exception:: BaseModel:: PdfMerge::" . $e->getMessage());
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ use Laracasts\Presenter\PresentableTrait;
|
||||
* @property string|null $smtp_port
|
||||
* @property string|null $smtp_encryption
|
||||
* @property string|null $smtp_local_domain
|
||||
* @property string|null $quickbooks
|
||||
* @property object|null $quickbooks
|
||||
* @property boolean $smtp_verify_peer
|
||||
* @property-read \App\Models\Account $account
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
||||
|
@ -431,9 +431,9 @@ class Invoice extends BaseModel
|
||||
|
||||
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;
|
||||
elseif ($this->status_id == self::STATUS_DRAFT && $this->is_deleted == false) {
|
||||
} elseif ($this->status_id == self::STATUS_DRAFT && $this->is_deleted == false) {
|
||||
return true;
|
||||
} elseif ($this->status_id == self::STATUS_SENT && $this->is_deleted == false) {
|
||||
return true;
|
||||
|
@ -435,8 +435,9 @@ class Quote extends BaseModel
|
||||
*/
|
||||
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 true;
|
||||
|
||||
|
@ -296,8 +296,7 @@ class Task extends BaseModel
|
||||
$client_currency = $this->client->getSetting('currency_id');
|
||||
$company_currency = $this->company->getSetting('currency_id');
|
||||
|
||||
if($client_currency != $company_currency)
|
||||
{
|
||||
if($client_currency != $company_currency) {
|
||||
$converter = new CurrencyApi();
|
||||
return $converter->convert($this->taskValue(), $client_currency, $company_currency);
|
||||
}
|
||||
@ -308,7 +307,7 @@ class Task extends BaseModel
|
||||
|
||||
public function taskValue(): float
|
||||
{
|
||||
return round(($this->calcDuration() / 3600) * $this->getRate(),2);
|
||||
return round(($this->calcDuration() / 3600) * $this->getRate(), 2);
|
||||
}
|
||||
|
||||
public function processLogs()
|
||||
@ -374,8 +373,9 @@ class Task extends BaseModel
|
||||
|
||||
public function assignedCompanyUser()
|
||||
{
|
||||
if(!$this->assigned_user_id)
|
||||
if(!$this->assigned_user_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CompanyUser::where('company_id', $this->company_id)->where('user_id', $this->assigned_user_id)->first();
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class EmailQuotaNotification extends Notification
|
||||
{
|
||||
$content = "Email quota exceeded by Account {$this->account->key} \n";
|
||||
|
||||
$owner = $this->account->companies()->first()->owner() ?? $this->account->users()->orderBy('id','asc')->first();
|
||||
$owner = $this->account->companies()->first()->owner() ?? $this->account->users()->orderBy('id', 'asc')->first();
|
||||
$owner_name = $owner->present()->name() ?? 'No Owner Found';
|
||||
$owner_email = $owner->email ?? 'No Owner Email Found';
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -536,7 +536,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
QuoteWasRestored::class => [
|
||||
QuoteRestoredActivity::class,
|
||||
],
|
||||
QuoteReminderWasEmailed::class =>[
|
||||
QuoteReminderWasEmailed::class => [
|
||||
QuoteReminderEmailActivity::class,
|
||||
// QuoteEmailedNotification::class,
|
||||
],
|
||||
|
@ -4,9 +4,9 @@ namespace App\Repositories\Import\Quickbooks\Contracts;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface RepositoryInterface {
|
||||
|
||||
function get(int $max = 100): Collection;
|
||||
function all(): Collection;
|
||||
function count(): int;
|
||||
interface RepositoryInterface
|
||||
{
|
||||
public function get(int $max = 100): Collection;
|
||||
public function all(): Collection;
|
||||
public function count(): int;
|
||||
}
|
@ -9,22 +9,22 @@ use App\Repositories\Import\Quickbooks\Transformers\Transformer as QuickbooksTra
|
||||
|
||||
abstract class Repository implements RepositoryInterface
|
||||
{
|
||||
|
||||
protected string $entity;
|
||||
protected QuickbooksInterface $db;
|
||||
protected QuickbooksTransformer $transfomer;
|
||||
|
||||
public function __construct(QuickbooksInterface $db, QuickbooksTransformer $transfomer)
|
||||
{
|
||||
$this->db= $db;
|
||||
$this->db = $db;
|
||||
$this->transformer = $transfomer;
|
||||
}
|
||||
|
||||
public function count() : int {
|
||||
public function count(): int
|
||||
{
|
||||
return $this->db->totalRecords($this->entity);
|
||||
}
|
||||
|
||||
public function all() : Collection
|
||||
public function all(): Collection
|
||||
{
|
||||
return $this->get($this->count());
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ class Transformer
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -78,7 +80,7 @@ class Transformer
|
||||
]);
|
||||
}
|
||||
|
||||
protected function transformation(array $items, array $keys) : Collection
|
||||
protected function transformation(array $items, array $keys): Collection
|
||||
{
|
||||
return collect($items)->select($keys);
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ class TaskRepository extends BaseRepository
|
||||
$this->new_task = false;
|
||||
}
|
||||
|
||||
if(!is_numeric($task->rate) && !isset($data['rate']))
|
||||
if(!is_numeric($task->rate) && !isset($data['rate'])) {
|
||||
$data['rate'] = 0;
|
||||
}
|
||||
|
||||
$task->fill($data);
|
||||
$task->saveQuietly();
|
||||
@ -118,13 +119,15 @@ class TaskRepository extends BaseRepository
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -147,7 +150,7 @@ class TaskRepository extends BaseRepository
|
||||
|
||||
$task->calculated_start_date = $this->harvestStartDate($time_log, $task);
|
||||
|
||||
if(isset(end($time_log)[1])){
|
||||
if(isset(end($time_log)[1])) {
|
||||
$task->is_running = end($time_log)[1] == 0;
|
||||
}
|
||||
|
||||
|
@ -72,19 +72,20 @@ class ProcessBankRules extends AbstractService
|
||||
// $client.custom4
|
||||
private function matchCredit()
|
||||
{
|
||||
$match_set = [];
|
||||
|
||||
$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'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($bank_transaction_rule['rules'] as $rule) {
|
||||
$rule_count = count($bank_transaction_rule['rules']);
|
||||
|
||||
foreach ($bank_transaction_rule['rules'] as $rule) {
|
||||
|
||||
$payments = Payment::query()
|
||||
->withTrashed()
|
||||
@ -93,46 +94,79 @@ class ProcessBankRules extends AbstractService
|
||||
->whereNull('transaction_id')
|
||||
->get();
|
||||
|
||||
match($rule['search_key']){
|
||||
'$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()
|
||||
$invoices = Invoice::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->bank_transaction->company_id)
|
||||
->whereIn('status_id', [1,2,3])
|
||||
->where('is_deleted', 0)
|
||||
->when($rule['search_key'] == 'description', function ($q) use ($rule, $column){
|
||||
return $q->cursor()->filter(function ($record) 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 $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']);
|
||||
});
|
||||
})
|
||||
->when($rule['search_key'] == 'amount', function ($q) use($rule,$column){
|
||||
->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']);
|
||||
});
|
||||
@ -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.transaction_reference => "Payment Transaction Reference", string
|
||||
|
@ -23,7 +23,6 @@ use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
*/
|
||||
trait ChartCalculations
|
||||
{
|
||||
|
||||
public function getActiveInvoices($data): int|float
|
||||
{
|
||||
$result = 0;
|
||||
@ -34,8 +33,9 @@ trait ChartCalculations
|
||||
->where('is_deleted', 0)
|
||||
->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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('amount'),
|
||||
@ -58,8 +58,9 @@ trait ChartCalculations
|
||||
->where('is_deleted', 0)
|
||||
->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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('balance'),
|
||||
@ -82,8 +83,9 @@ trait ChartCalculations
|
||||
->where('is_deleted', 0)
|
||||
->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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('amount'),
|
||||
@ -106,8 +108,9 @@ trait ChartCalculations
|
||||
->where('is_deleted', 0)
|
||||
->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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('refunded'),
|
||||
@ -129,12 +132,13 @@ trait ChartCalculations
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [2,3])
|
||||
->where(function ($qq){
|
||||
->where(function ($qq) {
|
||||
$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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('refunded'),
|
||||
@ -156,12 +160,13 @@ trait ChartCalculations
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [2])
|
||||
->where(function ($qq){
|
||||
->where(function ($qq) {
|
||||
$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']]);
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $q->sum('refunded'),
|
||||
@ -186,7 +191,7 @@ trait ChartCalculations
|
||||
public function getPaidTasks($data): int|float
|
||||
{
|
||||
$q = $this->taskQuery($data);
|
||||
$q->whereHas('invoice', function ($query){
|
||||
$q->whereHas('invoice', function ($query) {
|
||||
$query->where('status_id', 4)->where('is_deleted', 0);
|
||||
});
|
||||
|
||||
|
@ -234,7 +234,7 @@ class ChartService
|
||||
{
|
||||
$results = 0;
|
||||
|
||||
match($data['field']){
|
||||
match($data['field']) {
|
||||
'active_invoices' => $results = $this->getActiveInvoices($data),
|
||||
'outstanding_invoices' => $results = $this->getOutstandingInvoices($data),
|
||||
'completed_payments' => $results = $this->getCompletedPayments($data),
|
||||
|
@ -36,14 +36,24 @@ class PaymentMethod
|
||||
{
|
||||
$this->getGateways()
|
||||
->getMethods();
|
||||
// ->buildUrls();
|
||||
|
||||
return $this->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;
|
||||
|
||||
}
|
||||
|
||||
public function getPaymentMethods()
|
||||
@ -148,17 +158,15 @@ class PaymentMethod
|
||||
$this->payment_methods = $payment_methods_collections->intersectByKeys($payment_methods_collections->flatten(1)->unique());
|
||||
|
||||
//@15-06-2024
|
||||
foreach($this->payment_methods as $key => $type)
|
||||
{
|
||||
foreach ($type as $gateway_id => $gateway_type_id)
|
||||
{
|
||||
foreach($this->payment_methods as $key => $type) {
|
||||
foreach ($type as $gateway_id => $gateway_type_id) {
|
||||
$gate = $this->gateways->where('id', $gateway_id)->first();
|
||||
$this->buildUrl($gate, $gateway_type_id);
|
||||
}
|
||||
}
|
||||
|
||||
//@15-06-2024
|
||||
$this->payment_methods =[];
|
||||
$this->payment_methods = [];
|
||||
|
||||
/* Loop through custom gateways if any exist and append them to the methods collection*/
|
||||
$this->getCustomGateways();
|
||||
@ -168,13 +176,9 @@ class PaymentMethod
|
||||
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 ($this->validGatewayForAmount($gateway->fees_and_limits->{GatewayType::CREDIT_CARD}, $this->amount)) {
|
||||
// $this->payment_methods[] = [$gateway->id => $type];
|
||||
// @15-06-2024
|
||||
$this->buildUrl($gateway, $type);
|
||||
}
|
||||
} else {
|
||||
// $this->payment_methods[] = [$gateway->id => null];
|
||||
//@15-06-2024
|
||||
$this->buildUrl($gateway, null);
|
||||
}
|
||||
}
|
||||
@ -225,52 +229,6 @@ class PaymentMethod
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@ -242,7 +242,7 @@ class InstantPayment
|
||||
$hash_data['billing_context'] = Cache::get($this->request->query('hash'));
|
||||
} elseif ($this->request->hash) {
|
||||
$hash_data['billing_context'] = Cache::get($this->request->hash);
|
||||
} elseif ($old_hash = PaymentHash::query()->where('fee_invoice_id', $first_invoice->id)->whereNull('payment_id')->orderBy('id','desc')->first()) {
|
||||
} 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;
|
||||
}
|
||||
|
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';
|
||||
}
|
||||
|
||||
class Storecove {
|
||||
|
||||
class Storecove
|
||||
{
|
||||
private string $base_url = 'https://api.storecove.com/api/v2/';
|
||||
|
||||
private array $peppol_discovery = [
|
||||
@ -44,7 +44,9 @@ class Storecove {
|
||||
];
|
||||
|
||||
|
||||
public function __construct(){}
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
//config('ninja.storecove_api_key');
|
||||
|
||||
@ -113,7 +115,7 @@ class Storecove {
|
||||
// "parseStrategy" => "ubl",
|
||||
// ],
|
||||
// ],
|
||||
"document"=> [
|
||||
"document" => [
|
||||
"documentType" => "invoice",
|
||||
"invoice" => $document,
|
||||
],
|
||||
@ -141,12 +143,12 @@ class Storecove {
|
||||
|
||||
$payload = [
|
||||
"legalEntityId" => $routing_id,
|
||||
"idempotencyGuid"=> \Illuminate\Support\Str::uuid(),
|
||||
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
|
||||
"routing" => [
|
||||
"eIdentifiers" => [],
|
||||
"emails" => ["david@invoiceninja.com"]
|
||||
],
|
||||
"document"=> [
|
||||
"document" => [
|
||||
|
||||
],
|
||||
];
|
||||
@ -158,7 +160,7 @@ class Storecove {
|
||||
$payload['document']["rawDocumentData"] = [
|
||||
"document" => base64_encode($document),
|
||||
"parse" => true,
|
||||
"parseStrategy"=> "ubl",
|
||||
"parseStrategy" => "ubl",
|
||||
];
|
||||
|
||||
$uri = "document_submissions";
|
||||
@ -170,8 +172,9 @@ class Storecove {
|
||||
nlog($r->body());
|
||||
nlog($r->json());
|
||||
|
||||
if($r->successful())
|
||||
if($r->successful()) {
|
||||
return $r->json()['guid'];
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@ -265,8 +268,9 @@ class Storecove {
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload);
|
||||
|
||||
if($r->successful())
|
||||
if($r->successful()) {
|
||||
return $r->json();
|
||||
}
|
||||
|
||||
return $r;
|
||||
|
||||
|
@ -26,7 +26,8 @@ use horstoeko\zugferd\ZugferdDocumentReader;
|
||||
use horstoeko\zugferdvisualizer\ZugferdVisualizer;
|
||||
use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer;
|
||||
|
||||
class ZugferdEDocument extends AbstractService {
|
||||
class ZugferdEDocument extends AbstractService
|
||||
{
|
||||
public ZugferdDocumentReader|string $document;
|
||||
|
||||
/**
|
||||
@ -75,7 +76,7 @@ class ZugferdEDocument extends AbstractService {
|
||||
if ($taxCurrency && $taxCurrency != $invoiceCurrency) {
|
||||
$expense->private_notes = ctrans("texts.tax_currency_mismatch");
|
||||
}
|
||||
$expense->uses_inclusive_taxes = True;
|
||||
$expense->uses_inclusive_taxes = true;
|
||||
$expense->amount = $grandTotalAmount;
|
||||
$counter = 1;
|
||||
if ($this->document->firstDocumentTax()) {
|
||||
@ -117,8 +118,7 @@ class ZugferdEDocument extends AbstractService {
|
||||
$expense->vendor_id = $vendor->id;
|
||||
}
|
||||
$expense->transaction_reference = $documentno;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// The document exists as an expense
|
||||
// Handle accordingly
|
||||
nlog("Document already exists");
|
||||
@ -128,4 +128,3 @@ class ZugferdEDocument extends AbstractService {
|
||||
return $expense;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,7 +270,7 @@ class Peppol extends AbstractService
|
||||
private function setInvoice(): self
|
||||
{
|
||||
|
||||
if($this->invoice->e_invoice){
|
||||
if($this->invoice->e_invoice) {
|
||||
|
||||
$this->p_invoice = $this->e->decode('Peppol', json_encode($this->invoice->e_invoice->Invoice), 'json');
|
||||
|
||||
@ -341,8 +341,9 @@ class Peppol extends AbstractService
|
||||
$this->p_invoice->ID = $this->invoice->number;
|
||||
$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->InvoiceTypeCode = 380; //
|
||||
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
|
||||
@ -372,7 +373,7 @@ class Peppol extends AbstractService
|
||||
|
||||
$tea = new TaxExclusiveAmount();
|
||||
$tea->currencyID = $this->invoice->client->currency()->code;
|
||||
$tea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes,2) : $taxable;
|
||||
$tea->amount = $this->invoice->uses_inclusive_taxes ? round($this->invoice->amount - $this->invoice->total_taxes, 2) : $taxable;
|
||||
$lmt->TaxExclusiveAmount = $tea;
|
||||
|
||||
$tia = new TaxInclusiveAmount();
|
||||
@ -390,10 +391,11 @@ class Peppol extends AbstractService
|
||||
|
||||
private function getTotalTaxAmount(): float
|
||||
{
|
||||
if(!$this->invoice->total_taxes)
|
||||
if(!$this->invoice->total_taxes) {
|
||||
return 0;
|
||||
elseif($this->invoice->uses_inclusive_taxes)
|
||||
} elseif($this->invoice->uses_inclusive_taxes) {
|
||||
return $this->invoice->total_taxes;
|
||||
}
|
||||
|
||||
return $this->calcAmountLineTax($this->invoice->tax_rate1, $this->invoice->amount) ?? 0;
|
||||
}
|
||||
@ -446,7 +448,7 @@ class Peppol extends AbstractService
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount- $this->invoice->total_taxes : $this->invoice->amount;
|
||||
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount - $this->invoice->total_taxes : $this->invoice->amount;
|
||||
$tax_subtotal->TaxableAmount = $taxable_amount;
|
||||
|
||||
|
||||
@ -536,7 +538,7 @@ class Peppol extends AbstractService
|
||||
$price = new Price();
|
||||
$pa = new PriceAmount();
|
||||
$pa->currencyID = $this->invoice->client->currency()->code;
|
||||
$pa->amount = $this->costWithDiscount($item) - ( $this->invoice->uses_inclusive_taxes ? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total)/$item->quantity) : 0);
|
||||
$pa->amount = $this->costWithDiscount($item) - ($this->invoice->uses_inclusive_taxes ? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) / $item->quantity) : 0);
|
||||
$price->PriceAmount = $pa;
|
||||
|
||||
$line->Price = $price;
|
||||
@ -738,7 +740,7 @@ class Peppol extends AbstractService
|
||||
|
||||
$code = false;
|
||||
|
||||
match($this->invoice->client->classification){
|
||||
match($this->invoice->client->classification) {
|
||||
"business" => $code = "B",
|
||||
"government" => $code = "G",
|
||||
"individual" => $code = "C",
|
||||
@ -746,11 +748,11 @@ class Peppol extends AbstractService
|
||||
};
|
||||
|
||||
//single array
|
||||
if(is_array($rules) && !is_array($rules[0]))
|
||||
if(is_array($rules) && !is_array($rules[0])) {
|
||||
return $rules[2];
|
||||
}
|
||||
|
||||
foreach($rules as $rule)
|
||||
{
|
||||
foreach($rules as $rule) {
|
||||
if(stripos($rule[0], $code) !== false) {
|
||||
return $rule[2];
|
||||
}
|
||||
@ -768,12 +770,13 @@ class Peppol extends AbstractService
|
||||
|
||||
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->value = $this->invoice->client->vat_number;
|
||||
$pi->ID = $vatID;
|
||||
@ -804,7 +807,8 @@ class Peppol extends AbstractService
|
||||
$physical_location = new PhysicalLocation();
|
||||
$physical_location->Address = $address;
|
||||
|
||||
$party->PhysicalLocation = $physical_location;;
|
||||
$party->PhysicalLocation = $physical_location;
|
||||
;
|
||||
|
||||
$contact = new Contact();
|
||||
$contact->ElectronicMail = $this->invoice->client->present()->email();
|
||||
@ -871,17 +875,15 @@ class Peppol extends AbstractService
|
||||
|
||||
if(count($receiver_identifiers) > 1) {
|
||||
|
||||
foreach($receiver_identifiers as $ident)
|
||||
{
|
||||
if(str_contains($ident[0], $client_classification))
|
||||
{
|
||||
foreach($receiver_identifiers as $ident) {
|
||||
if(str_contains($ident[0], $client_classification)) {
|
||||
return $ident[3];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
elseif(count($receiver_identifiers) == 1)
|
||||
} elseif(count($receiver_identifiers) == 1) {
|
||||
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}");
|
||||
|
||||
@ -912,10 +914,11 @@ class Peppol extends AbstractService
|
||||
];
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -935,9 +938,9 @@ class Peppol extends AbstractService
|
||||
|
||||
if($prop_value = PropertyResolver::resolve($this->p_invoice, $property_path)) {
|
||||
return $prop_value;
|
||||
}elseif($prop_value = PropertyResolver::resolve($this->_client_settings, $property_path)) {
|
||||
} elseif($prop_value = PropertyResolver::resolve($this->_client_settings, $property_path)) {
|
||||
return $prop_value;
|
||||
}elseif($prop_value = PropertyResolver::resolve($this->_company_settings, $property_path)) {
|
||||
} elseif($prop_value = PropertyResolver::resolve($this->_company_settings, $property_path)) {
|
||||
return $prop_value;
|
||||
}
|
||||
return null;
|
||||
@ -962,11 +965,12 @@ class Peppol extends AbstractService
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
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}();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -979,11 +983,12 @@ class Peppol extends AbstractService
|
||||
* ie mutations that are required by the receiving country
|
||||
* @return self
|
||||
*/
|
||||
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}"}();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -999,9 +1004,9 @@ class Peppol extends AbstractService
|
||||
private function setPaymentMeans(bool $required = false): self
|
||||
{
|
||||
|
||||
if(isset($this->p_invoice->PaymentMeans))
|
||||
if(isset($this->p_invoice->PaymentMeans)) {
|
||||
return $this;
|
||||
elseif($paymentMeans = $this->getSetting('Invoice.PaymentMeans')){
|
||||
} elseif($paymentMeans = $this->getSetting('Invoice.PaymentMeans')) {
|
||||
$this->p_invoice->PaymentMeans = is_array($paymentMeans) ? $paymentMeans : [$paymentMeans];
|
||||
return $this;
|
||||
}
|
||||
@ -1022,8 +1027,7 @@ class Peppol extends AbstractService
|
||||
{
|
||||
$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();
|
||||
$id = new ID();
|
||||
$id->value = $this->invoice->po_number;
|
||||
@ -1062,15 +1066,13 @@ class Peppol extends AbstractService
|
||||
private function setCustomerAssignedAccountId(bool $required = false): self
|
||||
{
|
||||
//@phpstan-ignore-next-line
|
||||
if(isset($this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID)){
|
||||
if(isset($this->p_invoice->AccountingCustomerParty->CustomerAssignedAccountID)) {
|
||||
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;
|
||||
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->value = $this->invoice->client->id_number;
|
||||
@ -1126,12 +1128,11 @@ class Peppol extends AbstractService
|
||||
|
||||
$meta = $this->getStorecoveMeta();
|
||||
|
||||
if(isset($meta['routing']['emails'])){
|
||||
if(isset($meta['routing']['emails'])) {
|
||||
$emails = $meta['routing']['emails'];
|
||||
array_push($emails, $email);
|
||||
$meta['routing']['emails'] = $emails;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$meta['routing']['emails'] = [$email];
|
||||
}
|
||||
|
||||
@ -1246,36 +1247,37 @@ class Peppol extends AbstractService
|
||||
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);
|
||||
}
|
||||
|
||||
if($this->invoice->client->classification == 'business' && $this->invoice->company->getSetting('classification') == 'business') {
|
||||
//must have a paymentmeans as credit_transfer
|
||||
$this->setPaymentMeans(true);
|
||||
}
|
||||
|
||||
// For B2G, provide three ES:FACE identifiers in the routing object,
|
||||
// as well as the ES:VAT tax identifier in the accountingCustomerParty.publicIdentifiers.
|
||||
// The invoice will then be routed through the FACe network. The three required ES:FACE identifiers are as follows:
|
||||
// "routing": {
|
||||
// "eIdentifiers":[
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-01-FISCAL"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-02-RECEPTOR"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-03-PAGADOR"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// For B2G, provide three ES:FACE identifiers in the routing object,
|
||||
// as well as the ES:VAT tax identifier in the accountingCustomerParty.publicIdentifiers.
|
||||
// The invoice will then be routed through the FACe network. The three required ES:FACE identifiers are as follows:
|
||||
// "routing": {
|
||||
// "eIdentifiers":[
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-01-FISCAL"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-02-RECEPTOR"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-03-PAGADOR"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -1303,7 +1305,7 @@ class Peppol extends AbstractService
|
||||
// All invoices have to be routed to SIRET 0009:11000201100044. There is no test environment for sending to public entities.
|
||||
// The SIRET / 0009 identifier of the final recipient is to be included in the invoice.accountingCustomerParty.publicIdentifiers array.
|
||||
|
||||
if($this->invoice->client->classification == 'government'){
|
||||
if($this->invoice->client->classification == 'government') {
|
||||
//route to SIRET 0009:11000201100044
|
||||
$this->setStorecoveMeta($this->buildRouting([
|
||||
["scheme" => 'FR:SIRET', "id" => '11000201100044']
|
||||
@ -1323,8 +1325,7 @@ class Peppol extends AbstractService
|
||||
|
||||
// ["scheme" => 'FR:SIRET', "id" => "0002:{$this->invoice->client->id_number}"]
|
||||
]));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
//SIRET
|
||||
$this->setStorecoveMeta($this->buildRouting([
|
||||
["scheme" => 'FR:SIRET', "id" => "{$this->invoice->client->id_number}"]
|
||||
@ -1337,7 +1338,7 @@ class Peppol extends AbstractService
|
||||
// sounds like it is optional
|
||||
// The service code must be sent in invoice.buyerReference (deprecated) or the invoice.references array (documentType buyer_reference)
|
||||
|
||||
if(strlen($this->invoice->po_number ?? '') >1) {
|
||||
if(strlen($this->invoice->po_number ?? '') > 1) {
|
||||
$this->setOrderReference(false);
|
||||
}
|
||||
|
||||
@ -1461,7 +1462,7 @@ class Peppol extends AbstractService
|
||||
$meta = ["networks" => [
|
||||
[
|
||||
"application" => "ro-anaf",
|
||||
"settings"=> [
|
||||
"settings" => [
|
||||
"enabled" => true
|
||||
],
|
||||
],
|
||||
|
@ -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
|
||||
{
|
||||
$state_code = strlen($state_code ?? '') > 1 ? $state_code : $this->invoice->client->state;
|
||||
|
||||
//codes are configured by default
|
||||
if(isset($this->countrySubEntity[$state_code]))
|
||||
if(isset($this->countrySubEntity[$state_code])) {
|
||||
return $state_code;
|
||||
}
|
||||
|
||||
$key = array_search($state_code, $this->countrySubEntity);
|
||||
|
||||
@ -140,8 +143,9 @@ class RO
|
||||
{
|
||||
$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 $client_sector_code;
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ class PropertyResolver
|
||||
return self::traverse($object, $pathSegments);
|
||||
}
|
||||
|
||||
private static function traverse($object, array $pathSegments) {
|
||||
private static function traverse($object, array $pathSegments)
|
||||
{
|
||||
if (empty($pathSegments)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ namespace App\Services\Import\Quickbooks\Contracts;
|
||||
|
||||
interface SdkInterface
|
||||
{
|
||||
function getAuthorizationUrl(): string;
|
||||
function accessToken(string $code, string $realm): array;
|
||||
function refreshToken(): array;
|
||||
function getAccessToken(): array;
|
||||
function getRefreshToken(): array;
|
||||
function totalRecords(string $entity): int;
|
||||
function fetchRecords(string $entity, int $max): array;
|
||||
public function getAuthorizationUrl(): string;
|
||||
public function accessToken(string $code, string $realm): array;
|
||||
public function refreshToken(): array;
|
||||
public function getAccessToken();
|
||||
public function getRefreshToken(): array;
|
||||
public function totalRecords(string $entity): int;
|
||||
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\Cache;
|
||||
|
||||
class CompanyTokensRepository {
|
||||
|
||||
|
||||
class CompanyTokensRepository
|
||||
{
|
||||
private $company_key;
|
||||
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->store_key .= $key;
|
||||
$this->setCompanyDbByKey();
|
||||
}
|
||||
|
||||
public function save(array $tokens) {
|
||||
public function save(array $tokens)
|
||||
{
|
||||
$this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']);
|
||||
$this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']);
|
||||
}
|
||||
@ -35,7 +36,8 @@ class CompanyTokensRepository {
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
}
|
||||
|
||||
public function get() {
|
||||
public function get()
|
||||
{
|
||||
return $this->getAccessToken() + $this->getRefreshToken();
|
||||
}
|
||||
|
||||
@ -49,13 +51,13 @@ class CompanyTokensRepository {
|
||||
'quickbooks_refresh_expires' => $expires ]);
|
||||
}
|
||||
|
||||
protected function updateAccessToken(string $token, string $expires )
|
||||
protected function updateAccessToken(string $token, string $expires)
|
||||
{
|
||||
|
||||
Cache::put([$this->store_key => $token], $expires);
|
||||
}
|
||||
|
||||
protected function getAccessToken( )
|
||||
protected function getAccessToken()
|
||||
{
|
||||
$result = Cache::get($this->store_key);
|
||||
|
||||
@ -66,11 +68,11 @@ class CompanyTokensRepository {
|
||||
{
|
||||
$result = (array) DB::table('companies')
|
||||
->select('quickbooks_refresh_token', 'quickbooks_realm_id')
|
||||
->where('company_key',$this->company_key)
|
||||
->where('quickbooks_refresh_expires','>',now())
|
||||
->where('company_key', $this->company_key)
|
||||
->where('quickbooks_refresh_expires', '>', now())
|
||||
->first();
|
||||
|
||||
return $result? array_combine(['refresh_token','realm'], array_values($result) ) : [];
|
||||
return $result ? array_combine(['refresh_token','realm'], array_values($result)) : [];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface
|
||||
|
||||
final class SdkWrapper implements QuickbooksInterface
|
||||
{
|
||||
|
||||
const MAXRESULTS = 10000;
|
||||
public const MAXRESULTS = 10000;
|
||||
|
||||
private $sdk;
|
||||
private $entities = ['Customer','Invoice','Payment','Item'];
|
||||
@ -18,12 +17,12 @@ final class SdkWrapper implements QuickbooksInterface
|
||||
$this->sdk = $sdk;
|
||||
}
|
||||
|
||||
public function getAuthorizationUrl() : string
|
||||
public function getAuthorizationUrl(): string
|
||||
{
|
||||
return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL();
|
||||
}
|
||||
|
||||
public function getState() : string
|
||||
public function getState(): string
|
||||
{
|
||||
return ($this->sdk->getOAuth2LoginHelper())->getState();
|
||||
}
|
||||
@ -33,13 +32,14 @@ final class SdkWrapper implements QuickbooksInterface
|
||||
return $this->getTokens();
|
||||
}
|
||||
|
||||
public function getRefreshToken(): array{
|
||||
public function getRefreshToken(): array
|
||||
{
|
||||
return $this->getTokens();
|
||||
}
|
||||
|
||||
public function accessToken(string $code, string $realm) : array
|
||||
public function accessToken(string $code, string $realm): array
|
||||
{
|
||||
$token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code,$realm);
|
||||
$token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code, $realm);
|
||||
|
||||
return $this->getTokens();
|
||||
}
|
||||
@ -47,7 +47,7 @@ final class SdkWrapper implements QuickbooksInterface
|
||||
private function getTokens()
|
||||
{
|
||||
|
||||
$token =($this->sdk->getOAuth2LoginHelper())->getAccessToken();
|
||||
$token = ($this->sdk->getOAuth2LoginHelper())->getAccessToken();
|
||||
return $token;
|
||||
|
||||
// $access_token = $token->getAccessToken();
|
||||
@ -66,22 +66,27 @@ final class SdkWrapper implements QuickbooksInterface
|
||||
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");
|
||||
}
|
||||
|
||||
private function queryData(string $query, int $start = 1, $limit = 100) : array
|
||||
private function queryData(string $query, int $start = 1, $limit = 100): array
|
||||
{
|
||||
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 = [];
|
||||
$start = 0;
|
||||
@ -94,12 +99,16 @@ final class SdkWrapper implements QuickbooksInterface
|
||||
do {
|
||||
$limit = min(self::MAXRESULTS, $total - $start);
|
||||
$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;
|
||||
} while ($start < $total);
|
||||
if(empty($records)) throw new \Exceptions("No records retrieved!");
|
||||
if(empty($records)) {
|
||||
throw new \Exception("No records retrieved!");
|
||||
}
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
|
||||
|
@ -1,31 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Import\Quickbooks;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\Import\Quickbooks\Auth;
|
||||
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
|
||||
{
|
||||
private QuickbooksInterface $sdk;
|
||||
|
||||
public function __construct(QuickbooksInterface $quickbooks) {
|
||||
public function __construct(QuickbooksInterface $quickbooks)
|
||||
{
|
||||
$this->sdk = $quickbooks;
|
||||
}
|
||||
|
||||
public function getOAuth() : Auth
|
||||
public function getOAuth(): Auth
|
||||
{
|
||||
return new Auth($this->sdk);
|
||||
}
|
||||
|
||||
public function getAccessToken() : array
|
||||
public function getAccessToken(): array
|
||||
{
|
||||
return $this->getOAuth()->getAccessToken();
|
||||
}
|
||||
|
||||
public function getRefreshToken() : array
|
||||
public function getRefreshToken(): array
|
||||
{
|
||||
// TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire
|
||||
return $this->getAccessToken();
|
||||
@ -60,11 +62,12 @@ final class Service
|
||||
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);
|
||||
}
|
||||
|
||||
private static function RepositoryFactory(string $entity) : RepositoryInterface
|
||||
private static function RepositoryFactory(string $entity): RepositoryInterface
|
||||
{
|
||||
return app("\\App\\Repositories\\Import\Quickbooks\\{$entity}Repository");
|
||||
}
|
||||
@ -79,7 +82,7 @@ final class Service
|
||||
return $this->fetchRecords('Customer', $max) ;
|
||||
}
|
||||
|
||||
public function totalRecords(string $entity) : int
|
||||
public function totalRecords(string $entity): int
|
||||
{
|
||||
return (self::RepositoryFactory($entity))->count();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
private function triggeredActions(): self
|
||||
{
|
||||
if($this->invoice->quote){
|
||||
if($this->invoice->quote) {
|
||||
$this->invoice->quote->invoice_id = null;
|
||||
$this->invoice->quote->status_id = Quote::STATUS_SENT;
|
||||
$this->invoice->pushQuietly();
|
||||
|
@ -42,8 +42,9 @@ class CreateInvitations extends AbstractService
|
||||
public function run()
|
||||
{
|
||||
|
||||
if(!$this->purchase_order->vendor)
|
||||
if(!$this->purchase_order->vendor) {
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
$contacts = $this->purchase_order->vendor->contacts()->get();
|
||||
|
||||
|
@ -17,7 +17,6 @@ use App\Models\PurchaseOrder;
|
||||
|
||||
class MarkSent
|
||||
{
|
||||
|
||||
public function __construct(public Vendor $vendor, public PurchaseOrder $purchase_order)
|
||||
{
|
||||
}
|
||||
|
@ -99,6 +99,13 @@ class ProfitLoss
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -356,12 +363,6 @@ class ProfitLoss
|
||||
nlog($this->income_taxes);
|
||||
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->insertOne([ctrans('texts.profit_and_loss')]);
|
||||
|
@ -220,7 +220,7 @@ class TemplateAction implements ShouldQueue
|
||||
Project::class => 'projects',
|
||||
Client::class => 'clients',
|
||||
Vendor::class => 'vendors',
|
||||
default =>'invoices',
|
||||
default => 'invoices',
|
||||
};
|
||||
}
|
||||
|
||||
|
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