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))}} |
+
+
+
+
+
+
+
+ | No |
+ Date Form |
+ Form Number |
+ Supplier |
+ Items |
+ Note |
+ Tax |
+ Discount |
+ Amount |
+ Action |
+
+
+
+ @foreach($purchaseOrders as $index => $purchaseOrder)
+
+ | {{ $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 }} |
+
+
+ |
+
+ @endforeach
+
+
+
+
+@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