Autosave: 20260210-173042
This commit is contained in:
parent
b30330b17b
commit
1fe85ff3ce
Binary file not shown.
@ -5,7 +5,7 @@
|
|||||||
<div class="container-fluid mt-4 mb-5 no-print">
|
<div class="container-fluid mt-4 mb-5 no-print">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2 class="h4 mb-0"><i class="bi bi-upc-scan me-2"></i>Barcode Label Printing</h2>
|
<h2 class="h4 mb-0"><i class="bi bi-upc-scan me-2"></i>Barcode Label Printing</h2>
|
||||||
<button onclick="window.print()" class="btn btn-primary">
|
<button id="printQueueBtn" class="btn btn-primary">
|
||||||
<i class="bi bi-printer me-2"></i>Print Label Queue
|
<i class="bi bi-printer me-2"></i>Print Label Queue
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<td><code>{{ product.sku }}</code></td>
|
<td><code>{{ product.sku }}</code></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-outline-primary add-to-queue"
|
<button type="button" class="btn btn-sm btn-outline-primary add-to-queue-btn"
|
||||||
data-id="{{ product.id }}"
|
data-id="{{ product.id }}"
|
||||||
data-name="{{ product.name_en }}" data-name-ar="{{ product.name_ar }}"
|
data-name="{{ product.name_en }}" data-name-ar="{{ product.name_ar }}"
|
||||||
data-sku="{{ product.sku }}"
|
data-sku="{{ product.sku }}"
|
||||||
@ -49,8 +49,7 @@
|
|||||||
title="Add to queue">
|
title="Add to queue">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-success direct-print-btn"
|
<button type="button" class="btn btn-sm btn-outline-success direct-print-btn"
|
||||||
onclick="directPrintFromRow(this)"
|
|
||||||
data-id="{{ product.id }}"
|
data-id="{{ product.id }}"
|
||||||
data-name="{{ product.name_en }}" data-name-ar="{{ product.name_ar }}"
|
data-name="{{ product.name_en }}" data-name-ar="{{ product.name_ar }}"
|
||||||
data-sku="{{ product.sku }}"
|
data-sku="{{ product.sku }}"
|
||||||
@ -84,7 +83,7 @@
|
|||||||
<option value="small">Small Sticker (38mm x 25mm)</option>
|
<option value="small">Small Sticker (38mm x 25mm)</option>
|
||||||
<option value="a4-24">A4 Sheet (3x8 = 24 labels)</option>
|
<option value="a4-24">A4 Sheet (3x8 = 24 labels)</option>
|
||||||
<option value="a4-40">A4 Sheet (4x10 = 40 labels)</option>
|
<option value="a4-40">A4 Sheet (4x10 = 40 labels)</option>
|
||||||
<option value="l7651">A4 Avery L7651 (5x13 = 65 labels)</option>
|
<option value="l7651" selected>A4 Avery L7651 (5x13 = 65 labels)</option>
|
||||||
<option value="l7656">A4 Avery L7656 (4x21 = 84 labels)</option>
|
<option value="l7656">A4 Avery L7656 (4x21 = 84 labels)</option>
|
||||||
<option value="l7156">A4 Avery L7156 (3x7 = 21 labels)</option>
|
<option value="l7156">A4 Avery L7156 (3x7 = 21 labels)</option>
|
||||||
<option value="price-tag">Jewelry / Price Tag (Small)</option>
|
<option value="price-tag">Jewelry / Price Tag (Small)</option>
|
||||||
@ -134,7 +133,7 @@
|
|||||||
<div class="card shadow-sm border-0">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">3. Live Preview (Single Label)</h5>
|
<h5 class="card-title mb-0">3. Live Preview (Single Label)</h5>
|
||||||
<button class="btn btn-sm btn-success" onclick="printSingleFromPreview()">
|
<button type="button" class="btn btn-sm btn-success" id="printPreviewBtn">
|
||||||
<i class="bi bi-printer me-1"></i>Print This Label
|
<i class="bi bi-printer me-1"></i>Print This Label
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -166,228 +165,121 @@
|
|||||||
<style>
|
<style>
|
||||||
/* Print Styles */
|
/* Print Styles */
|
||||||
@media screen {
|
@media screen {
|
||||||
.print-only { display: none; }
|
.print-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
#sidebar, .top-navbar, .navbar, footer { display: none !important; }
|
/* Force hiding of everything except printArea */
|
||||||
#content, main { margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
body > *:not(#printArea) {
|
||||||
.no-print { display: none !important; }
|
display: none !important;
|
||||||
.print-only { display: block; }
|
}
|
||||||
body { margin: 0; padding: 0; background: white; -webkit-print-color-adjust: exact; }
|
|
||||||
|
/* Ensure printArea is visible */
|
||||||
|
#printArea {
|
||||||
|
display: block !important;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
overflow: visible !important;
|
||||||
|
z-index: 9999;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: white !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Direct Print Mode: Remove A4 constraints */
|
/* Direct Print Mode */
|
||||||
body.direct-print-mode @page {
|
body.direct-print-mode @page {
|
||||||
size: auto;
|
size: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
body.direct-print-mode .label-sheet {
|
|
||||||
width: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
body.direct-print-mode .label-item {
|
|
||||||
border: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-sheet {
|
.label-sheet {
|
||||||
display: flex;
|
position: relative;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 5mm;
|
|
||||||
width: 210mm;
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: white;
|
background: white;
|
||||||
|
page-break-after: always;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
font-size: 0; /* Remove whitespace between inline-blocks */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific A4 Grid Layouts to ensure alignment */
|
.label-item {
|
||||||
.sheet-a4-24 {
|
box-sizing: border-box;
|
||||||
display: grid !important;
|
display: inline-block; /* More robust than flex for print grids */
|
||||||
grid-template-columns: repeat(3, 63.5mm);
|
vertical-align: top;
|
||||||
grid-auto-rows: 33.9mm;
|
text-align: center;
|
||||||
padding: 12.9mm 9.75mm !important;
|
overflow: hidden;
|
||||||
gap: 0;
|
font-size: initial; /* Reset font size */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-a4-40 {
|
/* Avery L7651 (5x13 = 65) */
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(4, 48.5mm);
|
|
||||||
grid-auto-rows: 25.4mm;
|
|
||||||
padding: 21.5mm 8mm !important;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avery L7651 (5x13) */
|
|
||||||
.sheet-l7651 {
|
.sheet-l7651 {
|
||||||
display: grid !important;
|
padding: 10.7mm 9.75mm !important;
|
||||||
grid-template-columns: repeat(5, 38.1mm);
|
|
||||||
grid-auto-rows: 21.2mm;
|
|
||||||
padding: 10.7mm 9.75mm !important;
|
|
||||||
gap: 0;
|
|
||||||
}
|
}
|
||||||
|
.label-l7651 {
|
||||||
/* Avery L7656 (4x21) */
|
width: 38.1mm !important;
|
||||||
.sheet-l7656 {
|
height: 21.2mm !important;
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(4, 46mm);
|
|
||||||
grid-auto-rows: 11.1mm;
|
|
||||||
padding: 31.95mm 13mm !important;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avery L7156 (3x7) */
|
|
||||||
.sheet-l7156 {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(3, 63.5mm);
|
|
||||||
grid-auto-rows: 38.1mm;
|
|
||||||
padding: 15.15mm 9.75mm !important;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Standard Sticker (50x25) - usually for thermal printers */
|
|
||||||
.label-standard {
|
|
||||||
width: 50mm;
|
|
||||||
height: 25mm;
|
|
||||||
border: 0.1mm solid #eee;
|
|
||||||
margin: 1mm;
|
|
||||||
padding: 2mm;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Small Sticker (38x25) */
|
|
||||||
.label-small {
|
|
||||||
width: 38mm;
|
|
||||||
height: 25mm;
|
|
||||||
border: 0.1mm solid #eee;
|
|
||||||
margin: 1mm;
|
|
||||||
padding: 1.5mm;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A4 24 Labels (3x8) */
|
|
||||||
.label-a4-24 {
|
|
||||||
width: 63.5mm;
|
|
||||||
height: 33.9mm;
|
|
||||||
padding: 2mm;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A4 40 Labels (4x10) */
|
|
||||||
.label-a4-40 {
|
|
||||||
width: 48.5mm;
|
|
||||||
height: 25.4mm;
|
|
||||||
padding: 1mm;
|
padding: 1mm;
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avery L7651 (5x13) */
|
/* Other layouts ... */
|
||||||
.label-l7651 {
|
.sheet-a4-24 { padding: 0 !important; }
|
||||||
width: 38.1mm;
|
.label-a4-24 { width: 70mm; height: 37.125mm; padding: 2mm; }
|
||||||
height: 21.2mm;
|
|
||||||
padding: 1mm;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avery L7656 (4x21) */
|
.sheet-a4-40 { padding: 0 !important; }
|
||||||
.label-l7656 {
|
.label-a4-40 { width: 52.5mm; height: 29.7mm; padding: 1mm; }
|
||||||
width: 46mm;
|
|
||||||
height: 11.1mm;
|
|
||||||
padding: 0.5mm;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
.label-l7656 svg {
|
|
||||||
height: 8mm !important;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.label-l7656 .label-text {
|
|
||||||
font-size: 5pt !important;
|
|
||||||
width: auto !important;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avery L7156 (3x7) */
|
.sheet-l7656 { padding: 31.95mm 13mm !important; }
|
||||||
.label-l7156 {
|
.label-l7656 {
|
||||||
width: 63.5mm;
|
width: 46mm; height: 11.1mm; padding: 0.5mm;
|
||||||
height: 38.1mm;
|
|
||||||
padding: 2mm;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
}
|
||||||
|
.label-l7656 .inner-flex {
|
||||||
|
display: flex; flex-direction: row; justify-content: space-around; align-items: center; height: 100%; width: 100%;
|
||||||
|
}
|
||||||
|
.label-l7656 svg { height: 8mm !important; width: auto; }
|
||||||
|
.label-l7656 .label-text { font-size: 5pt !important; width: auto !important; margin: 0 2px; }
|
||||||
|
|
||||||
/* Price Tag */
|
.sheet-l7156 { padding: 15.15mm 9.75mm !important; }
|
||||||
.label-price-tag {
|
.label-l7156 { width: 63.5mm; height: 38.1mm; padding: 2mm; }
|
||||||
width: 30mm;
|
|
||||||
height: 15mm;
|
|
||||||
margin: 1mm;
|
|
||||||
padding: 1mm;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-item svg {
|
.label-item svg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-text {
|
.label-text {
|
||||||
font-family: Arial, sans-serif;
|
font-family: 'Cairo', Arial, sans-serif;
|
||||||
font-size: 7pt;
|
font-size: 7pt;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
.label-price {
|
.label-price {
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.d-flex-print { display: flex; justify-content: space-between; width: 100%; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#previewBarcode {
|
#previewBarcode {
|
||||||
@ -398,23 +290,59 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const queue = [];
|
const queue = [];
|
||||||
const labelQueueBody = document.getElementById('labelQueue');
|
const labelQueueBody = document.getElementById('labelQueue');
|
||||||
const emptyQueue = document.getElementById('emptyQueue');
|
const emptyQueue = document.getElementById('emptyQueue');
|
||||||
const printArea = document.getElementById('printArea');
|
const printArea = document.getElementById('printArea');
|
||||||
let lastAddedProduct = null;
|
let lastAddedProduct = null;
|
||||||
|
|
||||||
// Add to Queue
|
// Move printArea to body safely
|
||||||
document.querySelectorAll('.add-to-queue').forEach(btn => {
|
if (printArea && printArea.parentElement !== document.body) {
|
||||||
btn.addEventListener('click', () => {
|
document.body.appendChild(printArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for JsBarcode
|
||||||
|
if (typeof JsBarcode === 'undefined') {
|
||||||
|
console.error("JsBarcode library not loaded!");
|
||||||
|
alert("Barcode library failed to load. Please check your internet connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EVENT DELEGATION FOR PRODUCT TABLE ---
|
||||||
|
const productTable = document.getElementById('productTable');
|
||||||
|
if (productTable) {
|
||||||
|
productTable.addEventListener('click', function(e) {
|
||||||
|
// Handle "Add to Queue"
|
||||||
|
const addBtn = e.target.closest('.add-to-queue-btn');
|
||||||
|
if (addBtn) {
|
||||||
|
e.preventDefault();
|
||||||
|
addToQueue(addBtn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle "Direct Print"
|
||||||
|
const directBtn = e.target.closest('.direct-print-btn');
|
||||||
|
if (directBtn) {
|
||||||
|
e.preventDefault();
|
||||||
|
directPrintFromRow(directBtn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADD TO QUEUE LOGIC ---
|
||||||
|
function addToQueue(btn) {
|
||||||
|
try {
|
||||||
const product = {
|
const product = {
|
||||||
id: btn.dataset.id,
|
id: btn.dataset.id,
|
||||||
name: btn.dataset.name, nameAr: btn.dataset.nameAr,
|
name: btn.dataset.name,
|
||||||
|
nameAr: btn.dataset.nameAr,
|
||||||
sku: btn.dataset.sku,
|
sku: btn.dataset.sku,
|
||||||
price: btn.dataset.price,
|
price: btn.dataset.price,
|
||||||
qty: 1
|
qty: 1
|
||||||
};
|
};
|
||||||
lastAddedProduct = product;
|
lastAddedProduct = product;
|
||||||
|
|
||||||
const existing = queue.find(p => p.id === product.id);
|
const existing = queue.find(p => p.id === product.id);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.qty++;
|
existing.qty++;
|
||||||
@ -423,13 +351,18 @@
|
|||||||
}
|
}
|
||||||
renderQueue();
|
renderQueue();
|
||||||
updatePreview();
|
updatePreview();
|
||||||
});
|
} catch (err) {
|
||||||
});
|
console.error("Error adding to queue:", err);
|
||||||
|
alert("An error occurred while adding the item.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RENDER QUEUE ---
|
||||||
function renderQueue() {
|
function renderQueue() {
|
||||||
if (queue.length === 0) {
|
if (queue.length === 0) {
|
||||||
labelQueueBody.innerHTML = '';
|
labelQueueBody.innerHTML = '';
|
||||||
emptyQueue.classList.remove('d-none');
|
emptyQueue.classList.remove('d-none');
|
||||||
|
printArea.innerHTML = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emptyQueue.classList.add('d-none');
|
emptyQueue.classList.add('d-none');
|
||||||
@ -441,44 +374,79 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" class="form-control form-control-sm qty-input"
|
<input type="number" class="form-control form-control-sm qty-input"
|
||||||
value="${p.qty}" min="1" onchange="updateQty(${index}, this.value)">
|
value="${p.qty}" min="1" data-index="${index}">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<button class="btn btn-sm btn-link text-danger" onclick="removeFromQueue(${index})">
|
<button type="button" class="btn btn-sm btn-link text-danger remove-btn" data-index="${index}">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
// Re-attach listeners for dynamic queue items
|
||||||
|
document.querySelectorAll('.qty-input').forEach(input => {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
const idx = parseInt(this.dataset.index);
|
||||||
|
const val = parseInt(this.value);
|
||||||
|
if (val > 0) {
|
||||||
|
queue[idx].qty = val;
|
||||||
|
preparePrint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.remove-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const idx = parseInt(this.dataset.index);
|
||||||
|
queue.splice(idx, 1);
|
||||||
|
renderQueue();
|
||||||
|
updatePreview();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-render print area
|
||||||
preparePrint();
|
preparePrint();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.updateQty = (index, val) => {
|
// --- PRINT HANDLERS ---
|
||||||
queue[index].qty = parseInt(val) || 1;
|
const printQueueBtn = document.getElementById('printQueueBtn');
|
||||||
preparePrint();
|
if (printQueueBtn) {
|
||||||
};
|
printQueueBtn.addEventListener('click', handlePrintQueue);
|
||||||
|
}
|
||||||
|
|
||||||
window.removeFromQueue = (index) => {
|
function handlePrintQueue() {
|
||||||
queue.splice(index, 1);
|
if (queue.length === 0) return;
|
||||||
renderQueue();
|
preparePrint();
|
||||||
updatePreview();
|
setTimeout(() => {
|
||||||
};
|
window.print();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const printPreviewBtn = document.getElementById('printPreviewBtn');
|
||||||
|
if (printPreviewBtn) {
|
||||||
|
printPreviewBtn.addEventListener('click', printSingleFromPreview);
|
||||||
|
}
|
||||||
|
|
||||||
// Product Search
|
// Product Search
|
||||||
document.getElementById('productSearch').addEventListener('input', function() {
|
const searchInput = document.getElementById('productSearch');
|
||||||
const query = this.value.toLowerCase();
|
if (searchInput) {
|
||||||
document.querySelectorAll('.product-row').forEach(row => {
|
searchInput.addEventListener('input', function() {
|
||||||
const name = row.dataset.name.toLowerCase();
|
const query = this.value.toLowerCase();
|
||||||
const sku = row.dataset.sku.toLowerCase();
|
document.querySelectorAll('.product-row').forEach(row => {
|
||||||
if (name.includes(query) || sku.includes(query)) {
|
const name = row.dataset.name.toLowerCase();
|
||||||
row.style.display = '';
|
const nameAr = row.dataset.nameAr ? row.dataset.nameAr.toLowerCase() : '';
|
||||||
} else {
|
const sku = row.dataset.sku.toLowerCase();
|
||||||
row.style.display = 'none';
|
if (name.includes(query) || nameAr.includes(query) || sku.includes(query)) {
|
||||||
}
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// Preview Logic
|
// --- PREVIEW LOGIC ---
|
||||||
function updatePreview() {
|
function updatePreview() {
|
||||||
let product = null;
|
let product = null;
|
||||||
if (queue.length > 0) {
|
if (queue.length > 0) {
|
||||||
@ -489,25 +457,32 @@
|
|||||||
|
|
||||||
if (!product) return;
|
if (!product) return;
|
||||||
|
|
||||||
document.querySelector('.preview-name').innerHTML = (product.nameAr ? `<div class='text-end'>${product.nameAr}</div>` : '') + `<div>${product.name}</div>`;
|
const previewName = document.querySelector('.preview-name');
|
||||||
document.querySelector('.preview-sku').innerText = product.sku;
|
const previewSku = document.querySelector('.preview-sku');
|
||||||
document.querySelector('.preview-price').innerText = 'OMR ' + parseFloat(product.price).toFixed(3);
|
const previewPrice = document.querySelector('.preview-price');
|
||||||
|
|
||||||
JsBarcode("#previewBarcode", product.sku, {
|
if (previewName) previewName.innerHTML = (product.nameAr ? `<div class='text-end'>${product.nameAr}</div>` : '') + `<div>${product.name}</div>`;
|
||||||
format: "CODE128",
|
if (previewSku) previewSku.innerText = product.sku;
|
||||||
width: 2,
|
if (previewPrice) previewPrice.innerText = 'OMR ' + parseFloat(product.price).toFixed(3);
|
||||||
height: 40,
|
|
||||||
displayValue: false,
|
try {
|
||||||
margin: 0
|
if (typeof JsBarcode !== 'undefined') {
|
||||||
});
|
JsBarcode("#previewBarcode", product.sku, {
|
||||||
|
format: "CODE128", width: 2, height: 40, displayValue: false, margin: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
// Apply visibility toggles
|
const showName = document.getElementById('showName').checked;
|
||||||
document.querySelector('.preview-name').style.display = document.getElementById('showName').checked ? '' : 'none';
|
const showPrice = document.getElementById('showPrice').checked;
|
||||||
document.querySelector('.preview-price').style.display = document.getElementById('showPrice').checked ? '' : 'none';
|
const showSKU = document.getElementById('showSKU').checked;
|
||||||
document.querySelector('.preview-sku').style.display = document.getElementById('showSKU').checked ? '' : 'none';
|
|
||||||
|
if (previewName) previewName.style.display = showName ? '' : 'none';
|
||||||
|
if (previewPrice) previewPrice.style.display = showPrice ? '' : 'none';
|
||||||
|
if (previewSku) previewSku.style.display = showSKU ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct Print Logic
|
// --- DIRECT PRINT ---
|
||||||
function directPrintFromRow(btn) {
|
function directPrintFromRow(btn) {
|
||||||
const product = {
|
const product = {
|
||||||
id: btn.dataset.id,
|
id: btn.dataset.id,
|
||||||
@ -539,20 +514,25 @@
|
|||||||
|
|
||||||
const sheet = document.createElement('div');
|
const sheet = document.createElement('div');
|
||||||
sheet.className = 'label-sheet';
|
sheet.className = 'label-sheet';
|
||||||
|
sheet.style.height = 'auto';
|
||||||
|
sheet.style.width = 'auto';
|
||||||
|
sheet.style.display = 'block';
|
||||||
|
|
||||||
const label = document.createElement('div');
|
const label = document.createElement('div');
|
||||||
label.className = `label-item label-${labelType}`;
|
label.className = `label-item label-${labelType}`;
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
if (labelType === 'l7656') {
|
if (labelType === 'l7656') {
|
||||||
if (showName) content += `<div class="label-text" style="max-width: 40px">${product.nameAr ? product.nameAr + " " : ""}${product.name}</div>`;
|
content = `<div class="inner-flex">`;
|
||||||
content += `<svg id="direct-barcode"></svg>`;
|
if (showName) content += `<div class="label-text" style="max-width: 40px">${product.nameAr ? product.nameAr + " " : ""}${product.name}</div>`;
|
||||||
if (showPrice) content += `<div class="label-text label-price">${parseFloat(product.price).toFixed(3)}</div>`;
|
content += `<svg class="barcode-svg-direct"></svg>`;
|
||||||
|
if (showPrice) content += `<div class="label-text label-price">${parseFloat(product.price).toFixed(3)}</div>`;
|
||||||
|
content += `</div>`;
|
||||||
} else {
|
} else {
|
||||||
if (showName) { if (product.nameAr) content += `<div class="label-text">${product.nameAr}</div>`; content += `<div class="label-text">${product.name}</div>`; }
|
if (showName) { if (product.nameAr) content += `<div class="label-text">${product.nameAr}</div>`; content += `<div class="label-text">${product.name}</div>`; }
|
||||||
content += `<svg id="direct-barcode"></svg>`;
|
content += `<svg class="barcode-svg-direct"></svg>`;
|
||||||
if (showSKU || showPrice) {
|
if (showSKU || showPrice) {
|
||||||
content += `<div class="label-text d-flex justify-content-between">`;
|
content += `<div class="label-text d-flex-print">`;
|
||||||
if (showSKU) content += `<span>${product.sku}</span>`;
|
if (showSKU) content += `<span>${product.sku}</span>`;
|
||||||
if (showPrice) content += `<span class="label-price">OMR ${parseFloat(product.price).toFixed(3)}</span>`;
|
if (showPrice) content += `<span class="label-price">OMR ${parseFloat(product.price).toFixed(3)}</span>`;
|
||||||
content += `</div>`;
|
content += `</div>`;
|
||||||
@ -570,22 +550,23 @@
|
|||||||
else if (labelType === 'a4-40') { bWidth = 1.2; bHeight = 25; }
|
else if (labelType === 'a4-40') { bWidth = 1.2; bHeight = 25; }
|
||||||
else if (labelType === 'price-tag') { bWidth = 1.0; bHeight = 15; }
|
else if (labelType === 'price-tag') { bWidth = 1.0; bHeight = 15; }
|
||||||
|
|
||||||
JsBarcode("#direct-barcode", product.sku, {
|
const svgEl = label.querySelector('.barcode-svg-direct');
|
||||||
format: "CODE128",
|
if (svgEl && typeof JsBarcode !== 'undefined') {
|
||||||
width: bWidth,
|
try {
|
||||||
height: bHeight,
|
JsBarcode(svgEl, product.sku, {
|
||||||
displayValue: false,
|
format: "CODE128", width: bWidth, height: bHeight, displayValue: false, margin: 0
|
||||||
margin: 0
|
});
|
||||||
});
|
} catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.print();
|
window.print();
|
||||||
document.body.classList.remove('direct-print-mode');
|
document.body.classList.remove('direct-print-mode');
|
||||||
preparePrint(); // Restore full queue print area
|
preparePrint();
|
||||||
}, 100);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare Print Area (Full Queue)
|
// --- PREPARE PRINT (Full Queue) ---
|
||||||
function preparePrint() {
|
function preparePrint() {
|
||||||
if (document.body.classList.contains('direct-print-mode')) return;
|
if (document.body.classList.contains('direct-print-mode')) return;
|
||||||
|
|
||||||
@ -595,70 +576,92 @@
|
|||||||
const showPrice = document.getElementById('showPrice').checked;
|
const showPrice = document.getElementById('showPrice').checked;
|
||||||
const showSKU = document.getElementById('showSKU').checked;
|
const showSKU = document.getElementById('showSKU').checked;
|
||||||
|
|
||||||
const sheet = document.createElement('div');
|
let labelsPerSheet = 999999;
|
||||||
sheet.className = 'label-sheet';
|
if (labelType === 'a4-24') labelsPerSheet = 24;
|
||||||
|
else if (labelType === 'a4-40') labelsPerSheet = 40;
|
||||||
if (labelType === 'a4-24') sheet.classList.add('sheet-a4-24');
|
else if (labelType === 'l7651') labelsPerSheet = 65;
|
||||||
else if (labelType === 'a4-40') sheet.classList.add('sheet-a4-40');
|
else if (labelType === 'l7656') labelsPerSheet = 84;
|
||||||
else if (labelType === 'l7651') sheet.classList.add('sheet-l7651');
|
else if (labelType === 'l7156') labelsPerSheet = 21;
|
||||||
else if (labelType === 'l7656') sheet.classList.add('sheet-l7656');
|
|
||||||
else if (labelType === 'l7156') sheet.classList.add('sheet-l7156');
|
|
||||||
|
|
||||||
|
const allLabels = [];
|
||||||
queue.forEach(p => {
|
queue.forEach(p => {
|
||||||
for (let i = 0; i < p.qty; i++) {
|
for (let i = 0; i < p.qty; i++) {
|
||||||
|
allLabels.push({ ...p });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allLabels.length === 0) return;
|
||||||
|
|
||||||
|
for (let s = 0; s < allLabels.length; s += labelsPerSheet) {
|
||||||
|
const sheetLabels = allLabels.slice(s, s + labelsPerSheet);
|
||||||
|
const sheet = document.createElement('div');
|
||||||
|
sheet.className = `label-sheet sheet-${labelType}`;
|
||||||
|
|
||||||
|
// Add specific classes
|
||||||
|
if (labelType === 'a4-24') sheet.classList.add('sheet-a4-24');
|
||||||
|
else if (labelType === 'a4-40') sheet.classList.add('sheet-a4-40');
|
||||||
|
else if (labelType === 'l7651') sheet.classList.add('sheet-l7651');
|
||||||
|
else if (labelType === 'l7656') sheet.classList.add('sheet-l7656');
|
||||||
|
else if (labelType === 'l7156') sheet.classList.add('sheet-l7156');
|
||||||
|
|
||||||
|
printArea.appendChild(sheet);
|
||||||
|
|
||||||
|
sheetLabels.forEach((p, idx) => {
|
||||||
const label = document.createElement('div');
|
const label = document.createElement('div');
|
||||||
label.className = `label-item label-${labelType}`;
|
label.className = `label-item label-${labelType}`;
|
||||||
|
|
||||||
|
// Construct inner HTML
|
||||||
let content = '';
|
let content = '';
|
||||||
if (labelType === 'l7656') {
|
if (labelType === 'l7656') {
|
||||||
|
content = `<div class="inner-flex">`;
|
||||||
if (showName) content += `<div class="label-text" style="max-width: 40px">${p.nameAr ? p.nameAr + " " : ""}${p.name}</div>`;
|
if (showName) content += `<div class="label-text" style="max-width: 40px">${p.nameAr ? p.nameAr + " " : ""}${p.name}</div>`;
|
||||||
content += `<svg id="barcode-${p.id}-${i}"></svg>`;
|
content += `<svg class="barcode-item"></svg>`;
|
||||||
if (showPrice) content += `<div class="label-text label-price">${parseFloat(p.price).toFixed(3)}</div>`;
|
if (showPrice) content += `<div class="label-text label-price">${parseFloat(p.price).toFixed(3)}</div>`;
|
||||||
|
content += `</div>`;
|
||||||
} else {
|
} else {
|
||||||
if (showName) { if (p.nameAr) content += `<div class="label-text">${p.nameAr}</div>`; content += `<div class="label-text">${p.name}</div>`; }
|
if (showName) {
|
||||||
content += `<svg id="barcode-${p.id}-${i}"></svg>`;
|
if (p.nameAr) content += `<div class="label-text">${p.nameAr}</div>`;
|
||||||
|
content += `<div class="label-text">${p.name}</div>`;
|
||||||
|
}
|
||||||
|
content += `<svg class="barcode-item"></svg>`;
|
||||||
if (showSKU || showPrice) {
|
if (showSKU || showPrice) {
|
||||||
content += `<div class="label-text d-flex justify-content-between">`;
|
content += `<div class="label-text d-flex-print">`;
|
||||||
if (showSKU) content += `<span>${p.sku}</span>`;
|
if (showSKU) content += `<span>${p.sku}</span>`;
|
||||||
if (showPrice) content += `<span class="label-price">OMR ${parseFloat(p.price).toFixed(3)}</span>`;
|
if (showPrice) content += `<span class="label-price">${parseFloat(p.price).toFixed(3)}</span>`;
|
||||||
content += `</div>`;
|
content += `</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label.innerHTML = content;
|
label.innerHTML = content;
|
||||||
sheet.appendChild(label);
|
sheet.appendChild(label);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
printArea.appendChild(sheet);
|
// Render barcode immediately (synchronously)
|
||||||
|
const svgEl = label.querySelector('.barcode-item');
|
||||||
|
if (svgEl && typeof JsBarcode !== 'undefined') {
|
||||||
|
let bWidth = 1.5;
|
||||||
|
let bHeight = 30;
|
||||||
|
if (labelType === 'l7651') { bWidth = 1.0; bHeight = 18; }
|
||||||
|
else if (labelType === 'l7656') { bWidth = 0.8; bHeight = 8; }
|
||||||
|
else if (labelType === 'a4-40') { bWidth = 1.1; bHeight = 22; }
|
||||||
|
else if (labelType === 'price-tag') { bWidth = 1.0; bHeight = 12; }
|
||||||
|
|
||||||
queue.forEach(p => {
|
try {
|
||||||
for (let i = 0; i < p.qty; i++) {
|
JsBarcode(svgEl, p.sku, {
|
||||||
const svgId = `barcode-${p.id}-${i}`;
|
format: "CODE128", width: bWidth, height: bHeight, displayValue: false, margin: 0
|
||||||
let bWidth = 1.5;
|
});
|
||||||
let bHeight = 30;
|
} catch(e) { console.error(e); }
|
||||||
|
}
|
||||||
if (labelType === 'l7651') { bWidth = 1.0; bHeight = 20; }
|
});
|
||||||
else if (labelType === 'l7656') { bWidth = 0.8; bHeight = 8; }
|
}
|
||||||
else if (labelType === 'a4-40') { bWidth = 1.2; bHeight = 25; }
|
|
||||||
else if (labelType === 'price-tag') { bWidth = 1.0; bHeight = 15; }
|
|
||||||
|
|
||||||
JsBarcode(`#${svgId}`, p.sku, {
|
|
||||||
format: "CODE128",
|
|
||||||
width: bWidth,
|
|
||||||
height: bHeight,
|
|
||||||
displayValue: false,
|
|
||||||
margin: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('labelType').addEventListener('change', preparePrint);
|
// --- SETUP LISTENERS ---
|
||||||
|
document.getElementById('labelType').addEventListener('change', () => preparePrint());
|
||||||
document.getElementById('showName').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
document.getElementById('showName').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
||||||
document.getElementById('showPrice').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
document.getElementById('showPrice').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
||||||
document.getElementById('showSKU').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
document.getElementById('showSKU').addEventListener('change', () => { updatePreview(); preparePrint(); });
|
||||||
|
|
||||||
updatePreview();
|
updatePreview();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -102,7 +102,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
|
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
|
||||||
<select name="payment_method_id" class="form-select rounded-3 shadow-none">
|
<select name="payment_method" class="form-select rounded-3 shadow-none">
|
||||||
{% for method in payment_methods %}
|
{% for method in payment_methods %}
|
||||||
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
|
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
107
core/views.py
107
core/views.py
@ -487,10 +487,19 @@ def add_sale_payment(request, pk):
|
|||||||
amount = decimal.Decimal(request.POST.get('amount', 0))
|
amount = decimal.Decimal(request.POST.get('amount', 0))
|
||||||
payment_method_id = request.POST.get('payment_method')
|
payment_method_id = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
pm_name = "Cash"
|
||||||
|
if payment_method_id:
|
||||||
|
try:
|
||||||
|
pm = PaymentMethod.objects.get(id=payment_method_id)
|
||||||
|
pm_name = pm.name_en
|
||||||
|
except PaymentMethod.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
SalePayment.objects.create(
|
SalePayment.objects.create(
|
||||||
sale=sale,
|
sale=sale,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
payment_method_id=payment_method_id,
|
payment_method_id=payment_method_id,
|
||||||
|
payment_method_name=pm_name,
|
||||||
created_by=request.user,
|
created_by=request.user,
|
||||||
notes=request.POST.get('notes', '')
|
notes=request.POST.get('notes', '')
|
||||||
)
|
)
|
||||||
@ -631,10 +640,20 @@ def delete_sale_return(request, pk):
|
|||||||
|
|
||||||
# --- Purchases ---
|
# --- Purchases ---
|
||||||
|
|
||||||
|
@login_required
|
||||||
@login_required
|
@login_required
|
||||||
def purchases(request):
|
def purchases(request):
|
||||||
purchases = Purchase.objects.all().order_by('-created_at')
|
purchases = Purchase.objects.all().order_by('-created_at')
|
||||||
return render(request, 'core/purchases.html', {'purchases': purchases})
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
site_settings = SystemSetting.objects.first()
|
||||||
|
if not site_settings:
|
||||||
|
site_settings = SystemSetting.objects.create()
|
||||||
|
|
||||||
|
return render(request, 'core/purchases.html', {
|
||||||
|
'purchases': purchases,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
'site_settings': site_settings
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@login_required
|
@login_required
|
||||||
@ -658,8 +677,43 @@ def purchase_detail(request, pk):
|
|||||||
@login_required
|
@login_required
|
||||||
def edit_purchase(request, pk):
|
def edit_purchase(request, pk):
|
||||||
purchase = get_object_or_404(Purchase, pk=pk)
|
purchase = get_object_or_404(Purchase, pk=pk)
|
||||||
# Simplified edit view
|
suppliers = Supplier.objects.all()
|
||||||
return render(request, 'core/purchase_edit.html', {'purchase': purchase})
|
products = Product.objects.filter(is_active=True)
|
||||||
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
site_settings = SystemSetting.objects.first()
|
||||||
|
if not site_settings:
|
||||||
|
site_settings = SystemSetting.objects.create()
|
||||||
|
|
||||||
|
decimal_places = site_settings.decimal_places or 2
|
||||||
|
|
||||||
|
cart_items = []
|
||||||
|
for item in purchase.items.all().select_related('product'):
|
||||||
|
cart_items.append({
|
||||||
|
'id': item.product.id,
|
||||||
|
'name_en': item.product.name_en,
|
||||||
|
'name_ar': item.product.name_ar,
|
||||||
|
'sku': item.product.sku,
|
||||||
|
'cost_price': float(item.unit_price),
|
||||||
|
'quantity': float(item.quantity)
|
||||||
|
})
|
||||||
|
|
||||||
|
cart_json = json.dumps(cart_items)
|
||||||
|
|
||||||
|
payment_method_id = ""
|
||||||
|
first_payment = purchase.payments.first()
|
||||||
|
if first_payment and first_payment.payment_method:
|
||||||
|
payment_method_id = first_payment.payment_method.id
|
||||||
|
|
||||||
|
return render(request, 'core/purchase_edit.html', {
|
||||||
|
'purchase': purchase,
|
||||||
|
'suppliers': suppliers,
|
||||||
|
'products': products,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
'site_settings': site_settings,
|
||||||
|
'cart_json': cart_json,
|
||||||
|
'payment_method_id': payment_method_id,
|
||||||
|
'decimal_places': decimal_places
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_purchase_payment(request, pk):
|
def add_purchase_payment(request, pk):
|
||||||
@ -668,10 +722,19 @@ def add_purchase_payment(request, pk):
|
|||||||
amount = decimal.Decimal(request.POST.get('amount', 0))
|
amount = decimal.Decimal(request.POST.get('amount', 0))
|
||||||
payment_method_id = request.POST.get('payment_method')
|
payment_method_id = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
pm_name = "Cash"
|
||||||
|
if payment_method_id:
|
||||||
|
try:
|
||||||
|
pm = PaymentMethod.objects.get(id=payment_method_id)
|
||||||
|
pm_name = pm.name_en
|
||||||
|
except PaymentMethod.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
PurchasePayment.objects.create(
|
PurchasePayment.objects.create(
|
||||||
purchase=purchase,
|
purchase=purchase,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
payment_method_id=payment_method_id,
|
payment_method_id=payment_method_id,
|
||||||
|
payment_method_name=pm_name,
|
||||||
created_by=request.user,
|
created_by=request.user,
|
||||||
notes=request.POST.get('notes', '')
|
notes=request.POST.get('notes', '')
|
||||||
)
|
)
|
||||||
@ -731,8 +794,39 @@ def delete_purchase_return(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expenses_view(request):
|
def expenses_view(request):
|
||||||
expenses = Expense.objects.all().order_by('-date')
|
expenses = Expense.objects.select_related('category', 'payment_method').all().order_by('-date')
|
||||||
return render(request, 'core/expenses.html', {'expenses': expenses})
|
|
||||||
|
start_date = request.GET.get('start_date')
|
||||||
|
end_date = request.GET.get('end_date')
|
||||||
|
category_id = request.GET.get('category')
|
||||||
|
|
||||||
|
if start_date:
|
||||||
|
expenses = expenses.filter(date__gte=start_date)
|
||||||
|
if end_date:
|
||||||
|
expenses = expenses.filter(date__lte=end_date)
|
||||||
|
if category_id:
|
||||||
|
expenses = expenses.filter(category_id=category_id)
|
||||||
|
|
||||||
|
total_expenses = expenses.aggregate(total=Sum('amount'))['total'] or 0
|
||||||
|
|
||||||
|
site_settings = SystemSetting.objects.first()
|
||||||
|
if not site_settings:
|
||||||
|
site_settings = SystemSetting.objects.create()
|
||||||
|
|
||||||
|
categories = ExpenseCategory.objects.all()
|
||||||
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
paginator = Paginator(expenses, 20)
|
||||||
|
page_number = request.GET.get('page')
|
||||||
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
|
return render(request, 'core/expenses.html', {
|
||||||
|
'expenses': page_obj,
|
||||||
|
'categories': categories,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
'site_settings': site_settings,
|
||||||
|
'total_expenses': total_expenses
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expense_create_view(request):
|
def expense_create_view(request):
|
||||||
@ -921,7 +1015,8 @@ def delete_product(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def barcode_labels(request):
|
def barcode_labels(request):
|
||||||
return render(request, 'core/barcode_labels.html')
|
products = Product.objects.all().order_by('name_en')
|
||||||
|
return render(request, 'core/barcode_labels.html', {'products': products})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def suggest_sku(request):
|
def suggest_sku(request):
|
||||||
|
|||||||
2
static/js/JsBarcode.all.min.js
vendored
Normal file
2
static/js/JsBarcode.all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user