Deploy Ladill POS / deploy (push) Has been cancelled
Redeem at charge, earn on payment, reverse on cancel, and surface balances on the till, customer display, sales, and receipts.
169 lines
5.4 KiB
PHP
169 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Middleware\EnsurePlatformSession;
|
|
use App\Models\PosLocation;
|
|
use App\Models\PosSale;
|
|
use App\Models\User;
|
|
use App\Services\Pos\PosLoyaltyService;
|
|
use App\Services\Pos\PosSaleService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
class PosLoyaltyTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function owner(): User
|
|
{
|
|
return User::create([
|
|
'public_id' => 'owner-loyalty',
|
|
'name' => 'Owner',
|
|
'email' => 'owner-loyalty@example.com',
|
|
]);
|
|
}
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
|
$this->withoutVite();
|
|
config(['crm.url' => 'https://crm.test/api', 'crm.key' => 'pos-key']);
|
|
}
|
|
|
|
public function test_create_sale_redeems_points_via_crm_and_reduces_total(): void
|
|
{
|
|
Http::fake([
|
|
'crm.test/api/loyalty/redeem*' => Http::response([
|
|
'points' => 200,
|
|
'discount_minor' => 200,
|
|
'ledger_id' => 9,
|
|
'balance' => 50,
|
|
], 200),
|
|
'crm.test/api/loyalty/earn*' => Http::response([
|
|
'points' => 8,
|
|
'ledger_id' => 10,
|
|
'balance' => 58,
|
|
], 200),
|
|
]);
|
|
|
|
$owner = $this->owner();
|
|
PosLocation::create([
|
|
'owner_ref' => $owner->public_id,
|
|
'name' => 'Main',
|
|
'currency' => 'GHS',
|
|
'is_default' => true,
|
|
]);
|
|
|
|
$sales = app(PosSaleService::class);
|
|
$sale = $sales->createSale($owner, [
|
|
['name' => 'Shirt', 'unit_price_minor' => 1000, 'quantity' => 1],
|
|
], [
|
|
'crm_customer_id' => 42,
|
|
'loyalty_redeem_points' => 200,
|
|
'currency' => 'GHS',
|
|
]);
|
|
|
|
$this->assertSame(1000, $sale->subtotal_minor);
|
|
$this->assertSame(200, $sale->loyalty_discount_minor);
|
|
$this->assertSame(200, $sale->loyalty_points_redeemed);
|
|
$this->assertSame(800, $sale->total_minor);
|
|
|
|
$paid = $sales->recordCashPayment($sale);
|
|
$this->assertSame(8, $paid->loyalty_points_earned);
|
|
|
|
Http::assertSent(function ($request) {
|
|
return str_contains($request->url(), 'loyalty/redeem')
|
|
&& $request['customer_id'] === 42
|
|
&& $request['redeem_points'] === 200;
|
|
});
|
|
Http::assertSent(function ($request) {
|
|
return str_contains($request->url(), 'loyalty/earn')
|
|
&& $request['amount_minor'] === 800;
|
|
});
|
|
}
|
|
|
|
public function test_cancel_pending_sale_reverses_loyalty(): void
|
|
{
|
|
Http::fake([
|
|
'crm.test/api/loyalty/redeem*' => Http::response([
|
|
'points' => 100,
|
|
'discount_minor' => 100,
|
|
'ledger_id' => 1,
|
|
'balance' => 0,
|
|
], 200),
|
|
'crm.test/api/loyalty/reverse*' => Http::response(['reversed' => 1], 200),
|
|
]);
|
|
|
|
$owner = $this->owner();
|
|
$sales = app(PosSaleService::class);
|
|
$sale = $sales->createSale($owner, [
|
|
['name' => 'Item', 'unit_price_minor' => 500, 'quantity' => 1],
|
|
], [
|
|
'crm_customer_id' => 7,
|
|
'loyalty_redeem_points' => 100,
|
|
]);
|
|
|
|
$sales->cancelSale($sale);
|
|
$this->assertSame(PosSale::STATUS_CANCELLED, $sale->fresh()->status);
|
|
|
|
Http::assertSent(fn ($request) => str_contains($request->url(), 'loyalty/reverse'));
|
|
}
|
|
|
|
public function test_register_loyalty_endpoint_returns_quote(): void
|
|
{
|
|
Http::fake([
|
|
'crm.test/api/customers/5/loyalty*' => Http::response([
|
|
'program' => ['enabled' => true, 'name' => 'Rewards'],
|
|
'label' => 'Rewards',
|
|
'points_balance' => 300,
|
|
'points_earn' => 10,
|
|
'max_redeem_points' => 200,
|
|
], 200),
|
|
'crm.test/api/loyalty/quote*' => Http::response([
|
|
'points_balance' => 300,
|
|
'points' => 100,
|
|
'discount_minor' => 100,
|
|
'max_points' => 200,
|
|
'points_earn' => 9,
|
|
'payable_minor' => 900,
|
|
], 200),
|
|
]);
|
|
|
|
$owner = $this->owner();
|
|
PosLocation::create([
|
|
'owner_ref' => $owner->public_id,
|
|
'name' => 'Main',
|
|
'currency' => 'GHS',
|
|
'is_default' => true,
|
|
]);
|
|
|
|
$this->actingAs($owner)
|
|
->getJson(route('pos.register.loyalty', [
|
|
'crm_customer_id' => 5,
|
|
'subtotal_minor' => 1000,
|
|
'redeem_points' => 100,
|
|
]))
|
|
->assertOk()
|
|
->assertJsonPath('enabled', true)
|
|
->assertJsonPath('quote.discount_minor', 100)
|
|
->assertJsonPath('snapshot.points_balance', 300);
|
|
}
|
|
|
|
public function test_display_payload_maps_snapshot(): void
|
|
{
|
|
$payload = app(PosLoyaltyService::class)->displayPayload([
|
|
'program' => ['enabled' => true, 'name' => 'Rewards'],
|
|
'label' => 'Rewards',
|
|
'points_balance' => 40,
|
|
'points_earn' => 5,
|
|
], 10, 5);
|
|
|
|
$this->assertSame(40, $payload['points_balance']);
|
|
$this->assertSame(10, $payload['points_redeem']);
|
|
$this->assertSame(5, $payload['points_earn']);
|
|
}
|
|
}
|