fix(customers): allow cashiers to view customers and use refreshed token
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

This commit is contained in:
2026-07-02 14:49:47 +00:00
parent 670db75fcd
commit 944b9b51b3
3 changed files with 23 additions and 12 deletions

View File

@@ -11,13 +11,13 @@ customers_bp = Blueprint('customers', __name__, url_prefix='/pos/api/customers')
def _can_view_customers(): def _can_view_customers():
"""Taller/counter employees need customer autocomplete even without customers.view.""" """Cashiers, counter and workshop employees need customer access for POS/service flows."""
return g.employee_role == 'owner' or 'customers.view' in g.permissions or g.employee_role in ('workshop', 'mechanic', 'counter') return g.employee_role == 'owner' or 'customers.view' in g.permissions or g.employee_role in ('cashier', 'counter', 'workshop', 'mechanic')
def _can_create_customer(): def _can_create_customer():
"""Taller/counter employees can create customers on the fly from an order.""" """Cashiers, counter and workshop employees can create customers on the fly."""
return g.employee_role == 'owner' or 'customers.create' in g.permissions or g.employee_role in ('workshop', 'mechanic', 'counter') return g.employee_role == 'owner' or 'customers.create' in g.permissions or g.employee_role in ('cashier', 'counter', 'workshop', 'mechanic')
# ─── Customer CRUD ───────────────────────────── # ─── Customer CRUD ─────────────────────────────
@@ -126,8 +126,10 @@ def list_customers():
@customers_bp.route('/<int:customer_id>', methods=['GET']) @customers_bp.route('/<int:customer_id>', methods=['GET'])
@require_auth('customers.view') @require_auth()
def get_customer(customer_id): def get_customer(customer_id):
if not _can_view_customers():
return jsonify({'error': 'Missing permissions: customers.view'}), 403
"""Get customer details with credit info, vehicle history, and recent purchases.""" """Get customer details with credit info, vehicle history, and recent purchases."""
conn = get_tenant_conn(g.tenant_id) conn = get_tenant_conn(g.tenant_id)
cur = conn.cursor() cur = conn.cursor()
@@ -198,8 +200,10 @@ def get_customer(customer_id):
@customers_bp.route('/<int:customer_id>/purchases', methods=['GET']) @customers_bp.route('/<int:customer_id>/purchases', methods=['GET'])
@require_auth('customers.view') @require_auth()
def get_customer_purchases(customer_id): def get_customer_purchases(customer_id):
if not _can_view_customers():
return jsonify({'error': 'Missing permissions: customers.view'}), 403
"""Return full purchase history for a customer.""" """Return full purchase history for a customer."""
conn = get_tenant_conn(g.tenant_id) conn = get_tenant_conn(g.tenant_id)
cur = conn.cursor() cur = conn.cursor()
@@ -379,8 +383,10 @@ def delete_customer(customer_id):
@customers_bp.route('/<int:customer_id>/statement', methods=['GET']) @customers_bp.route('/<int:customer_id>/statement', methods=['GET'])
@require_auth('customers.view') @require_auth()
def customer_statement(customer_id): def customer_statement(customer_id):
if not _can_view_customers():
return jsonify({'error': 'Missing permissions: customers.view'}), 403
"""Account statement: sales (invoices), payments, running balance. """Account statement: sales (invoices), payments, running balance.
Query params: Query params:
@@ -479,8 +485,10 @@ def customer_statement(customer_id):
@customers_bp.route('/<int:customer_id>/vehicles', methods=['GET']) @customers_bp.route('/<int:customer_id>/vehicles', methods=['GET'])
@require_auth('customers.view') @require_auth()
def customer_vehicles(customer_id): def customer_vehicles(customer_id):
if not _can_view_customers():
return jsonify({'error': 'Missing permissions: customers.view'}), 403
"""Get customer's vehicle list with last purchases per vehicle. """Get customer's vehicle list with last purchases per vehicle.
Vehicle info is stored as JSONB in customers.vehicle_info: Vehicle info is stored as JSONB in customers.vehicle_info:

View File

@@ -5,7 +5,6 @@
* Wired to the design-system HTML (customers.html). * Wired to the design-system HTML (customers.html).
*/ */
const Customers = (() => { const Customers = (() => {
let token = localStorage.getItem('pos_token') || '';
let currentPage = 1; let currentPage = 1;
let totalPages = 1; let totalPages = 1;
let currentCustomer = null; let currentCustomer = null;
@@ -16,6 +15,10 @@ const Customers = (() => {
const userPerms = user.permissions || []; const userPerms = user.permissions || [];
const canDeleteCustomer = userRole === 'owner' || userRole === 'admin' || userPerms.includes('customers.delete'); const canDeleteCustomer = userRole === 'owner' || userRole === 'admin' || userPerms.includes('customers.delete');
function getToken() {
return localStorage.getItem('pos_token') || '';
}
const fmt = (n) => '$' + parseFloat(n || 0).toLocaleString('es-MX', { const fmt = (n) => '$' + parseFloat(n || 0).toLocaleString('es-MX', {
minimumFractionDigits: 2, maximumFractionDigits: 2 minimumFractionDigits: 2, maximumFractionDigits: 2
}); });
@@ -28,7 +31,7 @@ const Customers = (() => {
}; };
function headers() { function headers() {
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }; return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getToken() };
} }
async function api(url, options = {}) { async function api(url, options = {}) {
@@ -733,7 +736,7 @@ const Customers = (() => {
// ─── Init ──────────────────────────── // ─── Init ────────────────────────────
function init() { function init() {
// Auth check // Auth check
if (!token) { if (!getToken()) {
window.location.href = '/pos/login'; window.location.href = '/pos/login';
return; return;
} }

View File

@@ -656,7 +656,7 @@
<script src="/pos/static/js/pos-utils.js?v=33" defer></script> <script src="/pos/static/js/pos-utils.js?v=33" defer></script>
<script src="/pos/static/js/sidebar.js?v=44" defer></script> <script src="/pos/static/js/sidebar.js?v=44" defer></script>
<script src="/pos/static/js/virtual-scroll.js" defer></script> <script src="/pos/static/js/virtual-scroll.js" defer></script>
<script src="/pos/static/js/customers.js?v=34" defer></script> <script src="/pos/static/js/customers.js?v=35" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script> <script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>