feat(pos): add customers page — search, credit, vehicles, statements
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
307
pos/static/js/customers.js
Normal file
307
pos/static/js/customers.js
Normal file
@@ -0,0 +1,307 @@
|
||||
// /home/Autopartes/pos/static/js/customers.js
|
||||
/**
|
||||
* Customers management frontend.
|
||||
* Communicates with /pos/api/customers (customers_bp).
|
||||
*/
|
||||
const Customers = (() => {
|
||||
let token = localStorage.getItem('pos_token') || '';
|
||||
let currentPage = 1;
|
||||
let currentCustomer = null;
|
||||
let searchTimeout = null;
|
||||
|
||||
const fmt = (n) => '$' + parseFloat(n || 0).toLocaleString('es-MX', {
|
||||
minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||
});
|
||||
|
||||
function headers() {
|
||||
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
|
||||
}
|
||||
|
||||
async function api(url, options = {}) {
|
||||
options.headers = headers();
|
||||
const res = await fetch(url, options);
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ─── List ────────────────────────────
|
||||
async function loadCustomers(page, q) {
|
||||
page = page || currentPage;
|
||||
q = q !== undefined ? q : (document.getElementById('searchInput').value || '');
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({ page, per_page: 50 });
|
||||
if (q) params.append('q', q);
|
||||
|
||||
const data = await api(`/pos/api/customers?${params}`);
|
||||
renderTable(data.data);
|
||||
renderPagination(data.pagination);
|
||||
} catch (e) {
|
||||
console.error('Load customers failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable(customers) {
|
||||
const tbody = document.getElementById('customersBody');
|
||||
const tiers = { 1: ['Mostrador', 'tier-1'], 2: ['Taller', 'tier-2'], 3: ['Mayoreo', 'tier-3'] };
|
||||
|
||||
let html = '';
|
||||
customers.forEach(c => {
|
||||
const [tierName, tierClass] = tiers[c.price_tier] || ['P1', 'tier-1'];
|
||||
const limit = c.credit_limit || 0;
|
||||
const balance = c.credit_balance || 0;
|
||||
const usagePct = limit > 0 ? Math.min(100, (balance / limit) * 100) : 0;
|
||||
const fillClass = usagePct > 90 ? 'danger' : usagePct > 70 ? 'warning' : '';
|
||||
|
||||
html += `<tr onclick="Customers.showDetail(${c.id})">
|
||||
<td><strong>${c.name}</strong></td>
|
||||
<td>${c.rfc || '-'}</td>
|
||||
<td>${c.phone || '-'}</td>
|
||||
<td><span class="tier-badge ${tierClass}">${tierName}</span></td>
|
||||
<td>${fmt(limit)}
|
||||
${limit > 0 ? `<div class="credit-bar"><div class="credit-fill ${fillClass}" style="width:${usagePct}%"></div></div>` : ''}
|
||||
</td>
|
||||
<td>${balance > 0 ? fmt(balance) : '-'}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
if (customers.length === 0) {
|
||||
html = '<tr><td colspan="6" style="text-align:center;color:#999;padding:20px;">Sin resultados</td></tr>';
|
||||
}
|
||||
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderPagination(pag) {
|
||||
const container = document.getElementById('pagination');
|
||||
if (pag.total_pages <= 1) { container.innerHTML = ''; return; }
|
||||
|
||||
let html = '';
|
||||
if (pag.page > 1) {
|
||||
html += `<button class="btn btn-secondary" onclick="Customers.goToPage(${pag.page - 1})">Anterior</button>`;
|
||||
}
|
||||
for (let i = 1; i <= Math.min(pag.total_pages, 10); i++) {
|
||||
html += `<button class="btn ${i === pag.page ? 'active' : 'btn-secondary'}" onclick="Customers.goToPage(${i})">${i}</button>`;
|
||||
}
|
||||
if (pag.page < pag.total_pages) {
|
||||
html += `<button class="btn btn-secondary" onclick="Customers.goToPage(${pag.page + 1})">Siguiente</button>`;
|
||||
}
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function goToPage(page) {
|
||||
currentPage = page;
|
||||
loadCustomers(page);
|
||||
}
|
||||
|
||||
function search() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
currentPage = 1;
|
||||
loadCustomers(1);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// ─── Detail ──────────────────────────
|
||||
async function showDetail(id) {
|
||||
try {
|
||||
const c = await api(`/pos/api/customers/${id}`);
|
||||
currentCustomer = c;
|
||||
|
||||
document.getElementById('detailName').textContent = c.name;
|
||||
|
||||
// Credit
|
||||
const available = (c.credit_limit || 0) - (c.credit_balance || 0);
|
||||
document.getElementById('detailCreditAvailable').textContent = fmt(available);
|
||||
document.getElementById('detailCreditLimit').textContent = fmt(c.credit_limit);
|
||||
document.getElementById('detailCreditBalance').textContent = fmt(c.credit_balance);
|
||||
|
||||
// Fiscal
|
||||
let fiscalHtml = '';
|
||||
fiscalHtml += `<div class="detail-field"><span class="label">RFC</span><span>${c.rfc || '-'}</span></div>`;
|
||||
fiscalHtml += `<div class="detail-field"><span class="label">Razon Social</span><span>${c.razon_social || '-'}</span></div>`;
|
||||
fiscalHtml += `<div class="detail-field"><span class="label">Regimen</span><span>${c.regimen_fiscal || '-'}</span></div>`;
|
||||
fiscalHtml += `<div class="detail-field"><span class="label">Uso CFDI</span><span>${c.uso_cfdi || '-'}</span></div>`;
|
||||
fiscalHtml += `<div class="detail-field"><span class="label">CP</span><span>${c.cp || '-'}</span></div>`;
|
||||
document.getElementById('detailFiscal').innerHTML = fiscalHtml;
|
||||
|
||||
// Contact
|
||||
let contactHtml = '';
|
||||
contactHtml += `<div class="detail-field"><span class="label">Telefono</span><span>${c.phone || '-'}</span></div>`;
|
||||
contactHtml += `<div class="detail-field"><span class="label">Email</span><span>${c.email || '-'}</span></div>`;
|
||||
contactHtml += `<div class="detail-field"><span class="label">Direccion</span><span>${c.address || '-'}</span></div>`;
|
||||
document.getElementById('detailContact').innerHTML = contactHtml;
|
||||
|
||||
// Vehicles
|
||||
const vehicles = c.vehicle_info || [];
|
||||
if (vehicles.length === 0) {
|
||||
document.getElementById('detailVehicles').innerHTML = '<div style="color:#999;font-size:13px;">Sin vehiculos registrados</div>';
|
||||
} else {
|
||||
document.getElementById('detailVehicles').innerHTML = vehicles.map(v =>
|
||||
`<div class="vehicle-card">
|
||||
<strong>${v.make || ''} ${v.model || ''} ${v.year || ''}</strong>
|
||||
${v.plates ? `<span style="margin-left:8px;color:#666;">Placas: ${v.plates}</span>` : ''}
|
||||
${v.vin ? `<div style="font-size:11px;color:#999;">VIN: ${v.vin}</div>` : ''}
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Recent purchases
|
||||
const purchases = c.recent_purchases || [];
|
||||
if (purchases.length === 0) {
|
||||
document.getElementById('detailPurchases').innerHTML = '<div style="color:#999;font-size:13px;">Sin compras recientes</div>';
|
||||
} else {
|
||||
document.getElementById('detailPurchases').innerHTML = purchases.map(p =>
|
||||
`<div class="purchase-item">
|
||||
<span>Venta #${p.id} - ${new Date(p.created_at).toLocaleDateString('es-MX')}</span>
|
||||
<span>${fmt(p.total)}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
document.getElementById('detailPanel').classList.add('active');
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function closeDetail() {
|
||||
document.getElementById('detailPanel').classList.remove('active');
|
||||
currentCustomer = null;
|
||||
}
|
||||
|
||||
// ─── Create/Edit Modal ───────────────
|
||||
function showCreateModal() {
|
||||
document.getElementById('modalTitle').textContent = 'Nuevo Cliente';
|
||||
document.getElementById('editId').value = '';
|
||||
document.getElementById('fName').value = '';
|
||||
document.getElementById('fRfc').value = '';
|
||||
document.getElementById('fRazonSocial').value = '';
|
||||
document.getElementById('fRegimenFiscal').value = '';
|
||||
document.getElementById('fUsoCfdi').value = 'G03';
|
||||
document.getElementById('fCp').value = '';
|
||||
document.getElementById('fPhone').value = '';
|
||||
document.getElementById('fEmail').value = '';
|
||||
document.getElementById('fAddress').value = '';
|
||||
document.getElementById('fPriceTier').value = '1';
|
||||
document.getElementById('fCreditLimit').value = '0';
|
||||
document.getElementById('customerModal').classList.add('active');
|
||||
document.getElementById('fName').focus();
|
||||
}
|
||||
|
||||
function editCurrent() {
|
||||
if (!currentCustomer) return;
|
||||
const c = currentCustomer;
|
||||
document.getElementById('modalTitle').textContent = 'Editar Cliente';
|
||||
document.getElementById('editId').value = c.id;
|
||||
document.getElementById('fName').value = c.name || '';
|
||||
document.getElementById('fRfc').value = c.rfc || '';
|
||||
document.getElementById('fRazonSocial').value = c.razon_social || '';
|
||||
document.getElementById('fRegimenFiscal').value = c.regimen_fiscal || '';
|
||||
document.getElementById('fUsoCfdi').value = c.uso_cfdi || 'G03';
|
||||
document.getElementById('fCp').value = c.cp || '';
|
||||
document.getElementById('fPhone').value = c.phone || '';
|
||||
document.getElementById('fEmail').value = c.email || '';
|
||||
document.getElementById('fAddress').value = c.address || '';
|
||||
document.getElementById('fPriceTier').value = c.price_tier || '1';
|
||||
document.getElementById('fCreditLimit').value = c.credit_limit || 0;
|
||||
document.getElementById('customerModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('customerModal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const name = document.getElementById('fName').value.trim();
|
||||
if (!name) { alert('Nombre es requerido'); return; }
|
||||
|
||||
const body = {
|
||||
name: name,
|
||||
rfc: document.getElementById('fRfc').value.trim() || null,
|
||||
razon_social: document.getElementById('fRazonSocial').value.trim() || null,
|
||||
regimen_fiscal: document.getElementById('fRegimenFiscal').value || null,
|
||||
uso_cfdi: document.getElementById('fUsoCfdi').value || 'G03',
|
||||
cp: document.getElementById('fCp').value.trim() || null,
|
||||
phone: document.getElementById('fPhone').value.trim() || null,
|
||||
email: document.getElementById('fEmail').value.trim() || null,
|
||||
address: document.getElementById('fAddress').value.trim() || null,
|
||||
price_tier: parseInt(document.getElementById('fPriceTier').value) || 1,
|
||||
credit_limit: parseFloat(document.getElementById('fCreditLimit').value) || 0,
|
||||
};
|
||||
|
||||
const editId = document.getElementById('editId').value;
|
||||
|
||||
try {
|
||||
if (editId) {
|
||||
await api(`/pos/api/customers/${editId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
} else {
|
||||
await api('/pos/api/customers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
closeModal();
|
||||
loadCustomers();
|
||||
if (editId && currentCustomer) {
|
||||
showDetail(editId);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Statement ───────────────────────
|
||||
async function showStatement() {
|
||||
if (!currentCustomer) return;
|
||||
document.getElementById('statementName').textContent = currentCustomer.name;
|
||||
|
||||
try {
|
||||
const data = await api(`/pos/api/customers/${currentCustomer.id}/statement`);
|
||||
|
||||
let html = `<div style="margin-bottom:12px;font-size:14px;">
|
||||
<strong>Saldo actual: ${fmt(data.balance)}</strong> |
|
||||
Limite: ${fmt(data.customer.credit_limit)}
|
||||
</div>`;
|
||||
|
||||
if (data.entries.length === 0) {
|
||||
html += '<div style="color:#999;padding:20px;text-align:center;">Sin movimientos</div>';
|
||||
} else {
|
||||
html += '<table style="width:100%;border-collapse:collapse;font-size:13px;">';
|
||||
html += '<tr style="background:#f5f5f5;"><th style="padding:8px;text-align:left;">Fecha</th><th style="text-align:left;">Concepto</th><th style="text-align:right;padding:8px;">Cargo</th><th style="text-align:right;padding:8px;">Abono</th><th style="text-align:right;padding:8px;">Saldo</th></tr>';
|
||||
|
||||
data.entries.forEach(e => {
|
||||
const dateStr = new Date(e.date).toLocaleDateString('es-MX');
|
||||
html += `<tr style="border-bottom:1px solid #eee;">
|
||||
<td style="padding:6px 8px;">${dateStr}</td>
|
||||
<td>${e.description}</td>
|
||||
<td style="text-align:right;padding:6px 8px;">${e.type === 'charge' ? fmt(e.amount) : ''}</td>
|
||||
<td style="text-align:right;padding:6px 8px;">${e.type === 'payment' ? fmt(e.amount) : ''}</td>
|
||||
<td style="text-align:right;padding:6px 8px;">${fmt(e.running_balance)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
html += '</table>';
|
||||
}
|
||||
|
||||
document.getElementById('statementContent').innerHTML = html;
|
||||
document.getElementById('statementModal').classList.add('active');
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Init ────────────────────────────
|
||||
loadCustomers();
|
||||
|
||||
return {
|
||||
search, goToPage, loadCustomers,
|
||||
showDetail, closeDetail,
|
||||
showCreateModal, editCurrent, closeModal, save,
|
||||
showStatement,
|
||||
};
|
||||
})();
|
||||
221
pos/templates/customers.html
Normal file
221
pos/templates/customers.html
Normal file
@@ -0,0 +1,221 @@
|
||||
<!-- /home/Autopartes/pos/templates/customers.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Clientes - Nexus POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: var(--font-body, 'Segoe UI', system-ui, sans-serif); background: var(--color-bg, #f0f2f5); color: var(--color-text, #1a1a2e); }
|
||||
|
||||
.topbar { background: var(--color-primary, #1a1a2e); color: #fff; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.topbar h1 { font-size: 18px; font-weight: 600; }
|
||||
.topbar .nav-links a { color: #b0bec5; text-decoration: none; margin-left: 16px; font-size: 14px; }
|
||||
.topbar .nav-links a:hover { color: #fff; }
|
||||
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
|
||||
.toolbar { display: flex; gap: 12px; margin-bottom: 16px; align-items: center; }
|
||||
.toolbar input { flex: 1; padding: 10px 14px; border: 1px solid var(--color-border, #ddd); border-radius: var(--radius, 6px); font-size: 14px; }
|
||||
.toolbar .btn { padding: 10px 20px; border: none; border-radius: var(--radius, 6px); cursor: pointer; font-size: 14px; font-weight: 500; }
|
||||
.btn-primary { background: var(--color-primary, #1a1a2e); color: #fff; }
|
||||
.btn-secondary { background: #e0e0e0; color: #333; }
|
||||
|
||||
.customers-table { width: 100%; border-collapse: collapse; background: #fff; border-radius: var(--radius, 6px); overflow: hidden; box-shadow: var(--shadow, 0 1px 3px rgba(0,0,0,0.1)); }
|
||||
.customers-table th { background: var(--color-surface, #f8f9fa); padding: 10px 12px; text-align: left; font-size: 12px; font-weight: 600; color: #666; border-bottom: 2px solid var(--color-border, #ddd); }
|
||||
.customers-table td { padding: 10px 12px; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
|
||||
.customers-table tr { cursor: pointer; }
|
||||
.customers-table tr:hover { background: #f8f9fa; }
|
||||
|
||||
.tier-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; }
|
||||
.tier-1 { background: #e3f2fd; color: #1565c0; }
|
||||
.tier-2 { background: #e8f5e9; color: #2e7d32; }
|
||||
.tier-3 { background: #fff3e0; color: #e65100; }
|
||||
|
||||
.credit-bar { width: 80px; height: 6px; background: #e0e0e0; border-radius: 3px; display: inline-block; vertical-align: middle; margin-left: 6px; }
|
||||
.credit-fill { height: 100%; border-radius: 3px; background: #4caf50; }
|
||||
.credit-fill.warning { background: #ff9800; }
|
||||
.credit-fill.danger { background: #f44336; }
|
||||
|
||||
.pagination { display: flex; justify-content: center; gap: 8px; margin-top: 16px; }
|
||||
.pagination .btn { padding: 6px 12px; font-size: 13px; }
|
||||
.pagination .btn.active { background: var(--color-primary, #1a1a2e); color: #fff; }
|
||||
|
||||
/* Detail panel */
|
||||
.detail-panel { display: none; position: fixed; top: 0; right: 0; width: 480px; height: 100vh; background: #fff; box-shadow: -4px 0 20px rgba(0,0,0,0.15); z-index: 100; overflow-y: auto; }
|
||||
.detail-panel.active { display: block; }
|
||||
.detail-header { padding: 16px 20px; background: var(--color-primary, #1a1a2e); color: #fff; display: flex; justify-content: space-between; align-items: center; }
|
||||
.detail-header h2 { font-size: 16px; }
|
||||
.detail-header .btn-close { background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; }
|
||||
.detail-body { padding: 20px; }
|
||||
.detail-section { margin-bottom: 20px; }
|
||||
.detail-section h3 { font-size: 14px; font-weight: 600; color: #666; margin-bottom: 8px; border-bottom: 1px solid #eee; padding-bottom: 4px; }
|
||||
.detail-field { display: flex; justify-content: space-between; padding: 4px 0; font-size: 13px; }
|
||||
.detail-field .label { color: #999; }
|
||||
|
||||
.credit-summary { background: #f5f5f5; padding: 12px; border-radius: 8px; margin-bottom: 16px; }
|
||||
.credit-summary .big-number { font-size: 24px; font-weight: 700; }
|
||||
|
||||
.purchases-list { max-height: 300px; overflow-y: auto; }
|
||||
.purchase-item { padding: 8px 0; border-bottom: 1px solid #f0f0f0; font-size: 13px; display: flex; justify-content: space-between; }
|
||||
|
||||
.vehicle-card { background: #f5f5f5; padding: 10px 12px; border-radius: 8px; margin-bottom: 8px; font-size: 13px; }
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
|
||||
.modal-overlay.active { display: flex; }
|
||||
.modal { background: #fff; border-radius: 12px; padding: 24px; width: 550px; max-width: 95vw; max-height: 90vh; overflow-y: auto; }
|
||||
.modal h2 { margin-bottom: 16px; }
|
||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.form-grid .full-width { grid-column: 1 / -1; }
|
||||
.form-field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.form-field label { font-size: 12px; color: #666; font-weight: 500; }
|
||||
.form-field input, .form-field select { padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; }
|
||||
.modal-actions { display: flex; gap: 8px; margin-top: 16px; justify-content: flex-end; }
|
||||
.modal-actions .btn { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<h1>Clientes</h1>
|
||||
<div class="nav-links">
|
||||
<a href="/pos/sale">POS</a>
|
||||
<a href="/pos/catalog">Catalogo</a>
|
||||
<a href="/pos/inventory">Inventario</a>
|
||||
<a href="/pos/customers">Clientes</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="toolbar">
|
||||
<input type="text" id="searchInput" placeholder="Buscar por nombre, RFC, telefono..." oninput="Customers.search()">
|
||||
<button class="btn btn-primary" onclick="Customers.showCreateModal()">+ Nuevo Cliente</button>
|
||||
</div>
|
||||
|
||||
<table class="customers-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>RFC</th>
|
||||
<th>Telefono</th>
|
||||
<th>Lista</th>
|
||||
<th>Credito</th>
|
||||
<th>Saldo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="customersBody"></tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination" id="pagination"></div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Panel (slides in from right) -->
|
||||
<div class="detail-panel" id="detailPanel">
|
||||
<div class="detail-header">
|
||||
<h2 id="detailName">Cliente</h2>
|
||||
<button class="btn-close" onclick="Customers.closeDetail()">×</button>
|
||||
</div>
|
||||
<div class="detail-body">
|
||||
<div class="detail-section">
|
||||
<div class="credit-summary">
|
||||
<div>Credito disponible</div>
|
||||
<div class="big-number" id="detailCreditAvailable">$0.00</div>
|
||||
<div style="font-size: 12px; color: #666;">
|
||||
Limite: <span id="detailCreditLimit">$0.00</span> |
|
||||
Saldo: <span id="detailCreditBalance">$0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>Datos Fiscales</h3>
|
||||
<div id="detailFiscal"></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>Contacto</h3>
|
||||
<div id="detailContact"></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>Vehiculos</h3>
|
||||
<div id="detailVehicles"></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>Compras Recientes</h3>
|
||||
<div class="purchases-list" id="detailPurchases"></div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px; margin-top: 16px;">
|
||||
<button class="btn btn-primary" onclick="Customers.editCurrent()">Editar</button>
|
||||
<button class="btn btn-secondary" onclick="Customers.showStatement()">Estado de Cuenta</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Modal -->
|
||||
<div class="modal-overlay" id="customerModal">
|
||||
<div class="modal">
|
||||
<h2 id="modalTitle">Nuevo Cliente</h2>
|
||||
<input type="hidden" id="editId">
|
||||
<div class="form-grid">
|
||||
<div class="form-field full-width"><label>Nombre *</label><input type="text" id="fName"></div>
|
||||
<div class="form-field"><label>RFC</label><input type="text" id="fRfc" maxlength="13"></div>
|
||||
<div class="form-field"><label>Razon Social</label><input type="text" id="fRazonSocial"></div>
|
||||
<div class="form-field"><label>Regimen Fiscal</label>
|
||||
<select id="fRegimenFiscal">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="601">601 - General de Ley PM</option>
|
||||
<option value="603">603 - PM Fines No Lucrativos</option>
|
||||
<option value="605">605 - Sueldos y Salarios</option>
|
||||
<option value="606">606 - Arrendamiento</option>
|
||||
<option value="612">612 - PF Actividad Empresarial</option>
|
||||
<option value="616">616 - Sin Obligaciones Fiscales</option>
|
||||
<option value="621">621 - Incorporacion Fiscal</option>
|
||||
<option value="625">625 - RESICO</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field"><label>Uso CFDI</label>
|
||||
<select id="fUsoCfdi">
|
||||
<option value="G03">G03 - Gastos en general</option>
|
||||
<option value="G01">G01 - Adquisicion de mercancias</option>
|
||||
<option value="P01">P01 - Por definir</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field"><label>Codigo Postal</label><input type="text" id="fCp" maxlength="5"></div>
|
||||
<div class="form-field"><label>Telefono</label><input type="tel" id="fPhone"></div>
|
||||
<div class="form-field"><label>Email</label><input type="email" id="fEmail"></div>
|
||||
<div class="form-field full-width"><label>Direccion</label><input type="text" id="fAddress"></div>
|
||||
<div class="form-field"><label>Lista de precio</label>
|
||||
<select id="fPriceTier">
|
||||
<option value="1">1 - Mostrador</option>
|
||||
<option value="2">2 - Taller</option>
|
||||
<option value="3">3 - Mayoreo</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field"><label>Limite de credito</label><input type="number" id="fCreditLimit" value="0" min="0" step="100"></div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="Customers.closeModal()">Cancelar</button>
|
||||
<button class="btn btn-primary" onclick="Customers.save()">Guardar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statement Modal -->
|
||||
<div class="modal-overlay" id="statementModal">
|
||||
<div class="modal" style="width: 650px;">
|
||||
<h2>Estado de Cuenta: <span id="statementName"></span></h2>
|
||||
<div id="statementContent" style="max-height: 500px; overflow-y: auto;"></div>
|
||||
<div class="modal-actions" style="margin-top: 16px;">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('statementModal').classList.remove('active')">Cerrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/customers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user