Files
isaacclad 468346b183
Deploy Ladill POS / deploy (push) Has been cancelled
Wire POS register loyalty earn and redeem through CRM.
Redeem at charge, earn on payment, reverse on cancel, and surface
balances on the till, customer display, sales, and receipts.
2026-07-15 15:46:28 +00:00

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']);
}
}