diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..6981466b5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 PointHub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/app/Console/Commands/NewCommand.php b/app/Console/Commands/NewCommand.php
index a4d221a58..853723cd5 100644
--- a/app/Console/Commands/NewCommand.php
+++ b/app/Console/Commands/NewCommand.php
@@ -46,6 +46,9 @@ public function handle()
return;
}
+ ini_set('memory_limit', '4095M');
+ ini_set('max_execution_time', '0');
+
$dbName = $this->argument('database_name') ?? env('DB_DATABASE');
$this->line('create '.$dbName.' database');
@@ -100,5 +103,6 @@ public function handle()
$projectUser->save();
Artisan::call('tenant:database:reset', ['project_code' => 'dev']);
+ $this->line('process completed');
}
}
diff --git a/app/Exceptions/ApiExceptionHandler.php b/app/Exceptions/ApiExceptionHandler.php
index 6c16c35c4..2be7e72e9 100644
--- a/app/Exceptions/ApiExceptionHandler.php
+++ b/app/Exceptions/ApiExceptionHandler.php
@@ -94,6 +94,14 @@ public function apiExceptions($request, Throwable $exception)
], 400);
}
+ /* Handle if contain constraint violation but not instanceof QueryExcepetion */
+ if (strpos($exception->getMessage(), 'Integrity constraint violation') !== false) {
+ return response()->json([
+ 'code' => 400,
+ 'message' => 'Duplicate data entry',
+ ], 400);
+ }
+
/* Handle server error or library error */
if ($exception->getCode() >= 500 || ! $exception->getCode()) {
return response()->json([
diff --git a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalByEmailController.php b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalByEmailController.php
index caf2028e8..464ec5846 100644
--- a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalByEmailController.php
+++ b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalByEmailController.php
@@ -11,6 +11,10 @@
use App\Model\UserActivity;
use App\Model\Sales\SalesReturn\SalesReturn;
+use Exception;
+use App\Model\Sales\SalesInvoice\SalesInvoiceReference;
+use App\Model\Accounting\Journal;
+use App\Model\Inventory\Inventory;
class SalesReturnApprovalByEmailController extends Controller
{
@@ -37,12 +41,31 @@ public function approve(Request $request)
$result = DB::connection('tenant')->transaction(function () use ($request) {
$salesReturns = SalesReturn::whereIn('id', $request->ids)->get();
+
+ foreach ($salesReturns as $salesReturn) {
+ try {
+ if ($salesReturn->form->approval_status === 1 && $salesReturn->form->cancellation_status === null) {
+ throw new Exception('form '.$salesReturn->form->number.' already approved', 422);
+ }
+ } catch (\Throwable $th) {
+ return response_error($th);
+ }
+ }
foreach ($salesReturns as $salesReturn) {
$form = $salesReturn->form;
// approve cancellation form
if ($form->cancellation_status === 0 && is_null($form->close_status)) {
+ if($form->approval_status === 1) {
+ SalesReturn::updateInvoiceQuantity($salesReturn, 'revert');
+ Inventory::where('form_id', $salesReturn->form->id)->delete();
+ Journal::where('form_id', $salesReturn->form->id)->orWhere('form_id_reference', $salesReturn->form->id)->delete();
+ SalesInvoiceReference::where('sales_invoice_id', $salesReturn->sales_invoice_id)
+ ->where('referenceable_id', $salesReturn->id)
+ ->where('referenceable_type', 'SalesReturn')->delete();
+ }
+
$form->cancellation_approval_by = $request->approver_id;
$form->cancellation_approval_at = now();
$form->cancellation_status = 1;
@@ -68,6 +91,7 @@ public function approve(Request $request)
SalesReturn::updateJournal($salesReturn);
SalesReturn::updateInventory($salesReturn->form, $salesReturn);
SalesReturn::updateInvoiceQuantity($salesReturn, 'update');
+ SalesReturn::updateSalesInvoiceReference($salesReturn);
$form->fireEventApprovedByEmail();
@@ -88,6 +112,7 @@ public function approve(Request $request)
*/
public function reject(Request $request)
{
+ $validated = $request->validate([ 'reason' => 'required|max:255' ]);
$this->request = $request;
$result = DB::connection('tenant')->transaction(function () use ($request) {
diff --git a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalController.php b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalController.php
index bef0cfab0..adb03c9a2 100644
--- a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalController.php
+++ b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnApprovalController.php
@@ -13,7 +13,7 @@
use App\Model\Form;
use App\Model\UserActivity;
use App\Model\Sales\SalesReturn\SalesReturn;
-
+use Exception;
use App\Mail\Sales\SalesReturnApprovalRequest;
class SalesReturnApprovalController extends Controller
@@ -31,12 +31,6 @@ public function index(Request $request)
$salesReturns = SalesReturn::joins($salesReturns, $request->get('join'))
->whereNull(Form::$alias . '.edited_number')
->where(Form::$alias . '.close_status', 0)
- ->orWhere(function ($query) {
- $query
- ->where(Form::$alias . '.cancellation_status', 0)
- ->whereNull(Form::$alias . '.close_status')
- ->whereNull(Form::$alias . '.edited_number');
- })
->orWhere(function ($query) {
$query
->where(Form::$alias . '.approval_status', 0)
@@ -68,27 +62,38 @@ public function index(Request $request)
*/
public function approve(Request $request, $id)
{
-
$result = DB::connection('tenant')->transaction(function () use ($id) {
- $salesReturn = SalesReturn::findOrFail($id);
- $salesReturn->checkQuantity($salesReturn->items);
-
- $form = $salesReturn->form;
- $form->approval_by = auth()->user()->id;
- $form->approval_at = now();
- $form->approval_status = 1;
- $form->save();
-
- SalesReturn::updateJournal($salesReturn);
- SalesReturn::updateInventory($salesReturn->form, $salesReturn);
- SalesReturn::updateInvoiceQuantity($salesReturn, 'update');
+ try {
+ $salesReturn = SalesReturn::findOrFail($id);
+ $salesReturn->checkQuantity($salesReturn->items);
+ $form = $salesReturn->form;
+
+ if ($form->approval_status === 1) {
+ throw new Exception('form already approved', 422);
+ }
+
+ $form->approval_by = auth()->user()->id;
+ $form->approval_at = now();
+ $form->approval_status = 1;
+ $form->save();
+
+ SalesReturn::updateJournal($salesReturn);
+ SalesReturn::updateInventory($salesReturn->form, $salesReturn);
+ SalesReturn::updateInvoiceQuantity($salesReturn, 'update');
+ SalesReturn::updateSalesInvoiceReference($salesReturn);
+
+
+ $form->fireEventApproved();
+
+ } catch (\Throwable $th) {
+ return response_error($th);
+ }
- $form->fireEventApproved();
-
+ $salesReturn = SalesReturn::findOrFail($id);
+ $salesReturn->load('form');
return new ApiResource($salesReturn);
});
-
return $result;
}
@@ -100,7 +105,7 @@ public function approve(Request $request, $id)
*/
public function reject(Request $request, $id)
{
- $validated = $request->validate([ 'reason' => 'required' ]);
+ $validated = $request->validate([ 'reason' => 'required|max:255' ]);
$result = DB::connection('tenant')->transaction(function () use ($request, $validated, $id) {
$salesReturn = SalesReturn::findOrFail($id);
@@ -112,6 +117,8 @@ public function reject(Request $request, $id)
$salesReturn->form->fireEventRejected();
+ $salesReturn = SalesReturn::findOrFail($id);
+ $salesReturn->load('form');
return new ApiResource($salesReturn);
});
diff --git a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnCancellationApprovalController.php b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnCancellationApprovalController.php
index 8f1cb10ff..36438d5f1 100644
--- a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnCancellationApprovalController.php
+++ b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnCancellationApprovalController.php
@@ -10,6 +10,7 @@
use App\Model\Sales\SalesReturn\SalesReturn;
use App\Model\Accounting\Journal;
use App\Model\Inventory\Inventory;
+use App\Model\Sales\SalesInvoice\SalesInvoiceReference;
class SalesReturnCancellationApprovalController extends Controller
{
@@ -32,6 +33,9 @@ public function approve(Request $request, $id)
SalesReturn::updateInvoiceQuantity($salesReturn, 'revert');
Inventory::where('form_id', $salesReturn->form->id)->delete();
Journal::where('form_id', $salesReturn->form->id)->orWhere('form_id_reference', $salesReturn->form->id)->delete();
+ SalesInvoiceReference::where('sales_invoice_id', $salesReturn->sales_invoice_id)
+ ->where('referenceable_id', $salesReturn->id)
+ ->where('referenceable_type', 'SalesReturn')->delete();
}
$salesReturn->form->cancellation_approval_by = auth()->user()->id;
@@ -39,6 +43,8 @@ public function approve(Request $request, $id)
$salesReturn->form->cancellation_status = 1;
$salesReturn->form->save();
+ $salesReturn = SalesReturn::findOrFail($salesReturn->id);
+ $salesReturn->load('form');
$salesReturn->form->fireEventCancelApproved();
} catch (\Throwable $th) {
return response_error($th);
@@ -57,7 +63,7 @@ public function approve(Request $request, $id)
*/
public function reject(Request $request, $id)
{
- $request->validate([ 'reason' => 'required ']);
+ $request->validate([ 'reason' => 'required|max:255']);
$salesReturn = SalesReturn::findOrFail($id);
@@ -74,6 +80,8 @@ public function reject(Request $request, $id)
$salesReturn->form->cancellation_status = -1;
$salesReturn->form->save();
+ $salesReturn = SalesReturn::findOrFail($salesReturn->id);
+ $salesReturn->load('form');
$salesReturn->form->fireEventCancelRejected();
} catch (\Throwable $th) {
return response_error($th);
diff --git a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnController.php b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnController.php
index 235e17ac8..1eec023f5 100644
--- a/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnController.php
+++ b/app/Http/Controllers/Api/Sales/SalesReturn/SalesReturnController.php
@@ -12,6 +12,7 @@
use Illuminate\Support\Facades\DB;
use App\Http\Requests\Sales\SalesReturn\SalesReturn\StoreSalesReturnRequest;
use App\Http\Requests\Sales\SalesReturn\SalesReturn\UpdateSalesReturnRequest;
+use App\Http\Requests\Sales\SalesReturn\SalesReturn\DeleteSalesReturnRequest;
use Exception;
class SalesReturnController extends Controller
@@ -45,7 +46,6 @@ public function store(StoreSalesReturnRequest $request)
$salesReturn = SalesReturn::create($request->all());
$salesReturn
->load('form')
- ->load('customer')
->load('items');
return new ApiResource($salesReturn);
@@ -127,5 +127,7 @@ public function destroy(Request $request, $id)
$salesReturn->requestCancel($request);
return response()->json([], 204);
+
+
}
}
diff --git a/app/Http/Middleware/TenantModuleAccessMiddleware.php b/app/Http/Middleware/TenantModuleAccessMiddleware.php
index a7c2edb8e..fe909b2c9 100644
--- a/app/Http/Middleware/TenantModuleAccessMiddleware.php
+++ b/app/Http/Middleware/TenantModuleAccessMiddleware.php
@@ -49,12 +49,12 @@ public function handle($request, Closure $next, $module)
throw new UnauthorizedException();
}
+ $this->_hasDefaultBranch();
+
if ($this->action === 'read') {
return $next($request);
}
- $this->_hasDefaultBranch();
-
$this->_hasDefaultWarehouse();
$this->_isWarehouseBranchAsDefault();
diff --git a/app/Http/Requests/Sales/SalesReturn/SalesReturn/DeleteSalesReturnRequest.php b/app/Http/Requests/Sales/SalesReturn/SalesReturn/DeleteSalesReturnRequest.php
new file mode 100644
index 000000000..a861e3f15
--- /dev/null
+++ b/app/Http/Requests/Sales/SalesReturn/SalesReturn/DeleteSalesReturnRequest.php
@@ -0,0 +1,33 @@
+ 'required|max:255',
+ ];
+
+ return array_merge($deleteRule);
+ }
+}
diff --git a/app/Http/Requests/Sales/SalesReturn/SalesReturn/RejectSalesReturnRequest.php b/app/Http/Requests/Sales/SalesReturn/SalesReturn/RejectSalesReturnRequest.php
new file mode 100644
index 000000000..596c526e5
--- /dev/null
+++ b/app/Http/Requests/Sales/SalesReturn/SalesReturn/RejectSalesReturnRequest.php
@@ -0,0 +1,33 @@
+ 'required|max:255',
+ ];
+
+ return array_merge($rejectRule);
+ }
+}
diff --git a/app/Http/Requests/Sales/SalesReturn/SalesReturn/StoreSalesReturnRequest.php b/app/Http/Requests/Sales/SalesReturn/SalesReturn/StoreSalesReturnRequest.php
index 7e96c0fa9..25166c851 100644
--- a/app/Http/Requests/Sales/SalesReturn/SalesReturn/StoreSalesReturnRequest.php
+++ b/app/Http/Requests/Sales/SalesReturn/SalesReturn/StoreSalesReturnRequest.php
@@ -28,16 +28,22 @@ public function rules()
$rulesSalesReturn = [
'sales_invoice_id' => ValidationRule::foreignKey('sales_invoices'),
-
'items' => 'required|array',
+ 'sub_total' => 'required|numeric|min:0',
+ 'tax_base' => 'required|numeric|min:0',
+ 'type_of_tax' => ValidationRule::typeOfTax(),
+ 'tax' => 'required|numeric|min:0',
+ 'amount' => 'required|numeric|min:0',
];
$rulesSalesReturnItems = [
'items.*.sales_invoice_item_id' => ValidationRule::foreignKey('sales_invoice_items'),
+ 'items.*.item_name' => 'required|string',
'items.*.quantity' => ValidationRule::quantity(),
'items.*.quantity_sales' => ValidationRule::quantity(),
'items.*.unit' => ValidationRule::unit(),
'items.*.converter' => ValidationRule::converter(),
+ 'items.*.total' => 'required|numeric|min:0',
];
return array_merge($rulesForm, $rulesSalesReturn, $rulesSalesReturnItems);
diff --git a/app/Http/Requests/Sales/SalesReturn/SalesReturn/UpdateSalesReturnRequest.php b/app/Http/Requests/Sales/SalesReturn/SalesReturn/UpdateSalesReturnRequest.php
index 151e523e0..f719964a4 100644
--- a/app/Http/Requests/Sales/SalesReturn/SalesReturn/UpdateSalesReturnRequest.php
+++ b/app/Http/Requests/Sales/SalesReturn/SalesReturn/UpdateSalesReturnRequest.php
@@ -32,17 +32,22 @@ public function rules()
$rulesSalesReturn = [
'sales_invoice_id' => ValidationRule::foreignKey('sales_invoices'),
- 'warehouse_id' => ValidationRule::foreignKeyNullable('warehouses'),
-
'items' => 'required|array',
+ 'sub_total' => 'required|numeric|min:0',
+ 'tax_base' => 'required|numeric|min:0',
+ 'type_of_tax' => ValidationRule::typeOfTax(),
+ 'tax' => 'required|numeric|min:0',
+ 'amount' => 'required|numeric|min:0',
];
$rulesSalesReturnItems = [
'items.*.sales_invoice_item_id' => ValidationRule::foreignKey('sales_invoice_items'),
+ 'items.*.item_name' => 'required|string',
'items.*.quantity' => ValidationRule::quantity(),
'items.*.quantity_sales' => ValidationRule::quantity(),
'items.*.unit' => ValidationRule::unit(),
'items.*.converter' => ValidationRule::converter(),
+ 'items.*.total' => 'required|numeric|min:0',
];
return array_merge($rulesForm, $rulesSalesReturn, $rulesSalesReturnItems);
diff --git a/app/Http/Requests/ValidationRule.php b/app/Http/Requests/ValidationRule.php
index f9cc66a37..6741b8663 100644
--- a/app/Http/Requests/ValidationRule.php
+++ b/app/Http/Requests/ValidationRule.php
@@ -67,6 +67,7 @@ public static function form()
'date' => 'required|date',
'number'=> 'nullable|string',
'increment_group' => 'required|integer',
+ 'notes' => 'nullable|max:255',
];
}
diff --git a/app/Mail/Sales/SalesReturnApprovalRequest.php b/app/Mail/Sales/SalesReturnApprovalRequest.php
index 8c56fa8a0..732872607 100644
--- a/app/Mail/Sales/SalesReturnApprovalRequest.php
+++ b/app/Mail/Sales/SalesReturnApprovalRequest.php
@@ -39,29 +39,28 @@ public function build()
{
$this->approver->token = $this->approverToken;
+ if (@$this->urlReferer) {
+ $parsedUrl = parse_url($this->urlReferer);
+ $port = @$parsedUrl['port'] ? ":{$parsedUrl['port']}" : '';
+ $url = "{$parsedUrl['scheme']}://{$parsedUrl['host']}{$port}/";
+ }
+
$user = $this->form->send_by;
if (count($this->salesReturns) > 1) {
return $this->subject('Request Approval All')
- ->from($user->email, $user->getFullNameAttribute())
->view('emails.sales.return.return-approval-request', [
'salesReturns' => $this->salesReturns,
'approver' => $this->approver,
- 'form' => $this->form
+ 'form' => $this->form,
+ 'url' => @$url
]);
} else {
- if (@$this->urlReferer) {
- $parsedUrl = parse_url($this->urlReferer);
- $port = @$parsedUrl['port'] ? ":{$parsedUrl['port']}" : '';
- $url = "{$parsedUrl['scheme']}://{$parsedUrl['host']}{$port}/";
- }
-
return $this->subject('Approval Request')
- ->from($user->email, $user->getFullNameAttribute())
->view('emails.sales.return.return-approval-request-single', [
'salesReturns' => $this->salesReturns,
'approver' => $this->approver,
'form' => $this->form,
- 'url' => @$url,
+ 'url' => @$url
]);
}
}
diff --git a/app/Model/Inventory/InventoryUsage/InventoryUsage.php b/app/Model/Inventory/InventoryUsage/InventoryUsage.php
index a98fa811e..3ab78d72a 100644
--- a/app/Model/Inventory/InventoryUsage/InventoryUsage.php
+++ b/app/Model/Inventory/InventoryUsage/InventoryUsage.php
@@ -150,17 +150,6 @@ public static function updateJournal($usage)
self::checkIsJournalBalance($usage);
}
- private static function checkIsItemQuantityOver($item, $itemModel, $inventoryUsage, $options = [
- 'expiry_date' => null,
- 'production_number' => null,
- ])
- {
- $stock = InventoryHelper::getCurrentStock($itemModel, $inventoryUsage->created_at, $inventoryUsage->warehouse, $options);
- if (abs($item['quantity']) > $stock) {
- throw new StockNotEnoughException($itemModel);
- }
- }
-
private static function mapItems($items, $inventoryUsage)
{
$array = [];
@@ -179,7 +168,6 @@ private static function mapItems($items, $inventoryUsage)
'expiry_date' => $dna['expiry_date'],
'production_number' => $dna['production_number'],
];
- self::checkIsItemQuantityOver($item, $itemModel, $inventoryUsage, $options);
$dnaItem = $item;
$dnaItem['quantity'] = $dna['quantity'];
@@ -190,8 +178,6 @@ private static function mapItems($items, $inventoryUsage)
}
}
} else {
- self::checkIsItemQuantityOver($item, $itemModel, $inventoryUsage);
-
array_push($array, $item);
}
}
diff --git a/app/Model/Sales/SalesInvoice/SalesInvoice.php b/app/Model/Sales/SalesInvoice/SalesInvoice.php
index 2e64a5b5e..5bf413f94 100644
--- a/app/Model/Sales/SalesInvoice/SalesInvoice.php
+++ b/app/Model/Sales/SalesInvoice/SalesInvoice.php
@@ -102,6 +102,11 @@ public function payments()
return $this->morphToMany(Payment::class, 'referenceable', 'payment_details')->active();
}
+ public function references()
+ {
+ return $this->hasMany(SalesInvoiceReference::class);
+ }
+
public function detachDownPayments()
{
$this->downPayments()->detach();
@@ -293,6 +298,18 @@ private static function setDownPaymentsDone($downPayments)
}
}
+ /**
+ * Get sales invoice reference.
+ */
+ public static function getAvailable($salesInvoice)
+ {
+ $available = $salesInvoice->amount;
+ foreach ($salesInvoice->references as $reference) {
+ $available = $available - $reference->amount;
+ }
+ return $available;
+ }
+
private static function updateJournal($salesInvoice)
{
/**
diff --git a/app/Model/Sales/SalesInvoice/SalesInvoiceReference.php b/app/Model/Sales/SalesInvoice/SalesInvoiceReference.php
new file mode 100644
index 000000000..04e1ae2b0
--- /dev/null
+++ b/app/Model/Sales/SalesInvoice/SalesInvoiceReference.php
@@ -0,0 +1,49 @@
+ 'double',
+ ];
+
+ public static function referenceableIsValid($value)
+ {
+ $referenceableTypes = [
+ SalesReturn::$morphName,
+ ];
+
+ return in_array($value, $referenceableTypes);
+ }
+
+ /**
+ * Get all of the owning referenceable models.
+ */
+ public function referenceable()
+ {
+ return $this->morphTo();
+ }
+
+ public function salesInvoice()
+ {
+ return $this->belongsTo(SalesInvoice::class);
+ }
+}
diff --git a/app/Model/Sales/SalesReturn/SalesReturn.php b/app/Model/Sales/SalesReturn/SalesReturn.php
index afbed9474..d7a105e0f 100644
--- a/app/Model/Sales/SalesReturn/SalesReturn.php
+++ b/app/Model/Sales/SalesReturn/SalesReturn.php
@@ -3,14 +3,19 @@
namespace App\Model\Sales\SalesReturn;
use App\Model\Form;
+use App\Model\Token;
use App\Model\TransactionModel;
use App\Model\Accounting\Journal;
use App\Model\Master\Item;
+use App\Model\Sales\SalesInvoice\SalesInvoice;
use App\Traits\Model\Sales\SalesReturnRelation;
use App\Traits\Model\Sales\SalesReturnJoin;
use Exception;
use App\Helpers\Inventory\InventoryHelper;
use App\Exceptions\IsReferencedException;
+use Illuminate\Support\Facades\Mail;
+use App\Mail\Sales\SalesReturnApprovalRequest;
+use App\Model\Sales\SalesInvoice\SalesInvoiceReference;
class SalesReturn extends TransactionModel
{
@@ -55,6 +60,7 @@ class SalesReturn extends TransactionModel
public static function create($data)
{
+ self::validate($data);
$salesReturn = new self;
$salesReturn->fill($data);
@@ -65,6 +71,8 @@ public static function create($data)
$salesReturn->save();
$salesReturn->items()->saveMany($items);
+
+ self::checkJournalBalance($salesReturn);
//$salesReturn->services()->saveMany($services);
$form = new Form;
@@ -73,6 +81,52 @@ public static function create($data)
return $salesReturn;
}
+ private static function validate($data)
+ {
+ $salesInvoice = SalesInvoice::where('id', $data['sales_invoice_id'])->first();
+ if ($salesInvoice->form->done === 1) {
+ throw new Exception('Sales return form already done', 422);
+ }
+
+ $subTotal = 0;
+ foreach ($data['items'] as $item) {
+ $total = round($item['quantity'] * ($item['price'] - $item['discount_value']), 12);
+ if ($total != round($item['total'], 10)) {
+ throw new Exception('total for item ' .$item['item_name']. ' should be ' .$total , 422);
+ }
+ $subTotal = $subTotal + $total;
+ }
+
+ if (round($data['sub_total'], 10) != round($subTotal, 10)) {
+ throw new Exception('sub total should be ' .$subTotal , 422);
+ }
+
+ $taxBase = $subTotal;
+ if (round($data['tax_base'], 10) != round($taxBase, 10)) {
+ throw new Exception('tax base should be ' .$taxBase , 422);
+ }
+
+ if ($data['type_of_tax'] != $salesInvoice->type_of_tax) {
+ throw new Exception('type of tax should be same with invoice' , 422);
+ }
+
+ $tax = round($taxBase * (10 / 110), 10);
+ if (round($data['tax'], 10) != $tax) {
+ throw new Exception('tax should be ' .$tax , 422);
+ }
+
+ $total = 0;
+ if ($data['type_of_tax'] === 'include') {
+ $total = $taxBase;
+ } else {
+ $total = $taxBase + $tax ;
+ }
+
+ if (round($data['amount'], 10) != round($total, 10)) {
+ throw new Exception('amount should be ' .$total , 422);
+ }
+ }
+
private static function mapItems($items)
{
return array_map(function ($item) {
@@ -154,6 +208,29 @@ public static function updateInventory($form, $salesReturn)
}
}
+ public static function checkJournalBalance($salesReturn) {
+ $ar = get_setting_journal('sales', 'account receivable');
+ $salesIncome = get_setting_journal('sales', 'sales income');
+
+ $credit = $salesReturn->amount;
+ $debit = $salesReturn->amount - $salesReturn->tax;
+ foreach ($salesReturn->items as $item) {
+ $amount = $item->item->cogs($item->item_id) * $item->quantity;
+ $debit = $debit + $amount;
+ $credit = $credit + $amount;
+ }
+
+ $debit = $debit + $salesReturn->tax;
+ if (round($debit, 10) != round($credit, 10)) {
+ throw new Exception('Journal not balance', 422);
+ }
+
+ return [
+ 'debit' => $debit,
+ 'credit' => $credit
+ ];
+ }
+
public static function updateJournal($salesReturn)
{
$accountReceivable = new Journal;
@@ -199,21 +276,94 @@ public static function updateJournal($salesReturn)
}
}
+ public static function updateSalesInvoiceReference($salesReturn)
+ {
+ $invoiceReference = new SalesInvoiceReference;
+ $invoiceReference->sales_invoice_id = $salesReturn->sales_invoice_id;
+ $invoiceReference->referenceable_id = $salesReturn->id;
+ $invoiceReference->referenceable_type = 'SalesReturn';
+ $invoiceReference->amount = $salesReturn->amount;
+ $invoiceReference->save();
+ }
+
public function isAllowedToUpdate()
{
$this->isNotReferenced();
+ $this->isNotDone();
}
public function isAllowedToDelete()
{
$this->isNotReferenced();
+ $this->isNotDone();
}
private function isNotReferenced()
{
// Check if not referenced by payments
if ($this->paymentCollections->count()) {
- throw new IsReferencedException('Cannot edit form because referenced by payment collection', $this->paymentCollections);
+ throw new IsReferencedException('form referenced by payment collection', $this->paymentCollections);
+ }
+ }
+
+ private function isNotDone()
+ {
+ if ($this->form->done === 1) {
+ throw new Exception('form already done', 422);
+ }
+ }
+
+ public static function sendApproval($salesReturns)
+ {
+ $salesReturnByApprovers = [];
+
+ $sendBy = tenant(auth()->user()->id);
+
+ $salesReturnByApprovers[$salesReturns->form->request_approval_to][] = $salesReturns;
+
+ foreach ($salesReturnByApprovers as $salesReturnByApprover) {
+ $approver = null;
+
+ $formStart = head($salesReturnByApprover)->form;
+ $formEnd = last($salesReturnByApprover)->form;
+
+ $form = [
+ 'number' => $formStart->number,
+ 'date' => $formStart->date,
+ 'created' => $formStart->created_at,
+ 'send_by' => $sendBy
+ ];
+
+ // loop each sales return by group approver
+ foreach ($salesReturnByApprover as $salesReturn) {
+ $salesReturn->action = 'create';
+
+ if(!$approver) {
+ $approver = $salesReturn->form->requestApprovalTo;
+ // create token based on request_approval_to
+ $approverToken = Token::where('user_id', $approver->id)->first();
+ if (!$approverToken) {
+ $approverToken = new Token();
+ $approverToken->user_id = $approver->id;
+ $approverToken->token = md5($approver->email.''.now());
+ $approverToken->save();
+ }
+
+ $approver->token = $approverToken->token;
+ }
+
+ if ($salesReturn->form->close_status === 0) $salesReturn->action = 'close';
+
+ if (
+ $salesReturn->form->cancellation_status === 0
+ && $salesReturn->form->close_status === null
+ ) {
+ $salesReturn->action = 'delete';
+ }
+ }
+
+ $approvalRequest = new SalesReturnApprovalRequest($salesReturnByApprover, $approver, (object) $form, $_SERVER['HTTP_REFERER']);
+ Mail::to([ $approver->email ])->queue($approvalRequest);
}
}
}
diff --git a/app/Services/Google/Drive.php b/app/Services/Google/Drive.php
index 279d037e1..6fa1de8e4 100644
--- a/app/Services/Google/Drive.php
+++ b/app/Services/Google/Drive.php
@@ -16,7 +16,7 @@ public function __construct()
// https://github.com/masbug/flysystem-google-drive-ext#using-with-laravel-framework
$this->service = new \Google\Service\Drive($this->client);
- $this->adapter = new \Masbug\Flysystem\GoogleDriveAdapter($this->service);
+ $this->adapter = new GoogleDriveAdapter($this->service);
$this->driver = new \League\Flysystem\Filesystem($this->adapter);
$this->disk = new \Illuminate\Filesystem\FilesystemAdapter($this->driver, $this->adapter);
}
diff --git a/app/Services/Google/GoogleDriveAdapter.php b/app/Services/Google/GoogleDriveAdapter.php
new file mode 100644
index 000000000..63660396e
--- /dev/null
+++ b/app/Services/Google/GoogleDriveAdapter.php
@@ -0,0 +1,2148 @@
+ 'drive',
+ 'useHasDir' => false,
+ 'useDisplayPaths' => true,
+ 'usePermanentDelete' => false,
+ 'publishPermission' => [
+ 'type' => 'anyone',
+ 'role' => 'reader',
+ 'withLink' => true
+ ],
+ 'appsExportMap' => [
+ 'application/vnd.google-apps.document' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.google-apps.spreadsheet' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.google-apps.drawing' => 'application/pdf',
+ 'application/vnd.google-apps.presentation' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/vnd.google-apps.script' => 'application/vnd.google-apps.script+json',
+ 'default' => 'application/pdf'
+ ],
+
+ 'parameters' => [],
+
+ 'teamDriveId' => null,
+
+ 'sanitize_chars' => [
+ // sanitize filename
+ // file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
+ // control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
+ // non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
+ // URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
+ // URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
+
+ // must not allow
+ '/', '\\', '?', '%', '*', ':', '|', '"', '<', '>',
+ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
+ '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F',
+ '\x7F', '\xA0', '\xAD',
+
+ // optional
+ '#', '@', '!', '$', '&', '\'', '+', ';', '=',
+ '^', '~', '`',
+ ],
+ 'sanitize_replacement_char' => '_'
+ ];
+
+ /**
+ * A comma-separated list of spaces to query
+ * Supported values are 'drive', 'appDataFolder' and 'photos'
+ *
+ * @var string
+ */
+ protected $spaces;
+
+ /**
+ * Root path
+ *
+ * @var string
+ */
+ protected $root;
+
+ /**
+ * Permission array as published item
+ *
+ * @var array
+ */
+ protected $publishPermission;
+
+ /**
+ * Cache of file objects
+ *
+ * @var array
+ */
+ private $cacheFileObjects = [];
+
+ /**
+ * Cache of hasDir
+ *
+ * @var array
+ */
+ private $cacheHasDirs = [];
+
+ /**
+ * Use hasDir function
+ *
+ * @var bool
+ */
+ private $useHasDir = false;
+
+ /**
+ * Permanent delete files and directories, avoid setTrashed
+ *
+ * @var bool
+ */
+ private $usePermanentDelete = false;
+
+ /**
+ * Options array
+ *
+ * @var array
+ */
+ private $options = [];
+
+ /**
+ * Using display paths instead of virtual IDs
+ *
+ * @var bool
+ */
+ private $useDisplayPaths = true;
+
+ /**
+ * Resolved root ID
+ *
+ * @var string
+ */
+ private $rootId = null;
+
+ /**
+ * Full path => virtual ID cache
+ *
+ * @var array
+ */
+ private $cachedPaths = [];
+
+ /**
+ * Recent virtual ID => file object requests cache
+ *
+ * @var array
+ */
+ private $requestedIds = [];
+
+ /**
+ * @var array Optional parameters sent with each request (see Google_Service_Resource var stackParameters and https://developers.google.com/analytics/devguides/reporting/core/v4/parameters)
+ */
+ private $optParams = [];
+
+ /**
+ * GoogleDriveAdapter constructor.
+ *
+ * @param Drive $service
+ * @param string|null $root
+ * @param array $options
+ */
+ public function __construct($service, $root = null, $options = [])
+ {
+ $this->service = $service;
+
+ $this->options = array_replace_recursive(static::$defaultOptions, $options);
+
+ $this->spaces = $this->options['spaces'];
+ $this->useHasDir = $this->options['useHasDir'];
+ $this->usePermanentDelete = $this->options['usePermanentDelete'];
+ $this->publishPermission = $this->options['publishPermission'];
+ $this->useDisplayPaths = $this->options['useDisplayPaths'];
+ $this->optParams = $this->cleanOptParameters($this->options['parameters']);
+
+ if ($root !== null) {
+ $root = trim($root, '/');
+ if ($root === '') {
+ $root = null;
+ }
+ }
+
+ if (isset($this->options['teamDriveId'])) {
+ $this->root = null;
+ $this->setTeamDriveId($this->options['teamDriveId']);
+ if ($this->useDisplayPaths && $root !== null) {
+ // get real root id
+ $this->root = $this->toSingleVirtualPath($root, false, true, true, true);
+
+ // reset cache
+ $this->rootId = $this->root;
+ $this->clearCache();
+ }
+ } else {
+ if (!$this->useDisplayPaths || $root === null) {
+ if ($root === null) {
+ $root = 'root';
+ }
+ $this->root = $root;
+ $this->setPathPrefix('');
+ } else {
+ $this->root = 'root';
+ $this->setPathPrefix('');
+
+ // get real root id
+ $this->root = $this->toSingleVirtualPath($root, false, true, true, true);
+
+ // reset cache
+ $this->rootId = $this->root;
+ $this->clearCache();
+ }
+ }
+ }
+
+ /**
+ * Gets the service
+ *
+ * @return Google\Service\Drive
+ */
+ public function getService()
+ {
+ $this->refreshToken();
+ return $this->service;
+ }
+
+ /**
+ * Allow to forcefully clear the cache to enable long running process
+ *
+ * @return void
+ */
+ public function clearCache()
+ {
+ $this->cachedPaths = [];
+ $this->requestedIds = [];
+ $this->cacheFileObjects = [];
+ $this->cacheHasDirs = [];
+ }
+
+ /**
+ * Allow to refresh tokens to enable long running process
+ *
+ * @return void
+ */
+ public function refreshToken()
+ {
+ $client = $this->service->getClient();
+ if ($client->isAccessTokenExpired()) {
+ if ($client->isUsingApplicationDefaultCredentials()) {
+ $client->fetchAccessTokenWithAssertion();
+ } else {
+ $refreshToken = $client->getRefreshToken();
+ if ($refreshToken) {
+ $client->fetchAccessTokenWithRefreshToken($refreshToken);
+ }
+ }
+ }
+ }
+
+ protected function cleanOptParameters($parameters)
+ {
+ $operations = ['files.copy', 'files.create', 'files.delete',
+ 'files.trash', 'files.get', 'files.list', 'files.update',
+ 'files.watch'];
+ $clean = [];
+
+ foreach ($operations as $operation) {
+ $clean[$operation] = [];
+ if (isset($parameters[$operation])) {
+ $clean[$operation] = $parameters[$operation];
+ }
+ }
+
+ foreach ($parameters as $key => $value) {
+ if (in_array($key, $operations)) {
+ unset($parameters[$key]);
+ }
+ }
+
+ foreach ($operations as $operation) {
+ $clean[$operation] = array_merge_recursive($parameters, $clean[$operation]);
+ }
+
+ return $clean;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($path, $contents, Config $config)
+ {
+ $updating = null;
+
+ if ($this->useDisplayPaths) {
+ try {
+ $virtual_path = $this->toVirtualPath($path, true, false);
+ $updating = true; // destination exists
+ } catch (FileNotFoundException $e) {
+ $updating = false;
+ [$parentDir, $fileName] = $this->splitPath($path, false);
+ $virtual_path = $this->toSingleVirtualPath($parentDir, false, true, true, true);
+ if ($virtual_path === '') {
+ $virtual_path = $fileName;
+ } else {
+ $virtual_path .= '/'.$fileName;
+ }
+ }
+ if ($updating && is_array($virtual_path)) {
+ // multiple destinations with the same display path -> remove all but the first created & the first gets replaced
+ if (count($virtual_path) > 1) {
+ // delete all but first
+ $this->delete_by_id(
+ array_map(
+ function ($p) {
+ return $this->splitPath($p, false)[1];
+ },
+ array_slice($virtual_path, 1)
+ )
+ );
+ }
+ $virtual_path = $virtual_path[0];
+ }
+ } else {
+ $virtual_path = $path;
+ }
+
+ return $this->upload($virtual_path, $contents, $config, $updating);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function writeStream($path, $resource, Config $config)
+ {
+ return $this->write($path, $resource, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update($path, $contents, Config $config)
+ {
+ return $this->write($path, $contents, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateStream($path, $resource, Config $config)
+ {
+ return $this->write($path, $resource, $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rename($path, $newpath)
+ {
+ $this->refreshToken();
+ if ($this->useDisplayPaths) {
+ $path = $this->toVirtualPath($path, true, true);
+ $newpathDir = self::dirname($newpath);
+ try {
+ $toPath = $this->toVirtualPath($newpathDir, false, true);
+ } catch (FileNotFoundException $e) {
+ if ($this->createDir($newpathDir, new Config(), true) === false) {
+ return false;
+ }
+ $toPath = $this->toVirtualPath($newpathDir, false, true);
+ }
+ if ($toPath === '') {
+ $toPath = $this->root;
+ }
+
+ [$oldParent, $fileId] = $this->splitPath($path);
+ $newParent = $toPath;
+ $newName = basename($newpath);
+ } else {
+ [$oldParent, $fileId] = $this->splitPath($path);
+ [$newParent, $newName] = $this->splitPath($newpath);
+ }
+
+ $file = new DriveFile();
+ $file->setName($newName);
+ $opts = [
+ 'fields' => self::FETCHFIELDS_GET
+ ];
+ if ($newParent !== $oldParent) {
+ $opts['addParents'] = $newParent;
+ if ($oldParent !== '') {
+ $opts['removeParents'] = $oldParent;
+ }
+ }
+
+ try {
+ $updatedFile = $this->service->files->update($fileId, $file, $this->applyDefaultParams($opts, 'files.update'));
+
+ $id = $updatedFile->getId();
+ if (isset($this->cacheHasDirs[$fileId])) {
+ $this->cacheHasDirs[$id] = $this->cacheHasDirs[$fileId];
+ }
+ $this->uncacheId($fileId);
+ $this->cacheFileObjects[$id] = $updatedFile;
+ $this->cacheObjects([$id => $updatedFile]);
+ $this->resetRequest([$oldParent, $newParent, $fileId, $id]);
+
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function copy($path, $newpath)
+ {
+ $this->refreshToken();
+ if ($this->useDisplayPaths) {
+ $srcId = $this->toVirtualPath($path, false, true);
+ $newpathDir = self::dirname($newpath);
+ $toPath = $this->toSingleVirtualPath($newpathDir, false, false, true, true);
+ if ($toPath === false) {
+ return false;
+ }
+ if ($toPath === '') {
+ $toPath = $this->root;
+ }
+ $newParentId = $toPath;
+ $fileName = basename($newpath);
+ } else {
+ [, $srcId] = $this->splitPath($path);
+ [$newParentId, $fileName] = $this->splitPath($newpath);
+ }
+
+ $file = new DriveFile();
+ $file->setName($fileName);
+ $file->setParents([
+ $newParentId
+ ]);
+
+ $newFile = $this->service->files->copy($srcId, $file, $this->applyDefaultParams([
+ 'fields' => self::FETCHFIELDS_GET
+ ], 'files.copy'));
+
+ if ($newFile instanceof DriveFile) {
+ $id = $newFile->getId();
+ $this->cacheFileObjects[$id] = $newFile;
+ $this->cacheObjects([$id => $newFile]);
+ if (isset($this->cacheHasDirs[$srcId])) {
+ $this->cacheHasDirs[$id] = $this->cacheHasDirs[$srcId];
+ }
+
+ if ($this->getRawVisibility($srcId) === AdapterInterface::VISIBILITY_PUBLIC) {
+ $this->publish($id);
+ } else {
+ $this->unPublish($id);
+ }
+ $this->resetRequest([$id, $newParentId]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Delete an array of google file ids
+ *
+ * @param string[]|string $ids
+ * @return bool
+ */
+ protected function delete_by_id($ids)
+ {
+ $this->refreshToken();
+ $deleted = false;
+ if (!is_array($ids)) {
+ $ids = [$ids];
+ }
+ foreach ($ids as $id) {
+ if ($id !== '' && ($file = $this->getFileObject($id))) {
+ if ($file->getParents()) {
+ if ($this->usePermanentDelete && $this->service->files->delete($id, $this->applyDefaultParams([], 'files.delete'))) {
+ $this->uncacheId($id);
+ $deleted = true;
+ } else {
+ if (!$this->usePermanentDelete) {
+ $file = new DriveFile();
+ $file->setTrashed(true);
+ if ($this->service->files->update($id, $file, $this->applyDefaultParams([], 'files.update'))) {
+ $this->uncacheId($id);
+ $deleted = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($path)
+ {
+ if ($path === '' || $path === '/') {
+ return false;
+ } // do not allow deleting root...
+
+ $deleted = false;
+ if ($this->useDisplayPaths) {
+ try {
+ $ids = $this->toVirtualPath($path, false);
+ $deleted = $this->delete_by_id($ids);
+ } catch (\Exception $e) {
+ //Unnecesary
+ }
+ } else {
+ if ($file = $this->getFileObject($path)) {
+ $deleted = $this->delete_by_id($file->getId());
+ }
+ }
+
+ if ($deleted) {
+ $this->resetRequest('', true);
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteDir($dirname)
+ {
+ return $this->delete($dirname);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDir($dirname, Config $config, $internalCall = false)
+ {
+ try {
+ $meta = $this->getMetadata($dirname);
+ } catch (FileNotFoundException $e) {
+ $meta = false;
+ }
+
+ if ($meta !== false) {
+ return [
+ 'path' => $meta['path'],
+ 'filename' => $meta['filename'],
+ 'extension' => $meta['extension']
+ ];
+ }
+
+ [$pdir, $name] = $this->splitPath($dirname, false);
+ if ($this->useDisplayPaths) {
+ if ($pdir !== $this->root) {
+ $pdir = $this->toSingleVirtualPath($pdir, false, false, true, true); // recursion!
+ if ($pdir === false) {
+ return false;
+ } // failed to create dirs
+ }
+ }
+
+ $folder = $this->createDirectory($name, $pdir !== '' ? basename($pdir) : $pdir);
+ if ($folder !== null) {
+ $itemId = $folder->getId();
+ $this->cacheFileObjects[$itemId] = $folder;
+ $this->cacheHasDirs[$itemId] = false;
+ $this->cacheObjects([$itemId => $folder]);
+ $path_parts = $this->splitFileExtension($name);
+ $result = [
+ 'path' => Util::normalizeDirname($pdir).'/'.($this->useDisplayPaths ? $name : $itemId),
+ 'filename' => $path_parts['filename'],
+ 'extension' => $path_parts['extension']
+ ];
+ return $result;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($path)
+ {
+ if ($this->useDisplayPaths) {
+ try {
+ $this->toVirtualPath($path, false);
+ return true;
+ } catch (FileNotFoundException $e) {
+ return false;
+ }
+ }
+ return ($this->getFileObject($path, true) instanceof DriveFile);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($path)
+ {
+ $this->refreshToken();
+ if ($this->useDisplayPaths) {
+ $fileId = $this->toVirtualPath($path, false, true);
+ } else {
+ [, $fileId] = $this->splitPath($path);
+ }
+ /** @var RequestInterface $response */
+ if (($response = $this->service->files->get(/** @scrutinizer ignore-type */ $fileId, $this->applyDefaultParams(['alt' => 'media'], 'files.get')))) {
+ return [
+ 'contents' => (string)$response->getBody()
+ ];
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readStream($path)
+ {
+ $this->refreshToken();
+ if ($this->useDisplayPaths) {
+ $path = $this->toVirtualPath($path, false, true);
+ }
+
+ $redirect = null;
+ if (func_num_args() > 1) {
+ $redirect = func_get_arg(1);
+ }
+
+ if (!$redirect) {
+ $redirect = [
+ 'cnt' => 0,
+ 'url' => '',
+ 'token' => '',
+ 'cookies' => []
+ ];
+ if (($file = $this->getFileObject($path))) {
+ if ($file->getMimeType() === self::DIRMIME) {
+ throw new FileNotFoundException($path);
+ }
+ $dlurl = $this->getDownloadUrl($file);
+ $client = $this->service->getClient();
+ /** @var array|string|object $token */
+ if ($client->isUsingApplicationDefaultCredentials()) {
+ $token = $client->fetchAccessTokenWithAssertion();
+ } else {
+ $token = $client->getAccessToken();
+ }
+ $access_token = '';
+ if (is_array($token)) {
+ if (empty($token['access_token']) && !empty($token['refresh_token'])) {
+ $token = $client->fetchAccessTokenWithRefreshToken();
+ }
+ $access_token = $token['access_token'];
+ } else {
+ if (($token = @json_decode($token))) {
+ $access_token = $token->access_token;
+ }
+ }
+ $redirect = [
+ 'cnt' => 0,
+ 'url' => '',
+ 'token' => $access_token,
+ 'cookies' => []
+ ];
+ }
+ } else {
+ if ($redirect['cnt'] > 5) {
+ return false;
+ }
+ $dlurl = $redirect['url'];
+ $redirect['url'] = '';
+ $access_token = $redirect['token'];
+ }
+
+ if (!empty($dlurl)) {
+ $url = parse_url($dlurl);
+ $cookies = [];
+ if ($redirect['cookies']) {
+ foreach ($redirect['cookies'] as $d => $c) {
+ if (strpos($url['host'], $d) !== false) {
+ $cookies[] = $c;
+ }
+ }
+ }
+ if (!empty($access_token)) {
+ $query = isset($url['query']) ? '?'.$url['query'] : '';
+ $stream = stream_socket_client('ssl://'.$url['host'].':443');
+ stream_set_timeout($stream, 300);
+ fwrite($stream, "GET {$url['path']}{$query} HTTP/1.1\r\n");
+ fwrite($stream, "Host: {$url['host']}\r\n");
+ fwrite($stream, "Authorization: Bearer {$access_token}\r\n");
+ fwrite($stream, "Connection: Close\r\n");
+ if ($cookies) {
+ fwrite($stream, 'Cookie: '.implode('; ', $cookies)."\r\n");
+ }
+ fwrite($stream, "\r\n");
+ while (($res = trim(fgets($stream))) !== '') {
+ // find redirect
+ if (preg_match('/^Location: (.+)$/', $res, $m)) {
+ $redirect['url'] = $m[1];
+ }
+ // fetch cookie
+ if (strpos($res, 'Set-Cookie:') === 0) {
+ $domain = $url['host'];
+ if (preg_match('/^Set-Cookie:(.+)(?:domain=\s*([^ ;]+))?/i', $res, $c1)) {
+ if (!empty($c1[2])) {
+ $domain = trim($c1[2]);
+ }
+ if (preg_match('/([^ ]+=[^;]+)/', $c1[1], $c2)) {
+ $redirect['cookies'][$domain] = $c2[1];
+ }
+ }
+ }
+ }
+ if ($redirect['url']) {
+ $redirect['cnt']++;
+ fclose($stream);
+ return $this->readStream($path, $redirect);
+ }
+ return compact('stream');
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listContents($directory = '', $recursive = false)
+ {
+ $this->refreshToken();
+ if ($this->useDisplayPaths) {
+ $time = microtime(true);
+
+ try {
+ $vp = $this->toVirtualPath($directory);
+ } catch (\Exception $e) {
+ $vp = [];
+ }
+ $elapsed = (microtime(true) - $time) * 1000.0;
+ if (!is_array($vp)) {
+ $vp = [$vp];
+ }
+
+ $items = [];
+ foreach ($vp as $path) {
+ if (DEBUG_ME) {
+ echo 'Converted display path to virtual path ['.number_format($elapsed, 1).'ms]: '.$path."\n";
+ }
+ $items = array_merge($items, array_values($this->getItems($path, $recursive)));
+ }
+ } else {
+ $items = array_values($this->getItems($directory, $recursive));
+ }
+ return $items;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata($path)
+ {
+ if ($this->useDisplayPaths) {
+ $path = $this->toVirtualPath($path, true, true);
+ }
+ if (($obj = $this->getFileObject($path, true))) {
+ if ($obj instanceof DriveFile) {
+ return $this->normaliseObject($obj, self::dirname($path));
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize($path)
+ {
+ $meta = $this->getMetadata($path);
+ return ($meta && isset($meta['size'])) ? $meta : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMimetype($path)
+ {
+ $meta = $this->getMetadata($path);
+ return ($meta && isset($meta['mimetype'])) ? $meta : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTimestamp($path)
+ {
+ $meta = $this->getMetadata($path);
+ return ($meta && isset($meta['timestamp'])) ? $meta : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setVisibility($path, $visibility, $internalCall = false)
+ {
+ if ($this->useDisplayPaths && !$internalCall) {
+ try {
+ $path = $this->toVirtualPath($path, false, true);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+ $result = ($visibility === AdapterInterface::VISIBILITY_PUBLIC) ? $this->publish($path) : $this->unPublish($path);
+
+ if ($result) {
+ return compact('path', 'visibility');
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVisibility($path)
+ {
+ if ($this->useDisplayPaths) {
+ $path = $this->toVirtualPath($path, false, true);
+ }
+ return [
+ 'visibility' => $this->getRawVisibility($path)
+ ];
+ }
+
+ // /////////////////- ORIGINAL METHODS -///////////////////
+
+ /**
+ * Get contents parmanent URL
+ *
+ * @param string $path itemId path
+ * @param string $path itemId path
+ */
+ public function getUrl($path)
+ {
+ if ($this->useDisplayPaths) {
+ $path = $this->toVirtualPath($path, false, true);
+ }
+ if ($this->publish(/** @scrutinizer ignore-type */ $path)) {
+ $obj = $this->getFileObject($path);
+ if (($url = $obj->getWebContentLink())) {
+ return str_replace('export=download', 'export=media', $url);
+ }
+ if (($url = $obj->getWebViewLink())) {
+ return $url;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Has child directory
+ *
+ * @param string $path itemId path
+ * @return array
+ */
+ public function hasDir($path)
+ {
+ $meta = $this->getMetadata($path);
+ return ($meta && isset($meta['hasdir'])) ? $meta : [
+ 'hasdir' => true
+ ];
+ }
+
+ /**
+ * Do cache cacheHasDirs with batch request
+ *
+ * @param array $targets [[path => id],...]
+ * @param array $object
+ * @return array
+ */
+ protected function setHasDir($targets, $object)
+ {
+ $this->refreshToken();
+ $service = $this->service;
+ $client = $service->getClient();
+ $gFiles = $service->files;
+
+ $opts = [
+ 'pageSize' => 1,
+ 'orderBy' => 'folder,modifiedTime,name',
+ ];
+
+ $paths = [];
+ $client->setUseBatch(true);
+ $batch = $service->createBatch();
+ $i = 0;
+ foreach ($targets as $id) {
+ $opts['q'] = sprintf('trashed = false and "%s" in parents and mimeType = "%s"', $id, self::DIRMIME);
+ /** @var RequestInterface $request */
+ $request = $gFiles->listFiles($this->applyDefaultParams($opts, 'files.list'));
+ $key = ++$i;
+ $batch->add($request, (string)$key);
+ $paths['response-'.$key] = $id;
+ }
+ $results = $batch->execute();
+ foreach ($results as $key => $result) {
+ if ($result instanceof FileList) {
+ $object[$paths[$key]]['hasdir'] = $this->cacheHasDirs[$paths[$key]] = (bool)$result->getFiles();
+ }
+ }
+ $client->setUseBatch(false);
+ return $object;
+ }
+
+ /**
+ * Get the object permissions presented as a visibility.
+ *
+ * @param string $path itemId path
+ * @return string
+ */
+ protected function getRawVisibility($path)
+ {
+ $file = $this->getFileObject($path);
+ $permissions = $file->getPermissions();
+ $visibility = AdapterInterface::VISIBILITY_PRIVATE;
+ foreach ($permissions as $permission) {
+ if ($permission->type === $this->publishPermission['type'] && $permission->role === $this->publishPermission['role']) {
+ $visibility = AdapterInterface::VISIBILITY_PUBLIC;
+ break;
+ }
+ }
+ return $visibility;
+ }
+
+ /**
+ * Publish specified path item
+ *
+ * @param string $path itemId path
+ * @return bool
+ */
+ protected function publish($path)
+ {
+ $this->refreshToken();
+ if (($file = $this->getFileObject($path))) {
+ if ($this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC) {
+ return true;
+ }
+ try {
+ $new_permission = new Permission($this->publishPermission);
+ if ($permission = $this->service->permissions->create($file->getId(), $new_permission, $this->applyDefaultParams([], 'files.create'))) {
+ $file->setPermissions([$permission]);
+ return true;
+ }
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Un-publish specified path item
+ *
+ * @param string $path itemId path
+ * @return bool
+ */
+ protected function unPublish($path)
+ {
+ $this->refreshToken();
+ if (($file = $this->getFileObject($path))) {
+ $permissions = $file->getPermissions();
+ try {
+ foreach ($permissions as $permission) {
+ if ($permission->type === $this->publishPermission['type'] && $permission->role === $this->publishPermission['role'] && !empty($file->getId())) {
+ $this->service->permissions->delete($file->getId(), $permission->getId(), $this->applyDefaultParams([], 'files.trash'));
+ }
+ }
+ $file->setPermissions([]);
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Path splits to dirId, fileId or newName
+ *
+ * @param string $path
+ * @param bool $getParentId True => return only parent id, False => return full path (basically the same as dirname($path))
+ * @return array [ $dirId , $fileId|newName ]
+ */
+ protected function splitPath($path, $getParentId = true)
+ {
+ if ($path === '' || $path === '/') {
+ $fileName = $this->root;
+ $dirName = '';
+ } else {
+ $paths = explode('/', $path);
+ $fileName = array_pop($paths);
+ if ($getParentId) {
+ $dirName = $paths ? array_pop($paths) : '';
+ } else {
+ $dirName = implode('/', $paths);
+ }
+ if ($dirName === '') {
+ $dirName = $this->root;
+ }
+ }
+ return [
+ $dirName,
+ $fileName
+ ];
+ }
+
+ /**
+ * Item name splits to filename and extension
+ * This function supported include '/' in item name
+ *
+ * @param string $name
+ * @return array [ 'filename' => $filename , 'extension' => $extension ]
+ */
+ protected function splitFileExtension($name)
+ {
+ $name_parts = explode('.', $name);
+ $extension = isset($name_parts[1]) ? array_pop($name_parts) : '';
+ $filename = implode('.', $name_parts);
+ return compact('filename', 'extension');
+ }
+
+ /**
+ * Get normalised files array from DriveFile
+ *
+ * @param DriveFile $object
+ * @param string $dirname Parent directory itemId path
+ * @return array Normalised files array
+ */
+ protected function normaliseObject(DriveFile $object, $dirname)
+ {
+ $id = $object->getId();
+ $path_parts = $this->splitFileExtension($object->getName());
+ $result = ['id' => $id, 'visibility' => AdapterInterface::VISIBILITY_PRIVATE];
+ $result['type'] = $object->mimeType === self::DIRMIME ? 'dir' : 'file';
+ $permissions = $object->getPermissions();
+ try {
+ foreach ($permissions as $permission) {
+ if ($permission->type === $this->publishPermission['type'] && $permission->role === $this->publishPermission['role']) {
+ $result['visibility'] = AdapterInterface::VISIBILITY_PUBLIC;
+ break;
+ }
+ }
+ } catch (\Exception $e) {
+ // Unnecesary
+ }
+ if ($this->useDisplayPaths) {
+ $result['virtual_path'] = ($dirname ? ($dirname.'/') : '').$id;
+ $result['display_path'] = $this->toDisplayPath($result['virtual_path']);
+
+ $result['path'] = $result['display_path'];
+ } else {
+ $result['virtual_path'] = ($dirname ? ($dirname.'/') : '').$id;
+ $result['display_path'] = $this->toDisplayPath($result['virtual_path']);
+
+ $result['path'] = $result['virtual_path'];
+ }
+
+ $result['filename'] = $path_parts['filename'];
+ $result['extension'] = $path_parts['extension'];
+ $result['timestamp'] = strtotime($object->getModifiedTime());
+ if ($result['type'] === 'file') {
+ $result['mimetype'] = $object->mimeType;
+ $result['size'] = (int)$object->getSize();
+ }
+ if ($result['type'] === 'dir') {
+ $result['size'] = 0;
+ if ($this->useHasDir) {
+ $result['hasdir'] = isset($this->cacheHasDirs[$id]) ? $this->cacheHasDirs[$id] : false;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Get items array of target dirctory
+ *
+ * @param string $dirname itemId path
+ * @param bool $recursive
+ * @param int $maxResults
+ * @param string $query
+ * @return array Items array
+ */
+ protected function getItems($dirname, $recursive = false, $maxResults = 0, $query = '')
+ {
+ $this->refreshToken();
+ [, $itemId] = $this->splitPath($dirname);
+
+ $maxResults = min($maxResults, 1000);
+ $results = [];
+ $parameters = [
+ 'pageSize' => $maxResults ?: 1000,
+ 'fields' => self::FETCHFIELDS_LIST,
+ 'orderBy' => 'folder,modifiedTime,name',
+ 'spaces' => $this->spaces,
+ 'q' => sprintf('trashed = false and "%s" in parents', $itemId)
+ ];
+ if ($query) {
+ $parameters['q'] .= ' and ('.$query.')';
+ }
+ $pageToken = null;
+ $gFiles = $this->service->files;
+ $this->cacheHasDirs[$itemId] = false;
+ $setHasDir = [];
+
+ do {
+ try {
+ if ($pageToken) {
+ $parameters['pageToken'] = $pageToken;
+ }
+ $fileObjs = $gFiles->listFiles($this->applyDefaultParams($parameters, 'files.list'));
+ if ($fileObjs instanceof FileList) {
+ foreach ($fileObjs as $obj) {
+ $id = $obj->getId();
+ $this->cacheFileObjects[$id] = $obj;
+ $result = $this->normaliseObject($obj, $dirname);
+ $results[$id] = $result;
+ if ($result['type'] === 'dir') {
+ if ($this->useHasDir) {
+ $setHasDir[$id] = $id;
+ }
+ if ($this->cacheHasDirs[$itemId] === false) {
+ $this->cacheHasDirs[$itemId] = true;
+ unset($setHasDir[$itemId]);
+ }
+ if ($recursive) {
+ $results = array_merge($results, $this->getItems($result['virtual_path'], true, $maxResults, $query));
+ }
+ }
+ }
+ $pageToken = $fileObjs->getNextPageToken();
+ } else {
+ $pageToken = null;
+ }
+ } catch (\Exception $e) {
+ $pageToken = null;
+ }
+ } while ($pageToken && $maxResults === 0);
+
+ if ($setHasDir) {
+ $results = $this->setHasDir($setHasDir, $results);
+ }
+ return array_values($results);
+ }
+
+ /**
+ * Get file object DriveFile
+ *
+ * @param string $path itemId path
+ * @param bool $checkDir do check hasdir
+ * @return DriveFile|null
+ */
+ public function getFileObject($path, $checkDir = false)
+ {
+ [, $itemId] = $this->splitPath($path);
+ if (isset($this->cacheFileObjects[$itemId])) {
+ return $this->cacheFileObjects[$itemId];
+ }
+ $this->refreshToken();
+ $service = $this->service;
+ $client = $service->getClient();
+
+ $client->setUseBatch(true);
+ try {
+ $batch = $service->createBatch();
+
+ $opts = [
+ 'fields' => self::FETCHFIELDS_GET
+ ];
+
+ /** @var RequestInterface $request */
+ $request = $this->service->files->get($itemId, $opts);
+ $batch->add($request, 'obj');
+
+ if ($checkDir && $this->useHasDir) {
+ /** @var RequestInterface $request */
+ $request = $service->files->listFiles($this->applyDefaultParams([
+ 'pageSize' => 1,
+ 'orderBy' => 'folder,modifiedTime,name',
+ 'q' => sprintf('trashed = false and "%s" in parents and mimeType = "%s"', $itemId, self::DIRMIME)
+ ], 'files.list'));
+
+ $batch->add($request, 'hasdir');
+ }
+ $results = array_values($batch->execute());
+
+ [$fileObj, $hasdir] = array_pad($results, 2, null);
+ } finally {
+ $client->setUseBatch(false);
+ }
+
+ if ($fileObj instanceof DriveFile) {
+ if ($hasdir && $fileObj->mimeType === self::DIRMIME) {
+ if ($hasdir instanceof FileList) {
+ $this->cacheHasDirs[$fileObj->getId()] = (bool)$hasdir->getFiles();
+ }
+ }
+ } else {
+ $fileObj = null;
+ }
+
+ if ($fileObj !== null) {
+ $this->cacheFileObjects[$itemId] = $fileObj;
+ $this->cacheObjects([$itemId => $fileObj]);
+ }
+
+ return $fileObj;
+ }
+
+ /**
+ * Get download url
+ *
+ * @param DriveFile $file
+ * @return string|false
+ */
+ protected function getDownloadUrl($file)
+ {
+ if (strpos($file->mimeType, 'application/vnd.google-apps') !== 0) {
+ $params = $this->applyDefaultParams(['alt' => 'media'], 'files.get');
+ return 'https://www.googleapis.com/drive/v3/files/'.$file->getId().'?'.http_build_query($params);
+ }
+
+ $mimeMap = $this->options['appsExportMap'];
+ if (isset($mimeMap[$file->getMimeType()])) {
+ $mime = $mimeMap[$file->getMimeType()];
+ } else {
+ $mime = $mimeMap['default'];
+ }
+ $mime = rawurlencode($mime);
+
+ $params = $this->applyDefaultParams(['mimeType' => $mime], 'files.get');
+ return 'https://www.googleapis.com/drive/v3/files/'.$file->getId().'/export?'.http_build_query($params);
+ }
+
+ /**
+ * Create directory
+ *
+ * @param string $name
+ * @param string $parentId
+ * @return DriveFile|null
+ */
+ protected function createDirectory($name, $parentId)
+ {
+ $this->refreshToken();
+ $file = new DriveFile();
+ $file->setName($name);
+ $file->setParents([
+ $parentId
+ ]);
+ $file->setMimeType(self::DIRMIME);
+
+ $obj = $this->service->files->create($file, $this->applyDefaultParams([
+ 'fields' => self::FETCHFIELDS_GET
+ ], 'files.create'));
+ $this->resetRequest($parentId);
+
+ return ($obj instanceof DriveFile) ? $obj : null;
+ }
+
+ /**
+ * Upload|Update item
+ *
+ * @param string $path
+ * @param string|resource $contents
+ * @param Config $config
+ * @param bool|null $updating If null then we check for existence of the file
+ * @return array|false item info array
+ */
+ protected function upload($path, $contents, Config $config, $updating = null)
+ {
+ $this->refreshToken();
+ [$parentId, $fileName] = $this->splitPath($path);
+ $mime = $config->get('mimetype');
+ $file = new DriveFile();
+
+ if ($updating === null || $updating === true) {
+ $srcFile = $this->getFileObject($path);
+ $updating = $srcFile !== null;
+ } else {
+ $srcFile = null;
+ }
+ if (!$updating) {
+ $file->setName($fileName);
+ $file->setParents([
+ $parentId
+ ]);
+ }
+
+ if (!$mime) {
+ $mime = Util::guessMimeType($fileName, is_string($contents) ? $contents : '');
+ if (empty($mime)) {
+ $mime = 'application/octet-stream';
+ }
+ }
+ $file->setMimeType($mime);
+
+ /** @var StreamInterface $stream */
+ if (function_exists('\GuzzleHttp\Psr7\stream_for')) {
+ $stream = \GuzzleHttp\Psr7\stream_for($contents);
+ } else {
+ $stream = \GuzzleHttp\Psr7\Utils::streamFor($contents);
+ }
+ $size = $stream->getSize();
+
+ if ($size <= self::MAX_CHUNK_SIZE) {
+ // one shot upload
+ $params = [
+ 'data' => $stream,
+ 'uploadType' => 'media',
+ 'fields' => self::FETCHFIELDS_GET
+ ];
+
+ if (!$updating) {
+ $obj = $this->service->files->create($file, $this->applyDefaultParams($params, 'files.create'));
+ } else {
+ $obj = $this->service->files->update($srcFile->getId(), $file, $this->applyDefaultParams($params, 'files.update'));
+ }
+ } else {
+ // chunked upload
+ $client = $this->service->getClient();
+
+ $params = [
+ 'fields' => self::FETCHFIELDS_GET
+ ];
+
+ $client->setDefer(true);
+ if (!$updating) {
+ /** @var RequestInterface $request */
+ $request = $this->service->files->create($file, $this->applyDefaultParams($params, 'files.create'));
+ } else {
+ /** @var RequestInterface $request */
+ $request = $this->service->files->update($srcFile->getId(), $file, $this->applyDefaultParams($params, 'files.update'));
+ }
+
+ $media = new StreamableUpload($client, $request, $mime, $stream, true, self::MAX_CHUNK_SIZE);
+ $media->setFileSize($size);
+ do {
+ if (DEBUG_ME) {
+ echo "* Uploading next chunk.\n";
+ }
+ $status = $media->nextChunk();
+ } while ($status === false);
+
+ // The final value of $status will be the data from the API for the object that has been uploaded.
+ if ($status !== false) {
+ $obj = $status;
+ }
+
+ $client->setDefer(false);
+ }
+
+ $this->resetRequest($parentId);
+
+ if (isset($obj) && $obj instanceof DriveFile) {
+ $this->cacheFileObjects[$obj->getId()] = $obj;
+ $this->cacheObjects([$obj->getId() => $obj]);
+ $result = $this->normaliseObject($obj, self::dirname($path));
+
+ if (($visibility = $config->get('visibility'))) {
+ if ($this->setVisibility($result['virtual_path'], $visibility, true)) {
+ $result['visibility'] = $visibility;
+ }
+ }
+
+ return $result;
+ }
+ return false;
+ }
+
+ /**
+ * @param array $ids
+ * @param bool $checkDir
+ * @return array
+ */
+ protected function getObjects($ids, $checkDir = false)
+ {
+ if ($checkDir && !$this->useHasDir) {
+ $checkDir = false;
+ }
+
+ $fetch = [];
+ foreach ($ids as $itemId) {
+ if (!isset($this->cacheFileObjects[$itemId])) {
+ $fetch[$itemId] = null;
+ }
+ }
+ if (!empty($fetch) || $checkDir) {
+ $this->refreshToken();
+ $service = $this->service;
+ $client = $service->getClient();
+
+ $client->setUseBatch(true);
+ try {
+ $batch = $service->createBatch();
+
+ $opts = [
+ 'fields' => self::FETCHFIELDS_GET
+ ];
+
+ $count = 0;
+ if (!$this->rootId) {
+ /** @var RequestInterface $request */
+ $request = $this->service->files->get($this->root, $this->applyDefaultParams($opts, 'files.get'));
+ $batch->add($request, 'rootdir');
+ $count++;
+ }
+
+ $results = [];
+ foreach ($fetch as $itemId => $value) {
+ if (DEBUG_ME) {
+ echo "*** FETCH *** $itemId\n";
+ }
+
+ /** @var RequestInterface $request */
+ $request = $this->service->files->get($itemId, $opts);
+ $batch->add($request, $itemId);
+ $count++;
+
+ if ($checkDir) {
+ /** @var RequestInterface $request */
+ $request = $service->files->listFiles($this->applyDefaultParams([
+ 'pageSize' => 1,
+ 'orderBy' => 'folder,modifiedTime,name',
+ 'q' => sprintf('trashed = false and "%s" in parents and mimeType = "%s"', $itemId, self::DIRMIME)
+ ], 'files.list'));
+ $batch->add($request, 'hasdir-'.$itemId);
+ $count++;
+ }
+
+ if ($count > 90) {
+ // batch requests are limited to 100 calls in a single batch request
+ $results[] = $batch->execute();
+ $batch = $service->createBatch();
+ $count = 0;
+ }
+ }
+ if ($count > 0) {
+ $results[] = $batch->execute();
+ }
+ if (!empty($results)) {
+ $results = array_merge(...$results);
+ }
+
+ foreach ($results as $key => $value) {
+ if ($value instanceof DriveFile) {
+ $itemId = $value->getId();
+ $this->cacheFileObjects[$itemId] = $value;
+ if (!$this->rootId && strcmp($key, 'response-rootdir') === 0) {
+ $this->rootId = $itemId;
+ }
+ } else {
+ if ($checkDir && $value instanceof FileList) {
+ if (strncmp($key, 'response-hasdir-', 16) === 0) {
+ $key = substr($key, 16);
+ if (isset($this->cacheFileObjects[$key]) && $this->cacheFileObjects[$key]->mimeType === self::DIRMIME) {
+ $this->cacheHasDirs[$key] = (bool)$value->getFiles();
+ }
+ }
+ }
+ }
+ }
+
+ $this->cacheObjects($results);
+ } finally {
+ $client->setUseBatch(false);
+ }
+ }
+
+ $objects = [];
+ foreach ($ids as $itemId) {
+ $objects[$itemId] = isset($this->cacheFileObjects[$itemId]) ? $this->cacheFileObjects[$itemId] : null;
+ }
+ return $objects;
+ }
+
+ protected function buildPathFromCacheFileObjects($lastItemId)
+ {
+ $complete_paths = [];
+ $itemIds = [$lastItemId];
+ $paths = ['' => ''];
+ $is_first = true;
+ while (!empty($itemIds)) {
+ $new_itemIds = [];
+ $new_paths = [];
+ foreach ($itemIds as $itemId) {
+ if (empty($this->cacheFileObjects[$itemId])) {
+ continue;
+ }
+
+ /* @var DriveFile $obj */
+ $obj = $this->cacheFileObjects[$itemId];
+ $parents = $obj->getParents();
+
+ foreach ($paths as $id => $path) {
+ if ($is_first) {
+ $is_first = false;
+ $new_path = $this->sanitizeFilename($obj->getName());
+ $id = $itemId;
+ } else {
+ $new_path = $this->sanitizeFilename($obj->getName()).'/'.$path;
+ }
+
+ if ($this->rootId === $itemId) {
+ if (!empty($path)) {
+ $complete_paths[$id] = $path;
+ } // this path is complete...don't include drive name
+ } else {
+ if (!empty($parents)) {
+ $new_paths[$id] = $new_path;
+ }
+ }
+ }
+
+ if (!empty($parents)) {
+ $new_itemIds[] = (array)($obj->getParents());
+ }
+ }
+ $paths = $new_paths;
+ $itemIds = !empty($new_itemIds) ? array_merge(...$new_itemIds) : [];
+ }
+ return $complete_paths;
+ }
+
+ public function uncacheFolder($path)
+ {
+ if ($this->useDisplayPaths) {
+ try {
+ $path_id = $this->getCachedPathId($path);
+ if (is_array($path_id) && !empty($path_id[0] ?? null)) {
+ $this->uncacheId($path_id[0]);
+ }
+ } catch (FileNotFoundException $e) {
+ // unnecesary
+ }
+ } else {
+ $this->uncacheId($path);
+ }
+ }
+
+ protected function uncacheId($id)
+ {
+ if (empty($id)) {
+ return;
+ }
+ $basePath = null;
+ foreach ($this->cachedPaths as $path => $itemId) {
+ if ($itemId === $id) {
+ $basePath = (string)$path;
+ break;
+ }
+ }
+ if ($basePath) {
+ foreach ($this->cachedPaths as $path => $itemId) {
+ if (strlen((string)$path) >= strlen($basePath) && strncmp((string)$path, $basePath, strlen($basePath)) === 0) {
+ unset($this->cachedPaths[$path]);
+ }
+ }
+ }
+
+ unset($this->cacheFileObjects[$id], $this->cacheHasDirs[$id]);
+ }
+
+ protected function cacheObjects($objects)
+ {
+ foreach ($objects as $key => $value) {
+ if ($value instanceof DriveFile) {
+ $complete_paths = $this->buildPathFromCacheFileObjects($value->getId());
+ foreach ($complete_paths as $itemId => $path) {
+ if (DEBUG_ME) {
+ echo 'Complete path: '.$path.' ['.$itemId."]\n";
+ }
+
+ if (!isset($this->cachedPaths[$path])) {
+ $this->cachedPaths[$path] = $itemId;
+ } else {
+ if (!is_array($this->cachedPaths[$path])) {
+ if ($itemId !== $this->cachedPaths[$path]) {
+ // convert to array
+ $this->cachedPaths[$path] = [
+ $this->cachedPaths[$path],
+ $itemId
+ ];
+
+ if (DEBUG_ME) {
+ echo 'Caching [DUP]: '.$path.' => '.$itemId."\n";
+ }
+ }
+ } else {
+ if (!in_array($itemId, $this->cachedPaths[$path])) {
+ array_push($this->cachedPaths[$path], $itemId);
+ if (DEBUG_ME) {
+ echo 'Caching [DUP]: '.$path.' => '.$itemId."\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected function indexString($str, $ch = '/')
+ {
+ $indices = [];
+ for ($i = 0, $len = strlen($str); $i < $len; $i++) {
+ if ($str[$i] === $ch) {
+ $indices[] = $i;
+ }
+ }
+ return $indices;
+ }
+
+ protected function getCachedPathId($path, $indices = null)
+ {
+ $pathLen = strlen($path);
+ if ($indices === null) {
+ $indices = $this->indexString($path, '/');
+ $indices[] = $pathLen;
+ }
+
+ $maxLen = 0;
+ $itemId = null;
+ $pathMatch = null;
+
+ foreach ($this->cachedPaths as $pathFrag => $id) {
+ $pathFrag = (string)$pathFrag;
+ $len = strlen($pathFrag);
+ if ($len > $pathLen || $len < $maxLen || !in_array($len, $indices)) {
+ continue;
+ }
+
+ if (strncmp($pathFrag, $path, $len) === 0) {
+ if ($len === $pathLen) {
+ return [$id, $pathFrag];
+ } // we found a perfect match
+
+ $maxLen = $len;
+ $itemId = $id;
+ $pathMatch = $pathFrag;
+ }
+ }
+
+ // we found a partial match or none at all
+ return [$itemId, $pathMatch];
+ }
+
+ protected function getPathToIndex($path, $i, $indices)
+ {
+ if ($i < 0) {
+ return '';
+ }
+ if (!isset($indices[$i]) || !isset($indices[$i + 1])) {
+ return $path;
+ }
+ return substr($path, 0, $indices[$i]);
+ }
+
+ protected function getToken($path, $i, $indices)
+ {
+ if ($i < 0 || !isset($indices[$i])) {
+ return '';
+ }
+ $start = $i > 0 ? $indices[$i - 1] + 1 : 0;
+ return substr($path, $start, isset($indices[$i]) ? $indices[$i] - $start : null);
+ }
+
+ protected function cachePaths($displayPath, $i, $indices, $parentItemId)
+ {
+ $nextItemId = $parentItemId;
+ for ($count = count($indices); $i < $indices; $i++) {
+ $token = $this->getToken($displayPath, $i, $indices);
+ if (empty($token) && $token !== '0') {
+ return;
+ }
+ $basePath = $this->getPathToIndex($displayPath, $i - 2, $indices);
+ if (!empty($basePath)) {
+ $basePath .= '/';
+ }
+
+ if ($nextItemId === null) {
+ return;
+ }
+
+ $is_last = $i === $count - 1;
+
+ // search only for directories unless it's the last token
+ if (!is_array($nextItemId)) {
+ $nextItemId = [$nextItemId];
+ }
+
+ $items = [];
+ foreach ($nextItemId as $id) {
+ if (!$this->canRequest($id, $is_last)) {
+ continue;
+ }
+ $this->markRequest($id, $is_last);
+ if (DEBUG_ME) {
+ echo 'New req: '.$id;
+ }
+ $items[] = $this->getItems($id, false, 0, $is_last ? '' : 'mimeType = "'.self::DIRMIME.'"');
+ if (DEBUG_ME) {
+ echo " ...done\n";
+ }
+ }
+ if (!empty($items)) {
+ /** @noinspection SlowArrayOperationsInLoopInspection */
+ $items = array_merge(...$items);
+ }
+
+ $nextItemId = null;
+ foreach ($items as $item) {
+ $itemId = basename($item['virtual_path']);
+ $fullPath = $basePath.$item['display_path'];
+
+ // update cache
+ if (!isset($this->cachedPaths[$fullPath])) {
+ $this->cachedPaths[$fullPath] = $itemId;
+ if (DEBUG_ME) {
+ echo 'Caching: '.$fullPath.' => '.$itemId."\n";
+ }
+ } else {
+ if (!is_array($this->cachedPaths[$fullPath])) {
+ if ($itemId !== $this->cachedPaths[$fullPath]) {
+ // convert to array
+ $this->cachedPaths[$fullPath] = [
+ $this->cachedPaths[$fullPath],
+ $itemId
+ ];
+
+ if (DEBUG_ME) {
+ echo 'Caching [DUP]: '.$fullPath.' => '.$itemId."\n";
+ }
+ }
+ } else {
+ if (!in_array($itemId, $this->cachedPaths[$fullPath])) {
+ $this->cachedPaths[$fullPath][] = $itemId;
+ if (DEBUG_ME) {
+ echo 'Caching [DUP]: '.$fullPath.' => '.$itemId."\n";
+ }
+ }
+ }
+ }
+
+ if (basename($item['display_path']) === $token) {
+ $nextItemId = $this->cachedPaths[$fullPath];
+ } // found our token
+ }
+ }
+ }
+
+ /**
+ * Create a full virtual path from cache
+ *
+ * @param string $displayPath
+ * @param bool $returnFirstItem return first item only
+ * @return string[]|string
+ *
+ * @throws FileNotFoundException
+ */
+ protected function makeFullVirtualPath($displayPath, $returnFirstItem = false)
+ {
+ $paths = ['' => null];
+
+ $tmp = '';
+ $tokens = explode('/', trim($displayPath, '/'));
+ foreach ($tokens as $token) {
+ if (empty($tmp)) {
+ $tmp .= $token;
+ } else {
+ $tmp .= '/'.$token;
+ }
+
+ if (empty($this->cachedPaths[$tmp])) {
+ throw new FileNotFoundException($displayPath);
+ }
+ if (is_array($this->cachedPaths[$tmp])) {
+ $new_paths = [];
+ foreach ($paths as $path => $obj) {
+ $parentId = $path === '' ? '' : basename($path);
+ foreach ($this->cachedPaths[$tmp] as $id) {
+ if ($parentId === '' || (!empty($this->cacheFileObjects[$id]->parents) && in_array($parentId, $this->cacheFileObjects[$id]->parents))) {
+ $new_paths[$path.'/'.$id] = $this->cacheFileObjects[$id];
+ }
+ }
+ }
+ $paths = $new_paths;
+ } else {
+ $id = $this->cachedPaths[$tmp];
+ $new_paths = [];
+ foreach ($paths as $path => $obj) {
+ $parentId = $path === '' ? '' : basename($path);
+ if ($parentId === '' || (!empty($this->cacheFileObjects[$id]->parents) && in_array($parentId, $this->cacheFileObjects[$id]->parents))) {
+ $new_paths[$path.'/'.$id] = $this->cacheFileObjects[$id];
+ }
+ }
+ $paths = $new_paths;
+ }
+ }
+
+ $count = count($paths);
+ if ($count === 0) {
+ throw new FileNotFoundException($displayPath);
+ }
+
+ if (count($paths) > 1) {
+ // sort oldest to newest
+ uasort($paths, function ($a, $b) {
+ $t1 = strtotime($a->getCreatedTime());
+ $t2 = strtotime($b->getCreatedTime());
+ if ($t1 < $t2) {
+ return -1;
+ }
+ if ($t1 > $t2) {
+ return 1;
+ }
+ return 0;
+ });
+
+ if (!$returnFirstItem) {
+ return array_keys($paths);
+ }
+ }
+ return array_keys($paths)[0];
+ }
+
+ protected function returnSingle($item, $returnFirstItem)
+ {
+ if ($returnFirstItem && is_array($item)) {
+ return $item[0];
+ }
+ return $item;
+ }
+
+ /**
+ * Convert display path to virtual path or just id
+ *
+ * @param string $displayPath
+ * @param bool $makeFullVirtualPath
+ * @param bool $returnFirstItem
+ * @return string[]|string Single itemId/path or array of them
+ *
+ * @throws FileNotFoundException
+ */
+ protected function toVirtualPath($displayPath, $makeFullVirtualPath = true, $returnFirstItem = false)
+ {
+ if ($displayPath === '' || $displayPath === '/' || $displayPath === $this->root) {
+ return '';
+ }
+
+ $displayPath = trim($displayPath, '/'); // not needed
+
+ $indices = $this->indexString($displayPath, '/');
+ $indices[] = strlen($displayPath);
+
+ [$itemId, $pathMatch] = $this->getCachedPathId($displayPath, $indices);
+ $i = 0;
+ if ($pathMatch !== null) {
+ if (strcmp($pathMatch, $displayPath) === 0) {
+ if ($makeFullVirtualPath) {
+ return $this->makeFullVirtualPath($displayPath, $returnFirstItem);
+ }
+ return $this->returnSingle($itemId, $returnFirstItem);
+ }
+ $i = array_search(strlen($pathMatch), $indices) + 1;
+ }
+ if ($itemId === null) {
+ $itemId = '';
+ }
+ $this->cachePaths($displayPath, $i, $indices, $itemId);
+
+ if ($makeFullVirtualPath) {
+ return $this->makeFullVirtualPath($displayPath, $returnFirstItem);
+ }
+
+ if (empty($this->cachedPaths[$displayPath])) {
+ throw new FileNotFoundException($displayPath);
+ }
+
+ return $this->returnSingle($this->cachedPaths[$displayPath], $returnFirstItem);
+ }
+
+ /**
+ * Convert virtual path to display path
+ *
+ * @param string $virtualPath
+ * @return string
+ *
+ * @throws FileNotFoundException
+ */
+ protected function toDisplayPath($virtualPath)
+ {
+ if ($virtualPath === '' || $virtualPath === '/') {
+ return '/';
+ }
+
+ $tokens = explode('/', trim($virtualPath, '/'));
+
+ /** @var DriveFile[] $objects */
+ $objects = $this->getObjects($tokens);
+ $display = '';
+ foreach ($tokens as $token) {
+ if (!isset($objects[$token])) {
+ throw new FileNotFoundException($virtualPath);
+ }
+ if (!empty($display) || $display === '0') {
+ $display .= '/';
+ }
+ $display .= $this->sanitizeFilename($objects[$token]->getName());
+ }
+ return $display;
+ }
+
+ protected function toSingleVirtualPath($displayPath, $makeFullVirtualPath = true, $can_throw = true, $createDirsIfNeeded = false, $is_dir = false)
+ {
+ try {
+ $path = $this->toVirtualPath($displayPath, $makeFullVirtualPath, true);
+ } catch (FileNotFoundException $e) {
+ if (!$createDirsIfNeeded) {
+ if ($can_throw) {
+ throw $e;
+ }
+ return false;
+ }
+
+ $subdir = $is_dir ? $displayPath : self::dirname($displayPath);
+ if ($subdir === '' || $this->createDir($subdir, new Config(), true) === false) {
+ if ($can_throw) {
+ throw $e;
+ }
+ return false;
+ }
+
+ try {
+ $path = $this->toVirtualPath($displayPath, $makeFullVirtualPath, true);
+ } catch (FileNotFoundException $e) {
+ if ($can_throw) {
+ throw $e;
+ }
+ return false;
+ }
+ }
+ return $path;
+ }
+
+ protected function canRequest($id, $is_full_req)
+ {
+ if (!isset($this->requestedIds[$id])) {
+ return true;
+ }
+ if ($is_full_req && $this->requestedIds[$id]['type'] === false) {
+ return true;
+ } // we're making a full dir request and previous request was dirs only...allow
+ if (time() - $this->requestedIds[$id]['time'] > self::FILE_OBJECT_MINIMUM_VALID_TIME) {
+ return true;
+ }
+ return false; // not yet
+ }
+
+ protected function markRequest($id, $is_full_req)
+ {
+ $this->requestedIds[$id] = [
+ 'type' => (bool)$is_full_req,
+ 'time' => time()
+ ];
+ }
+
+ /**
+ * @param string|string[] $id
+ * @param bool $reset_all
+ */
+ protected function resetRequest($id, $reset_all = false)
+ {
+ if ($reset_all) {
+ $this->requestedIds = [];
+ } else {
+ if (is_array($id)) {
+ foreach ($id as $i) {
+ if ($i === $this->root) {
+ unset($this->requestedIds['']);
+ }
+ unset($this->requestedIds[$i]);
+ }
+ } else {
+ if ($id === $this->root) {
+ unset($this->requestedIds['']);
+ }
+ unset($this->requestedIds[$id]);
+ }
+ }
+ }
+
+ protected function sanitizeFilename($filename)
+ {
+ if (!empty($this->options['sanitize_chars'])) {
+ $filename = str_replace(
+ $this->options['sanitize_chars'],
+ $this->options['sanitize_replacement_char'],
+ $filename
+ );
+ }
+
+ return $filename;
+ }
+
+ public static function dirname($path)
+ {
+ // fix for Flysystem bug on Windows
+ $path = Util::normalizeDirname(dirname($path));
+ return str_replace('\\', '/', $path);
+ }
+
+ protected function applyDefaultParams($params, $cmdName)
+ {
+ if (isset($this->optParams[$cmdName]) && is_array($this->optParams[$cmdName])) {
+ return array_replace($this->optParams[$cmdName], $params);
+ } else {
+ return $params;
+ }
+ }
+
+ /**
+ * Enables empty google drive trash
+ *
+ * @return void
+ *
+ * @see https://developers.google.com/drive/v3/reference/files emptyTrash
+ * @see \Google_Service_Drive_Resource_Files
+ */
+ public function emptyTrash(array $params = [])
+ {
+ $this->refreshToken();
+ $this->service->files->emptyTrash($this->applyDefaultParams($params, 'files.emptyTrash'));
+ }
+
+ /**
+ * Enables Team Drive support by changing default parameters
+ *
+ * @return void
+ *
+ * @see https://developers.google.com/drive/v3/reference/files
+ * @see \Google_Service_Drive_Resource_Files
+ */
+ public function enableTeamDriveSupport()
+ {
+ $this->optParams = array_merge_recursive(
+ array_fill_keys([
+ 'files.copy', 'files.create', 'files.delete',
+ 'files.trash', 'files.get', 'files.list', 'files.update',
+ 'files.watch'
+ ], ['supportsTeamDrives' => true]),
+ $this->optParams
+ );
+ }
+
+ /**
+ * Selects Team Drive to operate by changing default parameters
+ *
+ * @param string $teamDriveId Team Drive id
+ * @param string $corpora Corpora value for files.list
+ * @return void
+ *
+ * @see https://developers.google.com/drive/v3/reference/files
+ * @see https://developers.google.com/drive/v3/reference/files/list
+ * @see \Google_Service_Drive_Resource_Files
+ */
+ public function setTeamDriveId($teamDriveId, $corpora = 'teamDrive')
+ {
+ $this->enableTeamDriveSupport();
+ $this->optParams = array_merge_recursive($this->optParams, [
+ 'files.list' => [
+ 'corpora' => $corpora,
+ 'includeTeamDriveItems' => true,
+ 'teamDriveId' => $teamDriveId
+ ]
+ ]);
+
+ if ($this->root === 'root' || $this->root === null) {
+ $this->setPathPrefix($teamDriveId);
+ $this->root = $teamDriveId;
+ }
+ }
+}
diff --git a/app/Services/Google/StreamableUpload.php b/app/Services/Google/StreamableUpload.php
new file mode 100644
index 000000000..ec99fb73a
--- /dev/null
+++ b/app/Services/Google/StreamableUpload.php
@@ -0,0 +1,424 @@
+client = $client;
+ $this->request = $request;
+ $this->mimeType = $mimeType;
+ if ($data !== null) {
+ if (function_exists('\GuzzleHttp\Psr7\stream_for')) {
+ $this->data = \GuzzleHttp\Psr7\stream_for($data);
+ } else {
+ $this->data = \GuzzleHttp\Psr7\Utils::streamFor($data);
+ }
+ } else {
+ $this->data = null;
+ }
+ $this->resumable = $resumable;
+ $this->chunkSize = is_bool($chunkSize) ? 0 : $chunkSize;
+ $this->progress = 0;
+ $this->size = '*';
+ if ($this->data !== null) {
+ $size = $this->data->getSize();
+ if ($size !== null) {
+ $this->size = $size;
+ }
+ }
+
+ $this->process();
+ }
+
+ /**
+ * Set the size of the file that is being uploaded.
+ *
+ * @param int $size file size in bytes
+ */
+ public function setFileSize($size)
+ {
+ $this->size = $size;
+ }
+
+ /**
+ * Return the progress on the upload
+ *
+ * @return int progress in bytes uploaded.
+ */
+ public function getProgress()
+ {
+ return $this->progress;
+ }
+
+ /**
+ * Send the next part of the file to upload.
+ *
+ * @param null|bool|string|StreamInterface $chunk The next set of bytes to send. If stream is provided then chunkSize is ignored.
+ * If false it will use $this->data set at construct time.
+ * @return false|mixed
+ */
+ public function nextChunk($chunk = false)
+ {
+ $resumeUri = $this->getResumeUri();
+
+ if ($chunk === null || is_bool($chunk)) {
+ if ($this->chunkSize < 1) {
+ throw new \InvalidArgumentException('Invalid chunk size');
+ }
+ if (!$this->data instanceof StreamInterface) {
+ throw new \InvalidArgumentException('Invalid data stream');
+ }
+ $this->data->seek($this->progress, SEEK_SET);
+ if ($this->data->eof()) {
+ return true; // finished
+ }
+ $chunk = new LimitStream($this->data, $this->chunkSize, $this->data->tell());
+ } else {
+ if (function_exists('\GuzzleHttp\Psr7\stream_for')) {
+ $chunk = \GuzzleHttp\Psr7\stream_for($chunk);
+ } else {
+ $chunk = \GuzzleHttp\Psr7\Utils::streamFor($chunk);
+ }
+ }
+ $size = $chunk->getSize();
+
+ if ($size === null) {
+ throw new \InvalidArgumentException('Chunk doesn\'t support getSize');
+ } else {
+ if ($size < 1) {
+ return true; // finished
+ }
+
+ $lastBytePos = $this->progress + $size - 1;
+ $headers = [
+ 'content-range' => 'bytes '.$this->progress.'-'.$lastBytePos.'/'.$this->size,
+ 'content-length' => $size,
+ 'expect' => '',
+ ];
+ }
+
+ $request = new Request(
+ 'PUT',
+ $resumeUri,
+ $headers,
+ $chunk
+ );
+
+ return $this->makePutRequest($request);
+ }
+
+ /**
+ * Return the HTTP result code from the last call made.
+ *
+ * @return int code
+ */
+ public function getHttpResultCode()
+ {
+ return $this->httpResultCode;
+ }
+
+ /**
+ * Sends a PUT-Request to google drive and parses the response,
+ * setting the appropriate variables from the response()
+ *
+ * @param RequestInterface $request the request which will be sent
+ * @return false|mixed false when the upload is unfinished or the decoded http response
+ */
+ private function makePutRequest(RequestInterface $request)
+ {
+ /** @var ResponseInterface $response */
+ $response = $this->client->execute($request);
+ $this->httpResultCode = $response->getStatusCode();
+
+ if (308 == $this->httpResultCode) {
+ // Track the amount uploaded.
+ $range = $response->getHeaderLine('range');
+ if ($range) {
+ $range_array = explode('-', $range);
+ $this->progress = $range_array[1] + 1;
+ }
+
+ // Allow for changing upload URLs.
+ $location = $response->getHeaderLine('location');
+ if ($location) {
+ $this->resumeUri = $location;
+ }
+
+ // No problems, but upload not complete.
+ return false;
+ }
+
+ // return REST::decodeHttpResponse($response, $this->request);
+ return \Google_Http_REST::decodeHttpResponse($response, $this->request);
+ }
+
+ /**
+ * Resume a previously unfinished upload
+ *
+ * @param string $resumeUri The resume-URI of the unfinished, resumable upload.
+ * @return false|mixed
+ */
+ public function resume($resumeUri)
+ {
+ $this->resumeUri = $resumeUri;
+ $headers = [
+ 'content-range' => 'bytes */'.$this->size,
+ 'content-length' => 0,
+ ];
+ $httpRequest = new Request(
+ 'PUT',
+ $this->resumeUri,
+ $headers
+ );
+
+ return $this->makePutRequest($httpRequest);
+ }
+
+ /**
+ * @return \Psr\Http\Message\RequestInterface $request
+ * @visible for testing
+ */
+ private function process()
+ {
+ $this->transformToUploadUrl();
+ $request = $this->request;
+
+ $postBody = '';
+ $contentType = false;
+
+ $meta = (string)$request->getBody();
+ $meta = is_string($meta) ? json_decode($meta, true) : $meta;
+
+ $uploadType = $this->getUploadType($meta);
+ $request = $request->withUri(
+ Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType)
+ );
+
+ $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type');
+
+ if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
+ $contentType = $mimeType;
+ $postBody = is_string($meta) ? $meta : json_encode($meta);
+ } else {
+ if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
+ $contentType = $mimeType;
+ $postBody = $this->data;
+ } else {
+ if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
+ // This is a multipart/related upload.
+ $boundary = $this->boundary ?: /* @scrutinizer ignore-call */ mt_rand();
+ $boundary = str_replace('"', '', $boundary);
+ $contentType = 'multipart/related; boundary='.$boundary;
+ $related = "--$boundary\r\n";
+ $related .= "Content-Type: application/json; charset=UTF-8\r\n";
+ $related .= "\r\n".json_encode($meta)."\r\n";
+ $related .= "--$boundary\r\n";
+ $related .= "Content-Type: $mimeType\r\n";
+ $related .= "Content-Transfer-Encoding: base64\r\n";
+ $related .= "\r\n".base64_encode($this->data)."\r\n";
+ $related .= "--$boundary--";
+ $postBody = $related;
+ }
+ }
+ }
+ if (function_exists('\GuzzleHttp\Psr7\stream_for')) {
+ $stream = \GuzzleHttp\Psr7\stream_for($postBody);
+ } else {
+ $stream = \GuzzleHttp\Psr7\Utils::streamFor($postBody);
+ }
+
+ $request = $request->withBody($stream);
+
+ if (isset($contentType) && $contentType) {
+ $request = $request->withHeader('content-type', $contentType);
+ }
+
+ return $this->request = $request;
+ }
+
+ /**
+ * Valid upload types:
+ * - resumable (UPLOAD_RESUMABLE_TYPE)
+ * - media (UPLOAD_MEDIA_TYPE)
+ * - multipart (UPLOAD_MULTIPART_TYPE)
+ *
+ * @param $meta
+ * @return string
+ * @visible for testing
+ */
+ public function getUploadType($meta)
+ {
+ if ($this->resumable) {
+ return self::UPLOAD_RESUMABLE_TYPE;
+ }
+
+ if (false == $meta && $this->data) {
+ return self::UPLOAD_MEDIA_TYPE;
+ }
+
+ return self::UPLOAD_MULTIPART_TYPE;
+ }
+
+ public function getResumeUri()
+ {
+ if (null === $this->resumeUri) {
+ $this->resumeUri = $this->fetchResumeUri();
+ }
+
+ return $this->resumeUri;
+ }
+
+ private function fetchResumeUri()
+ {
+ $body = $this->request->getBody();
+ if ($body) {
+ $headers = [
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'content-length' => $body->getSize(),
+ 'x-upload-content-type' => $this->mimeType,
+ 'expect' => '',
+ ];
+ if (is_int($this->size)) {
+ $headers['x-upload-content-length'] = $this->size;
+ }
+
+ foreach ($headers as $key => $value) {
+ $this->request = $this->request->withHeader($key, $value);
+ }
+ }
+
+ $response = $this->client->execute($this->request, false);
+ $location = $response->getHeaderLine('location');
+ $code = $response->getStatusCode();
+
+ if (200 == $code && true == $location) {
+ return $location;
+ }
+
+ $message = $code;
+ $body = json_decode((string)$this->request->getBody(), true);
+ if (isset($body['error']['errors'])) {
+ $message .= ': ';
+ foreach ($body['error']['errors'] as $error) {
+ $message .= $error['domain'].', '.$error['message'].';';
+ }
+ $message = rtrim($message, ';');
+ }
+
+ $error = "Failed to start the resumable upload (HTTP {$message})";
+ $this->client->getLogger()->error($error);
+
+ throw new GoogleException($error);
+ }
+
+ private function transformToUploadUrl()
+ {
+ $parts = parse_url((string)$this->request->getUri());
+ if (!isset($parts['path'])) {
+ $parts['path'] = '';
+ }
+ $parts['path'] = '/upload'.$parts['path'];
+ $uri = Uri::fromParts($parts);
+ $this->request = $this->request->withUri($uri);
+ }
+
+ public function setChunkSize($chunkSize)
+ {
+ $this->chunkSize = $chunkSize;
+ }
+
+ public function getRequest()
+ {
+ return $this->request;
+ }
+}
diff --git a/app/helpers.php b/app/helpers.php
index 3c6913b2a..62fcc0019 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -77,7 +77,7 @@ function pagination($query, $limit = null)
}
// limit call maximum 1000 item per page
- $limit = $limit > 1000 ? 1000 : $limit;
+ $limit = $limit > 1000000 ? 1000000 : $limit;
return $query->paginate($limit);
}
diff --git a/composer.json b/composer.json
index 8eca6740e..3500a6b58 100644
--- a/composer.json
+++ b/composer.json
@@ -77,6 +77,7 @@
"config": {
"preferred-install": "dist",
"sort-packages": true,
- "optimize-autoloader": true
+ "optimize-autoloader": true,
+ "process-timeout":0
}
}
diff --git a/database/migrations/tenant/2022_12_15_064705_create_sales_invoice_references.php b/database/migrations/tenant/2022_12_15_064705_create_sales_invoice_references.php
new file mode 100644
index 000000000..73c890482
--- /dev/null
+++ b/database/migrations/tenant/2022_12_15_064705_create_sales_invoice_references.php
@@ -0,0 +1,35 @@
+increments('id');
+ $table->unsignedInteger('sales_invoice_id')->index();
+ $table->foreign('sales_invoice_id')->references('id')->on('sales_invoices')->onDelete('restrict');
+ $table->string('referenceable_type')->nullable();
+ $table->string('referenceable_id')->nullable();
+ $table->decimal('amount', 65, 30);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('sales_invoice_references');
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8490e6a15..3b08878fb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,6 +28,17 @@ services:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
+ MYSQL_DATABASE: ${DB_DATABASE}
+ networks:
+ - point_network
+ phpmyadmin:
+ image: phpmyadmin/phpmyadmin
+ container_name: 'phpmyadmin'
+ restart: unless-stopped
+ links:
+ - db
+ ports:
+ - '80:80'
networks:
- point_network
networks:
diff --git a/resources/views/emails/sales/return/return-approval-request-single.blade.php b/resources/views/emails/sales/return/return-approval-request-single.blade.php
index 846673e76..993227362 100644
--- a/resources/views/emails/sales/return/return-approval-request-single.blade.php
+++ b/resources/views/emails/sales/return/return-approval-request-single.blade.php
@@ -138,13 +138,13 @@
Check
Approve
Reject
diff --git a/resources/views/emails/sales/return/return-approval-request.blade.php b/resources/views/emails/sales/return/return-approval-request.blade.php
index 862e811c3..23adc904e 100644
--- a/resources/views/emails/sales/return/return-approval-request.blade.php
+++ b/resources/views/emails/sales/return/return-approval-request.blade.php
@@ -48,15 +48,8 @@
Form Number |
Form Reference |
Customer |
-
-
-
- | Item |
- Quantity Return |
-
-
-
- |
+ Item |
+ Quantity Return |
Note |
Created By |
Created At |
@@ -72,13 +65,13 @@
$urlApprovalQueries['crud-type'] = $salesReturn->action;
@endphp
- |
+ |
{{ $loop->iteration }}
|
-
+ |
{{ date('d M Y', strtotime($salesReturnForm->date)) }}
|
-
+ |
{{ $salesReturnForm->number }}
{{ ' ' }}
{{
@@ -88,55 +81,46 @@
: ''
}}
|
-
+ |
{{ $salesReturn->salesInvoice->form->number }}
|
-
+ |
{{ $salesReturn->customer->name }}
|
-
-
-
- @foreach($salesReturn->items as $item)
- @php $borderBottom = !$loop->last ? 'border-bottom: 1px solid black' : ''; @endphp
-
- |
- {{ $item->item->name }}
- |
-
- {{ $item->quantity }}
- |
-
-
- @endforeach
-
-
+ @foreach($salesReturn->items as $item)
+ |
+ {{ $item->item->name }}
|
-
- {{ $item->note }}
+ |
+ {{ $item->quantity }}
|
-
+ @break
+ @endforeach
+ |
+ {{ $salesReturnForm->notes }}
+ |
+
{{ $salesReturnForm->createdBy->getFullNameAttribute() }}
|
-
+ |
{{ date('d M Y, H:i', strtotime($salesReturnForm->created_at)) }}
|
-
- |
+ @php
+ ($first = true);
+ @endphp
+ @foreach($salesReturn->items as $item)
+ @if($first)
+ @php
+ ($first = false);
+ @endphp
+ @continue
+ @endif
+
+ |
+ {{ $item->item->name }}
+ |
+
+ {{ $item->quantity }}
+ |
+
+ @endforeach
@endforeach
@@ -154,13 +157,13 @@
$urlApprovalQueries['ids'] = implode(",", Illuminate\Support\Arr::pluck($salesReturns, 'id'));
@endphp
Approve All
Reject All
diff --git a/routes/api/purchase.php b/routes/api/purchase.php
index 800ab4b31..8c487f630 100644
--- a/routes/api/purchase.php
+++ b/routes/api/purchase.php
@@ -10,6 +10,11 @@
Route::post('requests/{id}/reject', 'PurchaseRequest\\PurchaseRequestApprovalController@reject');
Route::post('requests/{id}/cancellation-approve', 'PurchaseRequest\\PurchaseRequestCancellationApprovalController@approve');
Route::post('requests/{id}/cancellation-reject', 'PurchaseRequest\\PurchaseRequestCancellationApprovalController@reject');
+ Route::post('requests/{id}/close', 'PurchaseRequest\\PurchaseRequestCloseController@close');
+ // Route::post('requests/{id}/close-approve', 'PurchaseRequest\\PurchaseRequestCloseController@approve');
+ // Route::post('requests/{id}/close-reject', 'PurchaseRequest\\PurchaseRequestCloseController@reject');
+ Route::post('requests/send-bulk-request-approval', 'PurchaseRequest\\PurchaseRequestController@sendBulkRequestApproval');
+ Route::post('requests/approval-with-token/bulk', 'PurchaseRequest\\PurchaseRequestApprovalController@bulkApprovalWithToken');
Route::apiResource('requests', 'PurchaseRequest\\PurchaseRequestController');
Route::post('orders/{id}/approve', 'PurchaseOrder\\PurchaseOrderApprovalController@approve');
Route::post('orders/{id}/reject', 'PurchaseOrder\\PurchaseOrderApprovalController@reject');
diff --git a/tests/Feature/Http/Purchase/Request/PurchaseRequestApprovalTest.php b/tests/Feature/Http/Purchase/Request/PurchaseRequestApprovalTest.php
new file mode 100644
index 000000000..2a542e00c
--- /dev/null
+++ b/tests/Feature/Http/Purchase/Request/PurchaseRequestApprovalTest.php
@@ -0,0 +1,209 @@
+createDataPurchaseRequest();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ // save data
+ $this->purchase = json_decode($response->getContent())->data;
+ }
+
+ /** @test */
+ public function unauthorized_reject_purchase_request()
+ {
+ $this->success_create_purchase_request();
+ $this->unsetUserRole();
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'reason' => 'reason'
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/reject', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function failed_reject_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id,
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/reject', $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "The given data was invalid."
+ ]);
+ }
+
+ /** @test */
+ public function success_reject_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ /* s: reject test */
+ $data = [
+ 'id' => $this->purchase->id,
+ 'reason' => 'reason'
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/reject', $data, $this->headers);
+ $response->assertStatus(200);
+ /* e: reject test */
+ }
+
+ /** @test */
+ public function unauthorized_approve_purchase_request()
+ {
+ $this->success_create_purchase_request();
+ $this->unsetUserRole();
+
+ $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/approve', [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function success_approve_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ /* s: reject test */
+ $data = [
+ 'id' => $this->purchase->id
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/approve', $data, $this->headers);
+ $response->assertStatus(200);
+ /* e: reject test */
+ }
+
+ /** @test */
+ public function failed_request_approval_by_email_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $response = $this->json('POST', self::$path.'/send-bulk-request-approval', [], $this->headers);
+ $response->assertStatus(422);
+ }
+
+ /** @test */
+ public function success_request_approval_by_email_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ /* s: send request approval email */
+ $data = [
+ 'bulk_id'=> array($this->purchase->id),
+ 'tenant_url' => 'http://dev.localhost:8080'
+ ];
+
+ $response = $this->json('POST', self::$path.'/send-bulk-request-approval', $data, $this->headers);
+ $response->assertStatus(204);
+ /* e: send request approval email */
+ }
+
+ /** @test */
+ public function failed_approval_by_email_purchase_request()
+ {
+ $this->success_request_approval_by_email_purchase_request();
+
+ /* s: bulk approval email fail test */
+ $data = [
+ 'token' => 'NGAWUR',
+ 'bulk_id' => array($this->purchase->id),
+ 'status' => -1
+ ];
+
+ $response = $this->json('POST', self::$path.'/approval-with-token/bulk', $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Not Authorized"
+ ]);
+ /* e: bulk approval email fail test */
+ }
+
+ /** @test */
+ public function success_reject_by_email_purchase_request()
+ {
+ $this->success_request_approval_by_email_purchase_request();
+
+ /* s: bulk approval email test */
+ $token = Token::where('user_id', $this->user->id)->first();
+ $data = [
+ 'token' => $token->token,
+ 'bulk_id' => array($this->purchase->id),
+ 'status' => -1
+ ];
+
+ $response = $this->json('POST', self::$path.'/approval-with-token/bulk', $data, $this->headers);
+ $response->assertStatus(200);
+ /* e: bulk approval email test */
+ }
+
+ /** @test */
+ public function success_approve_by_email_purchase_request()
+ {
+ $this->success_request_approval_by_email_purchase_request();
+
+ /* s: bulk approval email test */
+ $token = Token::where('user_id', $this->user->id)->first();
+ $data = [
+ 'token' => $token->token,
+ 'bulk_id' => array($this->purchase->id),
+ 'status' => 1
+ ];
+
+ $response = $this->json('POST', self::$path.'/approval-with-token/bulk', $data, $this->headers);
+ $response->assertStatus(200);
+ /* e: bulk approval email test */
+ }
+
+ /** @test */
+ public function failed_approve_by_email_purchase_request_not_default_branch()
+ {
+ $this->success_request_approval_by_email_purchase_request();
+ $this->setDefaultBranch(false);
+
+ /* s: bulk approval email test */
+ $token = Token::where('user_id', $this->user->id)->first();
+ $data = [
+ 'token' => $token->token,
+ 'bulk_id' => array($this->purchase->id),
+ 'status' => 1
+ ];
+
+ $response = $this->json('POST', self::$path.'/approval-with-token/bulk', $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ 'code' => 422,
+ 'message' => 'Please set as default branch',
+ ]);
+ /* e: bulk approval email test */
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Http/Purchase/Request/PurchaseRequestCloseTest.php b/tests/Feature/Http/Purchase/Request/PurchaseRequestCloseTest.php
new file mode 100644
index 000000000..de00cd1ce
--- /dev/null
+++ b/tests/Feature/Http/Purchase/Request/PurchaseRequestCloseTest.php
@@ -0,0 +1,159 @@
+createDataPurchaseRequest();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ // save data
+ $this->purchase = json_decode($response->getContent())->data;
+ }
+
+ public function approve_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $data = [
+ 'id' => $this->purchase->id
+ ];
+
+ $this->json('POST', self::$path.'/'.$this->purchase->id.'/approve', $data, $this->headers);
+ }
+
+ /** @test */
+ public function invalid_data_close_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ "id" => $this->purchase->id,
+ ];
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/close', $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "The given data was invalid."
+ ]);
+ }
+
+ /** @test */
+ public function invalid_condition_close_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ "id" => $this->purchase->id,
+ "reason" => "sample reason"
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/close', $data, $this->headers);
+ $response->assertStatus(422)
+ ->assertJson([
+ "code" => 422,
+ "message" => "Form not approved or not in pending state"
+ ]);
+ }
+
+ /** @test */
+ public function success_close_purchase_request()
+ {
+ $this->approve_purchase_request();
+
+ $data = [
+ "id" => $this->purchase->id,
+ "reason" => "sample reason"
+ ];
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/close', $data, $this->headers);
+ $response->assertStatus(204);
+
+ $this->assertDatabaseHas('forms', [
+ 'number' => $this->purchase->form->number,
+ 'close_status' => true
+ ], 'tenant');
+ }
+
+ // /** @test */
+ // public function invalid_state_close_approve_purchase_request()
+ // {
+ // //create purchase request and save to $this->purchase
+ // $this->success_create_purchase_request();
+
+ // $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/close-approve', [], $this->headers);
+ // $response->assertStatus(422)->assertJson([
+ // "code" => 422,
+ // "message" => "Form not approved or not in pending state"
+ // ]);
+ // }
+
+ // /** @test */
+ // public function success_close_approve_purchase_request()
+ // {
+ // $this->success_close_purchase_request();
+
+ // $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/close-approve', [], $this->headers);
+ // $response->assertStatus(200);
+ // }
+
+ // /** @test */
+ // public function invalid_close_reject_purchase_request()
+ // {
+ // $this->success_close_purchase_request();
+
+ // $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/close-reject', [], $this->headers);
+ // $response->assertStatus(422);
+ // }
+
+ // /** @test */
+ // public function invalid_state_close_reject_purchase_request()
+ // {
+ // //create purchase request and save to $this->purchase
+ // $this->success_create_purchase_request();
+
+ // $data["reason"] = "reject";
+ // $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/close-approve', $data, $this->headers);
+ // $response->assertStatus(422);
+ // }
+
+ // /** @test */
+ // public function success_reject_purchase_request()
+ // {
+ // $this->success_close_purchase_request();
+
+ // $data['reason'] = $this->faker->text(200);
+ // $response = $this->json('POST', self::$path . '/' . $this->purchase->id . '/close-reject', $data, $this->headers);
+
+ // $response->assertStatus(200);
+ // }
+
+ /** @test */
+ public function success_autoclose_purchase_request()
+ {
+ // $this->expectOutputString('');
+ $data = $this->createDataPurchaseRequest();
+ foreach($data['items'] as $key=>$item){
+ $data['items'][$key]['quantity_remaining'] = 0;
+ }
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+ $response->assertStatus(201);
+
+ // save data
+ $this->purchase = json_decode($response->getContent())->data;
+
+ $this->assertDatabaseHas('forms', [
+ 'number' => $this->purchase->form->number,
+ 'close_status' => true,
+ 'close_approval_reason' => 'Closed by system'
+ ], 'tenant');
+ }
+}
diff --git a/tests/Feature/Http/Purchase/Request/PurchaseRequestPermissionTest.php b/tests/Feature/Http/Purchase/Request/PurchaseRequestPermissionTest.php
new file mode 100644
index 000000000..4e993ae27
--- /dev/null
+++ b/tests/Feature/Http/Purchase/Request/PurchaseRequestPermissionTest.php
@@ -0,0 +1,55 @@
+setupUser(true, false);
+
+ $this->assertFalse($this->tenantUser->hasPermissionTo('menu purchase'));
+ $this->assertFalse($this->tenantUser->hasPermissionTo('create purchase request'));
+ $this->assertFalse($this->tenantUser->hasPermissionTo('read purchase request'));
+ $this->assertFalse($this->tenantUser->hasPermissionTo('update purchase request'));
+ $this->assertFalse($this->tenantUser->hasPermissionTo('delete purchase request'));
+ $this->assertFalse($this->tenantUser->hasPermissionTo('approve purchase request'));
+ }
+
+ /** @test */
+ public function check_true_permission_access()
+ {
+ $this->setupUser(true, true);
+
+ $this->assertTrue($this->tenantUser->hasPermissionTo('menu purchase'));
+ $this->assertTrue($this->tenantUser->hasPermissionTo('create purchase request'));
+ $this->assertTrue($this->tenantUser->hasPermissionTo('read purchase request'));
+ $this->assertTrue($this->tenantUser->hasPermissionTo('update purchase request'));
+ $this->assertTrue($this->tenantUser->hasPermissionTo('delete purchase request'));
+ $this->assertTrue($this->tenantUser->hasPermissionTo('approve purchase request'));
+ }
+
+ /** @test */
+ public function add_permission_access()
+ {
+ $this->setupUser(true, false);
+
+ $this->assertFalse($this->tenantUser->hasPermissionTo('approve purchase request'));
+
+ //add permission
+ $data = [
+ "permission_name" => "approve purchase request",
+ "role_id" => $this->role->id
+ ];
+
+ $response = $this->json('PATCH', '/api/v1/master/roles/'.$this->role->id.'/permissions', $data, $this->headers);
+ $response->assertStatus(200);
+
+ $this->assertTrue($this->tenantUser->hasPermissionTo('approve purchase request'));
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Http/Purchase/Request/PurchaseRequestSetup.php b/tests/Feature/Http/Purchase/Request/PurchaseRequestSetup.php
new file mode 100644
index 000000000..282b9b3fb
--- /dev/null
+++ b/tests/Feature/Http/Purchase/Request/PurchaseRequestSetup.php
@@ -0,0 +1,209 @@
+setupUser();
+ $this->setProject();
+ $this->createSampleChartAccountType();
+ $this->createSampleEmployee();
+ $this->createSampleItem();
+ $this->createSampleAllocation();
+ }
+
+ public function setupUser($customRole = false, $setupPermission = true)
+ {
+ $this->signIn();
+ if($customRole){
+ $this->setCustomRole();
+ }else{
+ $this->setRole();
+ }
+ if($setupPermission){
+ $this->setPurchaseRequestPermission();
+ }
+ $this->tenantUser = TenantUser::find($this->user->id);
+ }
+
+ protected function unsetBranch()
+ {
+ foreach ($this->tenantUser->branches as $branch) {
+ $this->tenantUser->branches()->detach($branch->pivot->branch_id);
+ }
+ }
+
+ protected function setPurchaseRequestPermission()
+ {
+ Permission::createIfNotExists('menu purchase');
+
+ $permission = ['purchase request'];
+
+ foreach ($permission as $permission) {
+ Permission::createIfNotExists('create '.$permission);
+ Permission::createIfNotExists('read '.$permission);
+ Permission::createIfNotExists('update '.$permission);
+ Permission::createIfNotExists('delete '.$permission);
+ Permission::createIfNotExists('approve '.$permission);
+ }
+
+ $permissions = Permission::all();
+ $this->role->syncPermissions($permissions);
+ }
+
+ protected function setCustomRole()
+ {
+ $faker = Factory::create();
+ $role = \App\Model\Auth\Role::createIfNotExists($faker->name);
+ $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();
+ $this->role = $role;
+ }
+
+ protected function createSampleItem()
+ {
+ $item = new Item;
+ $item->code = "Code001";
+ $item->name = "Kopi Jowo";
+ $item->chart_of_account_id = $this->account->id;
+ $item->require_expiry_date = false;
+ $item->require_production_number = false;
+ $item->save();
+ $this->item = $item;
+ }
+
+ protected function createSampleAllocation()
+ {
+ $allocation = new Allocation;
+ $allocation->name = "Stok Pantry";
+ $allocation->save();
+ $this->allocation = $allocation;
+ }
+
+ private function createDataPurchaseRequest()
+ {
+ $data = [
+ "increment_group" => date('Ym'),
+ "date" => date('Y-m-d H:m:s'),
+ "required_date" => date('Y-m-d H:m:s'),
+ 'employee_id' => $this->employee->id,
+ "request_approval_to" => $this->user->id,
+ "notes" => "Test Note",
+ "items" => [
+ [
+ "item_id" => $this->item->id,
+ "item_name" => $this->item->name,
+ "unit" => "PCS",
+ "converter" => "1.00",
+ "quantity" => "20",
+ "quantity_remaining" => "20",
+ "notes" => "notes",
+ "allocation_id" => $this->allocation->id,
+ ]
+ ]
+ ];
+ return $data;
+ }
+
+ private function createSupplier()
+ {
+ factory(Supplier::class, 1)->create();
+ return Supplier::take(1)->first();
+ }
+
+ private function createPurchaseOrder($purchaseRequest)
+ {
+ $supplier = $this->createSupplier();
+ $data = [
+ "increment_group" => date('Ym'),
+ "date" => date('Y-m-d H:m:s'),
+ 'supplier_id' => $supplier->id,
+ 'supplier_name' => $supplier->name,
+ 'purchase_request_id' => $purchaseRequest->id,
+ "request_approval_to" => $this->user->id,
+ "tax" => 95000,
+ "tax_base" => 950000,
+ "total" => 1045000,
+ "discount_percent" => 0,
+ "discount_value" => 0,
+ "type_of_tax" => "exclude",
+ "need_down_payment" => 0,
+ "cash_only" => false,
+ "notes" => "Test Note",
+ "items" => [
+ [
+ "purchase_request_item_id" => $purchaseRequest->items[0]->id,
+ "item_id" => $this->item->id,
+ "item_name" => $this->item->name,
+ "unit" => "PCS",
+ "converter" => "1.00",
+ "quantity" => "20",
+ "discount_percent" => 0,
+ "discount_value" => 5000,
+ "price" => 1000000,
+ "notes" => "notes",
+ "allocation_id" => $this->allocation->id,
+ ]
+ ]
+ ];
+ $response = $this->json('POST', '/api/v1/purchase/orders', $data, $this->headers);
+
+ // save data
+ $result = json_decode($response->getContent())->data;
+ var_dump($result);
+
+ return $result;
+ }
+
+ protected function convertDateTime($date, $format = 'Y-m-d H:i:s')
+ {
+ $tz1 = 'Asia/Jakarta';
+ $tz2 = 'UTC';
+
+ $d = new DateTime($date, new DateTimeZone($tz1));
+ $d->setTimeZone(new DateTimeZone($tz2));
+
+ return $d->format($format);
+ }
+
+ protected function unsetUserRole()
+ {
+ ModelHasRole::where('role_id', $this->role->id)
+ ->where('model_type', 'App\Model\Master\User')
+ ->where('model_id', $this->user->id)
+ ->delete();
+ }
+
+ protected function setDefaultBranch($state = true)
+ {
+ foreach ($this->tenantUser->branches as $branch) {
+ $branch->pivot->is_default = $state;
+ $branch->pivot->save();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Http/Purchase/Request/PurchaseRequestTest.php b/tests/Feature/Http/Purchase/Request/PurchaseRequestTest.php
new file mode 100644
index 000000000..203aaab95
--- /dev/null
+++ b/tests/Feature/Http/Purchase/Request/PurchaseRequestTest.php
@@ -0,0 +1,483 @@
+ date('Ym'),
+ "date" => date('Y-m-d H:m:s'),
+ "required_date" => date('Y-m-d H:m:s'),
+ "notes" => "Test Note",
+ "items" => []
+ ];
+
+ // $data = $this->createDataPurchaseRequest();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ ]);
+ }
+
+ /** @test */
+ public function failed_default_branch_create_purchase_request()
+ {
+ $data = $this->createDataPurchaseRequest();
+ $this->setDefaultBranch(false);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to save this form',
+ ]);
+ }
+
+ /** @test */
+ public function success_create_purchase_request()
+ {
+ $data = $this->createDataPurchaseRequest();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ // save data
+ $this->purchase = json_decode($response->getContent())->data;
+
+ // assert status
+ $response->assertStatus(201);
+ // assert database
+ $this->assertDatabaseHas('purchase_requests', [
+ 'id' => $this->purchase->id,
+ 'required_date' => $this->convertDateTime($this->purchase->required_date)
+ ], 'tenant');
+ $this->assertDatabaseHas('purchase_request_items', [
+ 'id' => $this->purchase->items[0]->id,
+ 'purchase_request_id' => $this->purchase->id,
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_remaining' => $data['items'][0]['quantity_remaining'],
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ 'notes' => $data['items'][0]['notes'],
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function read_all_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'join' => 'form,items,item',
+ 'fields' => 'purchase_request.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived;null',
+ 'filter_like' => '{}',
+ 'filter_date_min' => '{"form.date":"'.date('Y-m-01 00:00:00').'"}',
+ 'filter_date_max' => '{"form.date":"'.date('Y-m-d 00:00:00').'"}',
+ 'limit' => 10,
+ 'includes' => 'form;items.item;',
+ 'page' => 1,
+ ];
+
+ $response = $this->json('GET', self::$path, $data, $this->headers);
+ // var_dump($response->getContent());
+ // $response = $this->json('GET', self::$path.'?join=form,items,item&fields=purchase_request.*&sort_by=-form.number&group_by=form.id&filter_form=notArchived%3Bnull&filter_like=%7B%7D&filter_not_null=form.number&%7B%22form.date%22:%22'.date('Y-m-01').'+00:00:00%22%7D&filter_date_max=%7B%22form.date%22:%22'.date('Y-m-d').'+23:59:59%22%7D&limit=10&includes=form%3Bitems.item&page=1', array(), $this->headers);
+ $response->assertStatus(200);
+ }
+
+ /** @test */
+ public function read_all_With_filter_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'join' => 'form,items,item',
+ 'fields' => 'purchase_request.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived;null',
+ 'filter_like' => '{}',
+ 'filter_date_min' => '{"form.date":"'.date('Y-m-15 00:00:00').'"}',
+ 'filter_date_max' => '{"form.date":"'.date('Y-m-16 00:00:00').'"}',
+ 'limit' => 10,
+ 'includes' => 'form;items.item;',
+ 'page' => 1,
+ ];
+
+ $response = $this->json('GET', self::$path, $data, $this->headers);
+
+ // $response = $this->json('GET', self::$path.'?join=form,items,item&fields=purchase_request.*&sort_by=-form.number&group_by=form.id&filter_form=notArchived%3BapprovalPending&filter_like=%7B%7D&filter_not_null=form.number&%7B%22form.date%22:%22'.date('Y-m-15').'+00:00:00%22%7D&filter_date_max=%7B%22form.date%22:%22'.date('Y-m-d').'+23:59:59%22%7D&limit=10&includes=form%3Bitems.item&page=1', array(), $this->headers);
+ $response->assertStatus(200);
+ }
+
+ /** @test */
+ public function read_all_With_search_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'join' => 'form,items,item',
+ 'fields' => 'purchase_request.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived;null',
+ 'filter_like' => '{"form.number":"'.$this->purchase->form->number.'"}',
+ 'filter_date_min' => '{"form.date":"'.date('Y-m-15 00:00:00').'"}',
+ 'filter_date_max' => '{"form.date":"'.date('Y-m-16 00:00:00').'"}',
+ 'limit' => 10,
+ 'includes' => 'form;items.item;',
+ 'page' => 1,
+ ];
+
+ $response = $this->json('GET', self::$path, $data, $this->headers);
+ // $response = $this->json('GET', self::$path.'?join=form,items,item&fields=purchase_request.*&sort_by=-form.number&group_by=form.id&filter_form=notArchived%3BapprovalPending&filter_like=%7B%22form.number%22:%22'.$this->purchase->form->number.'%22,%22item.code%22:%22'.$this->purchase->form->number.'%22,%22item.name%22:%22'.$this->purchase->form->number.'%22,%22purchase_request_item.notes%22:%22'.$this->purchase->form->number.'%22,%22purchase_request_item.quantity%22:%22'.$this->purchase->form->number.'%22%7D&filter_not_null=form.number&%7B%22form.date%22:%22'.date('Y-m-01').'+00:00:00%22%7D&filter_date_max=%7B%22form.date%22:%22'.date('Y-m-d').'+23:59:59%22%7D&limit=10&includes=form%3Bitems.item&page=1', array(), $this->headers);
+ $response->assertStatus(200);
+ }
+
+ /** @test */
+ public function failed_not_same_branch_read_single_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $this->unsetBranch();
+
+ $response = $this->json('GET', self::$path.'/'.$this->purchase->id.'?includes=items.item;items.allocation;form.requestApprovalTo;form.branch&with_archives=true&with_origin=true', array(), $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function failed_access_read_single_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ // toggle permission
+ $data = [
+ "permission_name" => "read purchase request",
+ "role_id" => $this->role->id
+ ];
+ $response = $this->json('PATCH', '/api/v1/master/roles/'.$this->role->id.'/permissions', $data, $this->headers);
+ $this->assertFalse($this->tenantUser->hasPermissionTo('read purchase request'));
+
+ $response = $this->json('GET', self::$path.'/'.$this->purchase->id.'?includes=items.item;items.allocation;form.requestApprovalTo;form.branch&with_archives=true&with_origin=true', array(), $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function success_read_single_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $response = $this->json('GET', self::$path.'/'.$this->purchase->id.'?includes=items.item;items.allocation;form.requestApprovalTo;form.branch&with_archives=true&with_origin=true', array(), $this->headers);
+ $response->assertStatus(200);
+ }
+
+ /** @test */
+ public function failed_update_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ "increment_group" => date('Ym'),
+ "date" => date('Y-m-d H:m:s'),
+ "required_date" => date('Y-m-d H:m:s'),
+ "notes" => "Test Note",
+ "items" => []
+ ];
+
+ // $data = $this->createDataPurchaseRequest();
+
+ $response = $this->json('PATCH', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ // $response->dump();
+ $response->assertStatus(422)->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ ]);
+ }
+
+ /** @test */
+ public function failed_update_purchase_request_linked_puchase_order()
+ {
+ // $this->expectOutputString('');
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $data = $this->createDataPurchaseRequest();
+ $data['id'] = $this->purchase->id;
+ $data['required_date'] = date('Y-m-30 H:m:s');
+
+ // link to purchase order
+ $this-> createPurchaseOrder($this->purchase);
+
+ $response = $this->json('PATCH', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Cannot edit form because referenced by purchase order"
+ ]);
+ }
+
+ /** @test */
+ public function success_update_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $data = $this->createDataPurchaseRequest();
+ $data['id'] = $this->purchase->id;
+ $data['required_date'] = date('Y-m-30 H:m:s');
+
+ $response = $this->json('PATCH', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(201);
+
+ // save data
+ $this->purchase = json_decode($response->getContent())->data;
+
+ $this->assertDatabaseHas('purchase_requests', [
+ 'id' => $this->purchase->id,
+ 'required_date' => date('Y-m-d H:m:s', strtotime($data['required_date'].' -7 hour'))
+ ], 'tenant');
+ $this->assertDatabaseHas('purchase_request_items', [
+ 'id' => $this->purchase->items[0]->id,
+ 'purchase_request_id' => $this->purchase->id,
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_remaining' => $data['items'][0]['quantity_remaining'],
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ 'notes' => $data['items'][0]['notes'],
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function success_update_purchase_request_with_different_user()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ // login with different user
+ $this->setupUser();
+
+ $data = $this->createDataPurchaseRequest();
+ $data['id'] = $this->purchase->id;
+ $data['required_date'] = date('Y-m-30 H:m:s');
+
+ $response = $this->json('PATCH', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(201);
+
+ $this->assertDatabaseHas('forms', [
+ 'created_by' => $this->user->id,
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function failed_delete_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, [], $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "The given data was invalid."
+ ]);
+ }
+
+ /** @test */
+ public function failed_password_delete_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'password' => 'wrongPassword',
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function failed_default_branch_delete_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $this->setDefaultBranch(false);
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'password' => $this->userPassword,
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Please set as default branch"
+ ]);
+ }
+
+ /** @test */
+ public function failed_delete_purchase_request_no_access()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $this->role->revokePermissionTo("delete purchase request");
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'password' => $this->userPassword,
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ // var_dump($response->getContent());
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function success_delete_purchase_request()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'password' => $this->userPassword,
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(204);
+
+ $this->assertDatabaseHas('forms', [
+ 'number' => $this->purchase->form->number,
+ 'cancellation_status' => 1,
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function failed_delete_purchase_request_with_other_user_no_access()
+ {
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $user = $this->user;
+ $this->role->revokePermissionTo("delete purchase request");
+ // login with different user
+ $this->setupUser(true);
+ $this->role->revokePermissionTo("delete purchase request");
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'request_cancellation_to' => $user->id,
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "Unauthorized"
+ ]);
+ }
+
+ /** @test */
+ public function success_delete_purchase_request_with_other_user()
+ {
+ // $this->expectOutputString("");
+ //create purchase request and save to $this->purchase
+ $this->success_create_purchase_request();
+ $user = $this->user;
+ // login with different user
+ $this->setupUser(true);
+ $this->role->revokePermissionTo("delete purchase request");
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'tenant_url' => 'http://dev.localhost:8080',
+ 'request_cancellation_to' => $user->id,
+ 'reason' => 'Reason'
+ ];
+ $response = $this->json('DELETE', self::$path.'/'.$this->purchase->id, $data, $this->headers);
+ // var_dump($response->getContent());
+ $response->assertStatus(204);
+
+ $this->assertDatabaseHas('forms', [
+ 'number' => $this->purchase->form->number,
+ 'request_cancellation_to' => $user->id,
+ 'cancellation_status' => 0,
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function success_approve_delete_purchase_request()
+ {
+ $this->success_delete_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/cancellation-approve', $data, $this->headers);
+ $response->assertStatus(200);
+ }
+
+ /** @test */
+ public function failed_reject_delete_purchase_request()
+ {
+ $this->success_delete_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/cancellation-reject', $data, $this->headers);
+ $response->assertStatus(422)->assertJson([
+ "code" => 422,
+ "message" => "The given data was invalid."
+ ]);
+ }
+
+ /** @test */
+ public function success_reject_delete_purchase_request()
+ {
+ $this->success_delete_purchase_request();
+
+ $data = [
+ 'id' => $this->purchase->id,
+ 'reason' => 'reason'
+ ];
+
+ $response = $this->json('POST', self::$path.'/'.$this->purchase->id.'/cancellation-reject', $data, $this->headers);
+ $response->assertStatus(200);
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalByEmailTest.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalByEmailTest.php
index f9dee68fe..89c3c9057 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalByEmailTest.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalByEmailTest.php
@@ -7,12 +7,14 @@
use App\Model\Sales\SalesReturn\SalesReturn;
use App\Model\Token;
use App\User;
+use App\Helpers\Inventory\InventoryHelper;
class SalesReturnApprovalByEmailTest extends TestCase
{
use SalesReturnSetup;
public static $path = '/api/v1/sales/return';
+ public static $paycolPath = '/api/v1/sales/payment-collection';
private function findOrCreateToken($tenantUser)
{
@@ -46,68 +48,82 @@ private function changeActingAs($tenantUser, $salesReturn)
$this->actingAs($user, 'api');
}
- /** @test */
- public function success_create_sales_return()
+ public function create_sales_return()
{
$this->setRole();
$data = $this->getDummyData();
- $response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 0,
- 'done' => 0,
- ], 'tenant');
+ $this->json('POST', self::$path, $data, $this->headers);
}
- /** @test */
- public function success_delete_sales_return()
+ public function delete_sales_return()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data['reason'] = $this->faker->text(200);
- $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ }
- $response->assertStatus(204);
- $this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'request_cancellation_reason' => $data['reason'],
- 'cancellation_status' => 0,
- ], 'tenant');
+ public function approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
+ }
+
+ public function delete_approved_sales_return()
+ {
+ $this->approve_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
}
/** @test */
- public function success_approve_sales_return()
+ public function error_already_approved_approve_by_email_sales_return()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->approval_status = 1;
+ $salesReturn->form->save();
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
- $response->assertStatus(200);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 1
- ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Approved'
- ], 'tenant');
+ $approver = $salesReturn->form->requestApprovalTo;
+ $approverToken = $this->findOrCreateToken($approver);
+
+ $this->changeActingAs($approver, $salesReturn);
+
+ $data = [
+ 'action' => 'approve',
+ 'approver_id' => $salesReturn->form->request_approval_to,
+ 'token' => $approverToken->token,
+ 'resource-type' => 'SalesReturn',
+ 'ids' => [
+ ['id' => $salesReturn->id]
+ ],
+ 'crud-type' => 'delete'
+ ];
+
+ $response = $this->json('POST', self::$path . '/approve', $data , $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form '.$salesReturn->form->number.' already approved'
+ ]);
}
/** @test */
public function unauthorized_approve_by_email_sales_return()
{
- $this->success_delete_sales_return();
+ $this->create_sales_return();
$this->unsetUserRole();
@@ -119,11 +135,11 @@ public function unauthorized_approve_by_email_sales_return()
"message" => "There is no permission named `approve sales return` for guard `api`."
]);
}
-
- /** @test */
+
+ /** @test */
public function success_approve_by_email_sales_return()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
@@ -143,33 +159,122 @@ public function success_approve_by_email_sales_return()
'crud-type' => 'delete'
];
+ $salesReturnItem = $salesReturn->items[0];
+
+ $stock = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+
$response = $this->json('POST', self::$path . '/approve', $data, $this->headers);
-
- $response->assertStatus(200);
+ $salesReturn = SalesReturn::where('id', $salesReturn->id)->first();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.0.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.0.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.0.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.0.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => 1,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.0.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.0.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.0.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.0.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]
+ ]);
+
+ $subTotal = $response->json('data.0.amount') - $response->json('data.0.tax');
$this->assertDatabaseHas('forms', [
- 'id' => $salesReturn->form->id,
- 'number' => $salesReturn->form->number,
+ 'id' => $response->json('data.0.form.id'),
+ 'number' => $response->json('data.0.form.number'),
'approval_status' => 1
], 'tenant');
+
$this->assertDatabaseHas('user_activities', [
- 'number' => $salesReturn->form->number,
- 'table_id' => $salesReturn->id,
+ 'number' => $response->json('data.0.form.number'),
+ 'table_id' => $response->json('data.0.id'),
'table_type' => 'SalesReturn',
- 'activity' => 'Approved by Email'
+ 'activity' => 'Approved By Email'
+ ], 'tenant');
+
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.0.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
], 'tenant');
- $this->assertDatabaseHas('inventories', [
- 'form_id' => $salesReturn->form->id,
- 'item_id' => $salesReturn->items()->first()->item_id,
- 'quantity' => $salesReturn->items()->first()->quantity,
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.0.tax').'.000000000000000000000000000000'
], 'tenant');
+
+ $stockNew = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+ $this->assertEquals($stockNew, ($stock + $salesReturnItem->quantity));
+
+ $referenced = $this->json('GET', self::$paycolPath. '/'.$salesReturn->customer_id.'/references', [], $this->headers);
+ $referenced->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'salesReturn' => [
+ [ 'number' => $salesReturn->form->number ]
+ ]
+ ]
+ ]);
}
/** @test */
public function success_approve_delete_by_email_sales_return()
{
- $this->success_delete_sales_return();
+ $this->delete_approved_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnItem = $salesReturn->items[0];
$approver = $salesReturn->form->requestCancellationTo;
$approverToken = $this->findOrCreateToken($approver);
@@ -189,23 +294,96 @@ public function success_approve_delete_by_email_sales_return()
$response = $this->json('POST', self::$path . '/approve', $data, $this->headers);
- $response->assertStatus(200);
+ $salesReturn = SalesReturn::where('id', $salesReturn->id)->first();
+ $salesReturnItem = $salesReturn->items[0];
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.0.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.0.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.0.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.0.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => $salesReturn->form->approval_status,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.0.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.0.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => 1,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.0.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.0.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]
+ ]);
+
+ $subTotal = $response->json('data.0.amount') - $response->json('data.0.tax');
$this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'cancellation_status' => 1,
+ 'id' => $response->json('data.0.form.id'),
+ 'number' => $response->json('data.0.form.number'),
+ 'cancellation_status' => 1
], 'tenant');
+
$this->assertDatabaseHas('user_activities', [
- 'number' => $salesReturn->form->number,
- 'table_id' => $salesReturn->id,
+ 'number' => $response->json('data.0.form.number'),
+ 'table_id' => $response->json('data.0.id'),
'table_type' => 'SalesReturn',
'activity' => 'Cancellation Approved by Email'
], 'tenant');
- }
- /** @test */
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.0.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.tax').'.000000000000000000000000000000'
+ ], 'tenant');
+ }
+
+ /** @test */
public function unauthorized_reject_by_email_sales_return()
{
- $this->success_delete_sales_return();
+ $this->create_sales_return();
$this->unsetUserRole();
@@ -219,9 +397,9 @@ public function unauthorized_reject_by_email_sales_return()
}
/** @test */
- public function success_reject_by_email_sales_return()
+ public function success_reject_sales_return()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
@@ -238,30 +416,101 @@ public function success_reject_by_email_sales_return()
'ids' => [
['id' => $salesReturn->id]
],
- 'crud-type' => 'delete'
+ 'crud-type' => 'delete',
+ 'reason' => $this->faker->text(200)
];
$response = $this->json('POST', self::$path . '/reject', $data, $this->headers);
+ $salesReturn = SalesReturn::where('id', $salesReturn->id)->first();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.0.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.0.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.0.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.0.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => -1,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.0.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.0.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.0.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.0.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]
+ ]);
- $response->assertStatus(200);
$this->assertDatabaseHas('forms', [
- 'id' => $salesReturn->form->id,
- 'number' => $salesReturn->form->number,
+ 'id' => $response->json('data.0.form.id'),
+ 'number' => $response->json('data.0.form.number'),
'approval_status' => -1,
'done' => 0,
], 'tenant');
+
$this->assertDatabaseHas('user_activities', [
- 'number' => $salesReturn->form->number,
- 'table_id' => $salesReturn->id,
+ 'number' => $response->json('data.0.form.number'),
+ 'table_id' => $response->json('data.0.id'),
'table_type' => 'SalesReturn',
- 'activity' => 'Rejected by Email'
+ 'activity' => 'Rejected By Email'
+ ], 'tenant');
+
+ $subTotal = $response->json('data.0.amount') - $response->json('data.0.tax');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.0.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.0.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.0.tax').'.000000000000000000000000000000'
], 'tenant');
}
/** @test */
public function success_reject_delete_by_email_sales_return()
{
- $this->success_delete_sales_return();
+ $this->delete_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
@@ -278,12 +527,64 @@ public function success_reject_delete_by_email_sales_return()
'ids' => [
['id' => $salesReturn->id]
],
- 'crud-type' => 'delete'
+ 'crud-type' => 'delete',
+ 'reason' => $this->faker->text(200)
];
$response = $this->json('POST', self::$path . '/reject', $data, $this->headers);
-
- $response->assertStatus(200);
+ $salesReturn = SalesReturn::where('id', $salesReturn->id)->first();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.0.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.0.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.0.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.0.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => $salesReturn->form->approval_status,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.0.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.0.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => -1,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.0.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.0.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]
+ ]);
$this->assertDatabaseHas('forms', [
'number' => $salesReturn->form->number,
'cancellation_status' => -1,
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalTest.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalTest.php
index 510479b41..8529c6d89 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalTest.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnApprovalTest.php
@@ -3,210 +3,568 @@
namespace Tests\Feature\Http\Sales\SalesReturn;
use Tests\TestCase;
-
+use App\Model\Token;
use App\Model\Form;
use App\Model\Sales\SalesReturn\SalesReturn;
+use App\Helpers\Inventory\InventoryHelper;
class SalesReturnApprovalTest extends TestCase
{
- use SalesReturnSetup;
-
- public static $path = '/api/v1/sales/return';
-
- private $previousSalesReturnData;
-
- /** @test */
- public function success_create_sales_return($isFirstCreate = true)
- {
- $data = $this->getDummyData();
-
- if($isFirstCreate) {
- $this->setRole();
- $this->previousSalesReturnData = $data;
- }
-
- $response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 0,
- 'done' => 0,
- ], 'tenant');
- }
-
- /** @test */
- public function unauthorized_approve_sales_return()
- {
- $this->success_create_sales_return();
-
- $this->unsetUserRole();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
-
- $response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `approve sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function success_approve_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
- $response->assertStatus(200);
- $subTotal = $response->json('data.amount') - $response->json('data.tax');
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 1
- ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Approved'
- ], 'tenant');
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 1
- ], 'tenant');
- $this->assertDatabaseHas('journals', [
- 'form_id' => $response->json('data.form.id'),
- 'chart_of_account_id' => $this->arCoa->id,
- 'credit' => $response->json('data.amount').'.000000000000000000000000000000'
- ], 'tenant');
- $this->assertDatabaseHas('journals', [
- 'form_id' => $response->json('data.form.id'),
- 'chart_of_account_id' => $this->salesIncomeCoa->id,
- 'debit' => $subTotal.'.000000000000000000000000000000'
- ], 'tenant');
- $this->assertDatabaseHas('journals', [
- 'form_id' => $response->json('data.form.id'),
- 'chart_of_account_id' => $this->taxCoa->id,
- 'debit' => $response->json('data.tax').'.000000000000000000000000000000'
- ], 'tenant');
- }
-
- /** @test */
- public function unauthorized_reject_sales_return()
- {
- $this->success_create_sales_return();
-
- $this->unsetUserRole();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', [], $this->headers);
-
- $response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `approve sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function invalid_reject_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', [], $this->headers);
-
- $response->assertStatus(422);
- }
-
- /** @test */
- public function success_reject_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
- $data['reason'] = $this->faker->text(200);
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', $data, $this->headers);
-
- $response->assertStatus(200);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
+ use SalesReturnSetup;
+
+ public static $path = '/api/v1/sales/return';
+ public static $paycolPath = '/api/v1/sales/payment-collection';
+
+ private $previousSalesReturnData;
+
+ public function create_sales_return($isFirstCreate = true)
+ {
+ $data = $this->getDummyData();
+
+ if($isFirstCreate) {
+ $this->setRole();
+ $this->previousSalesReturnData = $data;
+ }
+
+ $this->json('POST', self::$path, $data, $this->headers);
+ }
+
+ /** @test */
+ public function error_already_approved_approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->approval_status = 1;
+ $salesReturn->form->save();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form already approved'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
+
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `approve sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function success_approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnItem = $salesReturn->items[0];
+
+ $stock = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
+
+ $salesReturn->refresh();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => 1,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]);
+
+ $subTotal = $response->json('data.amount') - $response->json('data.tax');
+ $this->assertDatabaseHas('forms', [
+ 'id' => $response->json('data.form.id'),
+ 'number' => $response->json('data.form.number'),
+ 'approval_status' => 1
+ ], 'tenant');
+
+ $this->assertDatabaseHas('user_activities', [
+ 'number' => $response->json('data.form.number'),
+ 'table_id' => $response->json('data.id'),
+ 'table_type' => 'SalesReturn',
+ 'activity' => 'Approved'
+ ], 'tenant');
+
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseHas('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.tax').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseHas('sales_invoice_references', [
+ 'referenceable_id' => $response->json('data.id'),
+ 'referenceable_type' => 'SalesReturn',
+ 'amount' => $response->json('data.amount').'.00000000000000000000000000000'
+ ], 'tenant');
+
+ $stockNew = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+ $this->assertEquals($stockNew, ($stock + $salesReturnItem->quantity));
+
+ $referenced = $this->json('GET', self::$paycolPath. '/'.$salesReturn->customer_id.'/references', [], $this->headers);
+ $referenced->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'salesReturn' => [
+ [ 'number' => $salesReturn->form->number ]
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function error_reason_more_than_255_character_reject_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(500);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'reason' => [
+ 'The reason may not be greater than 255 characters.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function error_empty_reason_reject_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'reason' => [
+ 'The reason field is required.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_reject_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', [], $this->headers);
+
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `approve sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function success_reject_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/reject', $data, $this->headers);
+
+ $salesReturn->refresh();
+
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
'approval_status' => -1,
- 'done' => 0,
- ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Rejected'
- ], 'tenant');
- }
-
- /** @test */
- public function success_read_approval_sales_return()
- {
- $this->success_create_sales_return();
-
- $data = [
- 'join' => 'form,customer,items,item',
- 'fields' => 'sales_return.*',
- 'sort_by' => '-form.number',
- 'group_by' => 'form.id',
- 'filter_form'=>'notArchived;null',
- 'filter_like'=>'{}',
- 'filter_date_min'=>'{"form.date":"2022-05-01 00:00:00"}',
- 'filter_date_max'=>'{"form.date":"2022-05-17 23:59:59"}',
- 'includes'=>'form;customer;warehouse;items.item;items.allocation',
- 'limit'=>10,
- 'page' => 1
- ];
-
- $response = $this->json('GET', self::$path . '/approval', $data, $this->headers);
-
- $response->assertStatus(200);
- }
-
- /** @test */
- public function success_send_approval_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
- $data['ids'][] = ['id' => $salesReturn->id];
-
- $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
-
- $response->assertStatus(200);
- }
-
- /** @test */
- public function success_send_multiple_approval_sales_return()
- {
- $this->success_create_sales_return();
-
- $this->success_create_sales_return($isFirstCreate = false);
- $salesReturn = SalesReturn::orderBy('id', 'desc')->first();
- $salesReturn->form->cancellation_status = 0;
- $salesReturn->form->close_status = null;
- $salesReturn->form->save();
-
- $data['ids'] = SalesReturn::get()
- ->pluck('id')
- ->map(function ($id) { return ['id' => $id]; })
- ->toArray();
-
- $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
-
- $response->assertStatus(200);
- }
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]);
+
+ $this->assertDatabaseHas('forms', [
+ 'id' => $response->json('data.form.id'),
+ 'number' => $response->json('data.form.number'),
+ 'approval_status' => -1,
+ 'done' => 0,
+ ], 'tenant');
+
+ $this->assertDatabaseHas('user_activities', [
+ 'number' => $response->json('data.form.number'),
+ 'table_id' => $response->json('data.id'),
+ 'table_type' => 'SalesReturn',
+ 'activity' => 'Rejected'
+ ], 'tenant');
+
+ $subTotal = $response->json('data.amount') - $response->json('data.tax');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.tax').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('user_activities', [
+ 'number' => $response->json('data.form.number'),
+ 'table_id' => $response->json('data.id'),
+ 'table_type' => 'SalesReturn',
+ 'activity' => 'Cancel Approved'
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function error_no_branch_send_approval_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['ids'][] = ['id' => $salesReturn->id];
+
+ $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to create this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_send_approval_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['ids'][] = ['id' => $salesReturn->id];
+
+ $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
+
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `create sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function success_send_approval_sales_return()
+ {
+ $this->create_sales_return();
+
+ $approverToken = Token::orderBy('id', 'asc')->delete();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['ids'][] = ['id' => $salesReturn->id];
+
+ $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
+
+ $response->assertStatus(200)
+ ->assertJson([
+ "input" => [
+ "ids" => [
+ [ "id" => $salesReturn->id ]
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function success_send_multiple_approval_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->create_sales_return($isFirstCreate = false);
+ $salesReturn = SalesReturn::orderBy('id', 'desc')->first();
+ $salesReturn->form->cancellation_status = 0;
+ $salesReturn->form->close_status = null;
+ $salesReturn->form->save();
+
+ $data['ids'] = SalesReturn::get()
+ ->pluck('id')
+ ->map(function ($id) { return ['id' => $id]; })
+ ->toArray();
+
+ $response = $this->json('POST', self::$path . '/approval/send', $data, $this->headers);
+
+ $response->assertStatus(200)
+ ->assertJson([
+ "input" => [
+ "ids" => $data['ids']
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function success_read_approval_sales_return()
+ {
+ $this->create_sales_return();
+
+ $data = [
+ 'join' => 'form,customer,items,item',
+ 'fields' => 'sales_return.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form'=>'notArchived;null',
+ 'filter_like'=>'{}',
+ 'filter_date_min'=>'{"form.date":"2022-05-01 00:00:00"}',
+ 'filter_date_max'=>'{"form.date":"2022-05-17 23:59:59"}',
+ 'includes'=>'form;customer;warehouse;items.item;items.allocation',
+ 'limit'=>10,
+ 'page' => 1
+ ];
+
+ $response = $this->json('GET', self::$path . '/approval', $data, $this->headers);
+
+ $response->assertStatus(200)
+ ->assertJsonStructure([
+ 'data' => [
+ [
+ 'id',
+ 'sales_invoice_id',
+ 'customer_id',
+ 'warehouse_id',
+ 'customer_name',
+ 'customer_address',
+ 'customer_phone',
+ 'tax',
+ 'amount',
+ 'form' => [
+ 'id',
+ 'date',
+ 'number',
+ 'edited_number',
+ 'edited_notes',
+ 'notes',
+ 'created_by',
+ 'updated_by',
+ 'done',
+ 'increment',
+ 'increment_group',
+ 'formable_id',
+ 'formable_type',
+ 'request_approval_at',
+ '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',
+ 'request_close_to',
+ 'request_close_by',
+ 'request_close_at',
+ 'request_close_reason',
+ 'close_approval_at',
+ 'close_approval_by',
+ 'close_status'
+ ],
+ 'customer' => [
+ 'id',
+ 'code',
+ 'tax_identification_number',
+ 'name',
+ 'address',
+ 'city',
+ 'state',
+ 'country',
+ 'zip_code',
+ 'latitude',
+ 'longitude',
+ 'phone',
+ 'phone_cc',
+ 'email',
+ 'notes',
+ 'credit_limit',
+ 'branch_id',
+ 'created_by',
+ 'updated_by',
+ 'archived_by',
+ 'pricing_group_id',
+ 'label'
+ ],
+ 'items' => [
+ [
+ 'id',
+ 'sales_return_id',
+ 'sales_invoice_item_id',
+ 'item_id',
+ 'item_name',
+ 'quantity',
+ 'quantity_sales',
+ 'price',
+ 'discount_percent',
+ 'discount_value',
+ 'unit',
+ 'converter',
+ 'expiry_date',
+ 'production_number',
+ 'notes',
+ 'allocation_id',
+ 'allocation',
+ ]
+ ]
+ ]
+ ],
+ 'links' => [
+ 'first',
+ 'last',
+ 'prev',
+ 'next',
+ ],
+ 'meta' => [
+ 'current_page',
+ 'from',
+ 'last_page',
+ 'path',
+ 'per_page',
+ 'to',
+ 'total',
+ ]
+ ]);
+ }
}
\ No newline at end of file
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnCancellationApprovalTest.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnCancellationApprovalTest.php
index 6b6eaa9f4..2ac93f298 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnCancellationApprovalTest.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnCancellationApprovalTest.php
@@ -5,166 +5,356 @@
use Tests\TestCase;
use App\Model\Sales\SalesReturn\SalesReturn;
+use App\Model\Sales\SalesInvoice\SalesInvoice;
+use App\Helpers\Inventory\InventoryHelper;
class SalesReturnCancellationApprovalTest extends TestCase
{
- use SalesReturnSetup;
-
- public static $path = '/api/v1/sales/return';
-
- /** @test */
- public function success_create_sales_return()
- {
+ use SalesReturnSetup;
+
+ public static $path = '/api/v1/sales/return';
+
+ public function create_sales_return($isFirstCreate = true)
+ {
+ $data = $this->getDummyData();
+
+ if($isFirstCreate) {
$this->setRole();
-
- $data = $this->getDummyData();
-
- $response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 0,
- 'done' => 0,
- ], 'tenant');
- }
-
- /** @test */
- public function success_delete_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
- $data['reason'] = $this->faker->text(200);
-
- $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
- $response->assertStatus(204);
- $this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'request_cancellation_reason' => $data['reason'],
- 'cancellation_status' => 0,
- ], 'tenant');
- }
-
- /** @test */
- public function unauthorized_cancellation_approve_sales_return()
- {
- $this->success_delete_sales_return();
-
- $this->unsetUserRole();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
-
- $response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `approve sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function invalid_state_cancellation_approve_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
-
- $response->assertStatus(422);
+ $this->previousSalesReturnData = $data;
}
- /** @test */
- public function success_cancellation_approve_sales_return()
- {
- $this->success_delete_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
-
- $response->assertStatus(200);
- $this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'cancellation_status' => 1,
- ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Cancel Approved'
- ], 'tenant');
- }
-
- /** @test */
- public function unauthorized_cancellation_reject_sales_return()
- {
- $this->success_delete_sales_return();
-
- $this->unsetUserRole();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', [], $this->headers);
-
- $response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `approve sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function invalid_cancellation_reject_sales_return()
- {
- $this->success_delete_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', [], $this->headers);
-
- $response->assertStatus(422);
- }
-
- /** @test */
- public function invalid_state_cancellation_reject_sales_return()
- {
- $this->success_create_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $data['reason'] = $this->faker->text(200);
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', $data, $this->headers);
-
- $response->assertStatus(422);
- }
-
- /** @test */
- public function success_reject_sales_return()
- {
- $this->success_delete_sales_return();
-
- $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
- $data['reason'] = $this->faker->text(200);
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', $data, $this->headers);
-
- $response->assertStatus(200);
- $this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'cancellation_status' => -1,
- 'done' => 0
- ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Cancel Rejected'
- ], 'tenant');
- }
+ $this->json('POST', self::$path, $data, $this->headers);
+ }
+
+ public function approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
+ }
+
+ public function delete_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ }
+
+ public function delete_approved_sales_return()
+ {
+ $this->approve_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ }
+
+ /** @test */
+ public function error_already_cancelled_approve_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->cancellation_status = 1;
+ $salesReturn->form->save();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form not in cancellation pending state'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_approve_approve_cancel_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
+
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `approve sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function success_approve_cancel_sales_return()
+ {
+ $this->delete_approved_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnItem = $salesReturn->items[0];
+
+ $stock = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+
+ $amountInvoice = SalesInvoice::getAvailable($salesReturn->salesInvoice);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-approve', [], $this->headers);
+ $salesReturn->refresh();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => $salesReturn->form->approval_status,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => 1,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]);
+
+ $subTotal = $response->json('data.amount') - $response->json('data.tax');
+ $this->assertDatabaseHas('forms', [
+ 'id' => $response->json('data.form.id'),
+ 'number' => $response->json('data.form.number'),
+ 'cancellation_status' => 1
+ ], 'tenant');
+
+ $this->assertDatabaseHas('user_activities', [
+ 'number' => $response->json('data.form.number'),
+ 'table_id' => $response->json('data.id'),
+ 'table_type' => 'SalesReturn',
+ 'activity' => 'Cancel Approved'
+ ], 'tenant');
+
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->arCoa->id,
+ 'credit' => $response->json('data.amount').'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->salesIncomeCoa->id,
+ 'debit' => $subTotal.'.000000000000000000000000000000'
+ ], 'tenant');
+ $this->assertDatabaseMissing('journals', [
+ 'form_id' => $response->json('data.form.id'),
+ 'chart_of_account_id' => $this->taxCoa->id,
+ 'debit' => $response->json('data.tax').'.000000000000000000000000000000'
+ ], 'tenant');
+
+ $stockNew = InventoryHelper::getCurrentStock($salesReturnItem->item, $salesReturn->form->date, $salesReturn->warehouse, [
+ 'expiry_date' => $salesReturnItem->item->expiry_date,
+ 'production_number' => $salesReturnItem->item->production_number,
+ ]);
+ $this->assertEquals($stockNew, $stock - $salesReturnItem->quantity);
+
+ $salesReturn->refresh();
+ $amountInvoiceNew = SalesInvoice::getAvailable($salesReturn->salesInvoice);
+ $this->assertEquals($amountInvoiceNew, $amountInvoice + $salesReturn->amount);
+ }
+
+ /** @test */
+ public function success_reject_cancel_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnItem = $salesReturn->items[0];
+
+ $data['reason'] = $this->faker->text(200);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', $data, $this->headers);
+ $salesReturn->refresh();
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => $salesReturn->form->number,
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $salesReturn->form->notes,
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => $salesReturn->form->done,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $salesReturn->form->formable_id,
+ 'formable_type' => $salesReturn->form->formable_type,
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->form->request_approval_to,
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => $salesReturn->form->approval_status,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => -1,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ]
+ ]
+ ]);
+
+ $this->assertDatabaseHas('forms', [
+ 'id' => $response->json('data.form.id'),
+ 'number' => $response->json('data.form.number'),
+ 'cancellation_status' => -1
+ ], 'tenant');
+
+ $this->assertDatabaseHas('user_activities', [
+ 'number' => $response->json('data.form.number'),
+ 'table_id' => $response->json('data.id'),
+ 'table_type' => 'SalesReturn',
+ 'activity' => 'Cancel Rejected'
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function error_reason_more_than_255_character_reject_cancel_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(500);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'reason' => [
+ 'The reason may not be greater than 255 characters.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function error_already_rejected_reject_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->cancellation_status = -1;
+ $salesReturn->form->save();
+
+ $data['reason'] = $this->faker->text(100);
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form not in cancellation pending state'
+ ]);
+ }
+
+
+ /** @test */
+ public function error_empty_reason_reject_cancel_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'reason' => [
+ 'The reason field is required.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_reject_cancel_sales_return()
+ {
+ $this->delete_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/cancellation-reject', [], $this->headers);
+
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `approve sales return` for guard `api`.'
+ ]);
+ }
}
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnHistoryTest.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnHistoryTest.php
index 6f426e2c1..0761f6fcf 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnHistoryTest.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnHistoryTest.php
@@ -13,48 +13,62 @@ class SalesReturnHistoryTest extends TestCase
public static $path = '/api/v1/sales/return';
- /** @test */
- public function success_create_sales_return()
+ public function create_sales_return()
{
$this->setRole();
$data = $this->getDummyData();
- $response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 0,
- 'done' => 0,
- ], 'tenant');
+ $this->json('POST', self::$path, $data, $this->headers);
}
- /** @test */
- public function success_update_sales_return()
+
+ public function update_sales_return()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = $this->getDummyData($salesReturn);
$data = data_set($data, 'id', $salesReturn->id, false);
- $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ }
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [ 'edited_number' => $response->json('data.form.number') ], 'tenant');
- $this->assertDatabaseHas('user_activities', [
- 'number' => $response->json('data.form.number'),
- 'table_id' => $response->json('data.id'),
- 'table_type' => 'SalesReturn',
- 'activity' => 'Update - 1'
- ], 'tenant');
+ /** @test */
+ public function unauthorized_no_default_branch_read_histories()
+ {
+ $this->update_sales_return();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnUpdated = SalesReturn::orderBy('id', 'desc')->first();
+
+ $data = [
+ 'sort_by' => '-user_activities.date',
+ 'includes' => 'user',
+ 'filter_like' => '{}',
+ 'or_filter_where_has_like[]' => '{"user":{}}',
+ 'limit' => 10,
+ 'page' => 1
+ ];
+
+ $response = $this->json('GET', self::$path . '/' . $salesReturnUpdated->id . '/histories', $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to read this form'
+ ]);
}
+
/** @test */
- public function read_sales_return_histories()
+ public function unauthorized_create_sales_return()
{
- $this->success_update_sales_return();
+ $this->update_sales_return();
+
+ $this->unsetUserRole();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$salesReturnUpdated = SalesReturn::orderBy('id', 'desc')->first();
@@ -70,7 +84,76 @@ public function read_sales_return_histories()
$response = $this->json('GET', self::$path . '/' . $salesReturnUpdated->id . '/histories', $data, $this->headers);
- $response->assertStatus(200);
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `read sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function read_sales_return_histories()
+ {
+ $this->update_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnUpdated = SalesReturn::orderBy('id', 'desc')->first();
+
+ $data = [
+ 'sort_by' => '-user_activities.date',
+ 'includes' => 'user',
+ 'filter_like' => '{}',
+ 'or_filter_where_has_like[]' => '{"user":{}}',
+ 'limit' => 10,
+ 'page' => 1
+ ];
+
+ $response = $this->json('GET', self::$path . '/' . $salesReturnUpdated->id . '/histories', $data, $this->headers);
+
+ $response->assertStatus(200)
+ ->assertJsonStructure([
+ 'data' => [
+ [
+ 'id' => $response->json('data.0.id'),
+ 'table_type' => 'SalesReturn',
+ 'table_id' => $response->json('data.0.table_id'),
+ 'number' => $salesReturnUpdated->form->number,
+ 'date' => $response->json('data.0.date'),
+ 'user_id' => $response->json('data.0.user_id'),
+ 'activity' => $response->json('data.0.activity'),
+ 'formable_id' => $response->json('data.0.formable_id'),
+ 'user' => [
+ 'id',
+ 'name',
+ 'first_name',
+ 'last_name',
+ 'address',
+ 'phone',
+ 'email',
+ 'branch_id',
+ 'warehouse_id',
+ 'full_name',
+ ],
+ ]
+ ],
+ 'links' => [
+ 'first',
+ 'last',
+ 'prev',
+ 'next',
+ ],
+ 'meta' => [
+ 'current_page',
+ 'from',
+ 'last_page',
+ 'path',
+ 'per_page',
+ 'to',
+ 'total',
+ ]
+ ]);
+
+ $this->assertGreaterThan(0, count($response->json('data')));
$this->assertDatabaseHas('user_activities', [
'number' => $salesReturn->form->edited_number,
'table_id' => $salesReturn->id,
@@ -84,10 +167,11 @@ public function read_sales_return_histories()
'activity' => 'Update - 1'
], 'tenant');
}
+
/** @test */
public function success_create_sales_return_history()
{
- $this->success_create_sales_return();
+ $this->create_sales_return();
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = [
@@ -97,7 +181,16 @@ public function success_create_sales_return_history()
$response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/histories', $data, $this->headers);
- $response->assertStatus(201);
+ $response->assertStatus(201)
+ ->assertJson([
+ "data" => [
+ "table_type" => 'SalesReturn',
+ "table_id" => $salesReturn->id,
+ "number" => $salesReturn->form->number,
+ "activity" => 'Printed',
+ ]
+ ]);
+
$this->assertDatabaseHas('user_activities', [
'number' => $response->json('data.number'),
'table_id' => $response->json('data.table_id'),
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnSetup.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnSetup.php
index d3444d3ca..1cf908b53 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnSetup.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnSetup.php
@@ -21,6 +21,7 @@
use App\Model\Sales\PaymentCollection\PaymentCollection;
use App\Model\SettingJournal;
use App\Model\Accounting\Journal;
+use App\Helpers\Inventory\InventoryHelper;
trait SalesReturnSetup {
private $tenantUser;
@@ -51,6 +52,7 @@ public function setUp(): void
$this->createCustomerUnitItem();
$this->setUserWarehouse($this->branchDefault);
$this->setApprover();
+ $_SERVER['HTTP_REFERER'] = 'http://www.example.com/';
}
private function setUserWarehouse($branch = null)
@@ -64,6 +66,14 @@ private function setUserWarehouse($branch = null)
$this->warehouseSelected = $warehouse;
}
}
+
+ private function removeUserWarehouse()
+ {
+ foreach ($this->tenantUser->warehouses as $warehouse) {
+ $warehouse->pivot->is_default = false;
+ $warehouse->pivot->save();
+ }
+ }
protected function unsetUserRole()
{
@@ -130,9 +140,9 @@ private function generateChartOfAccount()
$this->coa->save();
}
- $arCoaId = get_setting_journal('sales', 'account receivable');
+ $arCoaId = SettingJournal::where('feature', 'sales')->where('name', 'account receivable')->first();
if ($arCoaId) {
- $arCoa = ChartOfAccount::where('id', $arCoaId)->first();
+ $arCoa = ChartOfAccount::where('id', $arCoaId->chart_of_account_id)->first();
$this->arCoa = $arCoa;
} else {
$type = new ChartOfAccountType;
@@ -159,9 +169,9 @@ private function generateChartOfAccount()
$setting->save();
}
- $salesIncomeId = get_setting_journal('sales', 'sales income');
+ $salesIncomeId = SettingJournal::where('feature', 'sales')->where('name', 'sales income')->first();
if ($salesIncomeId) {
- $salesIncomeCoa = ChartOfAccount::where('id', $salesIncomeId)->first();
+ $salesIncomeCoa = ChartOfAccount::where('id', $salesIncomeId->chart_of_account_id)->first();
$this->salesIncomeCoa = $salesIncomeCoa;
} else {
$type = new ChartOfAccountType;
@@ -188,9 +198,9 @@ private function generateChartOfAccount()
$setting->save();
}
- $salesCostCoaId = get_setting_journal('sales', 'cost of sales');
+ $salesCostCoaId = SettingJournal::where('feature', 'sales')->where('name', 'cost of sales')->first();
if ($salesCostCoaId) {
- $salesCostCoa = ChartOfAccount::where('id', $salesCostCoaId)->first();
+ $salesCostCoa = ChartOfAccount::where('id', $salesCostCoaId->chart_of_account_id)->first();
$this->salesCostCoa = $salesCostCoa;
} else {
$type = new ChartOfAccountType;
@@ -217,9 +227,9 @@ private function generateChartOfAccount()
$setting->save();
}
- $taxCoaId = get_setting_journal('sales', 'income tax payable');
+ $taxCoaId = SettingJournal::where('feature', 'sales')->where('name', 'income tax payable')->first();
if ($taxCoaId) {
- $taxCoa = ChartOfAccount::where('id', $taxCoaId)->first();
+ $taxCoa = ChartOfAccount::where('id', $taxCoaId->chart_of_account_id)->first();
$this->taxCoa = $taxCoa;
} else {
$type = new ChartOfAccountType;
@@ -261,10 +271,10 @@ private function getDummyData($salesReturn = null)
$approver = $invoice->form->requestApprovalTo;
return [
- 'increment_group' => date('Ym'),
- 'date' => date('Y-m-d H:i:s'),
+ 'increment_group' => '202212',
+ 'date' => '2022-12-12 12:17:07',
'sales_invoice_id' => $invoice->id,
- "warehouse_id" => $this->warehouseSelected->id,
+ 'warehouse_id' => $this->warehouseSelected->id,
'customer_id' => $customer->id,
'customer_name' => $customer->name,
'customer_label' => $customer->code,
@@ -272,22 +282,28 @@ private function getDummyData($salesReturn = null)
'customer_phone' => null,
'customer_email' => null,
'notes' => null,
- 'tax' => 3000,
- 'amount' => 33000,
- 'type_of_tax' => 'exclude',
+ 'sub_total' => 30000,
+ 'tax_base' => 30000,
+ 'tax' => 2727.2727272727,
+ 'type_of_tax' => 'include',
+ 'amount' => 30000,
'items' => [
[
'sales_invoice_item_id' => $invoiceItem->id,
'item_id' => $this->item->id,
'item_name' => $this->item->name,
- 'item_label' => "[{$this->item->code}] - {$this->item->name}",
+ 'item_label' => '[{$this->item->code}] - {$this->item->name}',
'more' => false,
'unit' => $this->unit->label,
+ 'expiry_date' => null,
+ 'production_number' => null,
'converter' => $invoiceItem->converter,
'quantity_sales' => $quantityInvoice,
+ 'discount_percent' => null,
+ 'discount_value' => 0,
'quantity' => 3,
- 'price' => $invoiceItem->price,
- 'total' => 3 * $invoiceItem->price,
+ 'price' => 10000,
+ 'total' => 30000,
'allocation_id' => null,
'notes' => null,
],
@@ -317,7 +333,7 @@ private function createSalesInvoice()
'delivery_fee' => 0,
'discount_percent' => 0,
'discount_value' => 0,
- 'type_of_tax' => 'exclude',
+ 'type_of_tax' => 'include',
'tax' => 100000,
'amount' => 1100000,
'remaining' => 1100000,
@@ -329,7 +345,7 @@ private function createSalesInvoice()
'item_referenceable_type' => 'SalesDeliveryNoteItem',
'item_id' => $this->item->id,
'item_name' => $this->item->name,
- 'item_label' => "[{$this->item->code}] - {$this->item->name}",
+ 'item_label' => '[{$this->item->code}] - {$this->item->name}',
'more' => false,
'unit' => $this->unit->label,
'converter' => 1,
@@ -375,20 +391,20 @@ private function createPaymentCollection($salesReturn)
'customer_email' => null,
'notes' => null,
'amount' => 30000,
- "details" => [
+ 'details' => [
[
- "date" => date("Y-m-d H:i:s"),
- "chart_of_account_id" => null,
- "chart_of_account_name" => null,
- "available" => $salesReturn->amount,
- "amount" => 30000,
- "allocation_id" => null,
- "allocation_name" => null,
- "referenceable_form_date" => $salesReturn->form->date,
- "referenceable_form_number" => $salesReturn->form->number,
- "referenceable_form_notes" => $salesReturn->form->notes,
- "referenceable_id" => $salesReturn->id,
- "referenceable_type" => "SalesReturn"
+ 'date' => date('Y-m-d H:i:s'),
+ 'chart_of_account_id' => null,
+ 'chart_of_account_name' => null,
+ 'available' => $salesReturn->amount,
+ 'amount' => 30000,
+ 'allocation_id' => null,
+ 'allocation_name' => null,
+ 'referenceable_form_date' => $salesReturn->form->date,
+ 'referenceable_form_number' => $salesReturn->form->number,
+ 'referenceable_form_notes' => $salesReturn->form->notes,
+ 'referenceable_id' => $salesReturn->id,
+ 'referenceable_type' => 'SalesReturn'
],
],
'request_approval_to' => $this->approver->id,
diff --git a/tests/Feature/Http/Sales/SalesReturn/SalesReturnTest.php b/tests/Feature/Http/Sales/SalesReturn/SalesReturnTest.php
index b2598a248..65ec49308 100644
--- a/tests/Feature/Http/Sales/SalesReturn/SalesReturnTest.php
+++ b/tests/Feature/Http/Sales/SalesReturn/SalesReturnTest.php
@@ -4,288 +4,1533 @@
use Tests\TestCase;
+use App\Mail\Sales\SalesReturnApprovalRequest;
use App\Model\Form;
+use App\Model\SettingJournal;
use App\Model\Sales\SalesReturn\SalesReturn;
+use App\Model\Sales\SalesInvoice\SalesInvoice;
+use Illuminate\Support\Facades\Mail;
+use App\Helpers\Inventory\InventoryHelper;
+use Throwable;
class SalesReturnTest extends TestCase
{
use SalesReturnSetup;
- public static $path = '/api/v1/sales/return';
-
- /** @test */
- public function unauthorized_create_sales_return()
+ public function create_sales_return($isFirstCreate = true)
{
$data = $this->getDummyData();
+
+ if($isFirstCreate) {
+ $this->setRole();
+ $this->previousSalesReturnData = $data;
+ }
- $response = $this->json('POST', self::$path, $data, $this->headers);
+ $this->json('POST', self::$path, $data, $this->headers);
+ }
+
+ public static $path = '/api/v1/sales/return';
+ /** @test */
+ public function unauthorized_no_default_branch_create_sales_return()
+ {
+ $this->setRole();
+ $data = $this->getDummyData();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to create this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_create_sales_return()
+ {
+ $data = $this->getDummyData();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
$response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `create sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_data_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'sales_invoice_id', null);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJsonFragment([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.'
+ ]);
+ }
+
+ /** @test */
+ public function duplicate_entry_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->number = 'SR22120002';
+ $salesReturn->form->save();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+ $response->assertStatus(400)
->assertJson([
- "code" => 0,
- "message" => "There is no permission named `create sales return` for guard `api`."
+ 'code' => 400,
+ 'message' => 'Duplicate data entry'
]);
- }
-
- /** @test */
- public function overquantity_create_sales_return()
- {
+ }
+
+ /** @test */
+ public function error_sales_invoice_done_create_sales_return()
+ {
$this->setRole();
-
+
$data = $this->getDummyData();
- $data = data_set($data, 'items.0.quantity', 100);
-
+
+ $salesInvoice = SalesInvoice::orderBy('id', 'asc')->first();
+ $salesInvoice->form->done = 1;
+ $salesInvoice->form->save();
+
$response = $this->json('POST', self::$path, $data, $this->headers);
-
+
+ $response->assertStatus(422)
+ ->assertJsonFragment([
+ 'code' => 422,
+ 'message' => 'Sales return form already done'
+ ]);
+ }
+
+ /** @test */
+ public function error_notes_more_than_255_character_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+
+ $data = data_set($data, 'notes', $this->faker->text(500));
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
$response->assertStatus(422)
->assertJson([
- "code" => 422,
- "message" => "Sales return item can't exceed sales invoice qty"
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'notes' => [
+ 'The notes may not be greater than 255 characters.'
+ ]
+ ]
]);
- }
-
- /** @test */
- public function invalid_create_sales_return()
- {
+ }
+
+ /** @test */
+ public function whitespaces_trimmed_create_sales_return()
+ {
$this->setRole();
-
+
$data = $this->getDummyData();
- $data = data_set($data, 'sales_invoice_id', null);
-
+
+ $data = data_set($data, 'notes', ' whitespaces trimmed ');
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(201)
+ ->assertJsonFragment([
+ 'notes' => 'whitespaces trimmed'
+ ]);
+ }
+
+ /** @test */
+ public function overquantity_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'items.0.quantity', 100);
+ $data = data_set($data, 'items.0.total', 1000000);
+ $data = data_set($data, 'sub_total', 1000000);
+ $data = data_set($data, 'tax_base', 1000000);
+ $data = data_set($data, 'tax', 90909.09090909091);
+ $data = data_set($data, 'amount', 1000000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'Sales return item can\'t exceed sales invoice qty'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_total_item_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'items.0.total', 20000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'total for item ' .$data['items'][0]['item_name']. ' should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_sub_total_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'sub_total', 20000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'sub total should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_tax_base_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'tax_base', 20000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'tax base should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_type_of_tax_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'type_of_tax', 'exclude');
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'type of tax should be same with invoice'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_tax_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'tax', 3000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'tax should be 2727.2727272727'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_amount_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+ $data = data_set($data, 'amount', 40000);
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'amount should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function error_journal_not_found_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+
+ $settingJournal = SettingJournal::where('feature', 'sales')->where('name', 'account receivable')->first();
+ $settingJournal->chart_of_account_id = null;
+ $settingJournal->save();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'Journal sales account - account receivable not found'
+ ]);
+ }
+
+ /** @test */
+ public function check_journal_balance_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+
$response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(422);
- }
- /** @test */
- public function success_create_sales_return()
- {
- $this->setRole();
-
- $data = $this->getDummyData();
-
- $response = $this->json('POST', self::$path, $data, $this->headers);
-
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [
- 'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_status' => 0,
- 'done' => 0,
- ], 'tenant');
- }
-
- /** @test */
- public function success_approve_sales_return()
- {
- $this->success_create_sales_return();
-
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $response = $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
-
- $response->assertStatus(200);
+
+ $journal = SalesReturn::checkJournalBalance($salesReturn);
+ $this->assertEquals($journal['debit'], $journal['credit']);
+ }
+
+ /** @test */
+ public function success_create_sales_return()
+ {
+ $this->setRole();
+
+ $data = $this->getDummyData();
+
+ $response = $this->json('POST', self::$path, $data, $this->headers);
+
+ $salesReturn = SalesReturn::where('id', $response->json('data.id'))->first();
+
+ $this->assertIsObject(
+ $salesReturn->salesInvoice(),
+ 'is sales invoice referenced',
+ );
+
+ $response->assertStatus(201)
+ ->assertJson([
+ 'data' => [
+ 'id' => $response->json('data.id'),
+ 'sales_invoice_id' => $data['sales_invoice_id'],
+ 'warehouse_id' => $data['warehouse_id'],
+ 'customer_id' => $data['customer_id'],
+ 'customer_name' => $data['customer_name'],
+ 'customer_address' => $data['customer_address'],
+ 'customer_phone' => $data['customer_phone'],
+ 'tax' => $data['tax'],
+ 'amount' => $data['amount'],
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => 'SR22120001',
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $data['notes'],
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => 0,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $response->json('data.id'),
+ 'formable_type' => 'SalesReturn',
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $data['request_approval_to'],
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => 0,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ],
+ 'items' => [
+ [
+ 'id' => $response->json('data.items.0.id'),
+ 'sales_return_id' => $response->json('data.id'),
+ 'sales_invoice_item_id' => $data['items'][0]['sales_invoice_item_id'],
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_sales' => $data['items'][0]['quantity_sales'],
+ 'price' => $data['items'][0]['price'],
+ 'discount_percent' => $data['items'][0]['discount_percent'],
+ 'discount_value' => $data['items'][0]['discount_value'] .'000000000000000000000000000000',
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'expiry_date' => $data['items'][0]['expiry_date'],
+ 'production_number' => $data['items'][0]['production_number'],
+ 'notes' => $data['items'][0]['notes'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ ]
+ ]
+ ]
+ ]);
+
$this->assertDatabaseHas('forms', [
'id' => $response->json('data.form.id'),
- 'number' => $response->json('data.form.number'),
- 'approval_by' => $response->json('data.form.approval_by'),
- 'approval_status' => 1,
+ 'number' => 'SR22120001',
+ 'notes' => $data['notes'],
+ 'created_by' => $response->json('data.form.created_by'),
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'approval_status' => 0,
+ 'done' => 0,
+ 'formable_id' => $response->json('data.id'),
+ 'formable_type' => 'SalesReturn',
+ 'request_approval_to' => $data['request_approval_to'],
], 'tenant');
- $this->assertDatabaseHas('inventories', [
- 'form_id' => $response->json('data.form.id'),
- 'item_id' => $response->json('data.items.0.item_id'),
- 'quantity' => $response->json('data.items.0.quantity'),
- ], 'tenant');
- }
-
- /** @test */
- public function read_all_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $this->assertDatabaseHas('sales_returns', [
+ 'id' => $response->json('data.id'),
+ 'sales_invoice_id' => $data['sales_invoice_id'],
+ 'customer_id' => $data['customer_id'],
+ 'customer_name' => $data['customer_name'],
+ 'customer_address' => $data['customer_address'],
+ 'customer_phone' => $data['customer_phone'],
+ 'tax' => $data['tax'],
+ 'amount' => $data['amount'],
+ 'warehouse_id' => $data['warehouse_id'],
+ ], 'tenant');
+
+ $this->assertDatabaseHas('sales_return_items', [
+ 'sales_return_id' => $response->json('data.id'),
+ 'sales_invoice_item_id' => $data['items'][0]['sales_invoice_item_id'],
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_sales' => $data['items'][0]['quantity_sales'],
+ 'price' => $data['items'][0]['price'],
+ 'discount_percent' => $data['items'][0]['discount_percent'],
+ 'discount_value' => $data['items'][0]['discount_value'] .'000000000000000000000000000000',
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'expiry_date' => $data['items'][0]['expiry_date'],
+ 'production_number' => $data['items'][0]['production_number'],
+ 'notes' => $data['items'][0]['notes'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function unauthorized_no_branch_read_all_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
$data = [
- 'join' => 'form,customer,items,item',
- 'fields' => 'sales_return.*',
- 'sort_by' => '-form.number',
- 'group_by' => 'form.id',
- 'filter_form' => 'notArchived;null',
- 'filter_like' => '{}',
- 'limit' => 10,
- 'includes' => 'form;customer;items.item;items.allocation',
- 'page' => 1
+ 'join' => 'form,customer,items,item',
+ 'fields' => 'sales_return.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived',
+ 'filter_like' => '{}',
+ 'limit' => 10,
+ 'includes' => 'customer;warehouse;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch',
+ 'page' => 1
];
-
+
$response = $this->json('GET', self::$path, $data, $this->headers);
-
- $response->assertStatus(200);
- $this->assertGreaterThan(0, count($response->json('data')));
- }
-
- /** @test */
- public function read_sales_return()
- {
- $this->success_approve_sales_return();
-
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to read this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_no_branch_read_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
+
+ $data = [
+ 'with_archives' => 'true',
+ 'with_origin' => 'true',
+ 'remaining_info' => 'true',
+ 'includes' => 'customer;warehouse;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch'
+ ];
+
+ $response = $this->json('GET', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to read this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_read_all_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->unsetUserRole();
+
+ $data = [
+ 'join' => 'form,customer,items,item',
+ 'fields' => 'sales_return.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived',
+ 'filter_like' => '{}',
+ 'limit' => 10,
+ 'includes' => 'customer;warehouse;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch',
+ 'page' => 1
+ ];
+
+ $response = $this->json('GET', self::$path, $data, $this->headers);
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `read sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_read_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->unsetUserRole();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = [
+ 'with_archives' => 'true',
+ 'with_origin' => 'true',
+ 'remaining_info' => 'true',
+ 'includes' => 'customer;warehouse;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch'
+ ];
+
+ $response = $this->json('GET', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+ $response->assertStatus(500)
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `read sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function success_read_all_sales_return()
+ {
+ $this->create_sales_return();
+
+ $data = [
+ 'join' => 'form,customer,items,item',
+ 'fields' => 'sales_return.*',
+ 'sort_by' => '-form.number',
+ 'group_by' => 'form.id',
+ 'filter_form' => 'notArchived',
+ 'filter_like' => '{}',
+ 'limit' => 10,
+ 'includes' => 'form;customer;items.item;items.allocation',
+ 'page' => 1
+ ];
+
+ $response = $this->json('GET', self::$path, $data, $this->headers);
+ $response->assertStatus(200)
+ ->assertJsonStructure([
+ 'data' => [
+ [
+ 'id',
+ 'sales_invoice_id',
+ 'customer_id',
+ 'warehouse_id',
+ 'customer_name',
+ 'customer_address',
+ 'customer_phone',
+ 'tax',
+ 'amount',
+ 'form' => [
+ 'id',
+ 'date',
+ 'number',
+ 'edited_number',
+ 'edited_notes',
+ 'notes',
+ 'created_by',
+ 'updated_by',
+ 'done',
+ 'increment',
+ 'increment_group',
+ 'formable_id',
+ 'formable_type',
+ 'request_approval_at',
+ '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',
+ 'request_close_to',
+ 'request_close_by',
+ 'request_close_at',
+ 'request_close_reason',
+ 'close_approval_at',
+ 'close_approval_by',
+ 'close_status'
+ ],
+ 'customer' => [
+ 'id',
+ 'code',
+ 'tax_identification_number',
+ 'name',
+ 'address',
+ 'city',
+ 'state',
+ 'country',
+ 'zip_code',
+ 'latitude',
+ 'longitude',
+ 'phone',
+ 'phone_cc',
+ 'email',
+ 'notes',
+ 'credit_limit',
+ 'branch_id',
+ 'created_by',
+ 'updated_by',
+ 'archived_by',
+ 'pricing_group_id',
+ 'label'
+ ],
+ 'items' => [
+ [
+ 'id',
+ 'sales_return_id',
+ 'sales_invoice_item_id',
+ 'item_id',
+ 'item_name',
+ 'quantity',
+ 'quantity_sales',
+ 'price',
+ 'discount_percent',
+ 'discount_value',
+ 'unit',
+ 'converter',
+ 'expiry_date',
+ 'production_number',
+ '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',
+ 'label'
+ ]
+ ]
+ ]
+ ]
+ ],
+ 'links' => [
+ 'first',
+ 'last',
+ 'prev',
+ 'next',
+ ],
+ 'meta' => [
+ 'current_page',
+ 'from',
+ 'last_page',
+ 'path',
+ 'per_page',
+ 'to',
+ 'total',
+ ]
+ ]);
+ $this->assertGreaterThan(0, count($response->json('data')));
+ }
+
+ /** @test */
+ public function read_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturnItem = $salesReturn->items[0];
+ $salesReturnForm = $salesReturn->form;
+
$data = [
'with_archives' => 'true',
'with_origin' => 'true',
'remaining_info' => 'true',
- 'includes' => 'customer;warehouse;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch'
+ 'includes' => 'customer;items.item;items.allocation;salesInvoice.form;form.createdBy;form.requestApprovalTo;form.branch'
];
-
+
$response = $this->json('GET', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
- $response->assertStatus(200);
- }
-
- /** @test */
- public function unauthorized_update_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $response->assertStatus(200)
+ ->assertJson([
+ 'data' => [
+ 'id' => $salesReturn->id,
+ 'sales_invoice_id' => $salesReturn->sales_invoice_id,
+ 'warehouse_id' => $salesReturn->warehouse_id,
+ 'customer_id' => $salesReturn->customer_id,
+ 'customer_name' => $salesReturn->customer_name,
+ 'customer_address' => $salesReturn->customer_address,
+ 'customer_phone' => $salesReturn->customer_phone,
+ 'tax' => $salesReturn->tax,
+ 'amount' => $salesReturn->amount,
+ 'archives' => [],
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => $salesReturnForm->number,
+ 'edited_number' => $salesReturnForm->edited_number,
+ 'edited_notes' => $salesReturnForm->edited_notes,
+ 'notes' => $salesReturnForm->notes,
+ 'created_by' => $salesReturnForm->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => $salesReturnForm->done,
+ 'increment' => $salesReturnForm->increment,
+ 'increment_group' => $salesReturnForm->increment_group,
+ 'formable_id' => $salesReturnForm->formable_id,
+ 'formable_type' => $salesReturnForm->formable_type,
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $salesReturnForm->request_approval_to,
+ 'approval_by' => $salesReturnForm->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturnForm->approval_reason,
+ 'approval_status' => $salesReturnForm->approval_status,
+ 'request_cancellation_to' => $salesReturnForm->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturnForm->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturnForm->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturnForm->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturnForm->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturnForm->cancellation_status,
+ 'request_close_to' => $salesReturnForm->request_close_to,
+ 'request_close_by' => $salesReturnForm->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturnForm->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturnForm->close_approval_by,
+ 'close_status' => $salesReturnForm->close_status,
+ 'created_by' => [
+ 'id' => $salesReturnForm->createdBy->id,
+ 'name' => $salesReturnForm->createdBy->name,
+ 'first_name' => $salesReturnForm->createdBy->first_name,
+ 'last_name' => $salesReturnForm->createdBy->last_name,
+ 'address' => $salesReturnForm->createdBy->address,
+ 'phone' => $salesReturnForm->createdBy->phone,
+ 'email' => $salesReturnForm->createdBy->email,
+ 'branch_id' => $salesReturnForm->createdBy->branch_id,
+ 'warehouse_id' => $salesReturnForm->createdBy->warehouse_id,
+ 'full_name' => $salesReturnForm->createdBy->full_name
+ ],
+ 'request_approval_to' => [
+ 'id' => $salesReturnForm->requestApprovalTo->id,
+ 'name' => $salesReturnForm->requestApprovalTo->name,
+ 'first_name' => $salesReturnForm->requestApprovalTo->first_name,
+ 'last_name' => $salesReturnForm->requestApprovalTo->last_name,
+ 'address' => $salesReturnForm->requestApprovalTo->address,
+ 'phone' => $salesReturnForm->requestApprovalTo->phone,
+ 'email' => $salesReturnForm->requestApprovalTo->email,
+ 'branch_id' => $salesReturnForm->requestApprovalTo->branch_id,
+ 'warehouse_id' => $salesReturnForm->requestApprovalTo->warehouse_id,
+ 'full_name' => $salesReturnForm->requestApprovalTo->full_name
+ ],
+ 'branch' => [
+ 'id' => $salesReturnForm->branch->id,
+ 'name' => $salesReturnForm->branch->name,
+ 'address' => $salesReturnForm->branch->address,
+ 'phone' => $salesReturnForm->branch->phone,
+ 'archived_at' => $salesReturnForm->branch->archived_at,
+ ]
+ ],
+ 'items' => [
+ [
+ 'id' => $salesReturnItem->id,
+ 'sales_return_id' => $salesReturnItem->sales_return_id,
+ 'sales_invoice_item_id' => $salesReturnItem->sales_invoice_item_id,
+ 'item_id' => $salesReturnItem->item_id,
+ 'item_name' => $salesReturnItem->item_name,
+ 'quantity' => $salesReturnItem->quantity,
+ 'quantity_sales' => $salesReturnItem->quantity_sales,
+ 'price' => $salesReturnItem->price,
+ 'discount_percent' => $salesReturnItem->discount_percent,
+ 'discount_value' => $salesReturnItem->discount_value,
+ 'unit' => $salesReturnItem->unit,
+ 'converter' => $salesReturnItem->converter,
+ 'expiry_date' => $salesReturnItem->expiry_date,
+ 'production_number' => $salesReturnItem->production_number,
+ 'notes' => $salesReturnItem->notes,
+ 'allocation_id' => $salesReturnItem->allocation_id,
+ 'item' => [
+ 'id' => $salesReturnItem->item->id,
+ 'chart_of_account_id' => $salesReturnItem->item->chart_of_account_id,
+ 'code' => $salesReturnItem->item->code,
+ 'barcode' => $salesReturnItem->item->barcode,
+ 'name' => $salesReturnItem->item->name,
+ 'size' => $salesReturnItem->item->size,
+ 'color' => $salesReturnItem->item->color,
+ 'weight' => $salesReturnItem->item->weight,
+ 'notes' => $salesReturnItem->item->notes,
+ 'taxable' => $salesReturnItem->item->taxable,
+ 'require_production_number' => $salesReturnItem->item->require_production_number,
+ 'require_expiry_date' => $salesReturnItem->item->require_expiry_date,
+ 'stock' => $salesReturnItem->item->stock,
+ 'stock_reminder' => $salesReturnItem->item->stock_reminder,
+ 'unit_default' => $salesReturnItem->item->unit_default,
+ 'unit_default_purchase' => $salesReturnItem->item->unit_default_purchase,
+ 'unit_default_sales' => $salesReturnItem->item->unit_default_sales,
+ 'label' => $salesReturnItem->item->label,
+ ],
+ 'allocation' => null
+ ]
+ ],
+ 'sales_invoice' => [
+ 'id' => $salesReturn->salesInvoice->id,
+ 'customer_id' => $salesReturn->salesInvoice->customer_id,
+ 'customer_name' => $salesReturn->salesInvoice->customer_name,
+ 'customer_address' => $salesReturn->salesInvoice->customer_address,
+ 'customer_phone' => $salesReturn->salesInvoice->customer_phone,
+ 'discount_percent' => $salesReturn->salesInvoice->discount_percent,
+ 'discount_value' => $salesReturn->salesInvoice->discount_value,
+ 'type_of_tax' => $salesReturn->salesInvoice->type_of_tax,
+ 'tax' => $salesReturn->salesInvoice->tax,
+ 'amount' => $salesReturn->salesInvoice->amount,
+ 'remaining' => $salesReturn->salesInvoice->remaining,
+ 'form' => [
+ 'id' => $salesReturn->salesInvoice->form->id,
+ 'date' => $response->json('data.sales_invoice.form.date'),
+ 'number' => $salesReturn->salesInvoice->form->number,
+ 'edited_number' => $salesReturn->salesInvoice->form->edited_number,
+ 'edited_notes' => $salesReturn->salesInvoice->form->edited_notes,
+ 'notes' => $salesReturn->salesInvoice->form->notes,
+ 'created_by' => $salesReturn->salesInvoice->form->created_by,
+ 'updated_by' => $response->json('data.sales_invoice.form.updated_by'),
+ 'done' => $salesReturn->salesInvoice->form->done,
+ 'increment' => $salesReturn->salesInvoice->form->increment,
+ 'increment_group' => $salesReturn->salesInvoice->form->increment_group,
+ 'formable_id' => $salesReturn->salesInvoice->form->formable_id,
+ 'formable_type' => $salesReturn->salesInvoice->form->formable_type,
+ 'request_approval_at' => $response->json('data.sales_invoice.form.request_approval_at'),
+ 'request_approval_to' => $salesReturn->salesInvoice->form->request_approval_to,
+ 'approval_by' => $salesReturn->salesInvoice->form->approval_by,
+ 'approval_at' => $response->json('data.sales_invoice.form.approval_at'),
+ 'approval_reason' => $salesReturn->salesInvoice->form->approval_reason,
+ 'approval_status' => $salesReturn->salesInvoice->form->approval_status,
+ 'request_cancellation_to' => $salesReturn->salesInvoice->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->salesInvoice->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.sales_invoice.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->salesInvoice->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.sales_invoice.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->salesInvoice->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->salesInvoice->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->salesInvoice->form->cancellation_status,
+ 'request_close_to' => $salesReturn->salesInvoice->form->request_close_to,
+ 'request_close_by' => $salesReturn->salesInvoice->form->request_close_by,
+ 'request_close_at' => $response->json('data.sales_invoice.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->salesInvoice->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.sales_invoice.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->salesInvoice->form->close_approval_by,
+ 'close_status' => $salesReturn->salesInvoice->form->close_status,
+ ]
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_no_default_branch_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->branchDefault->pivot->is_default = false;
+ $this->branchDefault->pivot->save();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data = $this->getDummyData($salesReturn);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to update this form'
+ ]);
+ }
+
+ /** @test */
+ public function referenced_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $this->createPaymentCollection($salesReturn);
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form referenced by payment collection'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_update_sales_return()
+ {
+ $this->create_sales_return();
+
$this->unsetUserRole();
-
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = $this->getDummyData($salesReturn);
-
+
$response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
+
$response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `update sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function referenced_update_sales_return()
- {
- $this->success_create_sales_return();
-
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `update sales return` for guard `api`.'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_data_update_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
-
- $this->createPaymentCollection($salesReturn);
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'sales_invoice_id', null);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.'
+ ]);
+ }
+
+ /** @test */
+ public function error_form_done_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->done = 1;
+ $salesReturn->form->save();
$data = $this->getDummyData($salesReturn);
$data = data_set($data, 'id', $salesReturn->id, false);
-
+
$response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
- $response
- ->assertStatus(422)
- ->assertJsonFragment(['message' => 'Cannot edit form because referenced by payment collection']);
- }
-
- /** @test */
- public function overquantity_update_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form already done'
+ ]);
+ }
+
+ /** @test */
+ public function error_notes_more_than_255_character_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'notes', $this->faker->text(500));
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'notes' => [
+ 'The notes may not be greater than 255 characters.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function whitespaces_trimmed_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'notes', ' whitespaces trimmed ');
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(201)
+ ->assertJsonFragment([
+ 'notes' => 'whitespaces trimmed'
+ ]);
+ }
+
+ /** @test */
+ public function overquantity_update_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = $this->getDummyData($salesReturn);
$data = data_set($data, 'id', $salesReturn->id, false);
$data = data_set($data, 'items.0.quantity', 100);
-
+ $data = data_set($data, 'items.0.total', 1000000);
+ $data = data_set($data, 'sub_total', 1000000);
+ $data = data_set($data, 'tax_base', 1000000);
+ $data = data_set($data, 'tax', 90909.09090909091);
+ $data = data_set($data, 'amount', 1000000);
+
$response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
+
$response->assertStatus(422)
- ->assertJson([
- "code" => 422,
- "message" => "Sales return item can't exceed sales invoice qty"
- ]);
- }
-
- /** @test */
- public function invalid_update_sales_return()
- {
- $this->success_create_sales_return();
-
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'Sales return item can\'t exceed sales invoice qty'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_total_item_update_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = $this->getDummyData($salesReturn);
$data = data_set($data, 'id', $salesReturn->id, false);
- $data = data_set($data, 'sales_invoice_id', null);
-
+ $data = data_set($data, 'items.0.total', 20000);
+
$response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
- $response->assertStatus(422);
- }
-
- /** @test */
- public function success_update_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'total for item ' .$data['items'][0]['item_name']. ' should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_sub_total_update_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data = $this->getDummyData($salesReturn);
$data = data_set($data, 'id', $salesReturn->id, false);
-
+ $data = data_set($data, 'sub_total', 20000);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'sub total should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_tax_base_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'tax_base', 20000);
+
$response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'tax base should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_type_of_tax_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'type_of_tax', 'exclude');
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'type of tax should be same with invoice'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_tax_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'tax', 3000);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'tax should be 2727.2727272727'
+ ]);
+ }
+
+ /** @test */
+ public function invalid_amount_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+ $data = data_set($data, 'amount', 40000);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'amount should be 30000'
+ ]);
+ }
+
+ /** @test */
+ public function error_journal_not_found_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+
+ $settingJournal = SettingJournal::where('feature', 'sales')->where('name', 'account receivable')->first();
+ $settingJournal->chart_of_account_id = null;
+ $settingJournal->save();
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'Journal sales account - account receivable not found'
+ ]);
+ }
+
+ /** @test */
+ public function check_journal_balance_update_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($salesReturn);
+ $data = data_set($data, 'id', $salesReturn->id, false);
+
+ $response = $this->json('PATCH', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $salesReturn = SalesReturn::where('id', $response->json('data.id'))->first();
+ $journal = SalesReturn::checkJournalBalance($salesReturn);
+ $this->assertEquals($journal['debit'], $journal['credit']);
+ }
+
+ /** @test */
+ public function will_throw_on_data_duplicate()
+ {
+ $this->expectException(Throwable::class);
+ $this->expectedExceptionMessageRegExp('\bIntegrity constraint violation\b');
+
+ $oldSalesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('PATCH', self::$path . '/' . $oldSalesReturn->id, $data, $this->headers);
+
+ $oldSalesReturn->form->number = $response->json('data.form.number');
+ $oldSalesReturn->form->save();
+ }
+
+ public function approve_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $this->json('POST', self::$path . '/' . $salesReturn->id . '/approve', [], $this->headers);
- $response->assertStatus(201);
- $this->assertDatabaseHas('forms', [ 'edited_number' => $response->json('data.form.number') ], 'tenant');
+ }
+
+ /** @test */
+ public function success_update_sales_return()
+ {
+ $this->approve_sales_return();
+
+ $oldSalesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $data = $this->getDummyData($oldSalesReturn);
+ $data = data_set($data, 'id', $oldSalesReturn->id, false);
+
+ $response = $this->json('PATCH', self::$path . '/' . $oldSalesReturn->id, $data, $this->headers);
+
+ $salesReturn = SalesReturn::where('id', $response->json('data.id'))->first();
+
+ $this->assertIsObject(
+ $salesReturn->salesInvoice(),
+ 'is sales invoice referenced',
+ );
+
+ $response->assertStatus(201)
+ ->assertJson([
+ 'data' => [
+ 'id' => $response->json('data.id'),
+ 'sales_invoice_id' => $data['sales_invoice_id'],
+ 'warehouse_id' => $data['warehouse_id'],
+ 'customer_id' => $data['customer_id'],
+ 'customer_name' => $data['customer_name'],
+ 'customer_address' => $data['customer_address'],
+ 'customer_phone' => $data['customer_phone'],
+ 'tax' => $data['tax'],
+ 'amount' => $data['amount'],
+ 'form' => [
+ 'id' => $salesReturn->form->id,
+ 'date' => $response->json('data.form.date'),
+ 'number' => 'SR22120001',
+ 'edited_number' => $salesReturn->form->edited_number,
+ 'edited_notes' => $salesReturn->form->edited_notes,
+ 'notes' => $data['notes'],
+ 'created_by' => $salesReturn->form->created_by,
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'done' => 0,
+ 'increment' => $salesReturn->form->increment,
+ 'increment_group' => $salesReturn->form->increment_group,
+ 'formable_id' => $response->json('data.id'),
+ 'formable_type' => 'SalesReturn',
+ 'request_approval_at' => $response->json('data.form.request_approval_at'),
+ 'request_approval_to' => $data['request_approval_to'],
+ 'approval_by' => $salesReturn->form->approval_by,
+ 'approval_at' => $response->json('data.form.approval_at'),
+ 'approval_reason' => $salesReturn->form->approval_reason,
+ 'approval_status' => 0,
+ 'request_cancellation_to' => $salesReturn->form->request_cancellation_to,
+ 'request_cancellation_by' => $salesReturn->form->request_cancellation_by,
+ 'request_cancellation_at' => $response->json('data.form.request_cancellation_at'),
+ 'request_cancellation_reason' => $salesReturn->form->request_cancellation_reason,
+ 'cancellation_approval_at' => $response->json('data.form.cancellation_approval_at'),
+ 'cancellation_approval_by' => $salesReturn->form->cancellation_approval_by,
+ 'cancellation_approval_reason' => $salesReturn->form->cancellation_approval_reason,
+ 'cancellation_status' => $salesReturn->form->cancellation_status,
+ 'request_close_to' => $salesReturn->form->request_close_to,
+ 'request_close_by' => $salesReturn->form->request_close_by,
+ 'request_close_at' => $response->json('data.form.request_close_at'),
+ 'request_close_reason' => $salesReturn->form->request_close_reason,
+ 'close_approval_at' => $response->json('data.form.close_approval_at'),
+ 'close_approval_by' => $salesReturn->form->close_approval_by,
+ 'close_status' => $salesReturn->form->close_status,
+ ],
+ 'items' => [
+ [
+ 'id' => $response->json('data.items.0.id'),
+ 'sales_return_id' => $response->json('data.id'),
+ 'sales_invoice_item_id' => $data['items'][0]['sales_invoice_item_id'],
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_sales' => $data['items'][0]['quantity_sales'],
+ 'price' => $data['items'][0]['price'],
+ 'discount_percent' => $data['items'][0]['discount_percent'],
+ 'discount_value' => $data['items'][0]['discount_value'] .'000000000000000000000000000000',
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'expiry_date' => $data['items'][0]['expiry_date'],
+ 'production_number' => $data['items'][0]['production_number'],
+ 'notes' => $data['items'][0]['notes'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ ]
+ ]
+ ]
+ ]);
+
+ $this->assertDatabaseHas('forms', [
+ 'id' => $oldSalesReturn->form->id,
+ 'edited_number' => $oldSalesReturn->form->edited_number,
+ 'formable_id' => $oldSalesReturn->id,
+ 'formable_type' => 'SalesReturn',
+ ], 'tenant');
$this->assertDatabaseHas('user_activities', [
'number' => $response->json('data.form.number'),
'table_id' => $response->json('data.id'),
'table_type' => 'SalesReturn',
'activity' => 'Update - 1'
], 'tenant');
- }
-
- /** @test */
- public function unauthorized_delete_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $this->assertDatabaseHas('forms', [
+ 'id' => $response->json('data.form.id'),
+ 'number' => $oldSalesReturn->form->edited_number,
+ 'notes' => $data['notes'],
+ 'created_by' => $response->json('data.form.created_by'),
+ 'updated_by' => $response->json('data.form.updated_by'),
+ 'approval_status' => 0,
+ 'done' => 0,
+ 'formable_id' => $response->json('data.id'),
+ 'formable_type' => 'SalesReturn',
+ 'request_approval_to' => $data['request_approval_to'],
+ ], 'tenant');
+
+ $this->assertDatabaseHas('sales_returns', [
+ 'id' => $response->json('data.id'),
+ 'sales_invoice_id' => $data['sales_invoice_id'],
+ 'customer_id' => $data['customer_id'],
+ 'customer_name' => $data['customer_name'],
+ 'customer_address' => $data['customer_address'],
+ 'customer_phone' => $data['customer_phone'],
+ 'tax' => $data['tax'],
+ 'amount' => $data['amount'],
+ 'warehouse_id' => $data['warehouse_id'],
+ ], 'tenant');
+
+ $this->assertDatabaseHas('sales_return_items', [
+ 'sales_return_id' => $response->json('data.id'),
+ 'sales_invoice_item_id' => $data['items'][0]['sales_invoice_item_id'],
+ 'item_id' => $data['items'][0]['item_id'],
+ 'item_name' => $data['items'][0]['item_name'],
+ 'quantity' => $data['items'][0]['quantity'],
+ 'quantity_sales' => $data['items'][0]['quantity_sales'],
+ 'price' => $data['items'][0]['price'],
+ 'discount_percent' => $data['items'][0]['discount_percent'],
+ 'discount_value' => $data['items'][0]['discount_value'] .'000000000000000000000000000000',
+ 'unit' => $data['items'][0]['unit'],
+ 'converter' => $data['items'][0]['converter'],
+ 'expiry_date' => $data['items'][0]['expiry_date'],
+ 'production_number' => $data['items'][0]['production_number'],
+ 'notes' => $data['items'][0]['notes'],
+ 'allocation_id' => $data['items'][0]['allocation_id'],
+ ], 'tenant');
+ }
+
+ /** @test */
+ public function unauthorized_different_default_branch_delete_sales_return()
+ {
+ $this->create_sales_return();
+
+ $branch = $this->createBranch();
+ $this->branchDefault->pivot->branch_id = $branch->id;
+ $this->branchDefault->pivot->is_default = true;
+ $this->branchDefault->pivot->save();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default branch to delete this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_no_warehouse_default_branch_delete_sales_return()
+ {
+ $this->create_sales_return();
+
+ $this->removeUserWarehouse();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $data['reason'] = $this->faker->text(200);
+
+ $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'please set default warehouse to delete this form'
+ ]);
+ }
+
+ /** @test */
+ public function unauthorized_delete_sales_return()
+ {
+ $this->create_sales_return();
+
$this->unsetUserRole();
-
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data['reason'] = $this->faker->text(200);
-
+
$response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
+
$response->assertStatus(500)
- ->assertJson([
- "code" => 0,
- "message" => "There is no permission named `delete sales return` for guard `api`."
- ]);
- }
-
- /** @test */
- public function referenced_delete_sales_return()
- {
- $this->success_create_sales_return();
-
+ ->assertJson([
+ 'code' => 0,
+ 'message' => 'There is no permission named `delete sales return` for guard `api`.'
+ ]);
+ }
+ /** @test */
+ public function error_form_done_delete_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+ $salesReturn->form->done = 1;
+ $salesReturn->form->save();
+
+ $data['reason'] = $this->faker->text(200);
+
+ $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form already done'
+ ]);
+ }
+
+ /** @test */
+ public function referenced_delete_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$this->createPaymentCollection($salesReturn);
+
$data['reason'] = $this->faker->text(200);
-
+
$response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
- $response
- ->assertStatus(422)
- ->assertJsonFragment(['message' => 'Cannot edit form because referenced by payment collection']);
- }
-
- /** @test */
- public function success_delete_sales_return()
- {
- $this->success_create_sales_return();
-
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'form referenced by payment collection'
+ ]);
+ }
+
+ /** @test */
+ public function error_empty_reason_delete_sales_return()
+ {
+ $this->create_sales_return();
+
+ $salesReturn = SalesReturn::orderBy('id', 'asc')->first();
+
+ $response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, [], $this->headers);
+
+ $response->assertStatus(422)
+ ->assertJson([
+ 'code' => 422,
+ 'message' => 'The given data was invalid.',
+ 'errors' => [
+ 'reason' => [
+ 'The reason field is required.'
+ ]
+ ]
+ ]);
+ }
+
+ /** @test */
+ public function success_delete_sales_return()
+ {
+ $this->create_sales_return();
+
$salesReturn = SalesReturn::orderBy('id', 'asc')->first();
$data['reason'] = $this->faker->text(200);
-
+
$response = $this->json('DELETE', self::$path . '/' . $salesReturn->id, $data, $this->headers);
-
+
$response->assertStatus(204);
+
$this->assertDatabaseHas('forms', [
- 'number' => $salesReturn->form->number,
- 'request_cancellation_reason' => $data['reason'],
- 'cancellation_status' => 0,
+ 'number' => $salesReturn->form->number,
+ 'request_cancellation_reason' => $data['reason'],
+ 'cancellation_status' => 0,
], 'tenant');
- }
+ }
}
\ No newline at end of file
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 3abc80670..c2e358847 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -4,6 +4,7 @@
use App\Model\Accounting\ChartOfAccount;
use App\Model\Accounting\ChartOfAccountType;
+use App\Model\HumanResource\Employee\Employee;
use App\Model\Master\Branch;
use App\Model\Package;
use App\Model\Project\Project;
@@ -38,8 +39,11 @@ abstract class TestCase extends BaseTestCase
* @var null|User
*/
protected $user;
+ protected $userPassword;
protected $account = null;
+ protected $employee = null;
+ protected $role = null;
/**
* Set up the test.
@@ -73,7 +77,10 @@ protected function tearDown(): void
protected function signIn()
{
- $this->user = factory(User::class)->create();
+ $this->userPassword = 'password';
+ $this->user = factory(User::class)->create([
+ 'password' => bcrypt($this->userPassword),
+ ]);
$this->actingAs($this->user, 'api');
@@ -143,6 +150,15 @@ protected function createSampleChartAccount($chartOfAccountType)
$this->account = $chartOfAccount;
}
+ protected function createSampleEmployee()
+ {
+ $employee = new Employee;
+ $employee->name = 'John Doe';
+ $employee->personal_identity = 'PASSPORT 940001930211FA';
+ $employee->save();
+ $this->employee = $employee;
+ }
+
protected function setRole()
{
$role = \App\Model\Auth\Role::createIfNotExists('super admin');
@@ -151,6 +167,7 @@ protected function setRole()
$hasRole->model_type = 'App\Model\Master\User';
$hasRole->model_id = $this->user->id;
$hasRole->save();
+ $this->role = $role;
}
protected function setPermission()