feat: cashier/counter reports, service-order & remission flows, Rached migration utils
- Add "Mis cortes de caja" report for cashiers/counters with sales detail. - Cash register history scoped to own cuts for non-admin roles; new /register/<id>/sales endpoint. - Remove dashboard from cashier menu; add Reports to cashier/counter. - Service orders: assign mechanic, budget field, invoice flag, counter/cashier can add items/remissions, convert to remission. - Remission notes module (UI, CSS, courier, counter remissions). - Customer hard-delete and vehicle/customer linkage in workshop. - POS: always show search results, compact payment grid, credit validation, tier pricing (5%/10%), ticket with customer/folio. - Inventory: CSV template with sku_secondary, alias import. - Rached migration scripts and DB migrations. - Version-bump cached JS/CSS query strings. Excludes local Rached session tokens/captures (rached_*.json / rached_*.txt).
This commit is contained in:
@@ -30,7 +30,12 @@ const POS = (() => {
|
||||
let canEditPrice = false;
|
||||
let canCreateWorkshopOrder = false;
|
||||
let canCreateLayaway = false;
|
||||
let canCreateRemission = false;
|
||||
let counterRemissionEnabled = false;
|
||||
let currentPerms = [];
|
||||
let receiptConfig = {};
|
||||
let couriers = [];
|
||||
let selectedCourierId = null;
|
||||
|
||||
// Currency-aware formatter: reads pos_currency from localStorage
|
||||
const _posCurrency = localStorage.getItem('pos_currency') || 'MXN';
|
||||
@@ -38,6 +43,16 @@ const POS = (() => {
|
||||
const _currLocale = _posCurrency === 'USD' ? 'en-US' : 'es-MX';
|
||||
const fmt = (n) => (_currSymbols[_posCurrency] || '$') + parseFloat(n || 0).toLocaleString(_currLocale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
|
||||
// Price based on customer tier: base price (price_1) with tier discount.
|
||||
// Tier 2 = Taller (5% off), Tier 3 = Mayoreo (10% off), Tier 1 = base.
|
||||
function priceForTier(basePrice, tier) {
|
||||
const p = parseFloat(basePrice) || 0;
|
||||
const t = parseInt(tier, 10) || 1;
|
||||
if (t === 2) return Math.round(p * 0.95 * 100) / 100;
|
||||
if (t === 3) return Math.round(p * 0.90 * 100) / 100;
|
||||
return p;
|
||||
}
|
||||
|
||||
function headers() {
|
||||
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
|
||||
}
|
||||
@@ -66,6 +81,10 @@ const POS = (() => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
function show(selector) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.style.display = '';
|
||||
}
|
||||
if (!canCancel) {
|
||||
hide('#btnCancelSale');
|
||||
hide('#fkeyEsc');
|
||||
@@ -77,6 +96,25 @@ const POS = (() => {
|
||||
hide('[title="Orden de servicio"]');
|
||||
}
|
||||
if (!canCreateLayaway) hide('[onclick="POS.createLayaway()"]');
|
||||
|
||||
// Counter remission workflow
|
||||
if (canCreateRemission) {
|
||||
hide('#btnCobrar');
|
||||
hide('.fkey[onclick="POS.checkout()"]');
|
||||
|
||||
hide('[onclick="POS.createLayaway()"]');
|
||||
hide('[onclick="POS.saveQuotation()"]');
|
||||
show('#btnRemission');
|
||||
show('#courierSelectField');
|
||||
} else {
|
||||
hide('#btnRemission');
|
||||
hide('#courierSelectField');
|
||||
}
|
||||
if (!currentPerms.includes('pos.sell')) {
|
||||
hide('#btnPayRemission');
|
||||
} else {
|
||||
show('#btnPayRemission');
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Init ────────────────────────────
|
||||
@@ -87,14 +125,26 @@ const POS = (() => {
|
||||
document.getElementById('employeeName').textContent = payload.name || 'Empleado';
|
||||
document.getElementById('branchName').textContent = payload.branch_name || '';
|
||||
const perms = payload.permissions || [];
|
||||
currentPerms = perms;
|
||||
const employeeRole = payload.role || '';
|
||||
canViewCost = perms.includes('pos.view_cost');
|
||||
canCancel = perms.includes('pos.cancel');
|
||||
canDiscount = perms.includes('pos.discount');
|
||||
canEditPrice = perms.includes('config.edit_prices');
|
||||
canEditPrice = perms.includes('config.edit_prices') || employeeRole === 'cashier' || employeeRole === 'counter';
|
||||
canCreateWorkshopOrder = perms.includes('workshop.edit');
|
||||
canCreateLayaway = perms.includes('pos.sell');
|
||||
employeeMaxDiscount = payload.max_discount_pct || 100;
|
||||
|
||||
// Counter remission feature
|
||||
try {
|
||||
const crCfg = await api('/pos/api/config/counter-remission');
|
||||
counterRemissionEnabled = crCfg.enabled === true;
|
||||
} catch (e) {
|
||||
counterRemissionEnabled = false;
|
||||
}
|
||||
// Counter remission workflow applies only to the counter role.
|
||||
canCreateRemission = counterRemissionEnabled && employeeRole === 'counter' && perms.includes('pos.remission');
|
||||
|
||||
// Show cost/margin columns and toggle button if permission
|
||||
if (canViewCost) {
|
||||
document.getElementById('thCost').style.display = '';
|
||||
@@ -138,6 +188,7 @@ const POS = (() => {
|
||||
// Load current register and receipt config
|
||||
await loadRegister();
|
||||
await loadReceiptConfig();
|
||||
await loadCouriers();
|
||||
|
||||
// Setup event listeners
|
||||
setupKeyboard();
|
||||
@@ -157,8 +208,10 @@ const POS = (() => {
|
||||
currentRegister = null;
|
||||
document.getElementById('registerInfo').innerHTML =
|
||||
'<span style="color:var(--color-error);cursor:pointer;" onclick="POS.showOpenRegisterModal()" title="Clic para abrir caja">⚠ Sin caja abierta — Clic para abrir</span>';
|
||||
// Force open register modal on first load
|
||||
showOpenRegisterModal();
|
||||
// Force open register modal on first load only for users who can sell
|
||||
if (currentPerms.includes('pos.sell')) {
|
||||
showOpenRegisterModal();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Register check failed:', e);
|
||||
@@ -174,6 +227,24 @@ const POS = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCouriers() {
|
||||
try {
|
||||
const data = await api('/pos/api/logistics/couriers');
|
||||
couriers = data.couriers || [];
|
||||
const sel = document.getElementById('remissionCourier');
|
||||
if (sel) {
|
||||
sel.innerHTML = '<option value="">-- Sin repartidor --</option>' +
|
||||
couriers.map(c => `<option value="${c.id}">${c.name}</option>`).join('');
|
||||
sel.addEventListener('change', () => {
|
||||
selectedCourierId = sel.value ? parseInt(sel.value, 10) : null;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not load couriers:', e);
|
||||
couriers = [];
|
||||
}
|
||||
}
|
||||
|
||||
function rc(key, fallback) {
|
||||
const v = receiptConfig[key];
|
||||
return v !== undefined && v !== null && v !== '' ? v : fallback;
|
||||
@@ -570,26 +641,11 @@ const POS = (() => {
|
||||
if (data.data.length === 0) {
|
||||
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--color-text-muted);">Sin resultados</div>';
|
||||
if (window.BarcodeFeedback) BarcodeFeedback.error();
|
||||
} else if (data.data.length === 1 && q.length >= 8) {
|
||||
// Auto-select single result on barcode scan (long codes)
|
||||
const item = data.data[0];
|
||||
let price = item.price_1;
|
||||
if (currentCustomer) {
|
||||
const tier = currentCustomer.price_tier || 1;
|
||||
price = tier === 3 ? item.price_3 : tier === 2 ? item.price_2 : item.price_1;
|
||||
}
|
||||
addFromSearch(item, price);
|
||||
input.value = '';
|
||||
hideSearchResults();
|
||||
return;
|
||||
} else {
|
||||
let html = '';
|
||||
data.data.forEach(item => {
|
||||
let price = item.price_1;
|
||||
if (currentCustomer) {
|
||||
const tier = currentCustomer.price_tier || 1;
|
||||
price = tier === 3 ? item.price_3 : tier === 2 ? item.price_2 : item.price_1;
|
||||
}
|
||||
const tier = currentCustomer ? (currentCustomer.price_tier || 1) : 1;
|
||||
const price = priceForTier(item.price_1, tier);
|
||||
html += `<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--color-border);cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:var(--transition-fast);" onmouseover="this.style.background='var(--color-primary-muted)'" onmouseout="this.style.background=''" onclick='POS.addFromSearch(${JSON.stringify(item).replace(/'/g, "'")}, ${price})'>
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold);">${item.name}</div>
|
||||
@@ -684,7 +740,7 @@ const POS = (() => {
|
||||
const tier = currentCustomer ? (currentCustomer.price_tier || 1) : 1;
|
||||
cart.forEach(item => {
|
||||
if (item.price_1 > 0) {
|
||||
item.unit_price = tier === 3 ? item.price_3 : tier === 2 ? item.price_2 : item.price_1;
|
||||
item.unit_price = priceForTier(item.price_1, tier);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -880,9 +936,12 @@ const POS = (() => {
|
||||
transferencia: 'refPayment',
|
||||
tarjeta: 'refPayment',
|
||||
mixto: 'mixedPayment',
|
||||
credito: 'creditPayment',
|
||||
cheque: 'chequePayment',
|
||||
pendiente: 'pendingPayment',
|
||||
};
|
||||
|
||||
['cashPayment', 'refPayment', 'mixedPayment'].forEach(id => {
|
||||
['cashPayment', 'refPayment', 'mixedPayment', 'creditPayment', 'chequePayment', 'pendingPayment'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
const isActive = el.id === tabs[method];
|
||||
@@ -896,6 +955,12 @@ const POS = (() => {
|
||||
const ref = document.getElementById('paymentRef');
|
||||
if (ref) ref.focus();
|
||||
}
|
||||
if (method === 'cheque') {
|
||||
const chequeAmount = document.getElementById('chequeAmount');
|
||||
if (chequeAmount) chequeAmount.value = fmt(getTotal());
|
||||
const chequeRef = document.getElementById('chequeRef');
|
||||
if (chequeRef) chequeRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function updateChange() {
|
||||
@@ -933,6 +998,7 @@ const POS = (() => {
|
||||
let amountPaid = 0;
|
||||
let paymentDetails = [];
|
||||
let reference = '';
|
||||
let saleType = 'cash';
|
||||
|
||||
if (paymentMethod === 'efectivo') {
|
||||
amountPaid = parseFloat(document.getElementById('cashReceived').value) || 0;
|
||||
@@ -952,6 +1018,20 @@ const POS = (() => {
|
||||
}
|
||||
});
|
||||
if (amountPaid < total) { alert(`Monto total insuficiente. Falta: ${fmt(total - amountPaid)}`); return; }
|
||||
} else if (paymentMethod === 'credito') {
|
||||
if (!currentCustomer) { alert('Seleccione un cliente para venta a crédito'); return; }
|
||||
const available = (currentCustomer.credit_limit || 0) - (currentCustomer.credit_balance || 0);
|
||||
if (total > available) {
|
||||
alert(`Crédito insuficiente. Disponible: ${fmt(available)}, Total: ${fmt(total)}`);
|
||||
return;
|
||||
}
|
||||
saleType = 'credit';
|
||||
amountPaid = 0;
|
||||
} else if (paymentMethod === 'cheque') {
|
||||
amountPaid = total;
|
||||
reference = document.getElementById('chequeRef').value.trim();
|
||||
} else if (paymentMethod === 'pendiente') {
|
||||
amountPaid = 0;
|
||||
}
|
||||
|
||||
const saleData = {
|
||||
@@ -964,7 +1044,7 @@ const POS = (() => {
|
||||
})),
|
||||
customer_id: currentCustomer ? currentCustomer.id : null,
|
||||
payment_method: paymentMethod,
|
||||
sale_type: 'cash',
|
||||
sale_type: saleType,
|
||||
register_id: currentRegister ? currentRegister.id : null,
|
||||
amount_paid: amountPaid,
|
||||
payment_details: paymentDetails,
|
||||
@@ -983,9 +1063,10 @@ const POS = (() => {
|
||||
const convertData = {
|
||||
register_id: currentRegister ? currentRegister.id : null,
|
||||
payment_method: paymentMethod,
|
||||
sale_type: 'cash',
|
||||
sale_type: saleType,
|
||||
amount_paid: amountPaid,
|
||||
payment_details: paymentDetails,
|
||||
reference: reference,
|
||||
};
|
||||
sale = await api('/pos/api/quotations/' + convertQuoteId + '/convert', {
|
||||
method: 'POST',
|
||||
@@ -1020,22 +1101,13 @@ const POS = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Credit Sale ─────────────────────
|
||||
async function creditSale() {
|
||||
if (cart.length === 0) { alert('Carrito vacio'); return; }
|
||||
if (!currentCustomer) { alert('Seleccione un cliente para venta a credito'); return; }
|
||||
if (!currentRegister) { alert('No hay caja abierta.'); return; }
|
||||
// ─── Counter Remission Note ────────────
|
||||
async function createRemissionNote() {
|
||||
if (cart.length === 0) { showToast('Carrito vacio'); return; }
|
||||
if (!canCreateRemission) { showToast('No tienes permiso para generar notas de remision'); return; }
|
||||
|
||||
const total = getTotal();
|
||||
const available = (currentCustomer.credit_limit || 0) - (currentCustomer.credit_balance || 0);
|
||||
|
||||
if (currentCustomer.credit_limit > 0 && total > available) {
|
||||
if (!confirm(`Credito insuficiente. Disponible: ${fmt(available)}, Total: ${fmt(total)}. Continuar?`)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const saleData = {
|
||||
const noteData = {
|
||||
items: cart.map(item => ({
|
||||
inventory_id: item.inventory_id,
|
||||
quantity: item.quantity,
|
||||
@@ -1043,19 +1115,21 @@ const POS = (() => {
|
||||
discount_pct: item.discount_pct,
|
||||
tax_rate: item.tax_rate,
|
||||
})),
|
||||
customer_id: currentCustomer.id,
|
||||
payment_method: 'credito',
|
||||
sale_type: 'credit',
|
||||
customer_id: currentCustomer ? currentCustomer.id : null,
|
||||
notes: 'Nota de remision generada desde mostrador',
|
||||
register_id: currentRegister ? currentRegister.id : null,
|
||||
amount_paid: 0,
|
||||
courier_id: selectedCourierId,
|
||||
};
|
||||
|
||||
try {
|
||||
const sale = await api('/pos/api/sales', {
|
||||
const sale = await api('/pos/api/sales/remission', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(saleData),
|
||||
body: JSON.stringify(noteData),
|
||||
});
|
||||
|
||||
if (selectedCourierId) {
|
||||
const courier = couriers.find(c => c.id === selectedCourierId);
|
||||
sale.courier_name = courier ? courier.name : '';
|
||||
}
|
||||
lastSaleId = sale.id;
|
||||
lastSaleData = sale;
|
||||
try { sessionStorage.setItem('pos_last_sale_id', sale.id); } catch(e) {}
|
||||
@@ -1063,12 +1137,128 @@ const POS = (() => {
|
||||
cart = [];
|
||||
selectedRow = -1;
|
||||
clearCustomer();
|
||||
selectedCourierId = null;
|
||||
const sel = document.getElementById('remissionCourier');
|
||||
if (sel) sel.value = '';
|
||||
renderCart();
|
||||
showToast(`Nota de remision NR-${sale.id} generada`);
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
alert('Error al generar nota de remision: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function openPayRemissionModal() {
|
||||
if (!currentPerms.includes('pos.sell')) { showToast('No tienes permiso para cobrar notas'); return; }
|
||||
document.getElementById('payRemissionFolio').value = '';
|
||||
document.getElementById('payRemissionDetail').innerHTML = '';
|
||||
document.getElementById('payRemissionResult').innerHTML = '';
|
||||
document.getElementById('payRemissionActions').style.display = 'none';
|
||||
document.getElementById('payRemissionModal').classList.add('open');
|
||||
setTimeout(() => document.getElementById('payRemissionFolio').focus(), 100);
|
||||
}
|
||||
|
||||
function closePayRemissionModal() {
|
||||
document.getElementById('payRemissionModal').classList.remove('open');
|
||||
}
|
||||
|
||||
let pendingRemissionToPay = null;
|
||||
|
||||
async function searchRemissionToPay() {
|
||||
const folio = parseInt(document.getElementById('payRemissionFolio').value, 10);
|
||||
if (!folio) { showToast('Ingresa un folio valido'); return; }
|
||||
pendingRemissionToPay = null;
|
||||
try {
|
||||
const sale = await api('/pos/api/sales/' + folio);
|
||||
if (sale.status !== 'pending_payment') {
|
||||
document.getElementById('payRemissionDetail').innerHTML = `<div class="error-msg">La venta ${folio} no esta pendiente de pago</div>`;
|
||||
document.getElementById('payRemissionActions').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
pendingRemissionToPay = sale;
|
||||
let itemsHtml = (sale.items || []).map(it => `
|
||||
<div class="ticket-line">
|
||||
<span class="qty">${it.quantity}</span>
|
||||
<span class="name">${it.name || ''}</span>
|
||||
<span class="subtotal">${fmt(it.subtotal || 0)}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
document.getElementById('payRemissionDetail').innerHTML = `
|
||||
<div class="info-row"><span>Cliente:</span><span>${sale.customer_name || 'Publico General'}</span></div>
|
||||
<div class="info-row"><span>Vendedor:</span><span>${sale.employee_name || ''}</span></div>
|
||||
<div class="info-row"><span>Total:</span><span class="grand">${fmt(sale.total)}</span></div>
|
||||
<hr class="divider">
|
||||
${itemsHtml}
|
||||
`;
|
||||
document.getElementById('payRemissionActions').style.display = '';
|
||||
} catch (e) {
|
||||
document.getElementById('payRemissionDetail').innerHTML = `<div class="error-msg">${e.message}</div>`;
|
||||
document.getElementById('payRemissionActions').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmPayRemission() {
|
||||
if (!pendingRemissionToPay) return;
|
||||
const sale = pendingRemissionToPay;
|
||||
const paymentMethod = document.getElementById('payRemissionMethod').value;
|
||||
let amountPaid = parseFloat(sale.total);
|
||||
let paymentDetails = [];
|
||||
let reference = '';
|
||||
|
||||
if (paymentMethod === 'efectivo') {
|
||||
const received = parseFloat(document.getElementById('payRemissionReceived').value) || 0;
|
||||
if (received < sale.total) { alert('Monto insuficiente'); return; }
|
||||
amountPaid = received;
|
||||
} else if (paymentMethod === 'mixto') {
|
||||
const rows = document.querySelectorAll('#payRemissionMixed .mixed-row');
|
||||
let sum = 0;
|
||||
rows.forEach(row => {
|
||||
const method = row.querySelector('select').value;
|
||||
const amount = parseFloat(row.querySelector('.mixed-amount').value) || 0;
|
||||
const ref = row.querySelectorAll('input')[1]?.value || '';
|
||||
if (amount > 0) {
|
||||
paymentDetails.push({ method, amount, reference: ref });
|
||||
sum += amount;
|
||||
}
|
||||
});
|
||||
if (sum < sale.total) { alert(`Monto total insuficiente. Falta: ${fmt(sale.total - sum)}`); return; }
|
||||
amountPaid = sum;
|
||||
} else {
|
||||
reference = document.getElementById('payRemissionReference').value.trim();
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await api('/pos/api/sales/' + sale.id + '/pay', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
payment_method: paymentMethod,
|
||||
amount_paid: amountPaid,
|
||||
payment_details: paymentDetails,
|
||||
register_id: currentRegister ? currentRegister.id : null,
|
||||
reference: reference,
|
||||
}),
|
||||
});
|
||||
closePayRemissionModal();
|
||||
showToast(`Nota NR-${sale.id} pagada`);
|
||||
// Refresh sale object to print paid ticket
|
||||
const updated = await api('/pos/api/sales/' + sale.id);
|
||||
lastSaleId = updated.id;
|
||||
lastSaleData = updated;
|
||||
showTicket(updated);
|
||||
} catch (e) {
|
||||
alert('Error al cobrar nota: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePayRemissionMethod() {
|
||||
const method = document.getElementById('payRemissionMethod').value;
|
||||
const cashEl = document.getElementById('payRemissionCash');
|
||||
const refEl = document.getElementById('payRemissionRef');
|
||||
const mixedEl = document.getElementById('payRemissionMixed');
|
||||
if (cashEl) cashEl.style.display = method === 'efectivo' ? '' : 'none';
|
||||
if (refEl) refEl.style.display = (method === 'transferencia' || method === 'tarjeta') ? '' : 'none';
|
||||
if (mixedEl) mixedEl.style.display = method === 'mixto' ? '' : 'none';
|
||||
}
|
||||
|
||||
// ─── Quotation ───────────────────────
|
||||
async function saveQuotation() {
|
||||
if (cart.length === 0) { showToast('Carrito vacio'); return; }
|
||||
@@ -1284,8 +1474,9 @@ const POS = (() => {
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
|
||||
const customerName = currentCustomer ? currentCustomer.name : 'Publico General';
|
||||
const customerRfc = currentCustomer && currentCustomer.rfc ? currentCustomer.rfc : '';
|
||||
const isRemission = sale.status === 'pending_payment';
|
||||
const customerName = sale.customer_name || (currentCustomer ? currentCustomer.name : 'Publico General');
|
||||
const customerRfc = sale.customer_rfc || (currentCustomer && currentCustomer.rfc ? currentCustomer.rfc : '');
|
||||
|
||||
let itemsHtml = '';
|
||||
(sale.items || []).forEach(item => {
|
||||
@@ -1333,13 +1524,14 @@ const POS = (() => {
|
||||
</div>
|
||||
<hr class="divider-double">
|
||||
<div class="folio-line">
|
||||
<span>VENTA: V-${sale.id}</span>
|
||||
<span>${isRemission ? 'NOTA DE REMISION' : 'VENTA'}: ${isRemission ? 'NR' : 'V'}-${sale.id}</span>
|
||||
<span>${dateStr}</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-size: 9px; color: #555; margin-bottom: 4px;">
|
||||
<span>Cliente: ${customerName}</span>
|
||||
${customerRfc ? `<span>RFC: ${customerRfc}</span>` : ''}
|
||||
</div>
|
||||
${isRemission && sale.courier_name ? `<div class="ticket-row" style="font-size: 9px; color: #555; margin-bottom: 4px;"><span>Repartidor: ${sale.courier_name}</span></div>` : ''}
|
||||
<hr class="divider">
|
||||
<div class="item-line-wide" style="font-weight: bold; font-size: 9px; color: #555; text-transform: uppercase;">
|
||||
<span class="qty">Cant</span>
|
||||
@@ -1363,6 +1555,12 @@ const POS = (() => {
|
||||
${showPayment ? `
|
||||
<hr class="divider">
|
||||
<div class="payment-section">
|
||||
${isRemission ? `
|
||||
<div class="ticket-row" style="font-weight: bold; color: #b91c1c;">
|
||||
<span>Estado:</span><span>PENDIENTE DE PAGO</span>
|
||||
</div>
|
||||
<div style="font-size: 9px; text-align: center; margin-top: 4px;">Presente esta nota en caja para pagar</div>
|
||||
` : `
|
||||
<div class="ticket-row">
|
||||
<span>Forma de pago:</span><span>${sale.payment_method || paymentMethod}</span>
|
||||
</div>
|
||||
@@ -1373,11 +1571,13 @@ const POS = (() => {
|
||||
<div class="ticket-row" style="font-weight: bold;">
|
||||
<span>Cambio:</span><span>${fmt(sale.change_given || 0)}</span>
|
||||
</div>` : ''}
|
||||
`}
|
||||
</div>` : ''}
|
||||
<hr class="divider">
|
||||
<div class="footer-section">
|
||||
<div class="thanks">${thanksMsg}</div>
|
||||
${footerMsg ? `<div>${footerMsg}</div>` : ''}
|
||||
<div class="thanks">${isRemission ? 'Gracias por su preferencia' : thanksMsg}</div>
|
||||
${footerMsg && !isRemission ? `<div>${footerMsg}</div>` : ''}
|
||||
${isRemission ? '<div style="font-size: 9px;">Conserve esta nota para el pago</div>' : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1387,7 +1587,7 @@ const POS = (() => {
|
||||
const preview = document.getElementById('ticketPreviewContent');
|
||||
if (preview) preview.innerHTML = ticketHtml;
|
||||
const modalHeader = document.querySelector('#ticketModal .modal-header h3');
|
||||
if (modalHeader) modalHeader.textContent = 'Ticket de Venta';
|
||||
if (modalHeader) modalHeader.textContent = isRemission ? 'Nota de Remision' : 'Ticket de Venta';
|
||||
|
||||
document.getElementById('ticketModal').classList.add('open');
|
||||
}
|
||||
@@ -1592,7 +1792,9 @@ const POS = (() => {
|
||||
showNewCustomerModal, closeNewCustomerModal, saveNewCustomer,
|
||||
checkout, confirmPayment, closePaymentModal,
|
||||
selectPaymentMethod, updateChange, updateMixedTotal,
|
||||
creditSale, saveQuotation, createLayaway,
|
||||
createRemissionNote, openPayRemissionModal, closePayRemissionModal,
|
||||
searchRemissionToPay, confirmPayRemission, updatePayRemissionMethod,
|
||||
saveQuotation, createLayaway,
|
||||
createServiceOrder, closeServiceOrderModal, confirmServiceOrder, showServiceOrderTicket,
|
||||
showLastSale, openDrawer,
|
||||
showTicket, closeTicketModal, printTicket,
|
||||
|
||||
Reference in New Issue
Block a user