Wire POS register loyalty earn and redeem through CRM.
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.
This commit is contained in:
isaacclad
2026-07-15 15:46:28 +00:00
parent 38b7f96714
commit 468346b183
12 changed files with 680 additions and 25 deletions
@@ -10,6 +10,7 @@ use App\Models\PosSale;
use App\Services\Pos\CustomerDisplayService;
use App\Services\Pos\PosBarcodeLookupService;
use App\Services\Pos\PosLocationService;
use App\Services\Pos\PosLoyaltyService;
use App\Services\Pos\PosSaleService;
use App\Services\Crm\CrmClient;
use Illuminate\Http\JsonResponse;
@@ -27,6 +28,7 @@ class RegisterController extends Controller
private PosLocationService $locations,
private PosBarcodeLookupService $barcodes,
private CustomerDisplayService $customerDisplays,
private PosLoyaltyService $loyalty,
) {}
public function index(Request $request): View
@@ -34,12 +36,16 @@ class RegisterController extends Controller
$owner = $this->ownerRef($request);
$location = $this->location($request);
$display = $this->customerDisplays->ensureForLocation($location);
$loyaltyProgram = $this->loyalty->program($owner);
return view('pos.register', [
'products' => $this->registerProducts($owner, $location),
'location' => $location,
'crmCustomers' => $this->crmCustomers($owner),
'barcodeLookupUrl' => route('pos.register.lookup'),
'loyaltyUrl' => route('pos.register.loyalty'),
'loyaltyEnabled' => (bool) ($loyaltyProgram['enabled'] ?? false),
'loyaltyProgram' => $loyaltyProgram,
'customerDisplayUrl' => $this->customerDisplays->displayUrl($display),
'customerDisplayPushUrl' => route('pos.customer-display.push'),
'customerDisplayActionsUrl' => route('pos.customer-display.actions'),
@@ -47,6 +53,33 @@ class RegisterController extends Controller
]);
}
/** Live loyalty quote for the register (CRM-backed). */
public function loyalty(Request $request): JsonResponse
{
$data = $request->validate([
'crm_customer_id' => ['required', 'integer'],
'subtotal_minor' => ['nullable', 'integer', 'min:0'],
'redeem_points' => ['nullable', 'integer', 'min:0'],
]);
$owner = $this->ownerRef($request);
$subtotal = (int) ($data['subtotal_minor'] ?? 0);
$redeem = (int) ($data['redeem_points'] ?? 0);
$snapshot = $this->loyalty->snapshot($owner, (int) $data['crm_customer_id'], $subtotal);
if (! $snapshot) {
return response()->json(['enabled' => false, 'message' => 'Loyalty unavailable.'], 200);
}
$quote = $this->loyalty->quote($owner, (int) $data['crm_customer_id'], max(1, $subtotal), $redeem);
return response()->json([
'enabled' => (bool) ($snapshot['program']['enabled'] ?? false),
'snapshot' => $snapshot,
'quote' => $quote,
]);
}
public function lookup(Request $request): JsonResponse
{
$data = $request->validate([
@@ -146,6 +179,7 @@ class RegisterController extends Controller
'customer_email' => ['nullable', 'email', 'max:255'],
'customer_phone' => ['nullable', 'string', 'max:40'],
'crm_customer_id' => ['nullable', 'integer'],
'loyalty_redeem_points' => ['nullable', 'integer', 'min:0'],
'payment_method' => ['required', 'in:pay,cash'],
]);
@@ -166,11 +200,25 @@ class RegisterController extends Controller
'customer_email' => $data['customer_email'] ?? null,
'customer_phone' => $data['customer_phone'] ?? null,
'crm_customer_id' => $data['crm_customer_id'] ?? null,
'loyalty_redeem_points' => (int) ($data['loyalty_redeem_points'] ?? 0),
]);
if ($data['payment_method'] === 'cash') {
$this->sales->recordCashPayment($sale);
$this->customerDisplays->pushReceipt($location, $sale->fresh(['lines']));
$sale = $sale->fresh(['lines']);
$loyaltyPayload = null;
if ($sale->crm_customer_id) {
$snap = $this->loyalty->snapshot($merchant->public_id, (int) $sale->crm_customer_id, (int) $sale->total_minor);
$loyaltyPayload = $this->loyalty->displayPayload(
$snap,
(int) $sale->loyalty_points_redeemed,
(int) $sale->loyalty_points_earned,
);
}
$this->customerDisplays->pushReceipt($location, $sale, array_filter([
'loyalty' => $loyaltyPayload,
'discount_minor' => (int) $sale->loyalty_discount_minor,
]));
if ($location->printer_auto_print) {
return redirect()