From 6ffad400db37555aed80c98ddd4ce2ea43c0e726 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:05:01 +0200 Subject: [PATCH 01/17] INA-4 | migration for purchase orders --- ...28_234651_create_purchase_orders_table.php | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 database/migrations/2022_05_28_234651_create_purchase_orders_table.php diff --git a/database/migrations/2022_05_28_234651_create_purchase_orders_table.php b/database/migrations/2022_05_28_234651_create_purchase_orders_table.php new file mode 100644 index 000000000000..1229e46fe622 --- /dev/null +++ b/database/migrations/2022_05_28_234651_create_purchase_orders_table.php @@ -0,0 +1,122 @@ +id(); + $table->unsignedInteger('client_id')->index(); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('assigned_user_id')->nullable(); + $table->unsignedInteger('company_id')->index(); + $table->unsignedInteger('status_id'); + $table->unsignedInteger('project_id')->nullable(); + $table->unsignedInteger('vendor_id')->nullable(); + $table->unsignedInteger('recurring_id')->nullable(); + $table->unsignedInteger('design_id')->nullable(); + $table->unsignedInteger('invoice_id')->nullable(); + + $table->string('number')->nullable(); + $table->float('discount')->default(0); + $table->boolean('is_amount_discount')->default(0); + + $table->string('po_number')->nullable(); + $table->date('date')->nullable(); + $table->datetime('last_sent_date')->nullable(); + + $table->date('due_date')->nullable(); + + $table->boolean('is_deleted')->default(false); + $table->mediumText('line_items')->nullable(); + $table->mediumText('backup')->nullable(); + $table->text('footer')->nullable(); + $table->text('public_notes')->nullable(); + $table->text('private_notes')->nullable(); + $table->text('terms')->nullable(); + + $table->string('tax_name1')->nullable(); + + + $table->decimal('tax_rate1', 20, 6)->default(0); + + $table->string('tax_name2')->nullable(); + $table->decimal('tax_rate2', 20, 6)->default(0); + + $table->string('tax_name3')->nullable(); + $table->decimal('tax_rate3', 20, 6)->default(0); + + + $table->decimal('total_taxes', 20, 6)->default(0); + $table->boolean('uses_inclusive_taxes')->default(0); + + $table->date('reminder1_sent')->nullable(); + $table->date('reminder2_sent')->nullable(); + $table->date('reminder3_sent')->nullable(); + $table->date('reminder_last_sent')->nullable(); + + $table->text('custom_value1')->nullable(); + $table->text('custom_value2')->nullable(); + $table->text('custom_value3')->nullable(); + $table->text('custom_value4')->nullable(); + + + $table->datetime('next_send_date')->nullable(); + + + + $table->decimal('custom_surcharge1', 20,6)->nullable(); + $table->decimal('custom_surcharge2', 20,6)->nullable(); + $table->decimal('custom_surcharge3', 20,6)->nullable(); + $table->decimal('custom_surcharge4', 20,6)->nullable(); + + + $table->boolean('custom_surcharge_tax1')->default(false); + $table->boolean('custom_surcharge_tax2')->default(false); + $table->boolean('custom_surcharge_tax3')->default(false); + $table->boolean('custom_surcharge_tax4')->default(false); + + + $table->decimal('exchange_rate', 20, 6)->default(1); + $table->decimal('balance', 20, 6); + $table->decimal('partial', 20, 6)->nullable(); + $table->decimal('amount', 20, 6); + $table->decimal('paid_to_date', 20, 6)->default(0); + + $table->datetime('partial_due_date')->nullable(); + + $table->datetime('last_viewed')->nullable(); + + $table->softDeletes(); + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('purchase_orders'); + } +} From 19c286c37e204a33a6f2bab0d077f217a0211283 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:05:12 +0200 Subject: [PATCH 02/17] INA-4 | Purchase Order model --- app/Models/PurchaseOrder.php | 169 +++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 app/Models/PurchaseOrder.php diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php new file mode 100644 index 000000000000..4d73c609559d --- /dev/null +++ b/app/Models/PurchaseOrder.php @@ -0,0 +1,169 @@ + 'object', + 'backup' => 'object', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + 'is_amount_discount' => 'bool', + + ]; + + const STATUS_DRAFT = 1; + const STATUS_SENT = 2; + const STATUS_PARTIAL = 3; + const STATUS_APPLIED = 4; + + public function assigned_user() + { + 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); + } + + public function activities() + { + return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50); + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + + public function client() + { + return $this->belongsTo(Client::class)->withTrashed(); + } + + + public function invitations() + { + return $this->hasMany(CreditInvitation::class); + } + + public function project() + { + return $this->belongsTo(Project::class)->withTrashed(); + } + + public function invoice() + { + return $this->belongsTo(Invoice::class); + } + + public function service() + { + return new PurchaseOrderService($this); + } + + public function invoices() + { + return $this->belongsToMany(Invoice::class)->using(Paymentable::class); + } + + public function payments() + { + return $this->morphToMany(Payment::class, 'paymentable'); + } + + public function documents() + { + return $this->morphMany(Document::class, 'documentable'); + } + +} From c62e03de5bf4e2f9664c9c7973ad07b078702c86 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:05:28 +0200 Subject: [PATCH 03/17] INA-4 | Purchase Order Factory --- app/Factory/PurchaseOrderFactory.php | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/Factory/PurchaseOrderFactory.php diff --git a/app/Factory/PurchaseOrderFactory.php b/app/Factory/PurchaseOrderFactory.php new file mode 100644 index 000000000000..7bcc02635949 --- /dev/null +++ b/app/Factory/PurchaseOrderFactory.php @@ -0,0 +1,56 @@ +status_id = PurchaseOrder::STATUS_DRAFT; + $purchase_order->number = null; + $purchase_order->discount = 0; + $purchase_order->is_amount_discount = true; + $purchase_order->po_number = ''; + $purchase_order->footer = ''; + $purchase_order->terms = ''; + $purchase_order->public_notes = ''; + $purchase_order->private_notes = ''; + $purchase_order->date = now()->format('Y-m-d'); + $purchase_order->due_date = null; + $purchase_order->partial_due_date = null; + $purchase_order->is_deleted = false; + $purchase_order->line_items = json_encode([]); + $purchase_order->tax_name1 = ''; + $purchase_order->tax_rate1 = 0; + $purchase_order->tax_name2 = ''; + $purchase_order->tax_rate2 = 0; + $purchase_order->tax_name3 = ''; + $purchase_order->tax_rate3 = 0; + $purchase_order->custom_value1 = ''; + $purchase_order->custom_value2 = ''; + $purchase_order->custom_value3 = ''; + $purchase_order->custom_value4 = ''; + $purchase_order->amount = 0; + $purchase_order->balance = 0; + $purchase_order->partial = 0; + $purchase_order->user_id = $user_id; + $purchase_order->company_id = $company_id; + $purchase_order->recurring_id = null; + + return $purchase_order; + } +} From 8afb288fa08d5f1223116824bee6075f7d5799a9 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:06:18 +0200 Subject: [PATCH 04/17] INA-4 | Purchase Order form requests --- .../CreatePurchaseOrderRequest.php | 41 ++++++++++++ .../DestroyPurchaseOrderRequest.php | 40 ++++++++++++ .../EditPurchaseOrderRequest.php | 40 ++++++++++++ .../ShowPurchaseOrderRequest.php | 40 ++++++++++++ .../StorePurchaseOrderRequest.php | 63 +++++++++++++++++++ .../UpdatePurchaseOrderRequest.php | 63 +++++++++++++++++++ 6 files changed, 287 insertions(+) create mode 100644 app/Http/Requests/PurchaseOrder/CreatePurchaseOrderRequest.php create mode 100644 app/Http/Requests/PurchaseOrder/DestroyPurchaseOrderRequest.php create mode 100644 app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php create mode 100644 app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php create mode 100644 app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php create mode 100644 app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php diff --git a/app/Http/Requests/PurchaseOrder/CreatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/CreatePurchaseOrderRequest.php new file mode 100644 index 000000000000..439e1a025e95 --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/CreatePurchaseOrderRequest.php @@ -0,0 +1,41 @@ +user()->can('create', PurchaseOrder::class); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/PurchaseOrder/DestroyPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/DestroyPurchaseOrderRequest.php new file mode 100644 index 000000000000..652a0c7815ae --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/DestroyPurchaseOrderRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->purchase_order); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php new file mode 100644 index 000000000000..53f6b15106ce --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/EditPurchaseOrderRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->purchase_order); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php new file mode 100644 index 000000000000..fa8dad6f6088 --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/ShowPurchaseOrderRequest.php @@ -0,0 +1,40 @@ +user()->can('view', $this->purchase_order); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php new file mode 100644 index 000000000000..1f531178050b --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -0,0 +1,63 @@ +user()->can('create', PurchaseOrder::class); + } + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = []; + + $rules['client_id'] = 'required'; + + + $rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)]; + $rules['discount'] = 'sometimes|numeric'; + $rules['is_amount_discount'] = ['boolean']; + + + $rules['line_items'] = 'array'; + + return $rules; + } + + protected function prepareForValidation() + { + $input = $this->all(); + + $input = $this->decodePrimaryKeys($input); + + $this->replace($input); + } + +} diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php new file mode 100644 index 000000000000..806ea4057f12 --- /dev/null +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -0,0 +1,63 @@ +user()->can('edit', $this->purchase_order); + } + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = []; + + if($this->number) + $rules['number'] = Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)->ignore($this->purchaseOrder->id); + + $rules['line_items'] = 'array'; + $rules['discount'] = 'sometimes|numeric'; + $rules['is_amount_discount'] = ['boolean']; + + return $rules; + } + protected function prepareForValidation() + { + $input = $this->all(); + + $input = $this->decodePrimaryKeys($input); + + + $input['id'] = $this->purchase_order->id; + + $this->replace($input); + } +} From e161dc1d55b607a3733f2d66d8ba23867e2f001f Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:06:51 +0200 Subject: [PATCH 05/17] INA-4 | purchase_orders api route --- routes/api.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/api.php b/routes/api.php index de8bbf546188..e2c44808f08e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -206,6 +206,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk'); Route::put('vendors/{vendor}/upload', 'VendorController@upload'); + Route::resource('purchase_orders', 'PurchaseOrderController'); + Route::get('users', 'UserController@index'); Route::get('users/{user}', 'UserController@show')->middleware('password_protected'); Route::put('users/{user}', 'UserController@update')->middleware('password_protected'); From a4f67cdfb23ae55a090b13007dd2d6deedf57c64 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:08:09 +0200 Subject: [PATCH 06/17] INA-4 | Purchase Order Filters --- app/Filters/PurchaseOrderFilters.php | 185 +++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 app/Filters/PurchaseOrderFilters.php diff --git a/app/Filters/PurchaseOrderFilters.php b/app/Filters/PurchaseOrderFilters.php new file mode 100644 index 000000000000..38be4155d694 --- /dev/null +++ b/app/Filters/PurchaseOrderFilters.php @@ -0,0 +1,185 @@ +builder; + } + + $status_parameters = explode(',', $value); + + if (in_array('all', $status_parameters)) { + return $this->builder; + } + + if (in_array('draft', $status_parameters)) { + $this->builder->where('status_id', PurchaseOrder::STATUS_DRAFT); + } + + if (in_array('partial', $status_parameters)) { + $this->builder->where('status_id', PurchaseOrder::STATUS_PARTIAL); + } + + if (in_array('applied', $status_parameters)) { + $this->builder->where('status_id', PurchaseOrder::STATUS_APPLIED); + } + + //->where('due_date', '>', Carbon::now()) + //->orWhere('partial_due_date', '>', Carbon::now()); + + return $this->builder; + } + + /** + * Filter based on search text. + * + * @param string query filter + * @return Builder + * @deprecated + */ + public function filter(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + return $this->builder->where(function ($query) use ($filter) { + $query->where('purchase_orders.number', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.number', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.date', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.amount', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.balance', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.custom_value1', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.custom_value2', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.custom_value3', 'like', '%'.$filter.'%') + ->orWhere('purchase_orders.custom_value4', 'like', '%'.$filter.'%'); + }); + } + + /** + * Filters the list based on the status + * archived, active, deleted - legacy from V1. + * + * @param string filter + * @return Builder + */ + public function status(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + $table = 'purchase_orders'; + $filters = explode(',', $filter); + + return $this->builder->where(function ($query) use ($filters, $table) { + $query->whereNull($table.'.id'); + + if (in_array(parent::STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table.'.deleted_at'); + } + + if (in_array(parent::STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table.'.deleted_at'); + + if (! in_array($table, ['users'])) { + $query->where($table.'.is_deleted', '=', 0); + } + }); + } + + if (in_array(parent::STATUS_DELETED, $filters)) { + $query->orWhere($table.'.is_deleted', '=', 1); + } + }); + } + + /** + * Sorts the list based on $sort. + * + * @param string sort formatted as column|asc + * @return Builder + */ + public function sort(string $sort) : Builder + { + $sort_col = explode('|', $sort); + + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Returns the base query. + * + * @param int company_id + * @param User $user + * @return Builder + * @deprecated + */ + public function baseQuery(int $company_id, User $user) : Builder + { + // .. + } + + /** + * Filters the query by the users company ID. + * + * We need to ensure we are using the correct company ID + * as we could be hitting this from either the client or company auth guard + * + */ + public function entityFilter() + { + if (auth()->guard('contact')->user()) { + return $this->contactViewFilter(); + } else { + return $this->builder->company(); + } + +// return $this->builder->whereCompanyId(auth()->user()->company()->id); + } + + /** + * We need additional filters when showing purchase orders for the + * client portal. Need to automatically exclude drafts and cancelled purchase orders. + * + * @return Builder + */ + private function contactViewFilter() : Builder + { + return $this->builder + ->whereCompanyId(auth()->guard('contact')->user()->company->id) + ->whereNotIn('status_id', [PurchaseOrder::STATUS_DRAFT]); + } +} From dc57d939026bc1f5bcf2fcfa22dcc4f32a86701f Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:08:30 +0200 Subject: [PATCH 07/17] INA-4 | Purchase Order Transformer --- app/Transformers/PurchaseOrderTransformer.php | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/Transformers/PurchaseOrderTransformer.php diff --git a/app/Transformers/PurchaseOrderTransformer.php b/app/Transformers/PurchaseOrderTransformer.php new file mode 100644 index 000000000000..aef5ea1d6552 --- /dev/null +++ b/app/Transformers/PurchaseOrderTransformer.php @@ -0,0 +1,88 @@ + $this->encodePrimaryKey($purchase_order->id), + 'user_id' => $this->encodePrimaryKey($purchase_order->user_id), + 'project_id' => $this->encodePrimaryKey($purchase_order->project_id), + 'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id), + 'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id), + 'amount' => (float) $purchase_order->amount, + 'balance' => (float) $purchase_order->balance, + 'client_id' => (string) $this->encodePrimaryKey($purchase_order->client_id), + 'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id), + 'status_id' => (string) ($purchase_order->status_id ?: 1), + 'design_id' => (string) $this->encodePrimaryKey($purchase_order->design_id), + 'created_at' => (int) $purchase_order->created_at, + 'updated_at' => (int) $purchase_order->updated_at, + 'archived_at' => (int) $purchase_order->deleted_at, + 'is_deleted' => (bool) $purchase_order->is_deleted, + 'number' => $purchase_order->number ?: '', + 'discount' => (float) $purchase_order->discount, + 'po_number' => $purchase_order->po_number ?: '', + 'date' => $purchase_order->date ?: '', + 'last_sent_date' => $purchase_order->last_sent_date ?: '', + 'next_send_date' => $purchase_order->next_send_date ?: '', + 'reminder1_sent' => $purchase_order->reminder1_sent ?: '', + 'reminder2_sent' => $purchase_order->reminder2_sent ?: '', + 'reminder3_sent' => $purchase_order->reminder3_sent ?: '', + 'reminder_last_sent' => $purchase_order->reminder_last_sent ?: '', + 'due_date' => $purchase_order->due_date ?: '', + 'terms' => $purchase_order->terms ?: '', + 'public_notes' => $purchase_order->public_notes ?: '', + 'private_notes' => $purchase_order->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $purchase_order->uses_inclusive_taxes, + 'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '', + 'tax_rate1' => (float) $purchase_order->tax_rate1, + 'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '', + 'tax_rate2' => (float) $purchase_order->tax_rate2, + 'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '', + 'tax_rate3' => (float) $purchase_order->tax_rate3, + 'total_taxes' => (float) $purchase_order->total_taxes, + 'is_amount_discount' => (bool) ($purchase_order->is_amount_discount ?: false), + 'footer' => $purchase_order->footer ?: '', + 'partial' => (float) ($purchase_order->partial ?: 0.0), + 'partial_due_date' => $purchase_order->partial_due_date ?: '', + 'custom_value1' => (string) $purchase_order->custom_value1 ?: '', + 'custom_value2' => (string) $purchase_order->custom_value2 ?: '', + 'custom_value3' => (string) $purchase_order->custom_value3 ?: '', + 'custom_value4' => (string) $purchase_order->custom_value4 ?: '', + 'has_tasks' => (bool) $purchase_order->has_tasks, + 'has_expenses' => (bool) $purchase_order->has_expenses, + 'custom_surcharge1' => (float) $purchase_order->custom_surcharge1, + 'custom_surcharge2' => (float) $purchase_order->custom_surcharge2, + 'custom_surcharge3' => (float) $purchase_order->custom_surcharge3, + 'custom_surcharge4' => (float) $purchase_order->custom_surcharge4, + 'custom_surcharge_tax1' => (bool) $purchase_order->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $purchase_order->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $purchase_order->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $purchase_order->custom_surcharge_tax4, + 'line_items' => $purchase_order->line_items ?: (array) [], + 'entity_type' => 'credit', + 'exchange_rate' => (float) $purchase_order->exchange_rate, + 'paid_to_date' => (float) $purchase_order->paid_to_date, + 'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id), + ]; + } + +} From 18be96d7c68b1651dab9733c5130aa501979f791 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:13:09 +0200 Subject: [PATCH 08/17] INA-4 | Purchase Order Service --- .../PurchaseOrder/PurchaseOrderService.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 app/Services/PurchaseOrder/PurchaseOrderService.php diff --git a/app/Services/PurchaseOrder/PurchaseOrderService.php b/app/Services/PurchaseOrder/PurchaseOrderService.php new file mode 100644 index 000000000000..ffb2cb84d164 --- /dev/null +++ b/app/Services/PurchaseOrder/PurchaseOrderService.php @@ -0,0 +1,57 @@ +purchase_order = $purchase_order; + } + + /** + * Saves the purchase order. + * @return \App\Models\PurchaseOrder object + */ + public function save(): ?PurchaseOrder + { + $this->purchase_order->saveQuietly(); + + return $this->purchase_order; + } + + public function fillDefaults() + { + $settings = $this->purchase_order->client->getMergedSettings(); + + //TODO implement design, footer, terms + + + /* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/ + if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id) + $this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate; + + if (!isset($this->purchase_order->public_notes)) + $this->purchase_order->public_notes = $this->purchase_order->client->public_notes; + + + return $this; + } +} From a752be5f8ed7d7b37e4f09eb3476278cdfeefe5e Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:13:19 +0200 Subject: [PATCH 09/17] INA-4 | Purchase Order Repository --- app/Repositories/PurchaseOrderRepository.php | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/Repositories/PurchaseOrderRepository.php diff --git a/app/Repositories/PurchaseOrderRepository.php b/app/Repositories/PurchaseOrderRepository.php new file mode 100644 index 000000000000..cf0653c25c8a --- /dev/null +++ b/app/Repositories/PurchaseOrderRepository.php @@ -0,0 +1,34 @@ +fill($data); + $purchase_order->save(); + + return $purchase_order; + } + +} From 4b9a7a2b4799d88010db805466d04e42017b785b Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 04:29:06 +0200 Subject: [PATCH 10/17] INA-4 | Purchase Order Controller --- .../Controllers/PurchaseOrderController.php | 413 ++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 app/Http/Controllers/PurchaseOrderController.php diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php new file mode 100644 index 000000000000..677e00196903 --- /dev/null +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -0,0 +1,413 @@ +purchase_order_repository = $purchase_order_repository; + } + /** + * Show the list of Purchase Orders. + * + * @param \App\Filters\PurchaseOrderFilters $filters The filters + * + * @return Response + * + * @OA\Get( + * path="/api/v1/purchase_orders", + * operationId="getPurchaseOrders", + * tags={"purchase_orders"}, + * summary="Gets a list of purchase orders", + * description="Lists purchase orders, search and filters allow fine grained lists to be generated. + * + * Query parameters can be added to performed more fine grained filtering of the purchase orders, these are handled by the PurchaseOrderFilters class which defines the methods available", + * @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="A list of purchase orders", + * @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/Credit"), + * ), + * @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 index(PurchaseOrderFilters $filters) + { + $purchase_orders = PurchaseOrder::filter($filters); + + return $this->listResponse($purchase_orders); + } + /** + * Show the form for creating a new resource. + * + * @param CreatePurchaseOrderRequest $request The request + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/purchase_orders/create", + * operationId="getPurchaseOrderCreate", + * tags={"purchase_orders"}, + * summary="Gets a new blank purchase order object", + * description="Returns a blank object with default values", + * @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="A blank purchase order 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/Credit"), + * ), + * @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 create(CreatePurchaseOrderRequest $request) + { + $purchase_order = PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($purchase_order); + } + /** + * Store a newly created resource in storage. + * + * @param StorePurchaseOrderRequest $request The request + * + * @return Response + * + * + * @OA\Post( + * path="/api/v1/purchase_orders", + * operationId="storePurchaseOrder", + * tags={"purhcase_orders"}, + * summary="Adds a purchase order", + * description="Adds an purchase order 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 purchase order 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/Credit"), + * ), + * @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(StorePurchaseOrderRequest $request) + { + + $client = Client::find($request->get('client_id')); + + $purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + $purchase_order = $purchase_order->service() + ->fillDefaults() + ->save(); + + return $this->itemResponse($purchase_order); + } + /** + * Display the specified resource. + * + * @param ShowPurchaseOrderRequest $request The request + * @param PurchaseOrder $purchase_order The purchase order + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/purchase_orders/{id}", + * operationId="showPurchaseOrder", + * tags={"purchase_orders"}, + * summary="Shows an purcase orders", + * description="Displays an purchase order by id", + * @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\Parameter( + * name="id", + * in="path", + * description="The Purchase order Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the purchase order 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/Credit"), + * ), + * @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 show(ShowPurchaseOrderRequest $request, PurchaseOrder $purchase_order) + { + return $this->itemResponse($purchase_order); + } + /** + * Show the form for editing the specified resource. + * + * @param EditPurchaseOrderRequest $request The request + * @param PurchaseOrder $purchase_order The purchase order + * + * @return Response + * + * @OA\Get( + * path="/api/v1/purchase_orders/{id}/edit", + * operationId="editPurchaseOrder", + * tags={"purchase_orders"}, + * summary="Shows an purchase order for editting", + * description="Displays an purchase order by id", + * @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\Parameter( + * name="id", + * in="path", + * description="The purchase order Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the purchase order 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/Invoice"), + * ), + * @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 edit(EditPurchaseOrderRequest $request, PurchaseOrder $purchase_order) + { + return $this->itemResponse($purchase_order); + } + /** + * Update the specified resource in storage. + * + * @param UpdatePurchaseOrderRequest $request The request + * @param PurchaseOrder $purchase_order + * @return Response + * + * + * @throws \ReflectionException + * @OA\Put( + * path="/api/v1/purchase_order/{id}", + * operationId="updatePurchaseOrder", + * tags={"purchase_orders"}, + * summary="Updates an purchase order", + * description="Handles the updating of an purchase order by id", + * @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\Parameter( + * name="id", + * in="path", + * description="The purchase order Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the purchase order 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/Credit"), + * ), + * @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 update(UpdatePurchaseOrderRequest $request, PurchaseOrder $purchase_order) + { + if ($request->entityIsDeleted($purchase_order)) { + return $request->disallowUpdate(); + } + + $purchase_order = $this->purchase_order_repository->save($request->all(), $purchase_order); + + return $this->itemResponse($purchase_order); + } + /** + * Remove the specified resource from storage. + * + * @param DestroyPurchaseOrderRequest $request + * @param PurchaseOrder $purchase_order + * + * @return Response + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/purchase_orders/{id}", + * operationId="deletePurchaseOrder", + * tags={"purchase_orders"}, + * summary="Deletes a purchase order", + * description="Handles the deletion of an purchase orders by id", + * @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\Parameter( + * name="id", + * in="path", + * description="The purhcase order Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @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\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 destroy(DestroyPurchaseOrderRequest $request, PurchaseOrder $purchase_order) + { + $this->purchase_order_repository->delete($purchase_order); + + return $this->itemResponse($purchase_order->fresh()); + } +} From 50a65b8d813264d6982bcc2334f1fccfd1574138 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:22:00 +0200 Subject: [PATCH 11/17] INA-4 | Purchase Order Policy --- app/Policies/PurchaseOrderPolicy.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/Policies/PurchaseOrderPolicy.php diff --git a/app/Policies/PurchaseOrderPolicy.php b/app/Policies/PurchaseOrderPolicy.php new file mode 100644 index 000000000000..78656bb21b63 --- /dev/null +++ b/app/Policies/PurchaseOrderPolicy.php @@ -0,0 +1,19 @@ +isAdmin() || $user->hasPermission('create_purchase_order') || $user->hasPermission('create_all'); + } + +} From ca9dbc61c784589c5f8fe806a0f64d054df76206 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:22:15 +0200 Subject: [PATCH 12/17] INA-4 | Set purchase order data in mock account data --- tests/MockAccountData.php | 41 ++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index d018eb38dc36..0dddbaa32df2 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -20,6 +20,7 @@ use App\Factory\InvoiceFactory; use App\Factory\InvoiceInvitationFactory; use App\Factory\InvoiceItemFactory; use App\Factory\InvoiceToRecurringInvoiceFactory; +use App\Factory\PurchaseOrderFactory; use App\Helpers\Invoice\InvoiceSum; use App\Jobs\Company\CreateCompanyTaskStatuses; use App\Models\Account; @@ -181,10 +182,10 @@ trait MockAccountData 'hosted_client_count' => 1000, 'hosted_company_count' => 1000 ]); - + $this->account->num_users = 3; $this->account->save(); - + $this->company = Company::factory()->create([ 'account_id' => $this->account->id, ]); @@ -212,7 +213,7 @@ trait MockAccountData $settings->use_credits_payment = 'always'; $settings->timezone_id = '1'; $settings->entity_send_time = 0; - + $this->company->settings = $settings; $this->company->save(); @@ -387,7 +388,7 @@ trait MockAccountData $this->invoice->setRelation('company', $this->company); $this->invoice->save(); - + $this->invoice->load("client"); InvoiceInvitation::factory()->create([ @@ -447,13 +448,43 @@ trait MockAccountData $this->quote->save(); + + + + + + + + $this->purchase_order = PurchaseOrderFactory::create($this->company->id, $user_id); + $this->purchase_order->client_id = $this->client->id; + + + $this->purchase_order->amount = 10; + $this->purchase_order->balance = 10; + + // $this->credit->due_date = now()->addDays(200); + + $this->purchase_order->tax_name1 = ''; + $this->purchase_order->tax_name2 = ''; + $this->purchase_order->tax_name3 = ''; + + $this->purchase_order->tax_rate1 = 0; + $this->purchase_order->tax_rate2 = 0; + $this->purchase_order->tax_rate3 = 0; + + $this->purchase_order->uses_inclusive_taxes = false; + $this->purchase_order->save(); + + + + $this->credit = CreditFactory::create($this->company->id, $user_id); $this->credit->client_id = $this->client->id; $this->credit->line_items = $this->buildLineItems(); $this->credit->amount = 10; $this->credit->balance = 10; - + // $this->credit->due_date = now()->addDays(200); $this->credit->tax_name1 = ''; From 62089ed74f64e4bc297f609445662fa91da71f05 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:22:22 +0200 Subject: [PATCH 13/17] INA-4 | Fix syntax --- app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php index 806ea4057f12..55e8f5354490 100644 --- a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -41,7 +41,7 @@ class UpdatePurchaseOrderRequest extends Request $rules = []; if($this->number) - $rules['number'] = Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)->ignore($this->purchaseOrder->id); + $rules['number'] = Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)->ignore($this->purchase_order->id); $rules['line_items'] = 'array'; $rules['discount'] = 'sometimes|numeric'; From 34b99c78b1138cded199a8fab51d1aa2744842b5 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:22:37 +0200 Subject: [PATCH 14/17] INA-4 | PurchaseOrder Tests --- tests/Feature/PurchaseOrderTest.php | 142 ++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/Feature/PurchaseOrderTest.php diff --git a/tests/Feature/PurchaseOrderTest.php b/tests/Feature/PurchaseOrderTest.php new file mode 100644 index 000000000000..69504545a804 --- /dev/null +++ b/tests/Feature/PurchaseOrderTest.php @@ -0,0 +1,142 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + } + + public function testPurchaseOrderRest() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id)); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id).'/edit'); + + $response->assertStatus(200); + + $credit_update = [ + 'tax_name1' => 'dippy', + ]; + + $this->assertNotNull($this->purchase_order); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $credit_update) + ->assertStatus(200); + } + public function testPostNewPurchaseOrder() + { + $purchase_order = [ + 'status_id' => 1, + 'number' => 'dfdfd', + 'discount' => 0, + 'is_amount_discount' => 1, + 'number' => '34343xx43', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'status' => 1, + 'client_id' => $this->encodePrimaryKey($this->client->id), + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/purchase_orders/', $purchase_order) + ->assertStatus(200); + } + public function testPurchaseOrderDelete() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->delete('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id)); + + $response->assertStatus(200); + } + public function testPurchaseOrderUpdate() + { + $data = [ + 'status_id' => 1, + 'number' => 'dfdfd', + 'discount' => 0, + 'is_amount_discount' => 1, + 'number' => '3434343', + 'public_notes' => 'notes', + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'status' => 1, + 'client_id' => $this->encodePrimaryKey($this->client->id), + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/purchase_orders/'.$this->encodePrimaryKey($this->purchase_order->id), $data); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/purchase_orders/', $data); + + $response->assertStatus(302); + } +} From eaa08982225477cf7a9cd4ddb575fc0773fd5ee0 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:59:35 +0200 Subject: [PATCH 15/17] INA-4 | Register purchase order policy --- app/Providers/AuthServiceProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 53622359dca6..670d21b75d7e 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -27,6 +27,7 @@ use App\Models\Payment; use App\Models\PaymentTerm; use App\Models\Product; use App\Models\Project; +use App\Models\PurchaseOrder; use App\Models\Quote; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; @@ -54,6 +55,7 @@ use App\Policies\PaymentPolicy; use App\Policies\PaymentTermPolicy; use App\Policies\ProductPolicy; use App\Policies\ProjectPolicy; +use App\Policies\PurchaseOrderPolicy; use App\Policies\QuotePolicy; use App\Policies\RecurringExpensePolicy; use App\Policies\RecurringInvoicePolicy; @@ -103,6 +105,7 @@ class AuthServiceProvider extends ServiceProvider TaxRate::class => TaxRatePolicy::class, User::class => UserPolicy::class, Vendor::class => VendorPolicy::class, + PurchaseOrder::class => PurchaseOrderPolicy::class, ]; /** From 020cf10202793f54c9dd49fb32c6b416b67986d2 Mon Sep 17 00:00:00 2001 From: Nikola Cirkovic Date: Sun, 29 May 2022 05:59:44 +0200 Subject: [PATCH 16/17] INA-4 | Add Purchase Order Observer --- app/Observers/PurchaseOrderObserver.php | 64 +++++++++++++++++++++++++ app/Providers/EventServiceProvider.php | 3 ++ 2 files changed, 67 insertions(+) create mode 100644 app/Observers/PurchaseOrderObserver.php diff --git a/app/Observers/PurchaseOrderObserver.php b/app/Observers/PurchaseOrderObserver.php new file mode 100644 index 000000000000..ff3eb89ef99d --- /dev/null +++ b/app/Observers/PurchaseOrderObserver.php @@ -0,0 +1,64 @@ + Date: Mon, 30 May 2022 20:02:17 +0200 Subject: [PATCH 17/17] INA-4 | Update migration | add missing licenses --- app/Observers/PurchaseOrderObserver.php | 10 +++++++++- app/Policies/PurchaseOrderPolicy.php | 10 +++++++++- .../2022_05_28_234651_create_purchase_orders_table.php | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/Observers/PurchaseOrderObserver.php b/app/Observers/PurchaseOrderObserver.php index ff3eb89ef99d..4af8e1722ab5 100644 --- a/app/Observers/PurchaseOrderObserver.php +++ b/app/Observers/PurchaseOrderObserver.php @@ -1,5 +1,13 @@ datetime('last_viewed')->nullable(); + $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $table->index(['company_id', 'deleted_at']); + $table->softDeletes(); $table->timestamps();