diff --git a/app/Http/Controllers/ClientPortal/EntityViewController.php b/app/Http/Controllers/ClientPortal/EntityViewController.php index 3308e8a5c6..014faf172d 100644 --- a/app/Http/Controllers/ClientPortal/EntityViewController.php +++ b/app/Http/Controllers/ClientPortal/EntityViewController.php @@ -2,12 +2,19 @@ namespace App\Http\Controllers\ClientPortal; +use App\Events\Credit\CreditWasViewed; +use App\Events\Invoice\InvoiceWasViewed; +use App\Events\Misc\InvitationWasViewed; +use App\Events\Quote\QuoteWasViewed; use App\Http\Controllers\Controller; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; use Illuminate\Routing\Redirector; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Illuminate\View\View; class EntityViewController extends Controller @@ -19,7 +26,7 @@ class EntityViewController extends Controller * * @var array */ - private $entity_types = ['invoice', 'quote']; + private $entity_types = ['invoice', 'quote', 'credit', 'recurring_invoice']; /** * Show the entity outside client portal. @@ -118,4 +125,54 @@ class EntityViewController extends Controller return back(); } + + public function handlePasswordSet(Request $request) + { + $entity_obj = 'App\Models\\'.ucfirst(Str::camel($request->entity_type)).'Invitation'; + $key = $request->entity_type.'_id'; + + $invitation = $entity_obj::where('key', $request->invitation_key) + ->whereHas($request->entity_type, function ($query) { + $query->where('is_deleted',0); + }) + ->with('contact.client') + ->first(); + + $contact = $invitation->contact; + $contact->password = Hash::make($request->password); + $contact->save(); + + $request->session()->invalidate(); + auth()->guard('contact')->loginUsingId($contact->id, true); + + if (! $invitation->viewed_date) { + $invitation->markViewed(); + + event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars())); + + $this->fireEntityViewedEvent($invitation, $request->entity_type); + } + + return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]); + + } + + private function fireEntityViewedEvent($invitation, $entity_string) + { + switch ($entity_string) { + case 'invoice': + event(new InvoiceWasViewed($invitation, $invitation->company, Ninja::eventVars())); + break; + case 'quote': + event(new QuoteWasViewed($invitation, $invitation->company, Ninja::eventVars())); + break; + case 'credit': + event(new CreditWasViewed($invitation, $invitation->company, Ninja::eventVars())); + break; + default: + // code... + break; + } + } + } diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 0748afca7a..6554ecbbb6 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -102,6 +102,17 @@ class InvitationController extends Controller auth()->guard('contact')->loginUsingId($client_contact->id, true); } elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) { + + //if no contact password has been set - allow user to set password - then continue to view entity + if(empty($invitation->contact->password)){ + + return $this->render('view_entity.set_password', [ + 'root' => 'themes', + 'entity_type' => $entity, + 'invitation_key' => $invitation_key + ]); + } + $this->middleware('auth:contact'); return redirect()->route('client.login'); diff --git a/app/Http/Controllers/ClientPortal/QuoteController.php b/app/Http/Controllers/ClientPortal/QuoteController.php index 1b0fa82144..b58dd13b34 100644 --- a/app/Http/Controllers/ClientPortal/QuoteController.php +++ b/app/Http/Controllers/ClientPortal/QuoteController.php @@ -160,7 +160,7 @@ class QuoteController extends Controller $quotes = Quote::whereIn('id', $ids) ->where('client_id', auth('contact')->user()->client->id) ->where('company_id', auth('contact')->user()->client->company_id) - ->where('status_id', Quote::STATUS_SENT) + ->whereIn('status_id', [Quote::STATUS_DRAFT, Quote::STATUS_SENT]) ->withTrashed() ->get(); diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index f292c7e4f2..430ab1f105 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -76,6 +76,10 @@ class StoreRecurringInvoiceRequest extends Request $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); } + if (array_key_exists('vendor_id', $input) && is_string($input['vendor_id'])) { + $input['vendor_id'] = $this->decodePrimaryKey($input['vendor_id']); + } + if (isset($input['client_contacts'])) { foreach ($input['client_contacts'] as $key => $contact) { if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) { diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 291a815bae..dedb6bb679 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -78,6 +78,7 @@ class Credit extends BaseModel 'assigned_user_id', 'exchange_rate', 'subscription_id', + 'vendor_id', ]; protected $casts = [ @@ -123,6 +124,11 @@ class Credit extends BaseModel return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); } + public function vendor() + { + return $this->belongsTo(Vendor::class); + } + public function history() { return $this->hasManyThrough(Backup::class, Activity::class); diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 451c73f69d..b627cb5a26 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -76,6 +76,7 @@ class Quote extends BaseModel 'exchange_rate', 'subscription_id', 'uses_inclusive_taxes', + 'vendor_id', ]; protected $casts = [ @@ -133,6 +134,11 @@ class Quote extends BaseModel return $this->belongsTo(Company::class); } + public function vendor() + { + return $this->belongsTo(Vendor::class); + } + public function history() { return $this->hasManyThrough(Backup::class, Activity::class); diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index bbb80d4b93..689abc16d4 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -107,6 +107,7 @@ class RecurringInvoice extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'vendor_id', ]; protected $casts = [ @@ -157,6 +158,11 @@ class RecurringInvoice extends BaseModel return $value; } + public function vendor() + { + return $this->belongsTo(Vendor::class); + } + public function activities() { return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50); diff --git a/app/Repositories/Migration/PaymentMigrationRepository.php b/app/Repositories/Migration/PaymentMigrationRepository.php index 32c253e147..a93c6ea453 100644 --- a/app/Repositories/Migration/PaymentMigrationRepository.php +++ b/app/Repositories/Migration/PaymentMigrationRepository.php @@ -202,6 +202,9 @@ class PaymentMigrationRepository extends BaseRepository */ private function processExchangeRates($data, $payment) { + if($payment->exchange_rate != 1) + return $payment; + $client = Client::where('id', $data['client_id'])->withTrashed()->first(); $client_currency = $client->getSetting('currency_id'); diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index be59fe955d..850814295d 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -210,6 +210,7 @@ class PaymentRepository extends BaseRepository { $payment->exchange_currency_id = $company_currency; $payment->currency_id = $client_currency; + return $payment; } $payment->currency_id = $company_currency; diff --git a/app/Services/Invoice/ApplyPaymentAmount.php b/app/Services/Invoice/ApplyPaymentAmount.php index ee6a0f657e..51ec799595 100644 --- a/app/Services/Invoice/ApplyPaymentAmount.php +++ b/app/Services/Invoice/ApplyPaymentAmount.php @@ -111,6 +111,9 @@ class ApplyPaymentAmount extends AbstractService private function setExchangeRate(Payment $payment) { + if($payment->exchange_rate != 1) + return; + $client_currency = $payment->client->getSetting('currency_id'); $company_currency = $payment->client->company->settings->currency_id; diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 281cea3ebc..50e7249d0b 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -79,6 +79,9 @@ class InvoiceService public function setExchangeRate() { + if($this->invoice->exchange_rate != 1) + return $this; + $client_currency = $this->invoice->client->getSetting('currency_id'); $company_currency = $this->invoice->company->settings->currency_id; diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 026892f495..205c5cee57 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -116,6 +116,9 @@ class MarkPaid extends AbstractService private function setExchangeRate(Payment $payment) { + if($payment->exchange_rate != 1) + return; + $client_currency = $payment->client->getSetting('currency_id'); $company_currency = $payment->client->company->settings->currency_id; diff --git a/app/Transformers/CreditTransformer.php b/app/Transformers/CreditTransformer.php index 2ebb5849d3..47b2a920d4 100644 --- a/app/Transformers/CreditTransformer.php +++ b/app/Transformers/CreditTransformer.php @@ -89,6 +89,7 @@ class CreditTransformer extends EntityTransformer 'user_id' => $this->encodePrimaryKey($credit->user_id), 'project_id' => $this->encodePrimaryKey($credit->project_id), 'assigned_user_id' => $this->encodePrimaryKey($credit->assigned_user_id), + 'vendor_id' => (string) $this->encodePrimaryKey($credit->vendor_id), 'amount' => (float) $credit->amount, 'balance' => (float) $credit->balance, 'client_id' => (string) $this->encodePrimaryKey($credit->client_id), diff --git a/app/Transformers/QuoteTransformer.php b/app/Transformers/QuoteTransformer.php index ccc46f55f7..c752fabcd6 100644 --- a/app/Transformers/QuoteTransformer.php +++ b/app/Transformers/QuoteTransformer.php @@ -95,6 +95,7 @@ class QuoteTransformer extends EntityTransformer 'status_id' => (string) $quote->status_id, 'design_id' => (string) $this->encodePrimaryKey($quote->design_id), 'invoice_id' => (string) $this->encodePrimaryKey($quote->invoice_id), + 'vendor_id' => (string) $this->encodePrimaryKey($quote->vendor_id), 'updated_at' => (int) $quote->updated_at, 'archived_at' => (int) $quote->deleted_at, 'created_at' => (int) $quote->created_at, diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 894ce33d4c..bf4f62a6d5 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4544,6 +4544,7 @@ $LANG = array( 'activity_123' => ':user deleted recurring expense :recurring_expense', 'activity_124' => ':user restored recurring expense :recurring_expense', 'fpx' => "FPX", + 'to_view_entity_set_password' => 'To view the :entity you need to set password.', ); diff --git a/resources/views/portal/ninja2020/quotes/show.blade.php b/resources/views/portal/ninja2020/quotes/show.blade.php index 7d805fbab8..7a9b506338 100644 --- a/resources/views/portal/ninja2020/quotes/show.blade.php +++ b/resources/views/portal/ninja2020/quotes/show.blade.php @@ -21,14 +21,12 @@ @endcomponent @endif - @if($quote->status_id === \App\Models\Quote::STATUS_SENT) + @if(in_array($quote->status_id, [\App\Models\Quote::STATUS_SENT, \App\Models\Quote::STATUS_DRAFT]))
{{ ctrans('texts.approved') }}
- @else -{{ ctrans('texts.quotes_with_status_sent_can_be_approved') }}
@endif @include('portal.ninja2020.components.entity-documents', ['entity' => $quote]) diff --git a/routes/client.php b/routes/client.php index ecdf91f81a..3e69a4d082 100644 --- a/routes/client.php +++ b/routes/client.php @@ -18,6 +18,7 @@ Route::post('client/password/reset', 'Auth\ContactResetPasswordController@reset' Route::get('view/{entity_type}/{invitation_key}', 'ClientPortal\EntityViewController@index')->name('client.entity_view'); Route::get('view/{entity_type}/{invitation_key}/password', 'ClientPortal\EntityViewController@password')->name('client.entity_view.password'); Route::post('view/{entity_type}/{invitation_key}/password', 'ClientPortal\EntityViewController@handlePassword'); +Route::post('set_password', 'ClientPortal\EntityViewController@handlePasswordSet')->name('client.set_password'); Route::get('tmp_pdf/{hash}', 'ClientPortal\TempRouteController@index')->name('tmp_pdf'); diff --git a/tests/Feature/VendorApiTest.php b/tests/Feature/VendorApiTest.php index d4288c8b5f..259f82a8a6 100644 --- a/tests/Feature/VendorApiTest.php +++ b/tests/Feature/VendorApiTest.php @@ -41,6 +41,147 @@ class VendorApiTest extends TestCase Model::reguard(); } + public function testAddVendorToInvoice() + { + $data = [ + 'name' => $this->faker->firstName, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/vendors', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + $vendor_id =$arr['data']['id']; + + $data = [ + 'vendor_id' => $vendor_id, + 'client_id' => $this->client->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/invoices', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals($arr['data']['vendor_id'], $vendor_id); + + } + + + public function testAddVendorToRecurringInvoice() + { + $data = [ + 'name' => $this->faker->firstName, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/vendors', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + $vendor_id = $arr['data']['id']; + + $data = [ + 'vendor_id' => $vendor_id, + 'client_id' => $this->client->hashed_id, + 'frequency_id' => 1, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/recurring_invoices', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals($arr['data']['vendor_id'], $vendor_id); + + } + + public function testAddVendorToQuote() + { + $data = [ + 'name' => $this->faker->firstName, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/vendors', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + $vendor_id =$arr['data']['id']; + + $data = [ + 'vendor_id' => $vendor_id, + 'client_id' => $this->client->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/quotes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals($arr['data']['vendor_id'], $vendor_id); + + } + + + public function testAddVendorToCredit() + { + $data = [ + 'name' => $this->faker->firstName, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/vendors', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + $vendor_id =$arr['data']['id']; + + $data = [ + 'vendor_id' => $vendor_id, + 'client_id' => $this->client->hashed_id + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/credits', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals($arr['data']['vendor_id'], $vendor_id); + + } + + + public function testVendorPost() { $data = [