diff --git a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderApprovalController.php b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderApprovalController.php index 217e2ed3f..9e2fcd2ca 100644 --- a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderApprovalController.php +++ b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderApprovalController.php @@ -2,10 +2,18 @@ namespace App\Http\Controllers\Api\Purchase\PurchaseOrder; +use App\Exceptions\PointException; use App\Http\Controllers\Controller; use App\Http\Resources\ApiResource; +use App\Mail\PurchaseOrderBulkRequestApprovalNotificationMail; +use App\Model\Master\User; +use App\Model\Project\Project; use App\Model\Purchase\PurchaseOrder\PurchaseOrder; +use App\Model\Token; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; class PurchaseOrderApprovalController extends Controller { @@ -22,6 +30,8 @@ public function approve(Request $request, $id) $purchaseOrder->form->approval_status = 1; $purchaseOrder->form->save(); + $purchaseOrder->form->fireEventApproved(); + return new ApiResource($purchaseOrder); } @@ -39,6 +49,131 @@ public function reject(Request $request, $id) $purchaseOrder->form->approval_status = -1; $purchaseOrder->form->save(); + $purchaseOrder->form->fireEventRejected(); + return new ApiResource($purchaseOrder); } + + /** + * @param Request $request + * @return JsonResponse + */ + public function sendBulkRequestApproval(Request $request) + { + $purchaseOrderGroup = PurchaseOrder::whereIn('id', $request->get('bulk_id')) + ->with('form.requestApprovalTo','form.createdBy', 'supplier', 'items') + ->get() + ->groupBy('form.requestApprovalTo.email'); + + foreach($purchaseOrderGroup as $email => $purchaseOrders){ + // create token based on request_approval_to + $approver = User::findOrFail($purchaseOrders[0]->form->request_approval_to); + $token = Token::where('user_id', $approver->id)->first(); + + if (!$token) { + $token = new Token([ + 'user_id' => $approver->id, + 'token' => md5($approver->email.''.now()), + ]); + $token->save(); + } + + $project = Project::where('code', $request->header('Tenant'))->first(); + + Mail::to($email)->send(new PurchaseOrderBulkRequestApprovalNotificationMail($purchaseOrders, $request->header('Tenant'), $request->get('tenant_url'), $request->get('bulk_id'), $token->token, $project->name)); + + // record history + foreach($purchaseOrders as $purchaseOrder){ + $purchaseOrder->form->fireEventRequestApproval(); + } + } + + return response()->json([], 204); + } + + /** + * @param Request $request + * @param $id + * @return Json + * @throws PointException + */ + public function approvalWithToken(Request $request) + { + return DB::connection('tenant')->transaction(function () use ($request) { + // verify token + $token = Token::where('token', $request->get('token'))->first(); + if(!$token){ + throw new PointException('Not Authorized'); + } + + $purchaseOrder = PurchaseOrder::with('form')->findOrFail($request->get('id')); + if($purchaseOrder->form->approval_status == 0){ + if($request->get('status') == -1 || $request->get('status') == 1) { + $purchaseOrder->form->approval_by = $token->user_id; + $purchaseOrder->form->approval_at = now(); + $purchaseOrder->form->approval_status = $request->get('status'); + if($request->get('status') == -1) { + $purchaseOrder->form->approval_reason = 'rejected by email'; + } else if($request->get('status') == 1) { + $purchaseOrder->form->approval_reason = 'approved by email'; + } + $purchaseOrder->form->save(); + + // record history + if($request->get('status') == -1) { + $purchaseOrder->form->fireEventRejectedByEmail(); + } else if($request->get('status') == 1) { + $purchaseOrder->form->fireEventApprovedByEmail(); + } + } + } + + return new ApiResource($purchaseOrder); + }); + } + + /** + * @param Request $request + * @param $id + * @return ApiResource + * @throws UnauthorizedException + * @throws ApprovalNotFoundException + */ + public function bulkApprovalWithToken(Request $request) + { + return DB::connection('tenant')->transaction(function () use ($request) { + // verify token + $token = Token::where('token', $request->get('token'))->first(); + if(!$token){ + throw new PointException('Not Authorized'); + } + + $bulkId = $request->get('bulk_id'); + $purchaseOrders = PurchaseOrder::with('form')->whereIn('id', $bulkId)->get(); + foreach($purchaseOrders as $purchaseOrder) { + if($purchaseOrder->form->approval_status == 0){ + if($request->get('status') == -1 || $request->get('status') == 1) { + $purchaseOrder->form->approval_by = $token->user_id; + $purchaseOrder->form->approval_at = now(); + $purchaseOrder->form->approval_status = $request->get('status'); + if($request->get('status') == -1) { + $purchaseOrder->form->approval_reason = 'rejected by email'; + } else if($request->get('status') == 1) { + $purchaseOrder->form->approval_reason = 'approved by email'; + } + $purchaseOrder->form->save(); + + // record history + if($request->get('status') == -1) { + $purchaseOrder->form->fireEventRejectedByEmail(); + } else if($request->get('status') == 1) { + $purchaseOrder->form->fireEventApprovedByEmail(); + } + } + } + } + + return new ApiResource($purchaseOrders); + }); + } } diff --git a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderCancellationApprovalController.php b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderCancellationApprovalController.php index ea35273a1..91cc4cc77 100644 --- a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderCancellationApprovalController.php +++ b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderCancellationApprovalController.php @@ -27,6 +27,8 @@ public function approve(Request $request, $id) $purchaseOrder->updateReference(); + $purchaseOrder->form->fireEventCancelApproved(); + DB::connection('tenant')->commit(); return new ApiResource($purchaseOrder); @@ -46,6 +48,8 @@ public function reject(Request $request, $id) $purchaseOrder->form->cancellation_status = -1; $purchaseOrder->form->save(); + $purchaseOrder->form->fireEventCancelRejected(); + return new ApiResource($purchaseOrder); } } diff --git a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderController.php b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderController.php index 02c3b1463..3c7c1b8b7 100644 --- a/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderController.php +++ b/app/Http/Controllers/Api/Purchase/PurchaseOrder/PurchaseOrderController.php @@ -9,6 +9,7 @@ use App\Http\Resources\ApiResource; use App\Model\Master\Supplier; use App\Model\Purchase\PurchaseOrder\PurchaseOrder; +use App\Model\UserActivity; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Throwable; @@ -32,6 +33,21 @@ public function index(Request $request) return new ApiCollection($purchaseOrders); } + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return ApiCollection + */ + public function history(Request $request) + { + $userActivity = UserActivity::from(UserActivity::getTableName().' as '.UserActivity::$alias)->eloquentFilter($request); + + $userActivity = pagination($userActivity, $request->get('limit')); + + return new ApiCollection($userActivity); + } + /** * Store a newly created resource in storage. * Request : diff --git a/app/Mail/PurchaseOrderBulkRequestApprovalNotificationMail.php b/app/Mail/PurchaseOrderBulkRequestApprovalNotificationMail.php new file mode 100644 index 000000000..f51f74a80 --- /dev/null +++ b/app/Mail/PurchaseOrderBulkRequestApprovalNotificationMail.php @@ -0,0 +1,46 @@ +purchaseOrders = $purchaseOrders; + $this->tenant = $tenant; + $this->tenantUrl = $tenantUrl; + $this->bulkId = $bulkId; + $this->token = $token; + $this->projectName = $projectName; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->subject('Approval Email Purchase Order') + ->view('emails.purchase.purchase-order.bulk-request-approval-notification'); + } +} diff --git a/app/Model/Purchase/PurchaseOrder/PurchaseOrder.php b/app/Model/Purchase/PurchaseOrder/PurchaseOrder.php index 543bf6801..0e5280411 100644 --- a/app/Model/Purchase/PurchaseOrder/PurchaseOrder.php +++ b/app/Model/Purchase/PurchaseOrder/PurchaseOrder.php @@ -113,7 +113,7 @@ public function updateStatus() { } } - public static function create($data) + public static function create($data): PurchaseOrder { $purchaseOrder = new self; $purchaseOrder->fill($data); diff --git a/resources/views/emails/purchase/purchase-order/bulk-request-approval-notification.blade.php b/resources/views/emails/purchase/purchase-order/bulk-request-approval-notification.blade.php new file mode 100644 index 000000000..50f52b814 --- /dev/null +++ b/resources/views/emails/purchase/purchase-order/bulk-request-approval-notification.blade.php @@ -0,0 +1,70 @@ +@extends('emails.template') + +@section('content') +
+
Purchase Order
+
+
+

Hello Mrs/Mr/Ms {{ $purchaseOrders[0]->form->requestApprovalTo->full_name }}

+

You have an approval for Purchase Order. We would like to details as follows:

+ + + + + + + + + + + + + + + +
Form number: {{$purchaseOrders[0]->form->number}} - {{$purchaseOrders[count($purchaseOrders)-1]->form->number}}
Form date: {{date('d F Y', strtotime($purchaseOrders[0]->form->date))}} - {{date('d F Y', strtotime($purchaseOrders[count($purchaseOrders)-1]->form->date))}}
Created at: {{date('d F Y H:i', strtotime($purchaseOrders[0]->created_at))}} - {{date('d F Y H:i', strtotime($purchaseOrders[count($purchaseOrders)-1]->created_at))}}
+
+ + + + + + + + + + + + + + + + + @foreach($purchaseOrders as $index => $purchaseOrder) + + + + + + + + + + + + + @endforeach + +
NoDate FormForm NumberSupplierItemsNoteTaxDiscountAmountAction
{{ $index + 1 }}{{ date('d F Y', strtotime($purchaseOrder->form->date.' Asia/Jakarta')) }}{{ $purchaseOrder->form->number }}{{ $purchaseOrder->supplier->name }}{{ $purchaseOrder->items->count() }}{{ $purchaseOrder->form->notes }}{{ $purchaseOrder->tax }}{{ $purchaseOrder->discount ?: 0 }} ({{ ucwords($purchaseOrder->type_of_tax) }}){{ $purchaseOrder->amount }} +
+ Check + Approve + Reject +
+
+
+ Approve All + Reject All +
+
+@stop diff --git a/routes/api.php b/routes/api.php index 452e699d6..5e5ab4d3e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -44,19 +44,19 @@ }); Route::prefix('sales/return')->namespace('Sales\\SalesReturn') - ->middleware('tenant.module-access:sales return') + ->middleware('tenant.module-access:sales return') ->group(function () { Route::post('/approve', 'SalesReturnApprovalByEmailController@approve'); Route::post('/reject', 'SalesReturnApprovalByEmailController@reject'); }); Route::prefix('sales/delivery-orders')->namespace('Sales\\DeliveryOrder') - ->middleware('tenant.module-access:sales delivery order') + ->middleware('tenant.module-access:sales delivery order') ->group(function () { Route::post('/approve', 'DeliveryOrderApprovalByEmailController@approve'); Route::post('/reject', 'DeliveryOrderApprovalByEmailController@reject'); }); - + Route::prefix('accounting/memo-journals')->namespace('Accounting')->group(function () { Route::post('/approve', 'MemoJournalApprovalByEmailController@approve'); Route::post('/reject', 'MemoJournalApprovalByEmailController@reject'); @@ -110,10 +110,12 @@ Route::get('oauth/login/google/drive', 'OAuthController@requestGoogleDrive'); Route::post('oauth/login/google/drive', 'OAuthController@storeGoogleAccessToken'); Route::delete('oauth/login/google/drive', 'OAuthController@unlinkGoogleDrive'); - + //Approve/reject with token Route::prefix('approval-with-token')->group(function () { Route::post('finance/cash-advances', 'Finance\\CashAdvance\\CashAdvanceApprovalController@approvalWithToken'); Route::post('finance/cash-advances/bulk', 'Finance\\CashAdvance\\CashAdvanceApprovalController@bulkApprovalWithToken'); + Route::post('purchase/orders', 'Purchase\\PurchaseOrder\\PurchaseOrderApprovalController@approvalWithToken'); + Route::post('purchase/orders/bulk', 'Purchase\\PurchaseOrder\\PurchaseOrderApprovalController@bulkApprovalWithToken'); }); }); diff --git a/routes/api/purchase.php b/routes/api/purchase.php index 800ab4b31..289bc076f 100644 --- a/routes/api/purchase.php +++ b/routes/api/purchase.php @@ -13,8 +13,10 @@ Route::apiResource('requests', 'PurchaseRequest\\PurchaseRequestController'); Route::post('orders/{id}/approve', 'PurchaseOrder\\PurchaseOrderApprovalController@approve'); Route::post('orders/{id}/reject', 'PurchaseOrder\\PurchaseOrderApprovalController@reject'); + Route::post('orders/send-bulk-request-approval', 'PurchaseOrder\\PurchaseOrderApprovalController@sendBulkRequestApproval'); Route::post('orders/{id}/cancellation-approve', 'PurchaseOrder\\PurchaseOrderCancellationApprovalController@approve'); Route::post('orders/{id}/cancellation-reject', 'PurchaseOrder\\PurchaseOrderCancellationApprovalController@reject'); + Route::get('orders/history', 'PurchaseOrder\\PurchaseOrderController@history'); Route::apiResource('orders', 'PurchaseOrder\\PurchaseOrderController'); Route::post('down-payments/{id}/approve', 'PurchaseDownPayment\\PurchaseDownPaymentApprovalController@approve'); Route::post('down-payments/{id}/reject', 'PurchaseDownPayment\\PurchaseDownPaymentApprovalController@reject'); diff --git a/tests/Feature/Http/Purchase/PurchaseOrderSetup.php b/tests/Feature/Http/Purchase/PurchaseOrderSetup.php new file mode 100644 index 000000000..7ccae521b --- /dev/null +++ b/tests/Feature/Http/Purchase/PurchaseOrderSetup.php @@ -0,0 +1,73 @@ + env('DB_TENANT_DATABASE')]); + + $this->signIn(); + $this->setProject(); + + foreach ($this->permissionsSetup as $permission) { + $this->createPermission($permission); + } + + foreach ($this->roleSetup as $role) { + $this->createRole($role); + } + } + + protected function createPermission(string $permission) + { + $permission = \App\Model\Auth\Permission::createIfNotExists($permission); + $hasPermission = new \App\Model\Auth\ModelHasPermission(); + $hasPermission->permission_id = $permission->id; + $hasPermission->model_type = 'App\Model\Master\User'; + $hasPermission->model_id = $this->user->id; + $hasPermission->save(); + } + + protected function createRole(string $role) + { + $role = \App\Model\Auth\Role::createIfNotExists($role); + $hasRole = new \App\Model\Auth\ModelHasRole(); + $hasRole->role_id = $role->id; + $hasRole->model_type = 'App\Model\Master\User'; + $hasRole->model_id = $this->user->id; + $hasRole->save(); + } + + private function getItems() + { + return Item::limit(2)->get(); + } + + protected function getPurchaseRequest() + { + $form = Form::where('formable_type', 'PurchaseRequest') + ->orderBy('created_at', 'desc') + ->first(); + + return PurchaseRequest::find($form->formable_id); + } + + protected function getPurchaseOrder() + { + $form = Form::where('formable_type', 'PurchaseOrder') + ->orderBy('created_at', 'desc') + ->first(); + + return PurchaseOrder::find($form->formable_id); + } +} diff --git a/tests/Feature/Http/Purchase/PurchaseOrderTest.php b/tests/Feature/Http/Purchase/PurchaseOrderTest.php new file mode 100644 index 000000000..653307130 --- /dev/null +++ b/tests/Feature/Http/Purchase/PurchaseOrderTest.php @@ -0,0 +1,492 @@ +getItems(); + + $item1 = $items[0]; + $item2 = $items[1]; + + $data = [ + "approver_email" => $this->user->email, + "approver_name" => $this->user->full_name, + "request_approval_to" => $this->user->id, + "date" => date('Y-m-d H:i:s'), + "increment_group" => "202210", + "notes" => "test test", + "required_date" => date('Y-m-d H:i:s'), + "items" => [ + [ + "converter" => 1, + "item_id" => $item1->id, + "item_name" => $item1->name, + "notes" => "test 1", + "quantity" => 10, + "unit" => "PCS", + ], + [ + "converter" => 1, + "item_id" => $item2->id, + "item_name" => $item2->name, + "notes" => "test 2", + "quantity" => 10, + "unit" => "PCS", + ], + + ], + ]; + + // API Request + $response = $this->json('POST', '/api/v1/purchase/requests', $data, [$this->headers]); + + // $response->dumpHeaders(); + // $response->dumpSession(); + // $response->dump(); + + // Check Status Response + $response->assertStatus(201); + + // Check id + $this->assertTrue($response['data']['id'] > 0); + + // Check items + $this->assertTrue(is_array($response['data']['items'])); + + // Check Database + $this->assertDatabaseHas('purchase_requests', [ + 'id' => $response['data']['id'], + ], 'tenant'); + } + + /** + * Test purchase request approval + * + * @return void + */ + public function test_approve_purchase_request() + { + $purchaseRequest = $this->getPurchaseRequest(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/requests/{$purchaseRequest->id}/approve", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseRequest->id); + } + + /** + * Test create purchase order using previously generate purchase request + * + * @return void + */ + public function test_create_purchase_order() + { + $supplier = Supplier::first(); + $purchaseRequest = $this->getPurchaseRequest(); + $purchaseRequestItems = PurchaseRequestItem::limit(2)->get(); + + $item1 = $purchaseRequestItems[0]; + $item2 = $purchaseRequestItems[1]; + + $data = [ + "approver_email" => $this->user->email, + "approver_name" => $this->user->full_name, + "request_approval_to" => $this->user->id, + "cash_only" => false, + "date" => date('Y-m-d H:i:s'), + "increment_group" => "202210", + "discount_percent" => 0, + "discount_value" => 1000, + "need_down_payment" => 0, + "notes" => "test", + "purchase_request_id" => $purchaseRequest->id, + "subtotal" => 70000, + "supplier_address" => null, + "supplier_id" => $supplier->id, + "supplier_name" => $supplier->name, + "supplier_phone" => $supplier->phone, + "tax" => 6900, + "tax_base" => 69000, + "total" => 75900, + "type_of_tax" => "exclude", + "items" => [ + [ + "allocation_id" => null, + "converter" => 1, + "discount_percent" => 0, + "discount_value" => 0, + "item_id" => $item1->item_id, + "item_name" => $item1->item_name, + "price" => 5000, + "purchase_request_item_id" => $item1->id, + "quantity" => $item1->quantity, + "unit" => $item1->unit, + ], + [ + "allocation_id" => null, + "converter" => 1, + "discount_percent" => 0, + "discount_value" => 0, + "item_id" => $item2->item_id, + "item_name" => $item2->item_name, + "price" => 2000, + "purchase_request_item_id" => $item2->id, + "quantity" => $item2->quantity, + "unit" => $item2->unit, + ], + + ], + ]; + + // API Request + $response = $this->json('POST', '/api/v1/purchase/orders', $data, [$this->headers]); + + // Check Status Response + $response->assertStatus(201); + + // Check id + $this->assertTrue($response['data']['id'] > 0); + + // Check items + $this->assertTrue(is_array($response['data']['items'])); + + // Check Database + $this->assertDatabaseHas('purchase_orders', [ + 'id' => $response['data']['id'], + ], 'tenant'); + } + + /** + * Test approva new purchase order + * + * @return void + */ + public function test_approve_create_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/approve", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'approval_status' => 1, + ], 'tenant'); + } + + /** + * Test reject new purchase order + * + * @return void + */ + public function test_reject_create_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/reject", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'approval_status' => -1, + ], 'tenant'); + } + + /** + * Test edit purchase order + * + * @return void + */ + public function test_edit_purchase_order() + { + $supplier = Supplier::first(); + $purchaseOrder = $this->getPurchaseOrder(); + $purchaseOrderItems = PurchaseOrderItem::where('purchase_order_id', $purchaseOrder->id)->limit(2)->get(); + + $item1 = $purchaseOrderItems[0]; + $item2 = $purchaseOrderItems[1]; + + $data = [ + "id" => $purchaseOrder->id, + "approver_email" => $this->user->email, + "approver_name" => $this->user->full_name, + "request_approval_to" => $this->user->id, + "cash_only" => $purchaseOrder->cash_only, + "date" => date('Y-m-d H:i:s'), + "increment_group" => "202210", + "discount_percent" => $purchaseOrder->discount_percent, + "discount_value" => $purchaseOrder->discount_value, + "need_down_payment" => $purchaseOrder->need_down_payment, + "notes" => "test edit", + "purchase_request_id" => $purchaseOrder->purchase_request_id, + "subtotal" => $purchaseOrder->subtotal, + "supplier_address" => null, + "supplier_id" => $supplier->id, + "supplier_name" => $supplier->name, + "supplier_phone" => $supplier->phone, + "tax" => 6900, + "tax_base" => 69000, + "type_of_tax" => "exclude", + "items" => [ + [ + "allocation_id" => null, + "converter" => 1, + "discount_percent" => 0, + "discount_value" => 0, + "item_id" => $item1->item_id, + "item_name" => $item1->item_name, + "price" => 5000, + "purchase_request_item_id" => $item1->id, + "quantity" => $item1->quantity, + "unit" => $item1->unit, + ], + [ + "allocation_id" => null, + "converter" => 1, + "discount_percent" => 0, + "discount_value" => 0, + "item_id" => $item2->item_id, + "item_name" => $item2->item_name, + "price" => 2000, + "purchase_request_item_id" => $item2->id, + "quantity" => $item2->quantity, + "unit" => $item2->unit, + ], + + ], + ]; + + // API Request + $response = $this->json('PATCH', "/api/v1/purchase/orders/{$purchaseOrder->id}", $data, [$this->headers]); + + // Check Status Response + $response->assertStatus(201); + + // Check id + $this->assertTrue($response['data']['id'] > 0); + + // Check items + $this->assertTrue(is_array($response['data']['items'])); + + // Check Database + $this->assertDatabaseHas('purchase_orders', [ + 'id' => $response['data']['id'], + ], 'tenant'); + } + + /** + * Test edited purchase order approval + * + * @return void + */ + public function test_approve_edit_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/approve", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'approval_status' => 1, + ], 'tenant'); + } + + /** + * Test reject edited purchase order + * + * @return void + */ + public function test_reject_edit_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/reject", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'approval_status' => -1, + ], 'tenant'); + } + + /** + * Test deleting purchase order + * + * @return void + */ + public function test_delete_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + $data = [ + "reason" => "test delete", + ]; + + // API Request + $response = $this->json('DELETE', "/api/v1/purchase/orders/{$purchaseOrder->id}", $data, [$this->headers]); + + // Check Status Response + $response->assertStatus(204); + } + + /** + * Test delete purchase order approval + * + * @return void + */ + public function test_approve_delete_purchase_order() + { + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/cancellation-approve", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'cancellation_status' => 1, + ], 'tenant'); + } + + /** + * Test reject delete purchase order + * + * @return void + */ + public function test_reject_delete_purchase_order() + { + // Create PO + $this->test_create_purchase_order(); + + // Delete PO + $this->test_delete_purchase_order(); + + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $response = $this->json('POST', "/api/v1/purchase/orders/{$purchaseOrder->id}/cancellation-reject", [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Database + $this->assertDatabaseHas('forms', [ + 'formable_type' => 'PurchaseOrder', + 'formable_id' => $purchaseOrder->id, + 'cancellation_status' => -1, + ], 'tenant'); + } + + /** + * Test show details of purchase order + * + * @return void + */ + public function test_show_purchase_order() + { + // Create PO + $this->test_create_purchase_order(); + + $purchaseOrder = $this->getPurchaseOrder(); + + // API Request + $url = "/api/v1/purchase/orders/{$purchaseOrder->id}?with_archives=true&with_origin=true&includes=supplier;items.item;items.allocation;purchaseRequest.form;form.createdBy;form.requestApprovalTo;form.branch"; + $response = $this->json('GET', $url, [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check id + $response->assertJsonPath('data.id', $purchaseOrder->id); + + // Check Response Structure + $response->assertJsonStructure($this->jsonShowPurchaseOrder); + } + + /** + * Test purchase order index data + * + * @return void + */ + public function test_index_purchase_order() + { + $dateStart = date('Y-m-01'); + $dateEnd = date('Y-m-t'); + $url = "/api/v1/purchase/orders?join=form,supplier,items,item&fields=purchase_order.*&sort_by=-form.number&group_by=form.id&filter_form=notArchived;null&filter_like={}&filter_date_min={\"form.date\":\"{$dateStart} 00:00:00\"}&filter_date_max={\"form.date\":\"{$dateEnd} 23:59:59\"}&limit=10&includes=form;supplier;items.item;items.allocation&page=1"; + + // Create PO + $this->test_create_purchase_order(); + + // API Request + $response = $this->json('GET', $url, [], [$this->headers]); + + // Check Status Response + $response->assertStatus(200); + + // Check Response Structure + $response->assertJsonStructure($this->jsonIndexPurchaseOrder); + } +} diff --git a/tests/Feature/Http/Purchase/PurchaseOrderTestData.php b/tests/Feature/Http/Purchase/PurchaseOrderTestData.php new file mode 100644 index 000000000..98092d1c5 --- /dev/null +++ b/tests/Feature/Http/Purchase/PurchaseOrderTestData.php @@ -0,0 +1,394 @@ + [ + "id", + "purchase_request_id", + "purchase_contract_id", + "supplier_id", + "supplier_name", + "supplier_address", + "supplier_phone", + "billing_address", + "billing_phone", + "billing_email", + "shipping_address", + "shipping_phone", + "shipping_email", + "warehouse_id", + "eta", + "cash_only", + "need_down_payment", + "delivery_fee", + "discount_percent", + "discount_value", + "type_of_tax", + "tax", + "amount", + "supplier" => [ + "id", + "code", + "tax_identification_number", + "name", + "address", + "city", + "state", + "country", + "zip_code", + "latitude", + "longitude", + "phone", + "phone_cc", + "email", + "notes", + "branch_id", + "created_by", + "updated_by", + "archived_by", + "created_at", + "updated_at", + "archived_at", + "label", + ], + "items" => [ + "*" => [ + "id", + "purchase_order_id", + "purchase_request_item_id", + "item_id", + "item_name", + "quantity", + "price", + "discount_percent", + "discount_value", + "taxable", + "unit", + "converter", + "notes", + "allocation_id", + "item" => [ + "id", + "chart_of_account_id", + "code", + "barcode", + "name", + "size", + "color", + "weight", + "notes", + "taxable", + "require_production_number", + "require_expiry_date", + "stock", + "stock_reminder", + "unit_default", + "unit_default_purchase", + "unit_default_sales", + "created_by", + "updated_by", + "archived_by", + "created_at", + "updated_at", + "archived_at", + "label", + ], + "allocation", + ], + ], + "purchase_request" => [ + "id", + "required_date", + "supplier_id", + "supplier_name", + "supplier_address", + "supplier_phone", + "amount", + "form" => [ + "id", + "branch_id", + "date", + "number", + "edited_number", + "edited_notes", + "notes", + "created_by", + "updated_by", + "done", + "increment", + "increment_group", + "formable_id", + "formable_type", + "request_approval_to", + "approval_by", + "approval_at", + "approval_reason", + "approval_status", + "request_cancellation_to", + "request_cancellation_by", + "request_cancellation_at", + "request_cancellation_reason", + "cancellation_approval_at", + "cancellation_approval_by", + "cancellation_approval_reason", + "cancellation_status", + "created_at", + "updated_at", + "request_close_to", + "request_close_by", + "request_close_at", + "request_close_reason", + "close_approval_at", + "close_approval_by", + "close_status", + "request_approval_at", + "close_approval_reason", + ], + ], + "form" => [ + "id", + "branch_id", + "date", + "number", + "edited_number", + "edited_notes", + "notes", + "created_by" => [ + "id", + "name", + "first_name", + "last_name", + "address", + "phone", + "email", + "created_at", + "updated_at", + "branch_id", + "warehouse_id", + "full_name", + ], + "updated_by", + "done", + "increment", + "increment_group", + "formable_id", + "formable_type", + "request_approval_to" => [ + "id", + "name", + "first_name", + "last_name", + "address", + "phone", + "email", + "created_at", + "updated_at", + "branch_id", + "warehouse_id", + "full_name", + ], + "approval_by", + "approval_at", + "approval_reason", + "approval_status", + "request_cancellation_to", + "request_cancellation_by", + "request_cancellation_at", + "request_cancellation_reason", + "cancellation_approval_at", + "cancellation_approval_by", + "cancellation_approval_reason", + "cancellation_status", + "created_at", + "updated_at", + "request_close_to", + "request_close_by", + "request_close_at", + "request_close_reason", + "close_approval_at", + "close_approval_by", + "close_status", + "request_approval_at", + "close_approval_reason", + "branch" => [ + "id", + "name", + "address", + "phone", + "created_by", + "updated_by", + "archived_by", + "created_at", + "updated_at", + "archived_at", + ] + ] + ] + ]; + + private $jsonIndexPurchaseOrder = [ + 'data' => [ + "*" => [ + "id", + "purchase_request_id", + "purchase_contract_id", + "supplier_id", + "supplier_name", + "supplier_address", + "supplier_phone", + "billing_address", + "billing_phone", + "billing_email", + "shipping_address", + "shipping_phone", + "shipping_email", + "warehouse_id", + "eta", + "cash_only", + "need_down_payment", + "delivery_fee", + "discount_percent", + "discount_value", + "type_of_tax", + "tax", + "amount", + "form" => [ + "id", + "branch_id", + "date", + "number", + "edited_number", + "edited_notes", + "notes", + "created_by", + "updated_by", + "done", + "increment", + "increment_group", + "formable_id", + "formable_type", + "request_approval_to", + "approval_by", + "approval_at", + "approval_reason", + "approval_status", + "request_cancellation_to", + "request_cancellation_by", + "request_cancellation_at", + "request_cancellation_reason", + "cancellation_approval_at", + "cancellation_approval_by", + "cancellation_approval_reason", + "cancellation_status", + "created_at", + "updated_at", + "request_close_to", + "request_close_by", + "request_close_at", + "request_close_reason", + "close_approval_at", + "close_approval_by", + "close_status", + "request_approval_at", + "close_approval_reason", + ], + "supplier" => [ + "id", + "code", + "tax_identification_number", + "name", + "address", + "city", + "state", + "country", + "zip_code", + "latitude", + "longitude", + "phone", + "phone_cc", + "email", + "notes", + "branch_id", + "created_by", + "updated_by", + "archived_by", + "created_at", + "updated_at", + "archived_at", + "label", + ], + "items" => [ + "*" => [ + "id", + "purchase_order_id", + "purchase_request_item_id", + "item_id", + "item_name", + "quantity", + "price", + "discount_percent", + "discount_value", + "taxable", + "unit", + "converter", + "notes", + "allocation_id", + "item" => [ + "id", + "chart_of_account_id", + "code", + "barcode", + "name", + "size", + "color", + "weight", + "notes", + "taxable", + "require_production_number", + "require_expiry_date", + "stock", + "stock_reminder", + "unit_default", + "unit_default_purchase", + "unit_default_sales", + "created_by", + "updated_by", + "archived_by", + "created_at", + "updated_at", + "archived_at", + "label", + ], + "allocation", + ], + ], + ], + ], + 'links' => [ + "first", + "last", + "prev", + "next", + ], + 'meta' => [ + "current_page", + "from", + "last_page", + "path", + "per_page", + "to", + "total", + ], + ]; +} \ No newline at end of file