Merge pull request #8101 from turbo124/v5-develop

v5.5.50
This commit is contained in:
David Bomba 2023-01-02 17:56:23 +11:00 committed by GitHub
commit 03d8864652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 1170 additions and 317 deletions

View File

@ -1 +1 @@
5.5.49 5.5.50

View File

@ -119,6 +119,7 @@ class CheckData extends Command
$this->checkDuplicateRecurringInvoices(); $this->checkDuplicateRecurringInvoices();
$this->checkOauthSanity(); $this->checkOauthSanity();
$this->checkVendorSettings(); $this->checkVendorSettings();
$this->checkClientSettings();
if(Ninja::isHosted()){ if(Ninja::isHosted()){
$this->checkAccountStatuses(); $this->checkAccountStatuses();
@ -952,24 +953,24 @@ class CheckData extends Command
if ($this->option('fix') == 'true') { if ($this->option('fix') == 'true') {
Client::query()->whereNull('settings->currency_id')->cursor()->each(function ($client){ // Client::query()->whereNull('settings->currency_id')->cursor()->each(function ($client){
if(is_array($client->settings) && count($client->settings) == 0) // if(is_array($client->settings) && count($client->settings) == 0)
{ // {
$settings = ClientSettings::defaults(); // $settings = ClientSettings::defaults();
$settings->currency_id = $client->company->settings->currency_id; // $settings->currency_id = $client->company->settings->currency_id;
} // }
else { // else {
$settings = $client->settings; // $settings = $client->settings;
$settings->currency_id = $client->company->settings->currency_id; // $settings->currency_id = $client->company->settings->currency_id;
} // }
$client->settings = $settings; // $client->settings = $settings;
$client->save(); // $client->save();
$this->logMessage("Fixing currency for # {$client->id}"); // $this->logMessage("Fixing currency for # {$client->id}");
}); // });
Client::query()->whereNull('country_id')->cursor()->each(function ($client){ Client::query()->whereNull('country_id')->cursor()->each(function ($client){

View File

@ -307,7 +307,7 @@ class CreateSingleAccount extends Command
$webhook_config = [ $webhook_config = [
'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan', 'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan',
'post_purchase_rest_method' => 'POST', 'post_purchase_rest_method' => 'POST',
'post_purchase_headers' => [], 'post_purchase_headers' => [config('ninja.ninja_hosted_header') => config('ninja.ninja_hosted_secret')],
]; ];
$sub = SubscriptionFactory::create($company->id, $user->id); $sub = SubscriptionFactory::create($company->id, $user->id);

View File

@ -23,6 +23,7 @@ use App\Jobs\Ninja\QueueSize;
use App\Jobs\Ninja\SystemMaintenance; use App\Jobs\Ninja\SystemMaintenance;
use App\Jobs\Ninja\TaskScheduler; use App\Jobs\Ninja\TaskScheduler;
use App\Jobs\Quote\QuoteCheckExpired; use App\Jobs\Quote\QuoteCheckExpired;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
use App\Jobs\Util\DiskCleanup; use App\Jobs\Util\DiskCleanup;
use App\Jobs\Util\ReminderJob; use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck; use App\Jobs\Util\SchedulerCheck;
@ -68,6 +69,9 @@ class Kernel extends ConsoleKernel
/* Sends recurring invoices*/ /* Sends recurring invoices*/
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer(); $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer();
/* Stale Invoice Cleanup*/
$schedule->job(new CleanStaleInvoiceOrder)->hourly()->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
/* Sends recurring invoices*/ /* Sends recurring invoices*/
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping()->name('recurring-expense-job')->onOneServer(); $schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping()->name('recurring-expense-job')->onOneServer();

View File

@ -93,6 +93,10 @@ class ClientRegistrationFields
'key' => 'vat_number', 'key' => 'vat_number',
'required' => false, 'required' => false,
], ],
[
'key' => 'currency_id',
'required' => false,
],
]; ];
return $data; return $data;

View File

@ -77,28 +77,45 @@ class BankTransactionFilters extends QueryFilters
$status_parameters = explode(',', $value); $status_parameters = explode(',', $value);
$status_array = [];
$debit_or_withdrawal_array = [];
if (in_array('all', $status_parameters)) { if (in_array('all', $status_parameters)) {
return $this->builder; return $this->builder;
} }
if (in_array('unmatched', $status_parameters)) { if (in_array('unmatched', $status_parameters)) {
$this->builder->where('status_id', BankTransaction::STATUS_UNMATCHED); $status_array[] = BankTransaction::STATUS_UNMATCHED;
// $this->builder->orWhere('status_id', BankTransaction::STATUS_UNMATCHED);
} }
if (in_array('matched', $status_parameters)) { if (in_array('matched', $status_parameters)) {
$this->builder->where('status_id', BankTransaction::STATUS_MATCHED); $status_array[] = BankTransaction::STATUS_MATCHED;
// $this->builder->where('status_id', BankTransaction::STATUS_MATCHED);
} }
if (in_array('converted', $status_parameters)) { if (in_array('converted', $status_parameters)) {
$this->builder->where('status_id', BankTransaction::STATUS_CONVERTED); $status_array[] = BankTransaction::STATUS_CONVERTED;
// $this->builder->where('status_id', BankTransaction::STATUS_CONVERTED);
} }
if (in_array('deposits', $status_parameters)) { if (in_array('deposits', $status_parameters)) {
$this->builder->where('base_type', 'CREDIT'); $debit_or_withdrawal_array[] = 'CREDIT';
// $this->builder->where('base_type', 'CREDIT');
} }
if (in_array('withdrawals', $status_parameters)) { if (in_array('withdrawals', $status_parameters)) {
$this->builder->where('base_type', 'DEBIT'); $debit_or_withdrawal_array[] = 'DEBIT';
// $this->builder->where('base_type', 'DEBIT');
}
if(count($status_array) >=1) {
$this->builder->whereIn('status_id', $status_array);
}
if(count($debit_or_withdrawal_array) >=1) {
$this->builder->orWhereIn('base_type', $debit_or_withdrawal_array);
} }
return $this->builder; return $this->builder;

View File

@ -238,7 +238,6 @@ class ClientFilters extends QueryFilters
*/ */
public function entityFilter() public function entityFilter()
{ {
//return $this->builder->whereCompanyId(auth()->user()->company()->id);
return $this->builder->company(); return $this->builder->company();
} }
} }

View File

@ -47,20 +47,27 @@ class InvoiceFilters extends QueryFilters
$status_parameters = explode(',', $value); $status_parameters = explode(',', $value);
$invoice_filters = [];
if (in_array('all', $status_parameters)) { if (in_array('all', $status_parameters)) {
return $this->builder; return $this->builder;
} }
if (in_array('paid', $status_parameters)) { if (in_array('paid', $status_parameters)) {
$this->builder->where('status_id', Invoice::STATUS_PAID); $invoice_filters[] = Invoice::STATUS_PAID;
} }
if (in_array('unpaid', $status_parameters)) { if (in_array('unpaid', $status_parameters)) {
$this->builder->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]); $invoice_filters[] = Invoice::STATUS_SENT;
$invoice_filters[] = Invoice::STATUS_PARTIAL;
}
if(count($invoice_filters) >0){
$this->builder->whereIn('status_id', $invoice_filters);
} }
if (in_array('overdue', $status_parameters)) { if (in_array('overdue', $status_parameters)) {
$this->builder->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) $this->builder->orWhereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('due_date', '<', Carbon::now()) ->where('due_date', '<', Carbon::now())
->orWhere('partial_due_date', '<', Carbon::now()); ->orWhere('partial_due_date', '<', Carbon::now());
} }

View File

@ -42,20 +42,26 @@ class PurchaseOrderFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
$po_status = [];
if (in_array('draft', $status_parameters)) { if (in_array('draft', $status_parameters)) {
$this->builder->where('status_id', PurchaseOrder::STATUS_DRAFT); $po_status[] = PurchaseOrder::STATUS_DRAFT;
} }
if (in_array('sent', $status_parameters)) { if (in_array('sent', $status_parameters)) {
$this->builder->where('status_id', PurchaseOrder::STATUS_SENT); $po_status[] = PurchaseOrder::STATUS_SENT;
} }
if (in_array('accepted', $status_parameters)) { if (in_array('accepted', $status_parameters)) {
$this->builder->where('status_id', PurchaseOrder::STATUS_ACCEPTED); $po_status[] = PurchaseOrder::STATUS_ACCEPTED;
} }
if (in_array('cancelled', $status_parameters)) { if (in_array('cancelled', $status_parameters)) {
$this->builder->where('status_id', PurchaseOrder::STATUS_CANCELLED); $po_status[] = PurchaseOrder::STATUS_CANCELLED;
}
if(count($status_parameters) >=1) {
$this->builder->whereIn('status_id', $status_parameters);
} }
return $this->builder; return $this->builder;

View File

@ -15,6 +15,7 @@ namespace App\Filters;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
/** /**
* Class QueryFilters. * Class QueryFilters.
@ -173,22 +174,30 @@ abstract class QueryFilters
} }
} }
public function created_at($value) public function created_at($value = '')
{ {
$created_at = $value ? (int) $value : 0;
$created_at = date('Y-m-d H:i:s', $value); if($value == '')
return $this->builder;
if(is_string($created_at)){ try{
$created_at = strtotime(str_replace("/","-",$created_at)); if(is_numeric($value)){
$created_at = Carbon::createFromTimestamp((int)$value);
}
else{
$created_at = Carbon::parse($value);
}
if(!$created_at) return $this->builder->where('created_at', '>=', $created_at);
return $this->builder;
}
catch(\Exception $e) {
return $this->builder;
} }
return $this->builder->where('created_at', '>=', $created_at);
} }
public function is_deleted($value) public function is_deleted($value)

View File

@ -66,26 +66,32 @@ class QuoteFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
$quote_filters = [];
if (in_array('draft', $status_parameters)) { if (in_array('draft', $status_parameters)) {
$this->builder->where('status_id', Quote::STATUS_DRAFT); $quote_filters[] = Quote::STATUS_DRAFT;
} }
if (in_array('sent', $status_parameters)) { if (in_array('sent', $status_parameters)) {
$this->builder->where('status_id', Quote::STATUS_SENT); $quote_filters[] = Quote::STATUS_SENT;
} }
if (in_array('approved', $status_parameters)) { if (in_array('approved', $status_parameters)) {
$this->builder->where('status_id', Quote::STATUS_APPROVED); $quote_filters[] = Quote::STATUS_APPROVED;
}
if(count($quote_filters) >=1){
$this->builder->whereIn('status_id', $quote_filters);
} }
if (in_array('expired', $status_parameters)) { if (in_array('expired', $status_parameters)) {
$this->builder->where('status_id', Quote::STATUS_SENT) $this->builder->orWhere('status_id', Quote::STATUS_SENT)
->where('due_date', '>=', now()->toDateString()); ->where('due_date', '<=', now()->toDateString());
} }
if (in_array('upcoming', $status_parameters)) { if (in_array('upcoming', $status_parameters)) {
$this->builder->where('status_id', Quote::STATUS_SENT) $this->builder->orWhere('status_id', Quote::STATUS_SENT)
->where('due_date', '<=', now()->toDateString()) ->where('due_date', '>=', now()->toDateString())
->orderBy('due_date', 'DESC'); ->orderBy('due_date', 'DESC');
} }

View File

@ -51,6 +51,7 @@ class ContactRegisterController extends Controller
public function register(RegisterRequest $request) public function register(RegisterRequest $request)
{ {
$request->merge(['company' => $request->company()]); $request->merge(['company' => $request->company()]);
$client = $this->getClient($request->all()); $client = $this->getClient($request->all());
@ -58,7 +59,7 @@ class ContactRegisterController extends Controller
Auth::guard('contact')->loginUsingId($client_contact->id, true); Auth::guard('contact')->loginUsingId($client_contact->id, true);
return redirect()->route('client.dashboard'); return redirect()->intended(route('client.dashboard'));
} }
private function getClient(array $data) private function getClient(array $data)
@ -66,7 +67,15 @@ class ContactRegisterController extends Controller
$client = ClientFactory::create($data['company']->id, $data['company']->owner()->id); $client = ClientFactory::create($data['company']->id, $data['company']->owner()->id);
$client->fill($data); $client->fill($data);
$client->save(); $client->save();
if(isset($data['currency_id'])) {
$settings = $client->settings;
$settings->currency_id = isset($data['currency_id']) ? $data['currency_id'] : $data['company']->settings->currency_id;
$client->settings = $settings;
}
$client->number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);
$client->save(); $client->save();

View File

@ -52,7 +52,7 @@ class InvoiceController extends Controller
* *
* @return Factory|View * @return Factory|View
*/ */
public function show(ShowInvoiceRequest $request, Invoice $invoice) public function show(ShowInvoiceRequest $request, Invoice $invoice, ?string $hash = null)
{ {
set_time_limit(0); set_time_limit(0);
@ -69,6 +69,7 @@ class InvoiceController extends Controller
'invoice' => $invoice, 'invoice' => $invoice,
'invitation' => $invitation ?: $invoice->invitations->first(), 'invitation' => $invitation ?: $invoice->invitations->first(),
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'hash' => $hash,
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {

View File

@ -148,8 +148,17 @@ class PaymentController extends Controller
$payment = $payment->service()->applyCredits($payment_hash)->save(); $payment = $payment->service()->applyCredits($payment_hash)->save();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')));
event('eloquent.created: App\Models\Payment', $payment); event('eloquent.created: App\Models\Payment', $payment);
if($invoices->sum('balance') > 0){
$invoice = $invoices->first();
return redirect()->route('client.invoice.show', ['invoice' => $invoice->hashed_id, 'hash' => $request->input('hash')]);
}
if (property_exists($payment_hash->data, 'billing_context')) { if (property_exists($payment_hash->data, 'billing_context')) {
$billing_subscription = \App\Models\Subscription::find($payment_hash->data->billing_context->subscription_id); $billing_subscription = \App\Models\Subscription::find($payment_hash->data->billing_context->subscription_id);

View File

@ -33,7 +33,9 @@ class SubscriptionPlanSwitchController extends Controller
{ {
$amount = $recurring_invoice->subscription $amount = $recurring_invoice->subscription
->service() ->service()
->calculateUpgradePrice($recurring_invoice, $target); ->calculateUpgradePriceV2($recurring_invoice, $target);
nlog("payment amount = {$amount}");
/** /**
* Null value here is a proxy for * Null value here is a proxy for
* denying the user a change plan option * denying the user a change plan option
@ -42,6 +44,9 @@ class SubscriptionPlanSwitchController extends Controller
render('subscriptions.denied'); render('subscriptions.denied');
} }
$amount = max(0,$amount);
return render('subscriptions.switch', [ return render('subscriptions.switch', [
'subscription' => $recurring_invoice->subscription, 'subscription' => $recurring_invoice->subscription,
'recurring_invoice' => $recurring_invoice, 'recurring_invoice' => $recurring_invoice,

View File

@ -53,6 +53,16 @@ class SubscriptionPurchaseController extends Controller
$this->setLocale($request->query('locale')); $this->setLocale($request->query('locale'));
} }
if(!auth()->guard('contact')->check() && $subscription->registration_required && $subscription->company->client_can_register) {
session()->put('url.intended', route('client.subscription.upgrade',['subscription' => $subscription->hashed_id]));
return redirect()->route('client.register', ['company_key' => $subscription->company->company_key]);
}
elseif(!auth()->guard('contact')->check() && $subscription->registration_required && ! $subscription->company->client_can_register) {
return render('generic.subscription_blocked', ['account' => $subscription->company->account, 'company' => $subscription->company]);
}
return view('billing-portal.purchasev2', [ return view('billing-portal.purchasev2', [
'subscription' => $subscription, 'subscription' => $subscription,
'hash' => Str::uuid()->toString(), 'hash' => Str::uuid()->toString(),

View File

@ -521,7 +521,7 @@ class CompanyController extends BaseController
$nmo->company = $other_company; $nmo->company = $other_company;
$nmo->settings = $other_company->settings; $nmo->settings = $other_company->settings;
$nmo->to_user = auth()->user(); $nmo->to_user = auth()->user();
NinjaMailerJob::dispatch($nmo, true); (new NinjaMailerJob($nmo, true))->handle();
$company->delete(); $company->delete();

View File

@ -135,11 +135,45 @@ class ExpenseCategoryController extends BaseController
return $this->itemResponse($expense_category); return $this->itemResponse($expense_category);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param StoreExpenseCategoryRequest $request * @param StoreInvoiceRequest $request The request
*
* @return Response * @return Response
*
*
* @OA\Post(
* path="/api/v1/expense_categories",
* operationId="storeExpenseCategory",
* tags={"expense_categories"},
* summary="Adds a expense category",
* description="Adds an expense category to the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved invoice object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/ExpenseCategory"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function store(StoreExpenseCategoryRequest $request) public function store(StoreExpenseCategoryRequest $request)
{ {

View File

@ -198,7 +198,7 @@ class PurchaseOrderController extends BaseController
event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($purchase_order); return $this->itemResponse($purchase_order->fresh());
} }
/** /**
* Display the specified resource. * Display the specified resource.

View File

@ -174,6 +174,8 @@ class BillingPortalPurchase extends Component
*/ */
public $company; public $company;
public $db;
/** /**
* Campaign reference. * Campaign reference.
* *
@ -183,7 +185,11 @@ class BillingPortalPurchase extends Component
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->subscription = Subscription::with('company')->find($this->subscription);
$this->company = $this->subscription->company;
$this->quantity = 1; $this->quantity = 1;
@ -225,10 +231,10 @@ class BillingPortalPurchase extends Component
$this->steps['existing_user'] = false; $this->steps['existing_user'] = false;
$contact = $this->createBlankClient(); $this->contact = $this->createBlankClient();
if ($contact && $contact instanceof ClientContact) { if ($this->contact && $this->contact instanceof ClientContact) {
$this->getPaymentMethods($contact); $this->getPaymentMethods($this->contact);
} }
} }
@ -265,9 +271,6 @@ class BillingPortalPurchase extends Component
} }
} }
// nlog($this->subscription->group_settings->settings);
// nlog($this->subscription->group_settings->settings->currency_id);
if(array_key_exists('currency_id', $this->request_data)) { if(array_key_exists('currency_id', $this->request_data)) {
$currency = Cache::get('currencies')->filter(function ($item){ $currency = Cache::get('currencies')->filter(function ($item){

View File

@ -15,6 +15,7 @@ use App\DataMapper\ClientSettings;
use App\Factory\ClientFactory; use App\Factory\ClientFactory;
use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Mail\ContactPasswordlessLogin; use App\Mail\ContactPasswordlessLogin;
use App\Mail\Subscription\OtpCode; use App\Mail\Subscription\OtpCode;
@ -120,7 +121,7 @@ class BillingPortalPurchasev2 extends Component
* *
* @var array * @var array
*/ */
public $request_data; public $request_data = [];
/** /**
* Instance of company. * Instance of company.
@ -129,6 +130,14 @@ class BillingPortalPurchasev2 extends Component
*/ */
public $company; public $company;
/**
* Instance of company.
*
* @var string
*/
public string $db;
/** /**
* Campaign reference. * Campaign reference.
* *
@ -151,10 +160,23 @@ class BillingPortalPurchasev2 extends Component
public $valid_coupon = false; public $valid_coupon = false;
public $payable_invoices = []; public $payable_invoices = [];
public $payment_confirmed = false; public $payment_confirmed = false;
public $is_eligible = true;
public $not_eligible_message = '';
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->subscription = Subscription::with('company')->find($this->subscription);
$this->company = $this->subscription->company;
if(auth()->guard('contact')->check()){
$this->email = auth()->guard('contact')->user()->email;
$this->contact = auth()->guard('contact')->user();
$this->authenticated = true;
$this->payment_started = true;
}
$this->discount = 0; $this->discount = 0;
$this->sub_total = 0; $this->sub_total = 0;
@ -177,7 +199,7 @@ class BillingPortalPurchasev2 extends Component
$this->coupon = request()->query('coupon'); $this->coupon = request()->query('coupon');
$this->handleCoupon(); $this->handleCoupon();
} }
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){ elseif(isset($this->subscription->promo_code) && strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
$this->price = $this->subscription->promo_price; $this->price = $this->subscription->promo_price;
} }
@ -224,6 +246,8 @@ class BillingPortalPurchasev2 extends Component
public function resetEmail() public function resetEmail()
{ {
$this->resetErrorBag('login');
$this->resetValidation('login');
$this->email = null; $this->email = null;
} }
@ -449,8 +473,6 @@ class BillingPortalPurchasev2 extends Component
$this->buildBundle(); $this->buildBundle();
nlog($this->bundle);
return $this; return $this;
} }
@ -489,9 +511,20 @@ nlog($this->bundle);
* *
* @return void * @return void
*/ */
public function handleBeforePaymentEvents() :void public function handleBeforePaymentEvents() :self
{ {
$eligibility_check = $this->subscription->service()->isEligible($this->contact);
if(is_array($eligibility_check) && $eligibility_check['message'] != 'Success'){
$this->is_eligible = false;
$this->not_eligible_message = $eligibility_check['message'];
return $this;
}
$data = [ $data = [
'client_id' => $this->contact->client->id, 'client_id' => $this->contact->client->id,
'date' => now()->format('Y-m-d'), 'date' => now()->format('Y-m-d'),
@ -501,19 +534,9 @@ nlog($this->bundle);
]], ]],
'user_input_promo_code' => $this->coupon, 'user_input_promo_code' => $this->coupon,
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon, 'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
// 'quantity' => $this->quantity,
]; ];
$is_eligible = $this->subscription->service()->isEligible($this->contact);
// if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
// $this->steps['not_eligible'] = true;
// $this->steps['not_eligible_message'] = $is_eligible['message'];
// $this->steps['show_loading_bar'] = false;
// return;
// }
$this->invoice = $this->subscription $this->invoice = $this->subscription
->service() ->service()
->createInvoiceV2($this->bundle, $this->contact->client_id, $this->valid_coupon) ->createInvoiceV2($this->bundle, $this->contact->client_id, $this->valid_coupon)
@ -534,6 +557,9 @@ nlog($this->bundle);
], now()->addMinutes(60)); ], now()->addMinutes(60));
$this->emit('beforePaymentEventsCompleted'); $this->emit('beforePaymentEventsCompleted');
return $this;
} }
public function handleTrial() public function handleTrial()

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Credit; use App\Models\Credit;
use App\Utils\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Livewire\Component; use Livewire\Component;
@ -23,26 +24,31 @@ class CreditsTable extends Component
use WithPagination; use WithPagination;
use WithSorting; use WithSorting;
public $per_page = 10; public int $per_page = 10;
public $company; public Company $company;
public string $db;
public int $company_id;
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->company = Company::find($this->company_id);
} }
public function render() public function render()
{ {
$query = Credit::query() $query = Credit::query()
->where('client_id', auth()->guard('contact')->user()->client_id)
->where('company_id', $this->company->id) ->where('company_id', $this->company->id)
->where('client_id', auth()->guard('contact')->user()->client_id)
->where('status_id', '<>', Credit::STATUS_DRAFT) ->where('status_id', '<>', Credit::STATUS_DRAFT)
->where('is_deleted', 0) ->where('is_deleted', 0)
->where(function ($query) { ->where(function ($query) {
$query->whereDate('due_date', '>=', now()) $query->whereDate('due_date', '>=', now())
->orWhereNull('due_date'); ->orWhereNull('due_date');
//->orWhere('due_date', '=', '');
}) })
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->withTrashed() ->withTrashed()

View File

@ -14,6 +14,7 @@ namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Client; use App\Models\Client;
use App\Models\Company;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Document; use App\Models\Document;
use App\Models\Expense; use App\Models\Expense;
@ -31,21 +32,27 @@ class DocumentsTable extends Component
{ {
use WithPagination, WithSorting; use WithPagination, WithSorting;
public $client; public Company $company;
public $per_page = 10; public Client $client;
public $company; public int $client_id;
public int $per_page = 10;
public string $tab = 'documents'; public string $tab = 'documents';
public string $db;
protected $query; protected $query;
public function mount($client) public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->client = $client; $this->client = Client::with('company')->find($this->client_id);
$this->company = $this->client->company;
$this->query = $this->documents(); $this->query = $this->documents();
} }

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Invoice; use App\Models\Invoice;
use App\Utils\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Carbon\Carbon; use Carbon\Carbon;
@ -23,15 +24,21 @@ class InvoicesTable extends Component
{ {
use WithPagination, WithSorting; use WithPagination, WithSorting;
public $per_page = 10; public int $per_page = 10;
public $status = []; public array $status = [];
public $company; public Company $company;
public int $company_id;
public string $db;
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->company = Company::find($this->company_id);
$this->sort_asc = false; $this->sort_asc = false;

View File

@ -23,13 +23,11 @@ class PayNowDropdown extends Component
public $company; public $company;
public function mount(int $total) public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
$this->total = $total; $this->methods = auth()->guard('contact')->user()->client->service()->getPaymentMethods($this->total);
$this->methods = auth()->guard('contact')->user()->client->service()->getPaymentMethods($total);
} }
public function render() public function render()

View File

@ -3,7 +3,9 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Utils\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@ -13,17 +15,23 @@ class PaymentMethodsTable extends Component
use WithPagination; use WithPagination;
use WithSorting; use WithSorting;
public $per_page = 10; public int $per_page = 10;
public $client; public Client $client;
public $company; public Company $company;
public function mount($client) public int $client_id;
public string $db;
public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->client = $client; $this->client = Client::with('company')->find($this->client_id);
$this->company = $this->client->company;
} }
public function render() public function render()

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Payment; use App\Models\Payment;
use App\Utils\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Livewire\Component; use Livewire\Component;
@ -23,17 +24,19 @@ class PaymentsTable extends Component
use WithSorting; use WithSorting;
use WithPagination; use WithPagination;
public $per_page = 10; public int $per_page = 10;
public $user; public Company $company;
public $company; public int $company_id;
public string $db;
public function mount() public function mount()
{ {
MultiDB::setDb($this->company->db); MultiDB::setDb($this->db);
$this->user = auth()->user(); $this->company = Company::find($this->company_id);
} }
public function render() public function render()

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Quote; use App\Models\Quote;
use App\Utils\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Livewire\Component; use Livewire\Component;
@ -22,15 +23,27 @@ class QuotesTable extends Component
{ {
use WithPagination; use WithPagination;
public $per_page = 10; public int $per_page = 10;
public $status = []; public array $status = [];
public $company; public Company $company;
public $sort = 'status_id'; // Default sortBy. Feel free to change or pull from client/company settings. public string $sort = 'status_id';
public bool $sort_asc = true;
public int $company_id;
public string $db;
public function mount()
{
MultiDB::setDb($this->db);
$this->company = Company::find($this->company_id);
}
public $sort_asc = true;
public function sortBy($field) public function sortBy($field)
{ {
@ -41,16 +54,11 @@ class QuotesTable extends Component
$this->sort = $field; $this->sort = $field;
} }
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render() public function render()
{ {
$query = Quote::query() $query = Quote::query()
->with('client.gateway_tokens', 'company', 'client.contacts') ->with('client.contacts', 'company')
->orderBy($this->sort, $this->sort_asc ? 'asc' : 'desc'); ->orderBy($this->sort, $this->sort_asc ? 'asc' : 'desc');
if (count($this->status) > 0) { if (count($this->status) > 0) {

View File

@ -142,7 +142,7 @@ class SubscriptionPlanSwitch extends Component
{ {
$this->hide_button = true; $this->hide_button = true;
$response = $this->target->service()->createChangePlanCredit([ $response = $this->target->service()->createChangePlanCreditV2([
'recurring_invoice' => $this->recurring_invoice, 'recurring_invoice' => $this->recurring_invoice,
'subscription' => $this->subscription, 'subscription' => $this->subscription,
'target' => $this->target, 'target' => $this->target,

View File

@ -18,6 +18,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use stdClass; use stdClass;
class PasswordProtection class PasswordProtection
@ -111,7 +112,18 @@ class PasswordProtection
return $next($request); return $next($request);
} }
} }
elseif(auth()->user()->oauth_provider_id == 'apple')
{
$user = Socialite::driver('apple')->userFromToken($request->header('X-API-OAUTH-PASSWORD'));
if($user && ($user->email == auth()->user()->email)){
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
}
return response()->json($error, 412); return response()->json($error, 412);

View File

@ -47,7 +47,7 @@ class RegisterRequest extends FormRequest
foreach ($rules as $field => $properties) { foreach ($rules as $field => $properties) {
if ($field === 'email') { if ($field === 'email') {
$rules[$field] = array_merge($rules[$field], ['email:rfc,dns', 'max:255', Rule::unique('client_contacts')->where('company_id', $this->company()->id)]); $rules[$field] = array_merge($rules[$field], ['email:rfc,dns', 'max:191', Rule::unique('client_contacts')->where('company_id', $this->company()->id)]);
} }
if ($field === 'current_password') { if ($field === 'current_password') {

View File

@ -110,7 +110,8 @@ class UpdateCompanyRequest extends Request
} }
} }
$settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']); if(isset($settings['email_style_custom']))
$settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']);
if (! $account->isFreeHostedClient()) { if (! $account->isFreeHostedClient()) {
return $settings; return $settings;

View File

@ -41,7 +41,7 @@ class StoreExpenseRequest extends Request
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id); $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id);
} }
if (! empty($this->client_id)) { if ($this->client_id) {
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
} }

View File

@ -41,6 +41,10 @@ class UpdateExpenseRequest extends Request
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id); $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id);
} }
if ($this->client_id) {
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
}
$rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
return $this->globalRules($rules); return $this->globalRules($rules);

View File

@ -43,6 +43,7 @@ class StoreRecurringExpenseRequest extends Request
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
} }
$rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
$rules['frequency_id'] = 'required|integer|digits_between:1,12'; $rules['frequency_id'] = 'required|integer|digits_between:1,12';
$rules['tax_amount1'] = 'numeric'; $rules['tax_amount1'] = 'numeric';
$rules['tax_amount2'] = 'numeric'; $rules['tax_amount2'] = 'numeric';
@ -61,10 +62,6 @@ class StoreRecurringExpenseRequest extends Request
$input['next_send_date_client'] = $input['next_send_date']; $input['next_send_date_client'] = $input['next_send_date'];
} }
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
}
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) { if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; $input['currency_id'] = (string) auth()->user()->company()->settings->currency_id;
} }

View File

@ -46,6 +46,7 @@ class UpdateRecurringExpenseRequest extends Request
$rules['tax_amount1'] = 'numeric'; $rules['tax_amount1'] = 'numeric';
$rules['tax_amount2'] = 'numeric'; $rules['tax_amount2'] = 'numeric';
$rules['tax_amount3'] = 'numeric'; $rules['tax_amount3'] = 'numeric';
$rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
return $this->globalRules($rules); return $this->globalRules($rules);
} }
@ -70,10 +71,6 @@ class UpdateRecurringExpenseRequest extends Request
$input['next_send_date_client'] = $input['next_send_date']; $input['next_send_date_client'] = $input['next_send_date'];
} }
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
}
if (array_key_exists('documents', $input)) { if (array_key_exists('documents', $input)) {
unset($input['documents']); unset($input['documents']);
} }

View File

@ -0,0 +1,80 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Subscription;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Repositories\InvoiceRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CleanStaleInvoiceOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @param int invoice_id
* @param string $db
*/
public function __construct(){}
/**
* @param InvoiceRepository $repo
* @return void
*/
public function handle(InvoiceRepository $repo) : void
{
if (! config('ninja.db.multi_db_enabled')) {
Invoice::query()
->withTrashed()
->where('is_proforma', 1)
->where('created_at', '<', now()->subHour())
->cursor()
->each(function ($invoice) use ($repo) {
$invoice->is_proforma = false;
$repo->delete($invoice);
});
return;
}
foreach (MultiDB::$dbs as $db)
{
MultiDB::setDB($db);
Invoice::query()
->withTrashed()
->where('is_proforma', 1)
->where('created_at', '<', now()->subHour())
->cursor()
->each(function ($invoice) use ($repo) {
$invoice->is_proforma = false;
$repo->delete($invoice);
});
}
}
public function failed($exception = null)
{
}
}

View File

@ -21,12 +21,10 @@ use App\Notifications\Admin\EntitySentNotification;
use App\Utils\Traits\Notifications\UserNotifies; use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
class PurchaseOrderAcceptedNotification implements ShouldQueue class PurchaseOrderAcceptedListener implements ShouldQueue
{ {
use UserNotifies; use UserNotifies;
public $delay = 5;
public function __construct() public function __construct()
{ {
} }

View File

@ -0,0 +1,81 @@
<?php
/**
* PurchaseOrder Ninja (https://purchase_orderninja.com).
*
* @link https://github.com/purchase_orderninja/purchase_orderninja source repository
*
* @copyright Copyright (c) 2022. PurchaseOrder Ninja LLC (https://purchase_orderninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntityCreatedObject;
use App\Notifications\Admin\EntitySentNotification;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\Queue\ShouldQueue;
class PurchaseOrderCreatedListener implements ShouldQueue
{
use UserNotifies;
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(PurchaseOrderWasCreated $event)
{
MultiDB::setDb($event->company->db);
$first_notification_sent = true;
$purchase_order = $event->purchase_order;
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer((new EntityCreatedObject($purchase_order, 'purchase_order'))->build());
$nmo->company = $purchase_order->company;
$nmo->settings = $purchase_order->company->settings;
/* We loop through each user and determine whether they need to be notified */
foreach ($event->company->company_users as $company_user) {
/* The User */
$user = $company_user->user;
if (! $user) {
continue;
}
/* This is only here to handle the alternate message channels - ie Slack */
// $notification = new EntitySentNotification($event->invitation, 'purchase_order');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_created', 'purchase_order_created_all']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
/* This prevents more than one notification being sent */
$first_notification_sent = false;
}
}
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* PurchaseOrder Ninja (https://purchase_orderninja.com).
*
* @link https://github.com/purchase_orderninja/purchase_orderninja source repository
*
* @copyright Copyright (c) 2022. PurchaseOrder Ninja LLC (https://purchase_orderninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntitySentObject;
use App\Notifications\Admin\EntitySentNotification;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\Queue\ShouldQueue;
class PurchaseOrderEmailedNotification implements ShouldQueue
{
use UserNotifies;
public $delay = 5;
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$first_notification_sent = true;
$purchase_order = $event->invitation->purchase_order->fresh();
$purchase_order->last_sent_date = now();
$purchase_order->saveQuietly();
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer((new EntitySentObject($event->invitation, 'purchase_order', 'purchase_order'))->build());
$nmo->company = $purchase_order->company;
$nmo->settings = $purchase_order->company->settings;
/* We loop through each user and determine whether they need to be notified */
foreach ($event->invitation->company->company_users as $company_user) {
/* The User */
$user = $company_user->user;
/* This is only here to handle the alternate message channels - ie Slack */
// $notification = new EntitySentNotification($event->invitation, 'purchase_order');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'purchase_order', ['all_notifications', 'purchase_order_sent', 'purchase_order_sent_all']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
/* This prevents more than one notification being sent */
$first_notification_sent = false;
}
/* Override the methods in the Notification Class */
// $notification->method = $methods;
// Notify on the alternate channels
// $user->notify($notification);
}
}
}

View File

@ -64,7 +64,7 @@ class AutoBillingFailureObject
/* Set customized translations _NOW_ */ /* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings)); $t->replace(Ninja::transformTranslations($this->company->settings));
$this->$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get(); $this->invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
$mail_obj = new stdClass; $mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount(); $mail_obj->amount = $this->getAmount();

View File

@ -13,6 +13,7 @@ namespace App\Mail\Admin;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Number; use App\Utils\Number;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use stdClass; use stdClass;
@ -38,7 +39,11 @@ class EntityCreatedObject
$this->entity = $entity; $this->entity = $entity;
} }
public function build() /**
* @return stdClass
* @throws BindingResolutionException
*/
public function build() :stdClass
{ {
App::forgetInstance('translator'); App::forgetInstance('translator');
/* Init a new copy of the translator*/ /* Init a new copy of the translator*/
@ -47,26 +52,64 @@ class EntityCreatedObject
App::setLocale($this->entity->company->getLocale()); App::setLocale($this->entity->company->getLocale());
/* Set customized translations _NOW_ */ /* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->entity->company->settings)); $t->replace(Ninja::transformTranslations($this->entity->company->settings));
$this->setTemplate();
$this->entity->load('client.country', 'client.company');
$this->client = $this->entity->client;
$this->company = $this->entity->company; $this->company = $this->entity->company;
$this->setTemplate(); if($this->entity_type == 'purchase_order')
{
$mail_obj = new stdClass; $this->entity->load('vendor.company');
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject(); $mail_obj = new stdClass;
$mail_obj->data = $this->getData(); $mail_obj->amount = Number::formatMoney($this->entity->amount, $this->entity->vendor);
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key; $mail_obj->subject = ctrans($this->template_subject,
[
'vendor' => $this->entity->vendor->present()->name(),
'purchase_order' => $this->entity->number,
]
);
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
$mail_obj->data = [
'title' => $mail_obj->subject,
'message' => ctrans($this->template_body,
[
'amount' => $mail_obj->amount,
'vendor' => $this->entity->vendor->present()->name(),
'purchase_order' => $this->entity->number,
]
),
'url' => $this->entity->invitations()->first()->getAdminLink(),
'button' => ctrans("texts.view_{$this->entity_type}"),
'signature' => $this->company->settings->email_signature,
'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false,
];
}
else {
$this->entity->load('client.country', 'client.company');
$this->client = $this->entity->client;
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->entity->company->company_key;
}
return $mail_obj; return $mail_obj;
} }
private function setTemplate() private function setTemplate()
{ {
// nlog($this->template);
switch ($this->entity_type) { switch ($this->entity_type) {
case 'invoice': case 'invoice':
@ -81,7 +124,10 @@ class EntityCreatedObject
$this->template_subject = 'texts.notification_credit_created_subject'; $this->template_subject = 'texts.notification_credit_created_subject';
$this->template_body = 'texts.notification_credit_created_body'; $this->template_body = 'texts.notification_credit_created_body';
break; break;
case 'purchase_order':
$this->template_subject = 'texts.notification_purchase_order_created_subject';
$this->template_body = 'texts.notification_purchase_order_created_body';
break;
default: default:
$this->template_subject = 'texts.notification_invoice_created_subject'; $this->template_subject = 'texts.notification_invoice_created_subject';
$this->template_body = 'texts.notification_invoice_created_body'; $this->template_body = 'texts.notification_invoice_created_body';

View File

@ -58,12 +58,47 @@ class EntitySentObject
$this->setTemplate(); $this->setTemplate();
$mail_obj = new stdClass; if($this->template == 'purchase_order')
$mail_obj->amount = $this->getAmount(); {
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData(); $mail_obj = new stdClass;
$mail_obj->markdown = 'email.admin.generic'; $mail_obj->amount = Number::formatMoney($this->entity->amount, $this->entity->vendor);
$mail_obj->tag = $this->company->company_key; $mail_obj->subject = ctrans($this->template_subject,
[
'vendor' => $this->contact->vendor->present()->name(),
'purchase_order' => $this->entity->number,
]
);
$mail_obj->data = [
'title' => $mail_obj->subject,
'message' => ctrans($this->template_body,
[
'amount' => $mail_obj->amount,
'vendor' => $this->contact->vendor->present()->name(),
'purchase_order' => $this->entity->number,
]
),
'url' => $this->invitation->getAdminLink(),
'button' => ctrans("texts.view_{$this->entity_type}"),
'signature' => $this->company->settings->email_signature,
'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false,
];
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
}
else {
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
}
return $mail_obj; return $mail_obj;
} }
@ -101,7 +136,10 @@ class EntitySentObject
$this->template_subject = 'texts.notification_credit_sent_subject'; $this->template_subject = 'texts.notification_credit_sent_subject';
$this->template_body = 'texts.notification_credit_sent'; $this->template_body = 'texts.notification_credit_sent';
break; break;
case 'purchase_order':
$this->template_subject = 'texts.notification_purchase_order_sent_subject';
$this->template_body = 'texts.notification_purchase_order_sent';
break;
default: default:
$this->template_subject = 'texts.notification_invoice_sent_subject'; $this->template_subject = 'texts.notification_invoice_sent_subject';
$this->template_body = 'texts.notification_invoice_sent'; $this->template_body = 'texts.notification_invoice_sent';

View File

@ -129,6 +129,7 @@ class Company extends BaseModel
'invoice_task_lock', 'invoice_task_lock',
'convert_payment_currency', 'convert_payment_currency',
'convert_expense_currency', 'convert_expense_currency',
'notify_vendor_when_paid',
]; ];
protected $hidden = [ protected $hidden = [
@ -138,6 +139,7 @@ class Company extends BaseModel
]; ];
protected $casts = [ protected $casts = [
'is_proforma' => 'bool',
'country_id' => 'string', 'country_id' => 'string',
'custom_fields' => 'object', 'custom_fields' => 'object',
'settings' => 'object', 'settings' => 'object',

View File

@ -117,6 +117,11 @@ class Vendor extends BaseModel
} }
public function timezone()
{
return $this->company->timezone();
}
public function company() public function company()
{ {
return $this->belongsTo(Company::class); return $this->belongsTo(Company::class);

View File

@ -325,15 +325,6 @@ class BaseDriver extends AbstractPaymentDriver
$invoice->service()->toggleFeesPaid()->save(); $invoice->service()->toggleFeesPaid()->save();
} }
$transaction = [
'invoice' => $invoice->transaction_event(),
'payment' => [],
'client' => $invoice->client->transaction_event(),
'credit' => [],
'metadata' => [],
];
// TransactionLog::dispatch(TransactionEvent::INVOICE_FEE_APPLIED, $transaction, $invoice->company->db);
}); });
} }

View File

@ -124,18 +124,20 @@ class CreditCard implements MethodInterface
} }
} catch (CheckoutApiException $e) { } catch (CheckoutApiException $e) {
// API error // API error
$request_id = $e->request_id; $request_id = $e->request_id ?: '';
$http_status_code = $e->http_status_code; $http_status_code = $e->http_status_code ?: '';
$error_details = $e->error_details; $error_details = $e->error_details;
if(is_array($error_details)) { if(is_array($error_details)) {
$error_details = end($e->error_details['error_codes']); $error_details = end($e->error_details['error_codes']);
} }
$human_exception = $error_details ? new \Exception($error_details, 400) : $e; $human_exception = $error_details ? $error_details : $e->getMessage();
$human_exception = "{$human_exception} - Request ID: {$request_id}";
throw new PaymentFailed($human_exception, $http_status_code);
throw new PaymentFailed($human_exception);
} catch (CheckoutArgumentException $e) { } catch (CheckoutArgumentException $e) {
// Bad arguments // Bad arguments
@ -145,9 +147,9 @@ class CreditCard implements MethodInterface
$error_details = end($e->error_details['error_codes']); $error_details = end($e->error_details['error_codes']);
} }
$human_exception = $error_details ? new \Exception($error_details, 400) : $e; $human_exception = $error_details ? $error_details : $e->getMessage();
throw new PaymentFailed($human_exception); throw new PaymentFailed($human_exception, 422);
} catch (CheckoutAuthorizationException $e) { } catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization // Bad Invalid authorization
@ -157,9 +159,9 @@ class CreditCard implements MethodInterface
$error_details = end($e->error_details['error_codes']); $error_details = end($e->error_details['error_codes']);
} }
$human_exception = $error_details ? new \Exception($error_details, 400) : $e; $human_exception = $error_details ? $error_details : $e->getMessage();
throw new PaymentFailed($human_exception); throw new PaymentFailed($human_exception, 401);
} }
} }
@ -228,7 +230,7 @@ class CreditCard implements MethodInterface
private function completePayment($paymentRequest, PaymentResponseRequest $request) private function completePayment($paymentRequest, PaymentResponseRequest $request)
{ {
$paymentRequest->amount = $this->checkout->payment_hash->data->value; $paymentRequest->amount = $this->checkout->payment_hash->data->value;
$paymentRequest->reference = $this->checkout->getDescription(); $paymentRequest->reference = substr($this->checkout->getDescription(),0 , 49);
$paymentRequest->customer = $this->checkout->getCustomer(); $paymentRequest->customer = $this->checkout->getCustomer();
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja']; $paymentRequest->metadata = ['udf1' => 'Invoice Ninja'];
$paymentRequest->currency = $this->checkout->client->getCurrencyCode(); $paymentRequest->currency = $this->checkout->client->getCurrencyCode();

View File

@ -87,6 +87,9 @@ trait Utilities
$error_message = ''; $error_message = '';
nlog("checkout failure");
nlog($_payment);
if (is_array($_payment) && array_key_exists('actions', $_payment) && array_key_exists('response_summary', end($_payment['actions']))) { if (is_array($_payment) && array_key_exists('actions', $_payment) && array_key_exists('response_summary', end($_payment['actions']))) {
$error_message = end($_payment['actions'])['response_summary']; $error_message = end($_payment['actions'])['response_summary'];
} elseif (is_array($_payment) && array_key_exists('status', $_payment)) { } elseif (is_array($_payment) && array_key_exists('status', $_payment)) {

View File

@ -627,18 +627,16 @@ class StripePaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request) public function processWebhookRequest(PaymentWebhookRequest $request)
{ {
// Allow app to catch up with webhook request.
sleep(2);
//payment_intent.succeeded - this will confirm or cancel the payment //payment_intent.succeeded - this will confirm or cancel the payment
if ($request->type === 'payment_intent.succeeded') { if ($request->type === 'payment_intent.succeeded') {
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(2, 10))); PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));
return response()->json([], 200); return response()->json([], 200);
} }
if (in_array($request->type, ['payment_intent.payment_failed', 'charge.failed'])) { if (in_array($request->type, ['payment_intent.payment_failed', 'charge.failed'])) {
PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(2, 10))); PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));
return response()->json([], 200); return response()->json([], 200);
} }

View File

@ -157,15 +157,14 @@ use App\Listeners\Credit\CreditRestoredActivity;
use App\Listeners\Credit\CreditViewedActivity; use App\Listeners\Credit\CreditViewedActivity;
use App\Listeners\Document\DeleteCompanyDocuments; use App\Listeners\Document\DeleteCompanyDocuments;
use App\Listeners\Invoice\CreateInvoiceActivity; use App\Listeners\Invoice\CreateInvoiceActivity;
use App\Listeners\Invoice\CreateInvoiceHtmlBackup;
use App\Listeners\Invoice\CreateInvoicePdf; use App\Listeners\Invoice\CreateInvoicePdf;
use App\Listeners\Invoice\InvoiceArchivedActivity; use App\Listeners\Invoice\InvoiceArchivedActivity;
use App\Listeners\Invoice\InvoiceCancelledActivity; use App\Listeners\Invoice\InvoiceCancelledActivity;
use App\Listeners\Invoice\InvoiceCreatedNotification; use App\Listeners\Invoice\InvoiceCreatedNotification;
use App\Listeners\Invoice\InvoiceDeletedActivity; use App\Listeners\Invoice\InvoiceDeletedActivity;
use App\Listeners\Invoice\InvoiceEmailActivity; use App\Listeners\Invoice\InvoiceEmailActivity;
use App\Listeners\Invoice\InvoiceEmailedNotification;
use App\Listeners\Invoice\InvoiceEmailFailedActivity; use App\Listeners\Invoice\InvoiceEmailFailedActivity;
use App\Listeners\Invoice\InvoiceEmailedNotification;
use App\Listeners\Invoice\InvoiceFailedEmailNotification; use App\Listeners\Invoice\InvoiceFailedEmailNotification;
use App\Listeners\Invoice\InvoicePaidActivity; use App\Listeners\Invoice\InvoicePaidActivity;
use App\Listeners\Invoice\InvoiceReminderEmailActivity; use App\Listeners\Invoice\InvoiceReminderEmailActivity;
@ -175,18 +174,21 @@ use App\Listeners\Invoice\InvoiceViewedActivity;
use App\Listeners\Invoice\UpdateInvoiceActivity; use App\Listeners\Invoice\UpdateInvoiceActivity;
use App\Listeners\Mail\MailSentListener; use App\Listeners\Mail\MailSentListener;
use App\Listeners\Misc\InvitationViewedListener; use App\Listeners\Misc\InvitationViewedListener;
use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Payment\PaymentEmailFailureActivity; use App\Listeners\Payment\PaymentEmailFailureActivity;
use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentNotification;
use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\Payment\PaymentRestoredActivity;
use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedNotification; use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener;
use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener;
use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification;
use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity; use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderViewedNotification;
use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity; use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity;
use App\Listeners\Quote\QuoteApprovedActivity; use App\Listeners\Quote\QuoteApprovedActivity;
use App\Listeners\Quote\QuoteApprovedNotification; use App\Listeners\Quote\QuoteApprovedNotification;
@ -219,8 +221,8 @@ use App\Listeners\User\ArchivedUserActivity;
use App\Listeners\User\CreatedUserActivity; use App\Listeners\User\CreatedUserActivity;
use App\Listeners\User\DeletedUserActivity; use App\Listeners\User\DeletedUserActivity;
use App\Listeners\User\RestoredUserActivity; use App\Listeners\User\RestoredUserActivity;
use App\Listeners\User\UpdatedUserActivity;
use App\Listeners\User\UpdateUserLastLogin; use App\Listeners\User\UpdateUserLastLogin;
use App\Listeners\User\UpdatedUserActivity;
use App\Models\Account; use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\Company; use App\Models\Company;
@ -398,7 +400,6 @@ class EventServiceProvider extends ServiceProvider
], ],
//Invoices //Invoices
InvoiceWasMarkedSent::class => [ InvoiceWasMarkedSent::class => [
CreateInvoiceHtmlBackup::class,
], ],
InvoiceWasUpdated::class => [ InvoiceWasUpdated::class => [
UpdateInvoiceActivity::class, UpdateInvoiceActivity::class,
@ -458,12 +459,14 @@ class EventServiceProvider extends ServiceProvider
], ],
PurchaseOrderWasCreated::class => [ PurchaseOrderWasCreated::class => [
CreatePurchaseOrderActivity::class, CreatePurchaseOrderActivity::class,
PurchaseOrderCreatedListener::class,
], ],
PurchaseOrderWasDeleted::class => [ PurchaseOrderWasDeleted::class => [
PurchaseOrderDeletedActivity::class, PurchaseOrderDeletedActivity::class,
], ],
PurchaseOrderWasEmailed::class => [ PurchaseOrderWasEmailed::class => [
PurchaseOrderEmailActivity::class, PurchaseOrderEmailActivity::class,
PurchaseOrderEmailedNotification::class,
], ],
PurchaseOrderWasRestored::class => [ PurchaseOrderWasRestored::class => [
PurchaseOrderRestoredActivity::class, PurchaseOrderRestoredActivity::class,
@ -475,8 +478,8 @@ class EventServiceProvider extends ServiceProvider
PurchaseOrderViewedActivity::class, PurchaseOrderViewedActivity::class,
], ],
PurchaseOrderWasAccepted::class => [ PurchaseOrderWasAccepted::class => [
PurchaseOrderAcceptedListener::class,
PurchaseOrderAcceptedActivity::class, PurchaseOrderAcceptedActivity::class,
PurchaseOrderAcceptedNotification::class,
], ],
CompanyDocumentsDeleted::class => [ CompanyDocumentsDeleted::class => [
DeleteCompanyDocuments::class, DeleteCompanyDocuments::class,

View File

@ -21,6 +21,7 @@ use App\Models\Invoice;
use App\Services\Bank\BankService; use App\Services\Bank\BankService;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
@ -29,7 +30,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class BankMatchingService implements ShouldQueue class BankMatchingService implements ShouldQueue, ShouldBeUnique
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@ -37,13 +38,10 @@ class BankMatchingService implements ShouldQueue
protected $db; protected $db;
protected $middleware_key;
public function __construct($company_id, $db) public function __construct($company_id, $db)
{ {
$this->company_id = $company_id; $this->company_id = $company_id;
$this->db = $db; $this->db = $db;
$this->middleware_key = "bank_match_rate:{$this->company_id}";
} }
public function handle() :void public function handle() :void
@ -62,8 +60,14 @@ class BankMatchingService implements ShouldQueue
} }
public function middleware() /**
* The unique ID of the job.
*
* @return string
*/
public function uniqueId()
{ {
return [new WithoutOverlapping($this->middleware_key)]; return (string)$this->company_id;
} }
} }

View File

@ -48,7 +48,7 @@ class InstantPayment
public function run() public function run()
{ {
nlog($this->request->all());
$is_credit_payment = false; $is_credit_payment = false;
$tokens = []; $tokens = [];
@ -221,6 +221,9 @@ class InstantPayment
if ($this->request->query('hash')) { if ($this->request->query('hash')) {
$hash_data['billing_context'] = Cache::get($this->request->query('hash')); $hash_data['billing_context'] = Cache::get($this->request->query('hash'));
} }
elseif($this->request->hash){
$hash_data['billing_context'] = Cache::get($this->request->hash);
}
$payment_hash = new PaymentHash; $payment_hash = new PaymentHash;
$payment_hash->hash = Str::random(32); $payment_hash->hash = Str::random(32);

View File

@ -82,9 +82,6 @@ class MarkInvoiceDeleted extends AbstractService
{ {
//if total payments = adjustment amount - that means we need to delete the payments as well. //if total payments = adjustment amount - that means we need to delete the payments as well.
nlog($this->adjustment_amount);
nlog($this->total_payments);
if ($this->adjustment_amount == $this->total_payments) if ($this->adjustment_amount == $this->total_payments)
$this->invoice->payments()->update(['payments.deleted_at' => now(), 'payments.is_deleted' => true]); $this->invoice->payments()->update(['payments.deleted_at' => now(), 'payments.is_deleted' => true]);

View File

@ -140,6 +140,39 @@ class PaymentService
return $this; return $this;
} }
public function applyCreditsToInvoice($invoice)
{
$amount = $invoice->amount;
$credits = $invoice->client
->service()
->getCredits();
foreach ($credits as $credit) {
//starting invoice balance
$invoice_balance = $invoice->balance;
//credit payment applied
$credit->service()->applyPayment($invoice, $amount, $this->payment);
//amount paid from invoice calculated
$remaining_balance = ($invoice_balance - $invoice->fresh()->balance);
//reduce the amount to be paid on the invoice from the NEXT credit
$amount -= $remaining_balance;
//break if the invoice is no longer PAYABLE OR there is no more amount to be applied
if (! $invoice->isPayable() || (int) $amount == 0) {
break;
}
}
return $this;
}
public function save() public function save()
{ {
$this->payment->saveQuietly(); $this->payment->saveQuietly();

View File

@ -80,11 +80,19 @@ class UpdateInvoicePayment
->clearPartial() ->clearPartial()
->updateStatus() ->updateStatus()
->touchPdf() ->touchPdf()
->workFlow()
->save(); ->save();
$invoice->service() if($invoice->is_proforma)
->workFlow() {
->save(); $invoice->number = '';
$invoice->is_proforma = false;
$invoice->service()
->applyNumber()
->save();
}
/* Updates the company ledger */ /* Updates the company ledger */
$this->payment $this->payment
@ -101,17 +109,6 @@ class UpdateInvoicePayment
$this->payment->applied += $paid_amount; $this->payment->applied += $paid_amount;
$transaction = [
'invoice' => $invoice->transaction_event(),
'payment' => $this->payment->transaction_event(),
'client' => $client->transaction_event(),
'credit' => [],
'metadata' => [],
];
// TransactionLog::dispatch(TransactionEvent::GATEWAY_PAYMENT_MADE, $transaction, $invoice->company->db);
}); });
/* Remove the event updater from within the loop to prevent race conditions */ /* Remove the event updater from within the loop to prevent race conditions */

View File

@ -40,7 +40,7 @@ class MarkSent
->service() ->service()
->setStatus(PurchaseOrder::STATUS_SENT) ->setStatus(PurchaseOrder::STATUS_SENT)
->applyNumber() ->applyNumber()
// ->adjustBalance($this->purchase_order->amount) ->adjustBalance($this->purchase_order->amount) //why was this commented out previously?
// ->touchPdf() // ->touchPdf()
->save(); ->save();

View File

@ -39,6 +39,7 @@ class PurchaseOrderExpense
$expense->uses_inclusive_taxes = $this->purchase_order->uses_inclusive_taxes; $expense->uses_inclusive_taxes = $this->purchase_order->uses_inclusive_taxes;
$expense->calculate_tax_by_amount = true; $expense->calculate_tax_by_amount = true;
$expense->private_notes = ctrans('texts.purchase_order_number_short') . " " . $this->purchase_order->number; $expense->private_notes = ctrans('texts.purchase_order_number_short') . " " . $this->purchase_order->number;
$expense->currency_id = $this->purchase_order->vendor->currency_id;
$line_items = $this->purchase_order->line_items; $line_items = $this->purchase_order->line_items;

View File

@ -97,6 +97,13 @@ class PurchaseOrderService
return $this; return $this;
} }
public function adjustBalance($adjustment)
{
$this->purchase_order->balance += $adjustment;
return $this;
}
public function touchPdf($force = false) public function touchPdf($force = false)
{ {
try { try {

View File

@ -15,6 +15,7 @@ use App\DataMapper\InvoiceItem;
use App\Factory\CreditFactory; use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Factory\InvoiceToRecurringInvoiceFactory; use App\Factory\InvoiceToRecurringInvoiceFactory;
use App\Factory\PaymentFactory;
use App\Factory\RecurringInvoiceFactory; use App\Factory\RecurringInvoiceFactory;
use App\Jobs\Mail\NinjaMailer; use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerJob;
@ -28,6 +29,7 @@ use App\Models\ClientContact;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\PaymentHash; use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\Product; use App\Models\Product;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\Subscription; use App\Models\Subscription;
@ -89,11 +91,18 @@ class SubscriptionService
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
$recurring_invoice->auto_bill = $this->subscription->auto_bill; $recurring_invoice->auto_bill = $this->subscription->auto_bill;
/* Start the recurring service */ /* Start the recurring service */
$recurring_invoice->service() $recurring_invoice->service()
->start() ->start()
->save(); ->save();
//update the invoice and attach to the recurring invoice!!!!!
$invoice = Invoice::find($payment_hash->fee_invoice_id);
$invoice->recurring_id = $recurring_invoice->id;
$invoice->is_proforma = false;
$invoice->save();
//execute any webhooks //execute any webhooks
$context = [ $context = [
'context' => 'recurring_purchase', 'context' => 'recurring_purchase',
@ -101,7 +110,7 @@ class SubscriptionService
'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id), 'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id),
'client' => $recurring_invoice->client->hashed_id, 'client' => $recurring_invoice->client->hashed_id,
'subscription' => $this->subscription->hashed_id, 'subscription' => $this->subscription->hashed_id,
'contact' => auth()->guard('contact')->user() ? auth()->guard('contact')->user()->hashed_id : $recurring_invoice->client->contacts()->first()->hashed_id, 'contact' => auth()->guard('contact')->user() ? auth()->guard('contact')->user()->hashed_id : $recurring_invoice->client->contacts()->whereNotNull('email')->first()->hashed_id,
'account_key' => $recurring_invoice->client->custom_value2, 'account_key' => $recurring_invoice->client->custom_value2,
]; ];
@ -217,23 +226,70 @@ class SubscriptionService
* *
* @return float * @return float
*/ */
public function calculateUpgradePriceV2(RecurringInvoice $recurring_invoice, Subscription $target) :?float
{
$outstanding_credit = 0;
$use_credit_setting = $recurring_invoice->client->getSetting('use_credits_payment');
$last_invoice = Invoice::query()
->where('recurring_id', $recurring_invoice->id)
->where('is_deleted', 0)
->where('status_id', Invoice::STATUS_PAID)
->first();
$refund = $this->calculateProRataRefundForSubscription($last_invoice);
if($use_credit_setting != 'off')
{
$outstanding_credit = Credit::query()
->where('client_id', $recurring_invoice->client_id)
->whereIn('status_id', [Credit::STATUS_SENT,Credit::STATUS_PARTIAL])
->where('is_deleted', 0)
->where('balance', '>', 0)
->sum('balance');
}
nlog("{$target->price} - {$refund} - {$outstanding_credit}");
return $target->price - $refund - $outstanding_credit;
}
/**
* Returns an upgrade price when moving between plans
*
* However we only allow people to move between plans
* if their account is in good standing.
*
* @param RecurringInvoice $recurring_invoice
* @param Subscription $target
* @deprecated in favour of calculateUpgradePriceV2
* @return float
*/
public function calculateUpgradePrice(RecurringInvoice $recurring_invoice, Subscription $target) :?float public function calculateUpgradePrice(RecurringInvoice $recurring_invoice, Subscription $target) :?float
{ {
//calculate based on daily prices
//calculate based on daily prices
$current_amount = $recurring_invoice->amount; $current_amount = $recurring_invoice->amount;
$currency_frequency = $recurring_invoice->frequency_id; $currency_frequency = $recurring_invoice->frequency_id;
$outstanding = $recurring_invoice->invoices() $outstanding = Invoice::query()
->where('is_deleted', 0) ->where('recurring_id', $recurring_invoice->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('is_deleted', 0)
->where('balance', '>', 0); ->where('is_proforma',0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0);
$outstanding_amounts = $outstanding->sum('balance'); $outstanding_amounts = $outstanding->sum('balance');
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id) $outstanding_invoice = Invoice::where('client_id', $recurring_invoice->client_id)
->where('client_id', $recurring_invoice->client_id)
->where('is_deleted', 0) ->where('is_deleted', 0)
->where('is_proforma',0)
->where('subscription_id', $this->subscription->id)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->first(); ->first();
@ -242,6 +298,7 @@ class SubscriptionService
$outstanding_invoice = Credit::where('subscription_id', $this->subscription->id) $outstanding_invoice = Credit::where('subscription_id', $this->subscription->id)
->where('client_id', $recurring_invoice->client_id) ->where('client_id', $recurring_invoice->client_id)
->where('is_proforma',0)
->where('is_deleted', 0) ->where('is_deleted', 0)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->first(); ->first();
@ -289,13 +346,9 @@ class SubscriptionService
$days_in_frequency = $this->getDaysInFrequency(); $days_in_frequency = $this->getDaysInFrequency();
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $this->subscription->price ,2); $pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}"); return max(0, $pro_rata_refund);
// nlog("invoice amount = {$invoice->amount}");
// nlog("pro rata refund = {$pro_rata_refund}");
return $pro_rata_refund;
} }
@ -323,10 +376,6 @@ class SubscriptionService
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2); $pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
// nlog("invoice amount = {$invoice->amount}");
// nlog("pro rata refund = {$pro_rata_refund}");
return $pro_rata_refund; return $pro_rata_refund;
} }
@ -353,7 +402,6 @@ class SubscriptionService
$days_of_subscription_used = $start_date->diffInDays($current_date); $days_of_subscription_used = $start_date->diffInDays($current_date);
// $days_in_frequency = $this->getDaysInFrequency();
$days_in_frequency = $invoice->subscription->service()->getDaysInFrequency(); $days_in_frequency = $invoice->subscription->service()->getDaysInFrequency();
$ratio = ($days_in_frequency - $days_of_subscription_used)/$days_in_frequency; $ratio = ($days_in_frequency - $days_of_subscription_used)/$days_in_frequency;
@ -406,10 +454,76 @@ class SubscriptionService
return $pro_rata_charge; return $pro_rata_charge;
} }
/**
* This entry point assumes the user does not have to make a
* payment for the service.
*
* In this case, we generate a credit note for the old service
* Generate a new invoice for the new service
* Apply credits to the invoice
*
* @param array $data
*/
public function createChangePlanCreditV2($data)
{
/* Init vars */
$recurring_invoice = $data['recurring_invoice'];
$old_subscription = $data['subscription'];
$target_subscription = $data['target'];
$pro_rata_charge_amount = 0;
$pro_rata_refund_amount = 0;
$is_credit = false;
$credit = false;
/* Get last invoice */
$last_invoice = Invoice::where('subscription_id', $recurring_invoice->subscription_id)
->where('client_id', $recurring_invoice->client_id)
->where('is_proforma',0)
->where('is_deleted', 0)
->where('status_id', Invoice::STATUS_PAID)
->withTrashed()
->orderBy('id', 'desc')
->first();
if($this->calculateProRataRefundForSubscription($last_invoice) > 0)
$credit = $this->createCredit($last_invoice, $target_subscription, false);
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
$invoice = $this->changePlanInvoice($target_subscription, $recurring_invoice->client_id);
$invoice->recurring_id = $new_recurring_invoice->id;
$invoice->save();
$payment = PaymentFactory::create($invoice->company_id, $invoice->user_id, $invoice->client_id);
$payment->type_id = PaymentType::CREDIT;
$payment->client_id = $invoice->client_id;
$payment->is_manual = true;
$payment->save();
$payment->service()->applyCreditsToInvoice($invoice);
$context = [
'context' => 'change_plan',
'recurring_invoice' => $new_recurring_invoice->hashed_id,
'credit' => $credit ? $credit->hashed_id : null,
'client' => $new_recurring_invoice->client->hashed_id,
'subscription' => $target_subscription->hashed_id,
'contact' => auth()->guard('contact')->user()->hashed_id,
'account_key' => $new_recurring_invoice->client->custom_value2,
];
$response = $this->triggerWebhook($context);
return '/client/recurring_invoices/'.$new_recurring_invoice->hashed_id;
}
/** /**
* When downgrading, we may need to create * When downgrading, we may need to create
* a credit * a credit
* *
* @deprecated in favour of createChangePlanCreditV2
* @param array $data * @param array $data
*/ */
public function createChangePlanCredit($data) public function createChangePlanCredit($data)
@ -663,10 +777,10 @@ class SubscriptionService
$credit = CreditFactory::create($this->subscription->company_id, $this->subscription->user_id); $credit = CreditFactory::create($this->subscription->company_id, $this->subscription->user_id);
$credit->date = now()->format('Y-m-d'); $credit->date = now()->format('Y-m-d');
$credit->subscription_id = $this->subscription->id; $credit->subscription_id = $this->subscription->id;
$credit->discount = $last_invoice->discount;
$credit->is_amount_discount = $last_invoice->is_amount_discount;
$line_items = $subscription_repo->generateLineItems($target, false, true); $credit->line_items = $this->calculateProRataRefundItems($last_invoice, true);
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, $last_invoice_is_credit));
$data = [ $data = [
'client_id' => $last_invoice->client_id, 'client_id' => $last_invoice->client_id,
@ -696,6 +810,7 @@ class SubscriptionService
$invoice->subscription_id = $target->id; $invoice->subscription_id = $target->id;
$invoice->line_items = array_merge($subscription_repo->generateLineItems($target), $this->calculateProRataRefundItems($last_invoice)); $invoice->line_items = array_merge($subscription_repo->generateLineItems($target), $this->calculateProRataRefundItems($last_invoice));
$invoice->is_proforma = true;
$data = [ $data = [
'client_id' => $client_id, 'client_id' => $client_id,
@ -711,6 +826,40 @@ class SubscriptionService
} }
/**
* When changing plans we need to generate a pro rata
* invoice which takes into account any credits.
*
* @param Subscription $target
* @return Invoice
*/
private function changePlanInvoice($target, $client_id)
{
$subscription_repo = new SubscriptionRepository();
$invoice_repo = new InvoiceRepository();
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
$invoice->date = now()->format('Y-m-d');
$invoice->subscription_id = $target->id;
$invoice->line_items = $subscription_repo->generateLineItems($target);
$invoice->is_proforma = true;
$data = [
'client_id' => $client_id,
'quantity' => 1,
'date' => now()->format('Y-m-d'),
];
return $invoice_repo->save($data, $invoice)
->service()
->markSent()
->fillDefaults()
->save();
}
public function createInvoiceV2($bundle, $client_id, $valid_coupon = false) public function createInvoiceV2($bundle, $client_id, $valid_coupon = false)
{ {
@ -720,7 +869,8 @@ class SubscriptionService
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id); $invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
$invoice->subscription_id = $this->subscription->id; $invoice->subscription_id = $this->subscription->id;
$invoice->client_id = $client_id; $invoice->client_id = $client_id;
$invoice->is_proforma = true;
$invoice->number = ctrans('texts.subscription') . "_" . now()->format('Y-m-d') . "_" . rand(0,100000);
$line_items = $bundle->map(function ($item){ $line_items = $bundle->map(function ($item){
$line_item = new InvoiceItem; $line_item = new InvoiceItem;
@ -760,6 +910,7 @@ class SubscriptionService
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id); $invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
$invoice->line_items = $subscription_repo->generateLineItems($this->subscription); $invoice->line_items = $subscription_repo->generateLineItems($this->subscription);
$invoice->subscription_id = $this->subscription->id; $invoice->subscription_id = $this->subscription->id;
$invoice->is_proforman = true;
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0) if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
{ {
@ -771,7 +922,6 @@ class SubscriptionService
$invoice->is_amount_discount = $this->subscription->is_amount_discount; $invoice->is_amount_discount = $this->subscription->is_amount_discount;
} }
return $invoice_repo->save($data, $invoice); return $invoice_repo->save($data, $invoice);
} }
@ -860,14 +1010,11 @@ class SubscriptionService
*/ */
public function triggerWebhook($context) public function triggerWebhook($context)
{ {
nlog("trigger webhook");
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) { if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
return ["message" => "Success", "status_code" => 200]; return ["message" => "Success", "status_code" => 200];
} }
nlog("past first if");
$response = false; $response = false;
$body = array_merge($context, [ $body = array_merge($context, [
@ -876,8 +1023,6 @@ class SubscriptionService
$response = $this->sendLoad($this->subscription, $body); $response = $this->sendLoad($this->subscription, $body);
nlog("after response");
/* Append the response to the system logger body */ /* Append the response to the system logger body */
if(is_array($response)){ if(is_array($response)){
@ -1098,8 +1243,6 @@ class SubscriptionService
}); });
return $this->handleRedirect('client/subscriptions'); return $this->handleRedirect('client/subscriptions');
} }

View File

@ -193,6 +193,7 @@ class CompanyTransformer extends EntityTransformer
'invoice_task_lock' => (bool) $company->invoice_task_lock, 'invoice_task_lock' => (bool) $company->invoice_task_lock,
'convert_payment_currency' => (bool) $company->convert_payment_currency, 'convert_payment_currency' => (bool) $company->convert_payment_currency,
'convert_expense_currency' => (bool) $company->convert_expense_currency, 'convert_expense_currency' => (bool) $company->convert_expense_currency,
'notify_vendor_when_paid' => (bool) $company->notify_vendor_when_paid,
]; ];
} }

View File

@ -15,6 +15,7 @@ use App\Models\Document;
use App\Models\RecurringExpense; use App\Models\RecurringExpense;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use League\Fractal\Resource\Item;
/** /**
* class RecurringExpenseTransformer. * class RecurringExpenseTransformer.
@ -33,6 +34,8 @@ class RecurringExpenseTransformer extends EntityTransformer
*/ */
protected $availableIncludes = [ protected $availableIncludes = [
'documents', 'documents',
'client',
'vendor',
]; ];
public function includeDocuments(RecurringExpense $recurring_expense) public function includeDocuments(RecurringExpense $recurring_expense)
@ -42,6 +45,28 @@ class RecurringExpenseTransformer extends EntityTransformer
return $this->includeCollection($recurring_expense->documents, $transformer, Document::class); return $this->includeCollection($recurring_expense->documents, $transformer, Document::class);
} }
public function includeClient(RecurringExpense $recurring_expense): ?Item
{
$transformer = new ClientTransformer($this->serializer);
if (!$recurring_expense->client) {
return null;
}
return $this->includeItem($recurring_expense->client, $transformer, Client::class);
}
public function includeVendor(RecurringExpense $recurring_expense): ?Item
{
$transformer = new VendorTransformer($this->serializer);
if (!$recurring_expense->vendor) {
return null;
}
return $this->includeItem($recurring_expense->vendor, $transformer, Vendor::class);
}
/** /**
* @param RecurringExpense $recurring_expense * @param RecurringExpense $recurring_expense
* *

View File

@ -129,12 +129,12 @@ class Helpers
if(!$string_hit) if(!$string_hit)
return $value; return $value;
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expensive process
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expensive process
Carbon::setLocale($entity->locale()); Carbon::setLocale($entity->locale());
if (!$currentDateTime) { if (!$currentDateTime) {
$currentDateTime = Carbon::now(); $currentDateTime = Carbon::now()->timezone($entity->timezone()->name);
} }
$replacements = [ $replacements = [

View File

@ -66,7 +66,8 @@ trait GeneratesCounter
$counter = 1; $counter = 1;
} }
$counter_entity = $client->group_settings; // $counter_entity = $client->group_settings;
$counter_entity = $client->group_settings ?: $client->company;
} else { } else {
$counter = $client->company->settings->{$counter_string}; $counter = $client->company->settings->{$counter_string};
$counter_entity = $client->company; $counter_entity = $client->company;

View File

@ -25,7 +25,7 @@ class PDF extends FPDI
$this->SetTextColor(135, 135, 135); $this->SetTextColor(135, 135, 135);
$trans = ctrans('texts.pdf_page_info', ['current' => $this->PageNo(), 'total' => '{nb}']); $trans = ctrans('texts.pdf_page_info', ['current' => $this->PageNo(), 'total' => '{nb}']);
$trans = iconv('UTF-8', 'ISO-8859-7', $trans); // $trans = iconv('UTF-8', 'ISO-8859-7', $trans);
$this->Cell(0, 5, $trans, 0, 0, $this->text_alignment); $this->Cell(0, 5, $trans, 0, 0, $this->text_alignment);
} }

View File

@ -52,7 +52,7 @@ trait SettingsSaver
continue; continue;
} }
/*Separate loop if it is a _id field which is an integer cast as a string*/ /*Separate loop if it is a _id field which is an integer cast as a string*/
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && property_exists($settings, $key) && strlen($settings->{$key}) >= 1)) { elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && property_exists($settings, $key) && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && property_exists($settings, $key) && strlen($settings->{$key}) >= 1)) {
$value = 'integer'; $value = 'integer';
if($key == 'gmail_sending_user_id' || $key == 'besr_id') if($key == 'gmail_sending_user_id' || $key == 'besr_id')

View File

@ -12,6 +12,8 @@
namespace App\Utils\Traits; namespace App\Utils\Traits;
use GuzzleHttp\RequestOptions; use GuzzleHttp\RequestOptions;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Message;
/** /**
* Class SubscriptionHooker. * Class SubscriptionHooker.
@ -34,10 +36,6 @@ trait SubscriptionHooker
'headers' => $headers, 'headers' => $headers,
]); ]);
nlog('method name must be a string');
nlog($subscription->webhook_configuration['post_purchase_rest_method']);
nlog($subscription->webhook_configuration['post_purchase_url']);
$post_purchase_rest_method = (string) $subscription->webhook_configuration['post_purchase_rest_method']; $post_purchase_rest_method = (string) $subscription->webhook_configuration['post_purchase_rest_method'];
$post_purchase_url = (string) $subscription->webhook_configuration['post_purchase_url']; $post_purchase_url = (string) $subscription->webhook_configuration['post_purchase_url'];
@ -47,7 +45,18 @@ trait SubscriptionHooker
]); ]);
return array_merge($body, json_decode($response->getBody(), true)); return array_merge($body, json_decode($response->getBody(), true));
} catch (\Exception $e) { } catch (ClientException $e) {
$message = $e->getMessage();
$error = json_decode($e->getResponse()->getBody()->getContents());
if(property_exists($error, 'message'))
$message = $error->message;
return array_merge($body, ['message' => $message, 'status_code' => 500]);
}
catch (\Exception $e) {
return array_merge($body, ['message' => $e->getMessage(), 'status_code' => 500]); return array_merge($body, ['message' => $e->getMessage(), 'status_code' => 500]);
} }
} }

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.49', 'app_version' => '5.5.50',
'app_tag' => '5.5.49', 'app_tag' => '5.5.50',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),
@ -190,7 +190,8 @@ return [
'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null), 'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null),
'ninja_default_company_id' => env('NINJA_COMPANY_ID', null), 'ninja_default_company_id' => env('NINJA_COMPANY_ID', null),
'ninja_default_company_gateway_id' => env('NINJA_COMPANY_GATEWAY_ID', null), 'ninja_default_company_gateway_id' => env('NINJA_COMPANY_GATEWAY_ID', null),
'ninja_hosted_secret' => env('NINJA_HOSTED_SECRET', null), 'ninja_hosted_secret' => env('NINJA_HOSTED_SECRET', ''),
'ninja_hosted_header' =>env('NINJA_HEADER',''),
'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true), 'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true),
'ninja_apple_api_key' => env('APPLE_API_KEY', false), 'ninja_apple_api_key' => env('APPLE_API_KEY', false),
'ninja_apple_private_key' => env('APPLE_PRIVATE_KEY', false), 'ninja_apple_private_key' => env('APPLE_PRIVATE_KEY', false),

View File

@ -29,14 +29,8 @@ class ProductFactory extends Factory
'cost' => $this->faker->numberBetween(1, 1000), 'cost' => $this->faker->numberBetween(1, 1000),
'price' => $this->faker->numberBetween(1, 1000), 'price' => $this->faker->numberBetween(1, 1000),
'quantity' => $this->faker->numberBetween(1, 100), 'quantity' => $this->faker->numberBetween(1, 100),
// 'tax_name1' => 'GST', 'custom_value1' => 'https://picsum.photos/200',
// 'tax_rate1' => 10, 'custom_value2' => rand(0,100),
// 'tax_name2' => 'VAT',
// 'tax_rate2' => 17.5,
// 'tax_name3' => 'THIRDTAX',
// 'tax_rate3' => 5,
'custom_value1' => $this->faker->text(20),
'custom_value2' => $this->faker->text(20),
'custom_value3' => $this->faker->text(20), 'custom_value3' => $this->faker->text(20),
'custom_value4' => $this->faker->text(20), 'custom_value4' => $this->faker->text(20),
'is_deleted' => false, 'is_deleted' => false,

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table)
{
$table->boolean('notify_vendor_when_paid')->default(false);
});
Schema::table('invoices', function (Blueprint $table)
{
$table->boolean('is_proforma')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

View File

@ -4301,7 +4301,7 @@ $LANG = array(
'becs_mandate' => 'By providing your bank account details, you agree to this <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.', 'becs_mandate' => 'By providing your bank account details, you agree to this <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.',
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.', 'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
'direct_debit' => 'Direct Debit', 'direct_debit' => 'Direct Debit',
'clone_to_expense' => 'Clone to expense', 'clone_to_expense' => 'Clone to Expense',
'checkout' => 'Checkout', 'checkout' => 'Checkout',
'acss' => 'Pre-authorized debit payments', 'acss' => 'Pre-authorized debit payments',
'invalid_amount' => 'Invalid amount. Number/Decimal values only.', 'invalid_amount' => 'Invalid amount. Number/Decimal values only.',
@ -4906,7 +4906,12 @@ $LANG = array(
'backup_restore' => 'Backup | Restore', 'backup_restore' => 'Backup | Restore',
'export_company' => 'Create company backup', 'export_company' => 'Create company backup',
'backup' => 'Backup', 'backup' => 'Backup',
'notification_purchase_order_created_body' => 'The following purchase_order :purchase_order was created for vendor :vendor for :amount.',
'notification_purchase_order_created_subject' => 'Purchase Order :purchase_order was created for :vendor',
'notification_purchase_order_sent_subject' => 'Purchase Order :purchase_order was sent to :vendor',
'notification_purchase_order_sent' => 'The following vendor :vendor was emailed Purchase Order :purchase_order for :amount.',
'subscription_blocked' => 'This product is a restricted item, please contact the vendor for further information.',
'subscription_blocked_title' => 'Product not available.',
); );
return $LANG; return $LANG;

View File

@ -2,7 +2,7 @@
@section('meta_title', ctrans('texts.purchase')) @section('meta_title', ctrans('texts.purchase'))
@section('body') @section('body')
@livewire('billing-portal-purchase', ['subscription' => $subscription, 'company' => $subscription->company, 'contact' => auth()->guard('contact')->user(), 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null]) @livewire('billing-portal-purchase', ['subscription' => $subscription->id, 'db' => $subscription->company->db, 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null])
@stop @stop
@push('footer') @push('footer')

View File

@ -2,7 +2,7 @@
@section('meta_title', ctrans('texts.purchase')) @section('meta_title', ctrans('texts.purchase'))
@section('body') @section('body')
@livewire('billing-portal-purchasev2', ['subscription' => $subscription, 'company' => $subscription->company, 'contact' => auth()->guard('contact')->user(), 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null]) @livewire('billing-portal-purchasev2', ['subscription' => $subscription->id, 'db' => $subscription->company->db, 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null])
@stop @stop
@push('footer') @push('footer')

View File

@ -5,13 +5,21 @@
<div class="grid lg:grid-cols-12 py-8"> <div class="grid lg:grid-cols-12 py-8">
<div class="col-span-12 lg:col-span-8 lg:col-start-3 xl:col-span-6 xl:col-start-4 px-6"> <div class="col-span-12 lg:col-span-8 lg:col-start-3 xl:col-span-6 xl:col-start-4 px-6">
@if($register_company->account && !$register_company->account->isPaid())
<div class="flex justify-center"> <div class="flex justify-center">
<img class="h-32 w-auto" src="{{ $register_company->present()->logo() }}" alt="{{ ctrans('texts.logo') }}"> <img src="{{ asset('images/invoiceninja-black-logo-2.png') }}"
</div> class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
</div>
@elseif(isset($register_company) && !is_null($register_company))
<div class="flex justify-center">
<img src="{{ $register_company->present()->logo() }}"
class="mx-auto border-b border-gray-100 h-18 pb-4" alt="{{ $register_company->present()->name() }} logo">
</div>
@endif
<h1 class="text-center text-3xl mt-8">{{ ctrans('texts.register') }}</h1> <h1 class="text-center text-3xl mt-8">{{ ctrans('texts.register') }}</h1>
<p class="block text-center text-gray-600">{{ ctrans('texts.register_label') }}</p> <p class="block text-center text-gray-600">{{ ctrans('texts.register_label') }}</p>
<form action="{{ route('client.register', request()->route('company_key')) }}" method="POST" x-data="{more: false, busy: false, isSubmitted: false}" x-on:submit="isSubmitted = true"> <form id="register-form" action="{{ route('client.register', request()->route('company_key')) }}" method="POST" x-data="{more: false, busy: false, isSubmitted: false}" x-on:submit="isSubmitted = true">
@if($register_company) @if($register_company)
<input type="hidden" name="company_key" value="{{ $register_company->company_key }}"> <input type="hidden" name="company_key" value="{{ $register_company->company_key }}">
@endif @endif
@ -54,6 +62,18 @@
type="password" type="password"
name="{{ $field['key'] }}" name="{{ $field['key'] }}"
/> />
@elseif($field['key'] === 'currency_id')
<select
id="currency_id"
class="input w-full form-select bg-white"
name="currency_id">
@foreach(App\Utils\TranslationHelper::getCurrencies() as $currency)
<option
{{ $currency->id == $register_company->settings->currency_id ? 'selected' : null }} value="{{ $currency->id }}">
{{ $currency->name }}
</option>
@endforeach
</select>
@elseif($field['key'] === 'country_id') @elseif($field['key'] === 'country_id')
<select <select
id="shipping_country" id="shipping_country"
@ -112,6 +132,9 @@
</div> </div>
<div class="flex justify-between items-center mt-8"> <div class="flex justify-between items-center mt-8">
<a href="{{route('client.login')}}" class="button button-info bg-green-600 text-white">{{ ctrans('texts.login_label') }}</a>
<span class="inline-flex items-center" x-data="{ terms_of_service: false, privacy_policy: false }"> <span class="inline-flex items-center" x-data="{ terms_of_service: false, privacy_policy: false }">
@if(!empty($register_company->settings->client_portal_terms) || !empty($register_company->settings->client_portal_privacy_policy)) @if(!empty($register_company->settings->client_portal_terms) || !empty($register_company->settings->client_portal_privacy_policy))
<input type="checkbox" name="terms" class="form-checkbox mr-2 cursor-pointer" checked> <input type="checkbox" name="terms" class="form-checkbox mr-2 cursor-pointer" checked>
@ -129,7 +152,9 @@
</span> </span>
</span> </span>
<button class="button button-primary bg-blue-600" :disabled={{ $submitsForm == 'true' ? 'isSubmitted' : 'busy'}} x-on:click="busy = true">{{ ctrans('texts.register')}}</button> <button class="button button-primary bg-blue-600" :disabled={{ $submitsForm == 'true' ? 'isSubmitted' : 'busy'}} x-on:click="busy = true">
{{ ctrans('texts.register')}}
</button>
</div> </div>
</form> </form>

View File

@ -1,4 +1,4 @@
<div class="px-4 py-5 sm:px-6 lg:grid lg:grid-cols-3 lg:gap-4 lg:flex lg:items-center"> <div class="px-4 py-2 sm:px-6 lg:grid lg:grid-cols-3 lg:gap-4 lg:flex lg:items-center">
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4"> <dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
{{ $title }} {{ $title }}
</dt> </dt>

View File

@ -196,6 +196,13 @@
</li> </li>
@endforeach @endforeach
@endif @endif
@if(auth()->guard('contact')->check())
<li class="flex py-6">
<div class="flex w-full text-left mt-8">
<a href="{{route('client.dashboard')}}" class="button-link text-primary">{{ ctrans('texts.go_back') }}</a>
</div>
</li>
@endif
</ul> </ul>
</div> </div>
</div> </div>
@ -209,7 +216,7 @@
@foreach($bundle->toArray() as $item) @foreach($bundle->toArray() as $item)
<div class="flex justify-between mt-1 mb-1"> <div class="flex justify-between mt-1 mb-1">
<span class="font-light text-sm uppercase">{{$item['product']}} x {{$item['qty']}}</span> <span class="font-light text-sm">{{ $item['qty'] }} x {{ substr(str_replace(["\r","\n","<BR>","<BR />","<br>","<br />"]," ", $item['product']), 0, 30) . "..." }}</span>
<span class="font-bold text-sm">{{ $item['price'] }}</span> <span class="font-bold text-sm">{{ $item['price'] }}</span>
</div> </div>
@endforeach @endforeach
@ -284,6 +291,7 @@
</form> </form>
@endif @endif
@if($is_eligible)
<div class="mt-4 container mx-auto flex w-full justify-center" x-show.important="toggle" x-transition> <div class="mt-4 container mx-auto flex w-full justify-center" x-show.important="toggle" x-transition>
<span class=""> <span class="">
<svg class="animate-spin h-8 w-8 text-primary mx-auto justify-center w-full" xmlns="http://www.w3.org/2000/svg" <svg class="animate-spin h-8 w-8 text-primary mx-auto justify-center w-full" xmlns="http://www.w3.org/2000/svg"
@ -295,6 +303,9 @@
</svg> </svg>
</span> </span>
</div> </div>
@else
<small class="mt-4 block">{{ $this->not_eligible_message }}</small>
@endif
</div> </div>

View File

@ -13,6 +13,6 @@
@section('body') @section('body')
<div class="flex flex-col"> <div class="flex flex-col">
@livewire('credits-table', ['company' => $company]) @livewire('credits-table', ['company_id' => $company->id, 'db' => $company->db])
</div> </div>
@endsection @endsection

View File

@ -35,7 +35,6 @@
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $credit, 'invitation' => $invitation]) @include('portal.ninja2020.components.pdf-viewer', ['entity' => $credit, 'invitation' => $invitation])
@endsection @endsection
@section('footer') @section('footer')
@ -45,18 +44,5 @@
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
// clipboard.on('success', function(e) {
// console.info('Action:', e.action);
// console.info('Text:', e.text);
// console.info('Trigger:', e.trigger);
// e.clearSelection();
// });
// clipboard.on('error', function(e) {
// console.error('Action:', e.action);
// console.error('Trigger:', e.trigger);
// });
</script> </script>
@endsection @endsection

View File

@ -14,5 +14,5 @@
@csrf @csrf
</form> </form>
@livewire('documents-table', ['client' => $client, 'company' => $company]) @livewire('documents-table', ['client_id' => $client->id, 'db' => $company->db])
@endsection @endsection

View File

@ -5,6 +5,7 @@
<form action="{{route('client.payments.credit_response')}}" method="post" id="credit-payment"> <form action="{{route('client.payments.credit_response')}}" method="post" id="credit-payment">
@csrf @csrf
<input type="hidden" name="payment_hash" value="{{$payment_hash}}"> <input type="hidden" name="payment_hash" value="{{$payment_hash}}">
<input type="hidden" name="hash" value="{{ request()->query('hash')}}">
</form> </form>
<div class="container mx-auto"> <div class="container mx-auto">

View File

@ -0,0 +1,31 @@
@extends('portal.ninja2020.layout.clean')
@section('meta_title', ctrans('texts.error'))
@section('body')
<div class="flex h-screen">
<div class="m-auto md:w-1/2 lg:w-1/2">
<div class="flex flex-col items-center">
@if($account && !$account->isPaid())
<div>
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}"
class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
</div>
@elseif(isset($company) && !is_null($company))
<div>
<img src="{{ $company->present()->logo() }}"
class="mx-auto border-b border-gray-100 h-18 pb-4" alt="{{ $company->present()->name() }} logo">
</div>
@endif
<h1 class="text-center text-3xl mt-10">{{ ctrans("texts.subscription_blocked_title") }}</h1>
<p class="text-center opacity-75 mt-10">{{ ctrans('texts.subscription_blocked') }}</p>
</div>
</div>
</div>
@stop
@push('footer')
@endpush

View File

@ -23,6 +23,6 @@
</form> </form>
</div> </div>
<div class="flex flex-col mt-4"> <div class="flex flex-col mt-4">
@livewire('invoices-table', ['company' => $company]) @livewire('invoices-table', ['company_id' => $company->id, 'db' => $company->db])
</div> </div>
@endsection @endsection

View File

@ -31,7 +31,7 @@
<input type="hidden" name="company_gateway_id" id="company_gateway_id"> <input type="hidden" name="company_gateway_id" id="company_gateway_id">
<input type="hidden" name="payment_method_id" id="payment_method_id"> <input type="hidden" name="payment_method_id" id="payment_method_id">
<input type="hidden" name="signature"> <input type="hidden" name="signature">
<input type="hidden" name="hash" value="{{ $hash }}">
<input type="hidden" name="payable_invoices[0][amount]" value="{{ $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()) }}"> <input type="hidden" name="payable_invoices[0][amount]" value="{{ $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()) }}">
<input type="hidden" name="payable_invoices[0][invoice_id]" value="{{ $invoice->hashed_id }}"> <input type="hidden" name="payable_invoices[0][invoice_id]" value="{{ $invoice->hashed_id }}">

View File

@ -3,6 +3,6 @@
@section('body') @section('body')
<div class="flex flex-col"> <div class="flex flex-col">
@livewire('payment-methods-table', ['client' => $client, 'company' => $company]) @livewire('payment-methods-table', ['client_id' => $client->id, 'db' => $company->db])
</div> </div>
@endsection @endsection

View File

@ -3,6 +3,6 @@
@section('body') @section('body')
<div class="flex flex-col"> <div class="flex flex-col">
@livewire('payments-table', ['company' => $company]) @livewire('payments-table', ['company_id' => $company->id, 'db' => $company->db])
</div> </div>
@endsection @endsection

View File

@ -26,6 +26,6 @@
</div> </div>
<div class="flex flex-col mt-4"> <div class="flex flex-col mt-4">
@livewire('quotes-table', ['company' => $company]) @livewire('quotes-table', ['company_id' => $company->id, 'db' => $company->db])
</div> </div>
@endsection @endsection

View File

@ -54,7 +54,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
Route::post('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'bulk'])->name('invoices.bulk'); Route::post('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'bulk'])->name('invoices.bulk');
Route::get('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'catch_bulk'])->name('invoices.catch_bulk'); Route::get('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'catch_bulk'])->name('invoices.catch_bulk');
Route::post('invoices/download', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'download'])->name('invoices.download'); Route::post('invoices/download', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'download'])->name('invoices.download');
Route::get('invoices/{invoice}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'show'])->name('invoice.show'); Route::get('invoices/{invoice}/{hash?}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'show'])->name('invoice.show');
Route::get('invoices/{invoice_invitation}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'show'])->name('invoice.show_invitation'); Route::get('invoices/{invoice_invitation}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'show'])->name('invoice.show_invitation');
Route::get('recurring_invoices', [App\Http\Controllers\ClientPortal\RecurringInvoiceController::class, 'index'])->name('recurring_invoices.index')->middleware('portal_enabled'); Route::get('recurring_invoices', [App\Http\Controllers\ClientPortal\RecurringInvoiceController::class, 'index'])->name('recurring_invoices.index')->middleware('portal_enabled');

View File

@ -100,7 +100,7 @@ class CreditsTest extends TestCase
$c2->load('client'); $c2->load('client');
$c3->load('client'); $c3->load('client');
Livewire::test(CreditsTable::class, ['company' => $company]) Livewire::test(CreditsTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertDontSee('testing-number-01') ->assertDontSee('testing-number-01')
->assertSee('testing-number-02') ->assertSee('testing-number-02')
->assertSee('testing-number-03'); ->assertSee('testing-number-03');
@ -167,7 +167,7 @@ class CreditsTest extends TestCase
$this->actingAs($client->contacts->first(), 'contact'); $this->actingAs($client->contacts->first(), 'contact');
Livewire::test(CreditsTable::class, ['company' => $company]) Livewire::test(CreditsTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertSee('testing-number-01') ->assertSee('testing-number-01')
->assertSee('testing-number-02') ->assertSee('testing-number-02')
->assertSee('testing-number-03'); ->assertSee('testing-number-03');

View File

@ -86,12 +86,12 @@ class InvoicesTest extends TestCase
$this->actingAs($client->contacts->first(), 'contact'); $this->actingAs($client->contacts->first(), 'contact');
Livewire::test(InvoicesTable::class, ['company' => $company]) Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertSee($sent->number) ->assertSee($sent->number)
->assertSee($paid->number) ->assertSee($paid->number)
->assertSee($unpaid->number); ->assertSee($unpaid->number);
Livewire::test(InvoicesTable::class, ['company' => $company]) Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db])
->set('status', ['paid']) ->set('status', ['paid'])
->assertSee($paid->number) ->assertSee($paid->number)
->assertDontSee($unpaid->number); ->assertDontSee($unpaid->number);

View File

@ -144,7 +144,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 11, 'balance' => 11,
'status_id' => 2, 'status_id' => 2,
'total_taxes' => 1, 'total_taxes' => 1,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'terms' => 'nada', 'terms' => 'nada',
'discount' => 0, 'discount' => 0,
'tax_rate1' => 0, 'tax_rate1' => 0,
@ -183,7 +183,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10, 'balance' => 10,
'status_id' => 2, 'status_id' => 2,
'total_taxes' => 1, 'total_taxes' => 1,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'terms' => 'nada', 'terms' => 'nada',
'discount' => 0, 'discount' => 0,
'tax_rate1' => 10, 'tax_rate1' => 10,
@ -226,7 +226,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10, 'balance' => 10,
'status_id' => 2, 'status_id' => 2,
'total_taxes' => 1, 'total_taxes' => 1,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'terms' => 'nada', 'terms' => 'nada',
'discount' => 0, 'discount' => 0,
'tax_rate1' => 10, 'tax_rate1' => 10,
@ -282,7 +282,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10, 'balance' => 10,
'status_id' => 2, 'status_id' => 2,
'total_taxes' => 0, 'total_taxes' => 0,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'terms' => 'nada', 'terms' => 'nada',
'discount' => 0, 'discount' => 0,
'tax_rate1' => 0, 'tax_rate1' => 0,
@ -313,7 +313,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
]); ]);
$pl = new ProfitLoss($this->company, $this->payload); $pl = new ProfitLoss($this->company, $this->payload);
@ -334,7 +334,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id); $e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10; $e->amount = 10;
$e->date = '2022-01-01'; $e->date = now()->format('Y-m-d');
$e->calculate_tax_by_amount = true; $e->calculate_tax_by_amount = true;
$e->tax_amount1 = 10; $e->tax_amount1 = 10;
$e->save(); $e->save();
@ -358,7 +358,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id); $e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10; $e->amount = 10;
$e->date = '2022-01-01'; $e->date = now()->format('Y-m-d');
$e->tax_rate1 = 10; $e->tax_rate1 = 10;
$e->tax_name1 = 'GST'; $e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false; $e->uses_inclusive_taxes = false;
@ -383,7 +383,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id); $e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10; $e->amount = 10;
$e->date = '2022-01-01'; $e->date = now()->format('Y-m-d');
$e->tax_rate1 = 10; $e->tax_rate1 = 10;
$e->tax_name1 = 'GST'; $e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false; $e->uses_inclusive_taxes = false;
@ -410,7 +410,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'exchange_rate' => 1, 'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
]); ]);
@ -440,7 +440,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'exchange_rate' => 1, 'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
]); ]);
@ -454,7 +454,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'exchange_rate' => 1, 'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
]); ]);
@ -489,7 +489,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10, 'balance' => 10,
'status_id' => 2, 'status_id' => 2,
'total_taxes' => 1, 'total_taxes' => 1,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'terms' => 'nada', 'terms' => 'nada',
'discount' => 0, 'discount' => 0,
'tax_rate1' => 10, 'tax_rate1' => 10,
@ -510,7 +510,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'exchange_rate' => 1, 'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
]); ]);
@ -524,7 +524,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10, 'amount' => 10,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'date' => '2022-01-01', 'date' => now()->format('Y-m-d'),
'exchange_rate' => 1, 'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
]); ]);

View File

@ -70,4 +70,14 @@ class DatesTest extends TestCase
$this->assertFalse($date_in_future->gt(Carbon::parse($date_in_past)->addDays(14))); $this->assertFalse($date_in_future->gt(Carbon::parse($date_in_past)->addDays(14)));
} }
/*Test time travelling behaves as expected */
// public function testTimezoneShifts()
// {
// $this->travel(Carbon::parse('2022-12-20'));
// $this->assertEquals('2022-12-20', now()->setTimeZone('Pacific/Midway')->format('Y-m-d'));
// $this->travelBack();
// }
} }

View File

@ -115,8 +115,8 @@ class GeneratesConvertedQuoteCounterTest extends TestCase
$this->assertNotNull($invoice); $this->assertNotNull($invoice);
$this->assertEquals('2022-Q0001', $quote->number); $this->assertEquals(now()->format('Y'). '-Q0001', $quote->number);
$this->assertEquals('2022-I0001', $invoice->number); $this->assertEquals(now()->format('Y'). '-I0001', $invoice->number);
$settings = $this->client->getMergedSettings(); $settings = $this->client->getMergedSettings();
$settings->invoice_number_counter = 100; $settings->invoice_number_counter = 100;