diff --git a/.gitignore b/.gitignore index 2568217402f3..807279c6ceeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /node_modules /public/hot /public/storage +/public/react /storage/*.key /vendor /.idea diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index d9843fc34963..161356302fd7 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -12,9 +12,11 @@ namespace App\Http\Controllers; use App\Http\Requests\Account\CreateAccountRequest; +use App\Http\Requests\Account\UpdateAccountRequest; use App\Jobs\Account\CreateAccount; use App\Models\Account; use App\Models\CompanyUser; +use App\Transformers\AccountTransformer; use App\Transformers\CompanyUserTransformer; use App\Utils\TruthSource; use Illuminate\Foundation\Bus\DispatchesJobs; @@ -157,4 +159,17 @@ class AccountController extends BaseController return $this->listResponse($ct); } + + public function update(UpdateAccountRequest $request, Account $account) + { + + $account->fill($request->all()); + $account->save(); + + $this->entity_type = Account::class; + + $this->entity_transformer = AccountTransformer::class; + + return $this->itemResponse($account); + } } diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 051b4066d1a4..eed24a5f7c60 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -203,22 +203,36 @@ class SelfUpdateController extends BaseController foreach ($iterator as $file) { - if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName())) - { - - $directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS); - - foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex) + if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName())) { - unlink($filex->getPathName()); - } + $directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS); + + foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex) + { + unlink($filex->getPathName()); + } + + $this->deleteDirectory(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName())); } } } + private function deleteDirectory($dir) { + if (!file_exists($dir)) return true; + + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') continue; + if (!$this->deleteDirectory($dir . "/" . $item)) { + if (!$this->deleteDirectory($dir . "/" . $item)) return false; + }; + } + return rmdir($dir); + } + private function postHookUpdate() { if(config('ninja.app_version') == '5.3.82') diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index ba6abc9c25b6..0d3d730f4b97 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -75,14 +75,13 @@ class InvitationController extends Controller auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); } - if (auth()->guard('vendor')->user() && ! request()->has('silent') && ! $invitation->viewed_date) { + session()->put('is_silent', request()->has('silent')); - if(!session()->get('is_silent')){ - - $invitation->markViewed(); - event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars())); - } + if (auth()->guard('vendor')->user() && ! session()->get('is_silent') && ! $invitation->viewed_date) { + $invitation->markViewed(); + event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars())); + } else{ diff --git a/app/Http/Requests/Account/UpdateAccountRequest.php b/app/Http/Requests/Account/UpdateAccountRequest.php new file mode 100644 index 000000000000..a3197f51ee63 --- /dev/null +++ b/app/Http/Requests/Account/UpdateAccountRequest.php @@ -0,0 +1,54 @@ +user()->isAdmin() || auth()->user()->isOwner()) && (int)$this->account->id === auth()->user()->account_id; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'set_react_as_default_ap' => 'required|bail|bool' + ]; + } + + /* Only allow single field to update account table */ + protected function prepareForValidation() + { + $input = $this->all(); + + $cleaned_input = array_intersect_key( $input, array_flip(['set_react_as_default_ap'])); + + $this->replace($cleaned_input); + + } +} diff --git a/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php b/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php new file mode 100644 index 000000000000..b7d499c471f9 --- /dev/null +++ b/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php @@ -0,0 +1,79 @@ +company->db); + + $first_notification_sent = true; + + $purchase_order = $event->purchase_order; + + $nmo = new NinjaMailerObject; + $nmo->mailable = new NinjaMailer( (new PurchaseOrderAcceptedObject($purchase_order, $event->company))->build() ); + $nmo->company = $event->company; + $nmo->settings = $event->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; + + /* Returns an array of notification methods */ + $methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_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; + } + + } + } +} diff --git a/app/Mail/Admin/PurchaseOrderAcceptedObject.php b/app/Mail/Admin/PurchaseOrderAcceptedObject.php new file mode 100644 index 000000000000..431a1da549a9 --- /dev/null +++ b/app/Mail/Admin/PurchaseOrderAcceptedObject.php @@ -0,0 +1,103 @@ +purchase_order = $purchase_order; + $this->company = $company; + } + + public function build() + { + MultiDB::setDb($this->company->db); + + if(!$this->purchase_order) + return; + + App::forgetInstance('translator'); + /* Init a new copy of the translator*/ + $t = app('translator'); + /* Set the locale*/ + App::setLocale($this->company->getLocale()); + /* Set customized translations _NOW_ */ + $t->replace(Ninja::transformTranslations($this->company->settings)); + + $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; + } + + private function getAmount() + { + return Number::formatMoney($this->purchase_order->amount, $this->company); + } + + private function getSubject() + { + return + ctrans( + "texts.notification_purchase_order_accepted_subject", + [ + 'vendor' => $this->purchase_order->vendor->present()->name(), + 'purchase_order' => $this->purchase_order->number, + ] + ); + } + + private function getData() + { + $settings = $this->company->settings; + + $data = [ + 'title' => $this->getSubject(), + 'message' => ctrans( + "texts.notification_purchase_order_accepted", + [ + 'amount' => $this->getAmount(), + 'vendor' => $this->purchase_order->vendor->present()->name(), + 'purchase_order' => $this->purchase_order->number, + ] + ), + 'url' => $this->purchase_order->invitations->first()->getAdminLink(), + 'button' => ctrans("texts.view_purchase_order"), + 'signature' => $settings->email_signature, + 'logo' => $this->company->present()->logo(), + 'settings' => $settings, + 'whitelabel' => $this->company->account->isPaid() ? true : false, + ]; + + return $data; + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 1692ac7d6ea4..2aef2603d7c1 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -11,6 +11,7 @@ namespace App\Models; +use App\Exceptions\ModelNotFoundException; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Mail\Ninja\EmailQuotaExceeded; @@ -472,4 +473,14 @@ class Account extends BaseModel } + public function resolveRouteBinding($value, $field = null) + { + if (is_numeric($value)) { + throw new ModelNotFoundException("Record with value {$value} not found"); + } + + return $this + ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + } + } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index e619deb06319..e84aa13d98fe 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -181,6 +181,7 @@ use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedNotification; use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; @@ -475,6 +476,7 @@ class EventServiceProvider extends ServiceProvider ], PurchaseOrderWasAccepted::class => [ PurchaseOrderAcceptedActivity::class, + PurchaseOrderAcceptedNotification::class ], CompanyDocumentsDeleted::class => [ DeleteCompanyDocuments::class, diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 9c044f987481..bf7353d75fb9 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4631,6 +4631,8 @@ $LANG = array( 'accepted' => 'Accepted', 'activity_137' => ':contact accepted purchase order :purchase_order', 'vendor_information' => 'Vendor Information', + 'notification_purchase_order_accepted_subject' => 'Purchase Order :purchase_order was accepted by :vendor', + 'notification_purchase_order_accepted' => 'The following vendor :vendor accepted Purchase Order :purchase_order for :amount.', ); return $LANG; diff --git a/routes/api.php b/routes/api.php index 74d277181c70..e37eabb84bfa 100644 --- a/routes/api.php +++ b/routes/api.php @@ -24,6 +24,7 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], }); Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { + Route::put('accounts/{account}', 'AccountController@update')->name('account.update'); Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain'); Route::get('ping', 'PingController@index')->name('ping'); Route::get('health_check', 'PingController@health')->name('health_check');