V0.8.6 - Frefractor: Counting.py
This commit is contained in:
@@ -62,6 +62,8 @@
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<p>© 2026 Javier Torres. All Rights Reserved.</p>
|
||||
<p class="text-muted"><small>v{{ version }}</small></p>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</html>
|
||||
|
||||
@@ -97,9 +97,9 @@
|
||||
{% set row_class = 'weight_discrepancy' %}
|
||||
{% endif %}
|
||||
|
||||
<div class="scan-row scan-row-{{ row_class }}"
|
||||
data-entry-id="{{ scan.entry_id }}"
|
||||
onclick="openScanDetail({{ scan.entry_id }})">
|
||||
<div class="scan-row scan-row-{{ row_class }}"
|
||||
data-entry-id="{{ scan.entry_id }}"
|
||||
onclick="openScanDetail('{{ scan.entry_id }}')">
|
||||
<div class="scan-row-lot">{{ scan.lot_number }}</div>
|
||||
<div class="scan-row-item">{{ scan.item or 'N/A' }}</div>
|
||||
<div class="scan-row-weight">{{ scan.actual_weight }} lbs</div>
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
<div class="finish-section">
|
||||
<div class="action-buttons-row">
|
||||
<a href="{{ url_for('my_counts', session_id=session_id) }}" class="btn btn-secondary btn-block btn-lg">
|
||||
<a href="{{ url_for('counting.my_counts', session_id=session_id) }}" class="btn btn-secondary btn-block btn-lg">
|
||||
← Back to My Counts
|
||||
</a>
|
||||
<button id="finishBtn" class="btn btn-success btn-block btn-lg" onclick="finishLocation()">
|
||||
@@ -191,7 +191,7 @@ document.getElementById('lotScanForm').addEventListener('submit', function(e) {
|
||||
});
|
||||
|
||||
function checkDuplicate() {
|
||||
fetch('{{ url_for("scan_lot", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
fetch('{{ url_for("counting.scan_lot", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
@@ -260,7 +260,7 @@ function submitScan(weight) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('{{ url_for("scan_lot", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
fetch('{{ url_for("counting.scan_lot", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
@@ -560,7 +560,7 @@ function deleteFromDetail(entryId) {
|
||||
function finishLocation() {
|
||||
if (!confirm('Are you finished counting this location?')) return;
|
||||
|
||||
fetch('{{ url_for("finish_location", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
fetch('{{ url_for("counting.finish_location", session_id=session_id, location_count_id=location.location_count_id) }}', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bin-actions">
|
||||
<a href="{{ url_for('count_location', session_id=count_session.session_id, location_count_id=bin.location_count_id) }}" class="btn btn-primary btn-block">
|
||||
<a href="{{ url_for('counting.count_location', session_id=count_session.session_id, location_count_id=bin.location_count_id) }}" class="btn btn-primary btn-block">
|
||||
Resume Counting
|
||||
</a>
|
||||
<div class="bin-actions-row">
|
||||
<button class="btn btn-secondary" onclick="markComplete({{ bin.location_count_id }})">
|
||||
<button class="btn btn-secondary" onclick="markComplete('{{ bin.location_count_id }}')">
|
||||
✓ Mark Complete
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="deleteBinCount({{ bin.location_count_id }}, '{{ bin.location_name }}')">
|
||||
<button class="btn btn-danger" onclick="deleteBinCount('{{ bin.location_count_id }}', '{{ bin.location_name }}')">
|
||||
🗑️ Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@
|
||||
<button type="button" class="btn-close-modal" onclick="closeStartBinModal()">✕</button>
|
||||
</div>
|
||||
|
||||
<form id="startBinForm" action="{{ url_for('start_bin_count', session_id=count_session.session_id) }}" method="POST">
|
||||
<form id="startBinForm" action="{{ url_for('counting.start_bin_count', session_id=count_session.session_id) }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Bin Number *</label>
|
||||
<input type="text" name="location_name" class="form-input scan-input" required autofocus placeholder="Scan or type bin number">
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
<div>
|
||||
<a href="{{ url_for('dashboard') }}" class="breadcrumb">← Back to Dashboard</a>
|
||||
<h1 class="page-title">{{ count_session.session_name }}</h1>
|
||||
<!-- Fixed variable name from session.session_type to count_session.session_type -->
|
||||
<span class="session-type-badge session-type-{{ count_session.session_type }}">
|
||||
{{ 'Full Physical' if session.session_type == 'full_physical' else 'Cycle Count' }}
|
||||
{{ 'Full Physical' if count_session.session_type == 'full_physical' else 'Cycle Count' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,6 +31,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not count_session.master_baseline_timestamp %}
|
||||
<!-- Note: Using data_imports blueprint URL -->
|
||||
<form method="POST" action="{{ url_for('data_imports.upload_master', session_id=count_session.session_id) }}" enctype="multipart/form-data" class="upload-form">
|
||||
<input type="file" name="csv_file" accept=".csv" required class="file-input">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Upload MASTER</button>
|
||||
@@ -41,8 +43,8 @@
|
||||
<div class="baseline-label">CURRENT Baseline (Optional)</div>
|
||||
<div class="baseline-status">
|
||||
{% if count_session.current_baseline_timestamp %}
|
||||
<span class="status-badge status-success">Last Updated:
|
||||
<div>{{ count_session.current_baseline_timestamp[:16] if count_session.current_baseline_timestamp else 'Never' }}</div>
|
||||
<span class="status-badge status-success">✓ Uploaded</span>
|
||||
<small class="baseline-time">{{ count_session.current_baseline_timestamp[:16] }}</small>
|
||||
{% else %}
|
||||
<span class="status-badge status-neutral">Not Uploaded</span>
|
||||
{% endif %}
|
||||
@@ -65,27 +67,27 @@
|
||||
<h2 class="section-title">Real-Time Statistics</h2>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card stat-match" onclick="showStatusDetails('match', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-match" onclick="showStatusDetails('match')">
|
||||
<div class="stat-number">{{ stats.matched or 0 }}</div>
|
||||
<div class="stat-label">✓ Matched</div>
|
||||
</div>
|
||||
<div class="stat-card stat-duplicate" onclick="showStatusDetails('duplicates', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-duplicate" onclick="showStatusDetails('duplicates')">
|
||||
<div class="stat-number">{{ stats.duplicates or 0 }}</div>
|
||||
<div class="stat-label">🔵 Duplicates</div>
|
||||
</div>
|
||||
<div class="stat-card stat-weight-disc" onclick="showStatusDetails('weight_discrepancy', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-weight-disc" onclick="showStatusDetails('weight_discrepancy')">
|
||||
<div class="stat-number">{{ stats.weight_discrepancy or 0 }}</div>
|
||||
<div class="stat-label">⚖️ Weight Discrepancy</div>
|
||||
</div>
|
||||
<div class="stat-card stat-wrong" onclick="showStatusDetails('wrong_location', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-wrong" onclick="showStatusDetails('wrong_location')">
|
||||
<div class="stat-number">{{ stats.wrong_location or 0 }}</div>
|
||||
<div class="stat-label">⚠ Wrong Location</div>
|
||||
</div>
|
||||
<div class="stat-card stat-ghost" onclick="showStatusDetails('ghost_lot', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-ghost" onclick="showStatusDetails('ghost_lot')">
|
||||
<div class="stat-number">{{ stats.ghost_lots or 0 }}</div>
|
||||
<div class="stat-label">🟣 Ghost Lots</div>
|
||||
</div>
|
||||
<div class="stat-card stat-missing" onclick="showStatusDetails('missing', {{ count_session.session_id }})">
|
||||
<div class="stat-card stat-missing" onclick="showStatusDetails('missing')">
|
||||
<div class="stat-number">{{ stats.missing_lots or 0 }}</div>
|
||||
<div class="stat-label">🔴 Missing</div>
|
||||
</div>
|
||||
@@ -129,7 +131,12 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loc in locations %}
|
||||
<tr class="location-row-clickable" onclick="showLocationDetails({{ loc.location_count_id }}, '{{ loc.location_name }}', '{{ loc.status }}')">
|
||||
<!-- Refactored to use data attributes instead of direct Jinja injection in onclick -->
|
||||
<tr class="location-row-clickable"
|
||||
data-id="{{ loc.location_count_id }}"
|
||||
data-name="{{ loc.location_name }}"
|
||||
data-status="{{ loc.status }}"
|
||||
onclick="handleLocationClick(this)">
|
||||
<td><strong>{{ loc.location_name }}</strong></td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ loc.status }}">
|
||||
@@ -246,7 +253,10 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showStatusDetails(status, sessionId) {
|
||||
// Store the Session ID globally to use in functions without passing it every time
|
||||
const CURRENT_SESSION_ID = "{{ count_session.session_id }}";
|
||||
|
||||
function showStatusDetails(status) {
|
||||
document.getElementById('statusModal').style.display = 'flex';
|
||||
document.getElementById('statusDetailContent').innerHTML = '<div class="loading-spinner">Loading...</div>';
|
||||
|
||||
@@ -261,8 +271,8 @@ function showStatusDetails(status, sessionId) {
|
||||
};
|
||||
document.getElementById('statusModalTitle').textContent = titles[status] || 'Details';
|
||||
|
||||
// Fetch details
|
||||
fetch(`/session/${sessionId}/status-details/${status}`)
|
||||
// Fetch details using the blueprint URL structure
|
||||
fetch(`/session/${CURRENT_SESSION_ID}/status-details/${status}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
@@ -421,6 +431,14 @@ let currentLocationName = '';
|
||||
let currentLocationStatus = '';
|
||||
let currentLocationData = null;
|
||||
|
||||
// New helper function to handle click from data attributes
|
||||
function handleLocationClick(row) {
|
||||
const id = row.getAttribute('data-id');
|
||||
const name = row.getAttribute('data-name');
|
||||
const status = row.getAttribute('data-status');
|
||||
showLocationDetails(id, name, status);
|
||||
}
|
||||
|
||||
function showLocationDetails(locationCountId, locationName, status) {
|
||||
currentLocationId = locationCountId;
|
||||
currentLocationName = locationName;
|
||||
@@ -594,6 +612,7 @@ function closeFinalizeConfirm() {
|
||||
}
|
||||
|
||||
function confirmFinalize() {
|
||||
// Note: The /complete endpoint is handled by blueprints/counting.py
|
||||
fetch(`/location/${currentLocationId}/complete`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -625,6 +644,7 @@ function closeReopenConfirm() {
|
||||
}
|
||||
|
||||
function confirmReopen() {
|
||||
// Note: The /reopen endpoint is handled by blueprints/admin_locations.py
|
||||
fetch(`/location/${currentLocationId}/reopen`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -656,4 +676,4 @@ document.addEventListener('keydown', function(e) {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -7,11 +7,11 @@
|
||||
<!-- Mode Selector (only for admins) -->
|
||||
{% if session.role in ['owner', 'admin'] %}
|
||||
<div class="mode-selector">
|
||||
<button class="mode-btn" onclick="window.location.href='{{ url_for('dashboard') }}'">
|
||||
👔 Admin Console
|
||||
</button>
|
||||
<a href="{{ url_for('dashboard') }}" class="mode-btn">
|
||||
Admin Console
|
||||
</a>
|
||||
<button class="mode-btn mode-btn-active">
|
||||
📦 Scanning Mode
|
||||
Scanning Mode
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
{% if sessions %}
|
||||
<div class="sessions-list">
|
||||
{% for session in sessions %}
|
||||
<a href="{{ url_for('count_session', session_id=session.session_id) }}" class="session-list-item">
|
||||
{% for s in sessions %}
|
||||
<a href="{{ url_for('counting.count_session', session_id=s.session_id) }}" class="session-list-item">
|
||||
<div class="session-list-info">
|
||||
<h3 class="session-list-name">{{ session.session_name }}</h3>
|
||||
<span class="session-list-type">{{ 'Full Physical' if session.session_type == 'full_physical' else 'Cycle Count' }}</span>
|
||||
<h3 class="session-list-name">{{ s.session_name }}</h3>
|
||||
<span class="session-list-type">{{ 'Full Physical' if s.session_type == 'full_physical' else 'Cycle Count' }}</span>
|
||||
</div>
|
||||
<div class="session-list-action">
|
||||
<span class="arrow-icon">→</span>
|
||||
|
||||
Reference in New Issue
Block a user