fix(customers): allow cashiers to view customers and use refreshed token
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user