323 lines
12 KiB
PHP
323 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\FinanceDocument;
|
|
use App\Models\PaymentOrder;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Foundation\Testing\WithFaker;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Payment Order Workflow Feature Tests
|
|
*
|
|
* Tests payment order creation, verification, and execution
|
|
*/
|
|
class PaymentOrderWorkflowTest extends TestCase
|
|
{
|
|
use RefreshDatabase, WithFaker;
|
|
|
|
protected User $accountant;
|
|
protected User $cashier;
|
|
protected FinanceDocument $approvedDocument;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withoutMiddleware([\App\Http\Middleware\EnsureUserIsAdmin::class]);
|
|
|
|
Permission::findOrCreate('create_payment_order', 'web');
|
|
Permission::findOrCreate('view_payment_orders', 'web');
|
|
Permission::findOrCreate('verify_payment_order', 'web');
|
|
Permission::findOrCreate('execute_payment', 'web');
|
|
|
|
Role::firstOrCreate(['name' => 'admin']);
|
|
Role::firstOrCreate(['name' => 'finance_accountant']);
|
|
Role::firstOrCreate(['name' => 'finance_cashier']);
|
|
|
|
$this->accountant = User::factory()->create(['email' => 'accountant@test.com']);
|
|
$this->cashier = User::factory()->create(['email' => 'cashier@test.com']);
|
|
|
|
$this->accountant->assignRole('admin');
|
|
$this->cashier->assignRole('admin');
|
|
$this->accountant->assignRole('finance_accountant');
|
|
$this->cashier->assignRole('finance_cashier');
|
|
|
|
$this->accountant->givePermissionTo(['create_payment_order', 'view_payment_orders']);
|
|
$this->cashier->givePermissionTo(['verify_payment_order', 'execute_payment', 'view_payment_orders']);
|
|
|
|
// Create an approved finance document
|
|
$this->approvedDocument = FinanceDocument::create([
|
|
'title' => 'Approved Document',
|
|
'description' => 'Test',
|
|
'amount' => 5000,
|
|
'request_type' => 'expense_reimbursement',
|
|
'status' => FinanceDocument::STATUS_APPROVED_ACCOUNTANT,
|
|
'submitted_by_id' => $this->accountant->id,
|
|
'submitted_at' => now(),
|
|
'cashier_approved_at' => now(),
|
|
'accountant_approved_at' => now(),
|
|
'amount_tier' => 'small',
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function accountant_can_create_payment_order_for_approved_document()
|
|
{
|
|
$this->actingAs($this->accountant);
|
|
|
|
$response = $this->post(route('admin.payment-orders.store'), [
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payee_name' => 'John Doe',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'bank_transfer',
|
|
'payee_bank_name' => 'Test Bank',
|
|
'payee_bank_code' => '012',
|
|
'payee_account_number' => '1234567890',
|
|
'notes' => 'Test payment order',
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('payment_orders', [
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payee_name' => 'John Doe',
|
|
'payment_amount' => 5000,
|
|
'status' => 'pending_verification',
|
|
]);
|
|
|
|
// Check finance document is updated
|
|
$this->approvedDocument->refresh();
|
|
$this->assertNotNull($this->approvedDocument->payment_order_created_at);
|
|
}
|
|
|
|
/** @test */
|
|
public function payment_order_number_is_automatically_generated()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->assertNotEmpty($paymentOrder->payment_order_number);
|
|
$this->assertStringStartsWith('PO-', $paymentOrder->payment_order_number);
|
|
}
|
|
|
|
/** @test */
|
|
public function cashier_can_verify_payment_order()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->actingAs($this->cashier);
|
|
|
|
$response = $this->post(route('admin.payment-orders.verify', $paymentOrder), [
|
|
'action' => 'approve',
|
|
'verification_notes' => 'Verified and approved',
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$paymentOrder->refresh();
|
|
$this->assertEquals('approved', $paymentOrder->verification_status);
|
|
$this->assertEquals('verified', $paymentOrder->status);
|
|
$this->assertNotNull($paymentOrder->verified_at);
|
|
$this->assertEquals($this->cashier->id, $paymentOrder->verified_by_cashier_id);
|
|
|
|
// Check finance document is updated
|
|
$this->approvedDocument->refresh();
|
|
$this->assertNotNull($this->approvedDocument->payment_verified_at);
|
|
}
|
|
|
|
/** @test */
|
|
public function cashier_can_reject_payment_order_during_verification()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->actingAs($this->cashier);
|
|
|
|
$response = $this->post(route('admin.payment-orders.verify', $paymentOrder), [
|
|
'action' => 'reject',
|
|
'verification_notes' => 'Incorrect amount',
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$paymentOrder->refresh();
|
|
$this->assertEquals('rejected', $paymentOrder->verification_status);
|
|
$this->assertNotNull($paymentOrder->verified_at);
|
|
}
|
|
|
|
/** @test */
|
|
public function cashier_can_execute_verified_payment_order()
|
|
{
|
|
Storage::fake('local');
|
|
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'bank_transfer',
|
|
'status' => 'verified',
|
|
'verification_status' => 'approved',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
'verified_by_cashier_id' => $this->cashier->id,
|
|
'verified_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($this->cashier);
|
|
|
|
$receipt = UploadedFile::fake()->create('receipt.pdf', 100);
|
|
|
|
$response = $this->post(route('admin.payment-orders.execute', $paymentOrder), [
|
|
'transaction_reference' => 'TXN123456',
|
|
'payment_receipt' => $receipt,
|
|
'execution_notes' => 'Payment completed successfully',
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$paymentOrder->refresh();
|
|
$this->assertEquals('executed', $paymentOrder->status);
|
|
$this->assertEquals('completed', $paymentOrder->execution_status);
|
|
$this->assertNotNull($paymentOrder->executed_at);
|
|
$this->assertEquals($this->cashier->id, $paymentOrder->executed_by_cashier_id);
|
|
$this->assertEquals('TXN123456', $paymentOrder->transaction_reference);
|
|
|
|
// Check finance document is updated
|
|
$this->approvedDocument->refresh();
|
|
$this->assertNotNull($this->approvedDocument->payment_executed_at);
|
|
}
|
|
|
|
/** @test */
|
|
public function cannot_execute_unverified_payment_order()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->assertFalse($paymentOrder->canBeExecuted());
|
|
}
|
|
|
|
/** @test */
|
|
public function cannot_verify_already_verified_payment_order()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'verified',
|
|
'verification_status' => 'approved',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
'verified_by_cashier_id' => $this->cashier->id,
|
|
'verified_at' => now(),
|
|
]);
|
|
|
|
$this->assertFalse($paymentOrder->canBeVerifiedByCashier());
|
|
}
|
|
|
|
/** @test */
|
|
public function accountant_can_cancel_unexecuted_payment_order()
|
|
{
|
|
$paymentOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 5000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->actingAs($this->accountant);
|
|
|
|
$response = $this->post(route('admin.payment-orders.cancel', $paymentOrder));
|
|
|
|
$response->assertRedirect();
|
|
|
|
$paymentOrder->refresh();
|
|
$this->assertEquals('cancelled', $paymentOrder->status);
|
|
}
|
|
|
|
/** @test */
|
|
public function payment_order_for_different_payment_methods()
|
|
{
|
|
// Test cash payment
|
|
$cashOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 1000,
|
|
'payment_method' => 'cash',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->assertEquals('現金', $cashOrder->getPaymentMethodText());
|
|
|
|
// Test check payment
|
|
$checkOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 2000,
|
|
'payment_method' => 'check',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->assertEquals('支票', $checkOrder->getPaymentMethodText());
|
|
|
|
// Test bank transfer
|
|
$transferOrder = PaymentOrder::create([
|
|
'finance_document_id' => $this->approvedDocument->id,
|
|
'payment_order_number' => PaymentOrder::generatePaymentOrderNumber(),
|
|
'payee_name' => 'Test Payee',
|
|
'payment_amount' => 3000,
|
|
'payment_method' => 'bank_transfer',
|
|
'payee_bank_name' => 'Test Bank',
|
|
'payee_bank_code' => '012',
|
|
'payee_account_number' => '1234567890',
|
|
'status' => 'pending_verification',
|
|
'created_by_accountant_id' => $this->accountant->id,
|
|
]);
|
|
|
|
$this->assertEquals('銀行轉帳', $transferOrder->getPaymentMethodText());
|
|
}
|
|
}
|