Compare commits
2 Commits
f7e25c8d1f
...
136fe03e07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
136fe03e07 | ||
|
|
ff577a6cbf |
6
app.py
6
app.py
@@ -17,7 +17,7 @@ from blueprints.data_imports import data_imports_bp
|
|||||||
from blueprints.users import users_bp
|
from blueprints.users import users_bp
|
||||||
from blueprints.sessions import sessions_bp
|
from blueprints.sessions import sessions_bp
|
||||||
from blueprints.admin_locations import admin_locations_bp
|
from blueprints.admin_locations import admin_locations_bp
|
||||||
from blueprints.counting import counting_bp # Add this import
|
from blueprints.counting import counting_bp
|
||||||
from utils import login_required
|
from utils import login_required
|
||||||
|
|
||||||
# Register Blueprints
|
# Register Blueprints
|
||||||
@@ -25,7 +25,7 @@ app.register_blueprint(data_imports_bp)
|
|||||||
app.register_blueprint(users_bp)
|
app.register_blueprint(users_bp)
|
||||||
app.register_blueprint(sessions_bp)
|
app.register_blueprint(sessions_bp)
|
||||||
app.register_blueprint(admin_locations_bp)
|
app.register_blueprint(admin_locations_bp)
|
||||||
app.register_blueprint(counting_bp) # Add this registration
|
app.register_blueprint(counting_bp)
|
||||||
|
|
||||||
# V1.0: Use environment variable for production, fallback to demo key for development
|
# V1.0: Use environment variable for production, fallback to demo key for development
|
||||||
app.secret_key = os.environ.get('SCANLOOK_SECRET_KEY', 'scanlook-demo-key-replace-for-production')
|
app.secret_key = os.environ.get('SCANLOOK_SECRET_KEY', 'scanlook-demo-key-replace-for-production')
|
||||||
@@ -36,7 +36,7 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
|
|||||||
|
|
||||||
|
|
||||||
# 1. Define the version
|
# 1. Define the version
|
||||||
APP_VERSION = '0.10.0'
|
APP_VERSION = '0.11.1'
|
||||||
|
|
||||||
# 2. Inject it into all templates automatically
|
# 2. Inject it into all templates automatically
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -79,6 +79,9 @@ def start_bin_count(session_id):
|
|||||||
if not sess:
|
if not sess:
|
||||||
flash('Session not found or archived', 'warning')
|
flash('Session not found or archived', 'warning')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
if not sess['master_baseline_timestamp']:
|
||||||
|
flash('Master File not uploaded. Please upload it before starting bins.', 'warning')
|
||||||
|
return redirect(url_for('counting.my_counts', session_id=session_id))
|
||||||
|
|
||||||
location_name = request.form.get('location_name', '').strip().upper()
|
location_name = request.form.get('location_name', '').strip().upper()
|
||||||
|
|
||||||
@@ -144,6 +147,9 @@ def count_location(session_id, location_count_id):
|
|||||||
if not sess:
|
if not sess:
|
||||||
flash('Session not found or archived', 'warning')
|
flash('Session not found or archived', 'warning')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
if not sess['master_baseline_timestamp']:
|
||||||
|
flash('Master File not uploaded. Please upload it before starting bins.', 'warning')
|
||||||
|
return redirect(url_for('counting.my_counts', session_id=session_id))
|
||||||
|
|
||||||
location = query_db('''
|
location = query_db('''
|
||||||
SELECT * FROM LocationCounts
|
SELECT * FROM LocationCounts
|
||||||
@@ -304,8 +310,14 @@ def scan_lot(session_id, location_count_id):
|
|||||||
|
|
||||||
# Check against MASTER baseline
|
# Check against MASTER baseline
|
||||||
master = query_db('''
|
master = query_db('''
|
||||||
SELECT * FROM BaselineInventory_Master
|
SELECT
|
||||||
|
system_bin,
|
||||||
|
SUM(system_quantity) as system_quantity,
|
||||||
|
MAX(item) as item,
|
||||||
|
MAX(description) as description
|
||||||
|
FROM BaselineInventory_Master
|
||||||
WHERE session_id = ? AND lot_number = ? AND system_bin = ?
|
WHERE session_id = ? AND lot_number = ? AND system_bin = ?
|
||||||
|
GROUP BY system_bin
|
||||||
''', [session_id, lot_number, location['location_name']], one=True)
|
''', [session_id, lot_number, location['location_name']], one=True)
|
||||||
|
|
||||||
# Determine master_status (only if not a duplicate issue)
|
# Determine master_status (only if not a duplicate issue)
|
||||||
@@ -322,8 +334,16 @@ def scan_lot(session_id, location_count_id):
|
|||||||
else:
|
else:
|
||||||
# Check if lot exists in different location
|
# Check if lot exists in different location
|
||||||
master_other = query_db('''
|
master_other = query_db('''
|
||||||
SELECT * FROM BaselineInventory_Master
|
SELECT
|
||||||
|
system_bin,
|
||||||
|
SUM(system_quantity) as system_quantity,
|
||||||
|
MAX(item) as item,
|
||||||
|
MAX(description) as description
|
||||||
|
FROM BaselineInventory_Master
|
||||||
WHERE session_id = ? AND lot_number = ?
|
WHERE session_id = ? AND lot_number = ?
|
||||||
|
GROUP BY system_bin
|
||||||
|
ORDER BY system_bin
|
||||||
|
LIMIT 1
|
||||||
''', [session_id, lot_number], one=True)
|
''', [session_id, lot_number], one=True)
|
||||||
|
|
||||||
if master_other:
|
if master_other:
|
||||||
@@ -340,8 +360,16 @@ def scan_lot(session_id, location_count_id):
|
|||||||
# For duplicates, still check baseline for item info
|
# For duplicates, still check baseline for item info
|
||||||
if not master:
|
if not master:
|
||||||
master = query_db('''
|
master = query_db('''
|
||||||
SELECT * FROM BaselineInventory_Master
|
SELECT
|
||||||
|
system_bin,
|
||||||
|
SUM(system_quantity) as system_quantity,
|
||||||
|
MAX(item) as item,
|
||||||
|
MAX(description) as description
|
||||||
|
FROM BaselineInventory_Master
|
||||||
WHERE session_id = ? AND lot_number = ?
|
WHERE session_id = ? AND lot_number = ?
|
||||||
|
GROUP BY system_bin
|
||||||
|
ORDER BY system_bin
|
||||||
|
LIMIT 1
|
||||||
''', [session_id, lot_number], one=True)
|
''', [session_id, lot_number], one=True)
|
||||||
master_status = 'match' # Don't override with wrong_location for duplicates
|
master_status = 'match' # Don't override with wrong_location for duplicates
|
||||||
variance_lbs = None
|
variance_lbs = None
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ def upload_master(session_id):
|
|||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('DELETE FROM BaselineInventory_Master WHERE session_id = ?', [session_id])
|
||||||
try:
|
try:
|
||||||
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
||||||
csv_reader = csv.DictReader(stream)
|
csv_reader = csv.DictReader(stream)
|
||||||
@@ -152,4 +153,4 @@ def upload_master(session_id):
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return redirect(url_for('sessions. session_detail', session_id=session_id))
|
return redirect(url_for('sessions.session_detail', session_id=session_id))
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
onclick="openScanDetail('{{ scan.entry_id }}')">
|
onclick="openScanDetail('{{ scan.entry_id }}')">
|
||||||
<div class="scan-row-lot">{{ scan.lot_number }}</div>
|
<div class="scan-row-lot">{{ scan.lot_number }}</div>
|
||||||
<div class="scan-row-item">{{ scan.item or 'N/A' }}</div>
|
<div class="scan-row-item">{{ scan.item or 'N/A' }}</div>
|
||||||
<div class="scan-row-weight">{{ scan.actual_weight }} lbs</div>
|
<div class="scan-row-weight">{{ '%.1f'|format(scan.actual_weight) if scan.actual_weight is not none else '-' }} lbs</div>
|
||||||
<div class="scan-row-status">
|
<div class="scan-row-status">
|
||||||
{% if scan.duplicate_status == '01' or scan.duplicate_status == '04' %}
|
{% if scan.duplicate_status == '01' or scan.duplicate_status == '04' %}
|
||||||
<span class="status-dot status-dot-blue"></span> Duplicate
|
<span class="status-dot status-dot-blue"></span> Duplicate
|
||||||
@@ -185,6 +185,23 @@ let currentLotNumber = '';
|
|||||||
let isDuplicateConfirmed = false;
|
let isDuplicateConfirmed = false;
|
||||||
let isProcessing = false;
|
let isProcessing = false;
|
||||||
|
|
||||||
|
function parseWeight(value) {
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isFinite(num) ? num : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatWeight(value) {
|
||||||
|
const num = parseWeight(value);
|
||||||
|
return num === null ? '-' : num.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function weightsDiffer(actual, expected) {
|
||||||
|
const actualNum = parseWeight(actual);
|
||||||
|
const expectedNum = parseWeight(expected);
|
||||||
|
if (actualNum === null || expectedNum === null) return false;
|
||||||
|
return Math.abs(actualNum - expectedNum) >= 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
// Lot scan handler
|
// Lot scan handler
|
||||||
document.getElementById('lotScanForm').addEventListener('submit', function(e) {
|
document.getElementById('lotScanForm').addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -388,7 +405,7 @@ function addScanToList(data, weight) {
|
|||||||
statusDot = 'orange';
|
statusDot = 'orange';
|
||||||
}
|
}
|
||||||
} else if (data.master_status === 'match') {
|
} else if (data.master_status === 'match') {
|
||||||
if (data.master_expected_weight && Math.abs(weight - data.master_expected_weight) >= 0.01) {
|
if (data.master_expected_weight && weightsDiffer(weight, data.master_expected_weight)) {
|
||||||
statusClass = 'weight_discrepancy';
|
statusClass = 'weight_discrepancy';
|
||||||
statusText = 'Weight Off';
|
statusText = 'Weight Off';
|
||||||
statusDot = 'orange';
|
statusDot = 'orange';
|
||||||
@@ -414,7 +431,7 @@ function addScanToList(data, weight) {
|
|||||||
scanRow.innerHTML = `
|
scanRow.innerHTML = `
|
||||||
<div class="scan-row-lot">${currentLotNumber}</div>
|
<div class="scan-row-lot">${currentLotNumber}</div>
|
||||||
<div class="scan-row-item">${data.item || 'N/A'}</div>
|
<div class="scan-row-item">${data.item || 'N/A'}</div>
|
||||||
<div class="scan-row-weight">${weight} lbs</div>
|
<div class="scan-row-weight">${formatWeight(weight)} lbs</div>
|
||||||
<div class="scan-row-status">
|
<div class="scan-row-status">
|
||||||
<span class="status-dot status-dot-${statusDot}"></span> ${statusText}
|
<span class="status-dot status-dot-${statusDot}"></span> ${statusText}
|
||||||
</div>
|
</div>
|
||||||
@@ -451,7 +468,7 @@ function displayScanDetail(scan) {
|
|||||||
// Check for weight discrepancy (Tolerance 0.01)
|
// Check for weight discrepancy (Tolerance 0.01)
|
||||||
let isWeightOff = false;
|
let isWeightOff = false;
|
||||||
if (scan.master_status === 'match' && scan.master_expected_weight) {
|
if (scan.master_status === 'match' && scan.master_expected_weight) {
|
||||||
if (Math.abs(scan.actual_weight - scan.master_expected_weight) >= 0.01) {
|
if (weightsDiffer(scan.actual_weight, scan.master_expected_weight)) {
|
||||||
isWeightOff = true;
|
isWeightOff = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,7 +519,7 @@ function displayScanDetail(scan) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Weight (lbs)</label>
|
<label class="form-label">Weight (lbs)</label>
|
||||||
<input type="number" id="editWeight" class="form-input" value="${scan.actual_weight}" step="0.01" min="0" inputmode="decimal">
|
<input type="number" id="editWeight" class="form-input" value="${formatWeight(scan.actual_weight)}" step="0.01" min="0" inputmode="decimal">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Comment</label>
|
<label class="form-label">Comment</label>
|
||||||
|
|||||||
@@ -9,10 +9,19 @@
|
|||||||
<a href="{{ url_for('dashboard') }}" class="breadcrumb">← Back to Dashboard</a>
|
<a href="{{ url_for('dashboard') }}" class="breadcrumb">← Back to Dashboard</a>
|
||||||
<h1 class="page-title">My Active Counts</h1>
|
<h1 class="page-title">My Active Counts</h1>
|
||||||
<p class="page-subtitle">{{ count_session.session_name }}</p>
|
<p class="page-subtitle">{{ count_session.session_name }}</p>
|
||||||
|
{% if not count_session.master_baseline_timestamp %}
|
||||||
|
<p class="page-subtitle">Master File not uploaded yet. Please contact an admin before starting bins.</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if count_session.master_baseline_timestamp %}
|
||||||
<button class="btn btn-primary" onclick="showStartBinModal()">
|
<button class="btn btn-primary" onclick="showStartBinModal()">
|
||||||
<span class="btn-icon">+</span> Start New Bin
|
<span class="btn-icon">+</span> Start New Bin
|
||||||
</button>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-primary" disabled title="Upload a Master File to start bins">
|
||||||
|
<span class="btn-icon">+</span> Start New Bin
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active Bins -->
|
<!-- Active Bins -->
|
||||||
|
|||||||
Reference in New Issue
Block a user