- Created warehouse_orders.py module with 8 order management functions - Added /warehouse/set-orders-on-boxes route and 7 API endpoints - Implemented 4-tab interface: assign, find, move, and view orders - Changed assign input from dropdown to text field with BOX validation - Fixed location join issue in warehouse.py (use boxes_crates.location_id) - Added debouncing flag to prevent multiple rapid form submissions - Added page refresh after successful order assignment - Disabled assign button during processing - Added page refresh with 2 second delay for UX feedback - Added CP code validation in inventory page - Improved modal styling with theme support - Fixed set_boxes_locations page to refresh box info after assignments
1081 lines
37 KiB
HTML
1081 lines
37 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Set Boxes Locations - Quality App v2{% endblock %}
|
|
|
|
{% block content %}
|
|
<style>
|
|
.location-manager-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 15px;
|
|
}
|
|
|
|
.main-card {
|
|
background: var(--bg-primary);
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px var(--card-shadow);
|
|
overflow: hidden;
|
|
color: var(--text-primary);
|
|
transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
|
|
}
|
|
|
|
/* Tab Navigation */
|
|
.tab-navigation {
|
|
display: flex;
|
|
background: var(--bg-tertiary);
|
|
border-bottom: 2px solid var(--border-color);
|
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
|
|
.tab-button {
|
|
flex: 1;
|
|
padding: 15px 10px;
|
|
background: none;
|
|
border: none;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
border-bottom: 3px solid transparent;
|
|
}
|
|
|
|
.tab-button.active {
|
|
color: #0d6efd;
|
|
border-bottom-color: #0d6efd;
|
|
background: rgba(13, 110, 253, 0.08);
|
|
}
|
|
|
|
.tab-button:hover {
|
|
background: var(--bg-tertiary);
|
|
color: #0d6efd;
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
padding: 25px;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Form Sections */
|
|
.form-section {
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.form-section h3 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
margin-bottom: 15px;
|
|
color: var(--text-primary);
|
|
padding-bottom: 10px;
|
|
border-bottom: 2px solid var(--border-color);
|
|
transition: color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
|
|
.section-icon {
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.input-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.input-group label {
|
|
display: block;
|
|
font-weight: 500;
|
|
margin-bottom: 8px;
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.input-group input,
|
|
.input-group select {
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
background: var(--input-bg);
|
|
color: var(--text-primary);
|
|
transition: border-color 0.3s, background-color 0.3s ease, color 0.3s ease;
|
|
}
|
|
|
|
.input-group input:focus,
|
|
.input-group select:focus {
|
|
outline: none;
|
|
border-color: var(--input-focus-border);
|
|
box-shadow: 0 0 0 3px var(--input-focus-shadow);
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn-primary {
|
|
background: #0d6efd;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #0b5ed7;
|
|
}
|
|
|
|
.btn-primary:disabled {
|
|
background: var(--border-color);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #6c757d;
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
font-size: 14px;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #5a6268;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #198754;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #157347;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #dc3545;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #c82333;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* Alerts */
|
|
.alert {
|
|
padding: 12px 15px;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
display: none;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.alert.show {
|
|
display: block;
|
|
}
|
|
|
|
.alert-success {
|
|
background: rgba(209, 231, 221, 0.3);
|
|
color: #0f5132;
|
|
border: 1px solid rgba(186, 219, 204, 0.5);
|
|
}
|
|
|
|
[data-theme="dark"] .alert-success {
|
|
background: rgba(15, 81, 50, 0.2);
|
|
color: #a6d5b9;
|
|
border: 1px solid rgba(166, 213, 185, 0.3);
|
|
}
|
|
|
|
.alert-error {
|
|
background: rgba(248, 215, 218, 0.3);
|
|
color: #842029;
|
|
border: 1px solid rgba(245, 194, 199, 0.5);
|
|
}
|
|
|
|
[data-theme="dark"] .alert-error {
|
|
background: rgba(132, 32, 41, 0.2);
|
|
color: #f8b9c2;
|
|
border: 1px solid rgba(248, 185, 194, 0.3);
|
|
}
|
|
|
|
.alert-info {
|
|
background: rgba(207, 226, 255, 0.3);
|
|
color: #084298;
|
|
border: 1px solid rgba(182, 212, 254, 0.5);
|
|
}
|
|
|
|
[data-theme="dark"] .alert-info {
|
|
background: rgba(8, 66, 152, 0.2);
|
|
color: #8ab4f8;
|
|
border: 1px solid rgba(138, 180, 248, 0.3);
|
|
}
|
|
|
|
.alert-warning {
|
|
background: rgba(255, 243, 205, 0.3);
|
|
color: #664d03;
|
|
border: 1px solid rgba(255, 236, 181, 0.5);
|
|
}
|
|
|
|
[data-theme="dark"] .alert-warning {
|
|
background: rgba(102, 77, 3, 0.2);
|
|
color: #ffd966;
|
|
border: 1px solid rgba(255, 217, 102, 0.3);
|
|
}
|
|
|
|
/* Info Boxes */
|
|
.info-box {
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-bottom: 15px;
|
|
display: none;
|
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
|
|
.info-box.show {
|
|
display: block;
|
|
}
|
|
|
|
.info-box h4 {
|
|
margin-top: 0;
|
|
margin-bottom: 10px;
|
|
color: var(--text-primary);
|
|
font-size: 16px;
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid var(--border-color);
|
|
transition: border-color 0.3s ease;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.info-value {
|
|
color: #0d6efd;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Box List */
|
|
.box-list {
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-bottom: 15px;
|
|
display: none;
|
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
|
|
.box-list.show {
|
|
display: block;
|
|
}
|
|
|
|
.box-list h4 {
|
|
margin-top: 0;
|
|
margin-bottom: 12px;
|
|
color: var(--text-primary);
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.box-item {
|
|
background: var(--bg-primary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.box-item:hover {
|
|
background: rgba(13, 110, 253, 0.06);
|
|
border-color: #0d6efd;
|
|
}
|
|
|
|
.box-item.selected {
|
|
background: rgba(13, 110, 253, 0.12);
|
|
border-color: #0d6efd;
|
|
}
|
|
|
|
.box-item-info {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.box-number {
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.box-status {
|
|
font-size: 12px;
|
|
padding: 3px 8px;
|
|
border-radius: 3px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.box-status.open {
|
|
background: rgba(212, 237, 218, 0.3);
|
|
color: #155724;
|
|
}
|
|
|
|
[data-theme="dark"] .box-status.open {
|
|
background: rgba(21, 87, 36, 0.2);
|
|
color: #9fee99;
|
|
}
|
|
|
|
.box-status.closed {
|
|
background: rgba(248, 215, 218, 0.3);
|
|
color: #721c24;
|
|
}
|
|
|
|
[data-theme="dark"] .box-status.closed {
|
|
background: rgba(114, 28, 36, 0.2);
|
|
color: #f8a6b0;
|
|
}
|
|
|
|
/* Spinner */
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 14px;
|
|
height: 14px;
|
|
border: 2px solid var(--border-color);
|
|
border-top: 2px solid #0d6efd;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Back Button */
|
|
.back-button {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.tab-button {
|
|
font-size: 12px;
|
|
padding: 12px 8px;
|
|
}
|
|
|
|
.tab-content {
|
|
padding: 15px;
|
|
}
|
|
|
|
.btn-group {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.btn-primary, .btn-secondary, .btn-success, .btn-danger {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="location-manager-container">
|
|
<!-- Back Button -->
|
|
<div class="back-button">
|
|
<a href="{{ url_for('warehouse.warehouse_index') }}" class="btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Back to Warehouse
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Main Card -->
|
|
<div class="main-card">
|
|
<!-- Tab Navigation -->
|
|
<div class="tab-navigation">
|
|
<button class="tab-button active" data-tab="0">
|
|
<i class="fas fa-cube"></i> Assign Box to Location
|
|
</button>
|
|
<button class="tab-button" data-tab="1">
|
|
<i class="fas fa-map-pin"></i> Find Boxes by Location
|
|
</button>
|
|
<button class="tab-button" data-tab="2">
|
|
<i class="fas fa-exchange-alt"></i> Move Box to New Location
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab 0: Assign Box to Location -->
|
|
<div class="tab-content active" id="tab-0">
|
|
<div class="form-section">
|
|
<h3><span class="section-icon">🔍</span> Scan or Enter Box Number</h3>
|
|
|
|
<div id="alert-tab0" class="alert"></div>
|
|
|
|
<div class="input-group">
|
|
<label for="box-number-input-tab0">Box Number</label>
|
|
<input
|
|
type="text"
|
|
id="box-number-input-tab0"
|
|
placeholder="Scan or enter box number"
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
|
|
<button class="btn-primary" onclick="searchBoxTab0()">
|
|
<span class="spinner" id="spinner-search-tab0" style="display: none;"></span>
|
|
<span id="btn-search-tab0-text">🔍 Search Box</span>
|
|
</button>
|
|
|
|
<!-- Box Information -->
|
|
<div class="info-box" id="box-info-tab0">
|
|
<h4>Box Details</h4>
|
|
<div class="info-row">
|
|
<span class="info-label">Box Number:</span>
|
|
<span class="info-value" id="display-box-number-tab0">-</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Current Status:</span>
|
|
<span class="info-value" id="display-box-status-tab0">-</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Current Location:</span>
|
|
<span class="info-value" id="display-box-location-tab0">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assign Location Section -->
|
|
<div class="form-section" id="assign-section-tab0" style="display: none;">
|
|
<h3><span class="section-icon">📍</span> Assign to Location</h3>
|
|
|
|
<div class="input-group">
|
|
<label for="location-code-input-tab0">Location Code</label>
|
|
<input
|
|
type="text"
|
|
id="location-code-input-tab0"
|
|
placeholder="Scan or enter location code"
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button class="btn-primary btn-success" onclick="assignBoxToLocationTab0()">
|
|
<span class="spinner" id="spinner-assign-tab0" style="display: none;"></span>
|
|
<span id="btn-assign-tab0-text">✅ Assign to Location</span>
|
|
</button>
|
|
<button class="btn-secondary" onclick="clearTab0()">
|
|
Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 1: Find Boxes by Location -->
|
|
<div class="tab-content" id="tab-1">
|
|
<div class="form-section">
|
|
<h3><span class="section-icon">📍</span> Scan or Enter Location Code</h3>
|
|
|
|
<div id="alert-tab1" class="alert"></div>
|
|
|
|
<div class="input-group">
|
|
<label for="location-code-input-tab1">Location Code</label>
|
|
<input
|
|
type="text"
|
|
id="location-code-input-tab1"
|
|
placeholder="Scan or enter location code"
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
|
|
<button class="btn-primary" onclick="searchLocationTab1()">
|
|
<span class="spinner" id="spinner-search-tab1" style="display: none;"></span>
|
|
<span id="btn-search-tab1-text">🔍 Search Location</span>
|
|
</button>
|
|
|
|
<!-- Location Information -->
|
|
<div class="info-box" id="location-info-tab1">
|
|
<h4>Location Details</h4>
|
|
<div class="info-row">
|
|
<span class="info-label">Location Code:</span>
|
|
<span class="info-value" id="display-location-code-tab1">-</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Boxes Count:</span>
|
|
<span class="info-value" id="display-boxes-count-tab1">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Boxes List -->
|
|
<div class="box-list" id="boxes-list-tab1">
|
|
<h4>Boxes in This Location</h4>
|
|
<div id="boxes-container-tab1"></div>
|
|
</div>
|
|
|
|
<button class="btn-secondary" onclick="clearTab1()" style="display: none;" id="clear-tab1-btn">
|
|
Clear Search
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 2: Move Box to New Location -->
|
|
<div class="tab-content" id="tab-2">
|
|
<div class="form-section">
|
|
<h3><span class="section-icon">🔍</span> Find Box to Move</h3>
|
|
|
|
<div id="alert-tab2" class="alert"></div>
|
|
|
|
<div class="input-group">
|
|
<label for="location-code-input-tab2">Location Code</label>
|
|
<input
|
|
type="text"
|
|
id="location-code-input-tab2"
|
|
placeholder="Scan or enter location code"
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
|
|
<button class="btn-primary" onclick="searchLocationTab2()">
|
|
<span class="spinner" id="spinner-search-tab2" style="display: none;"></span>
|
|
<span id="btn-search-tab2-text">🔍 Search Location</span>
|
|
</button>
|
|
|
|
<!-- Location Information -->
|
|
<div class="info-box" id="location-info-tab2">
|
|
<h4>Location Details</h4>
|
|
<div class="info-row">
|
|
<span class="info-label">Location Code:</span>
|
|
<span class="info-value" id="display-location-code-tab2">-</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Boxes Count:</span>
|
|
<span class="info-value" id="display-boxes-count-tab2">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Boxes List -->
|
|
<div class="box-list" id="boxes-list-tab2">
|
|
<h4>Boxes in This Location</h4>
|
|
<div id="boxes-container-tab2"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Move Box Section -->
|
|
<div class="form-section" id="move-section-tab2" style="display: none;">
|
|
<h3><span class="section-icon">🚚</span> Move Selected Box</h3>
|
|
|
|
<div class="info-box show">
|
|
<div class="info-row">
|
|
<span class="info-label">Selected Box:</span>
|
|
<span class="info-value" id="selected-box-display-tab2">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<label for="new-location-input-tab2">New Location Code</label>
|
|
<input
|
|
type="text"
|
|
id="new-location-input-tab2"
|
|
placeholder="Scan or enter new location code"
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button class="btn-primary btn-success" onclick="moveBoxTab2()">
|
|
<span class="spinner" id="spinner-move-tab2" style="display: none;"></span>
|
|
<span id="btn-move-tab2-text">🚚 Move to New Location</span>
|
|
</button>
|
|
<button class="btn-secondary" onclick="clearTab2()">
|
|
Clear Selection
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables
|
|
let currentBoxId_Tab0 = null;
|
|
let currentBoxId_Tab2 = null;
|
|
|
|
// Show/hide spinner
|
|
function showSpinner(spinnerId, textId, text) {
|
|
const spinner = document.getElementById(spinnerId);
|
|
const textSpan = document.getElementById(textId);
|
|
if (spinner) spinner.style.display = 'inline-block';
|
|
if (textSpan) textSpan.textContent = text;
|
|
}
|
|
|
|
function hideSpinner(spinnerId, textId, text) {
|
|
const spinner = document.getElementById(spinnerId);
|
|
const textSpan = document.getElementById(textId);
|
|
if (spinner) spinner.style.display = 'none';
|
|
if (textSpan) textSpan.textContent = text;
|
|
}
|
|
|
|
// Show alert
|
|
function showAlert(alertId, message, type = 'info') {
|
|
const alertEl = document.getElementById(alertId);
|
|
if (alertEl) {
|
|
alertEl.textContent = message;
|
|
alertEl.className = `alert alert-${type} show`;
|
|
alertEl.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
function hideAlert(alertId) {
|
|
const alertEl = document.getElementById(alertId);
|
|
if (alertEl) {
|
|
alertEl.className = 'alert';
|
|
alertEl.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Tab switching
|
|
document.querySelectorAll('.tab-button').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const tabIndex = this.dataset.tab;
|
|
|
|
// Remove active from all tabs
|
|
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
|
|
// Add active to clicked tab
|
|
this.classList.add('active');
|
|
document.getElementById(`tab-${tabIndex}`).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// ========== TAB 0: ASSIGN BOX TO LOCATION ==========
|
|
|
|
async function searchBoxTab0() {
|
|
const boxNumber = document.getElementById('box-number-input-tab0').value.trim();
|
|
|
|
if (!boxNumber) {
|
|
showAlert('alert-tab0', 'Please enter a box number', 'error');
|
|
return;
|
|
}
|
|
|
|
hideAlert('alert-tab0');
|
|
showSpinner('spinner-search-tab0', 'btn-search-tab0-text', 'Searching...');
|
|
|
|
try {
|
|
const response = await fetch('{{ url_for("warehouse.api_search_box") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ box_number: boxNumber })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
currentBoxId_Tab0 = data.box.id;
|
|
document.getElementById('display-box-number-tab0').textContent = data.box.box_number;
|
|
document.getElementById('display-box-status-tab0').textContent = data.box.status.toUpperCase();
|
|
document.getElementById('display-box-location-tab0').textContent = data.box.location_code;
|
|
|
|
document.getElementById('box-info-tab0').classList.add('show');
|
|
document.getElementById('assign-section-tab0').style.display = 'block';
|
|
|
|
showAlert('alert-tab0', `Box "${boxNumber}" found!`, 'success');
|
|
} else {
|
|
document.getElementById('assign-section-tab0').style.display = 'none';
|
|
showAlert('alert-tab0', data.error || 'Box not found', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showAlert('alert-tab0', 'An error occurred while searching', 'error');
|
|
} finally {
|
|
hideSpinner('spinner-search-tab0', 'btn-search-tab0-text', '🔍 Search Box');
|
|
}
|
|
}
|
|
|
|
async function assignBoxToLocationTab0() {
|
|
const locationCode = document.getElementById('location-code-input-tab0').value.trim();
|
|
const boxNumber = document.getElementById('box-number-input-tab0').value.trim();
|
|
|
|
if (!currentBoxId_Tab0) {
|
|
showAlert('alert-tab0', 'Please search for a box first', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!locationCode) {
|
|
showAlert('alert-tab0', 'Please enter a location code', 'error');
|
|
return;
|
|
}
|
|
|
|
hideAlert('alert-tab0');
|
|
showSpinner('spinner-assign-tab0', 'btn-assign-tab0-text', 'Assigning...');
|
|
|
|
try {
|
|
const response = await fetch('{{ url_for("warehouse.api_assign_box_to_location") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
box_id: currentBoxId_Tab0,
|
|
location_code: locationCode
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showAlert('alert-tab0', data.message + ' - Refreshing box info...', 'success');
|
|
// Clear location input but keep box number to refresh details
|
|
document.getElementById('location-code-input-tab0').value = '';
|
|
document.getElementById('assign-section-tab0').style.display = 'none';
|
|
|
|
// Automatically refresh box details after a brief delay
|
|
setTimeout(async () => {
|
|
try {
|
|
const searchResponse = await fetch('{{ url_for("warehouse.api_search_box") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ box_number: boxNumber })
|
|
});
|
|
|
|
const searchData = await searchResponse.json();
|
|
|
|
if (searchData.success) {
|
|
document.getElementById('display-box-number-tab0').textContent = searchData.box.box_number;
|
|
document.getElementById('display-box-status-tab0').textContent = searchData.box.status;
|
|
document.getElementById('display-box-location-tab0').textContent = searchData.box.location_code || 'Unassigned';
|
|
|
|
document.getElementById('box-info-tab0').classList.add('show');
|
|
showAlert('alert-tab0', 'Box updated successfully', 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error refreshing box:', error);
|
|
}
|
|
}, 800);
|
|
} else {
|
|
showAlert('alert-tab0', data.message || 'Error assigning box', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showAlert('alert-tab0', 'An error occurred', 'error');
|
|
} finally {
|
|
hideSpinner('spinner-assign-tab0', 'btn-assign-tab0-text', '✅ Assign to Location');
|
|
}
|
|
}
|
|
|
|
function clearTab0() {
|
|
document.getElementById('box-number-input-tab0').value = '';
|
|
document.getElementById('location-code-input-tab0').value = '';
|
|
document.getElementById('box-info-tab0').classList.remove('show');
|
|
document.getElementById('assign-section-tab0').style.display = 'none';
|
|
hideAlert('alert-tab0');
|
|
currentBoxId_Tab0 = null;
|
|
}
|
|
|
|
// ========== TAB 1: FIND BOXES BY LOCATION ==========
|
|
|
|
async function searchLocationTab1() {
|
|
const locationCode = document.getElementById('location-code-input-tab1').value.trim();
|
|
|
|
if (!locationCode) {
|
|
showAlert('alert-tab1', 'Please enter a location code', 'error');
|
|
return;
|
|
}
|
|
|
|
hideAlert('alert-tab1');
|
|
showSpinner('spinner-search-tab1', 'btn-search-tab1-text', 'Searching...');
|
|
|
|
try {
|
|
const response = await fetch('{{ url_for("warehouse.api_search_location") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ location_code: locationCode })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
document.getElementById('display-location-code-tab1').textContent = data.location.location_code;
|
|
document.getElementById('display-boxes-count-tab1').textContent = data.boxes.length;
|
|
|
|
document.getElementById('location-info-tab1').classList.add('show');
|
|
document.getElementById('clear-tab1-btn').style.display = 'block';
|
|
|
|
if (data.boxes.length > 0) {
|
|
displayBoxesTab1(data.boxes);
|
|
document.getElementById('boxes-list-tab1').classList.add('show');
|
|
} else {
|
|
document.getElementById('boxes-list-tab1').classList.remove('show');
|
|
showAlert('alert-tab1', `Location "${locationCode}" found but no boxes inside`, 'info');
|
|
}
|
|
} else {
|
|
document.getElementById('location-info-tab1').classList.remove('show');
|
|
document.getElementById('boxes-list-tab1').classList.remove('show');
|
|
document.getElementById('clear-tab1-btn').style.display = 'none';
|
|
showAlert('alert-tab1', data.error || 'Location not found', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showAlert('alert-tab1', 'An error occurred', 'error');
|
|
} finally {
|
|
hideSpinner('spinner-search-tab1', 'btn-search-tab1-text', '🔍 Search Location');
|
|
}
|
|
}
|
|
|
|
function displayBoxesTab1(boxes) {
|
|
const container = document.getElementById('boxes-container-tab1');
|
|
container.innerHTML = '';
|
|
|
|
boxes.forEach(box => {
|
|
const boxEl = document.createElement('div');
|
|
boxEl.className = 'box-item';
|
|
boxEl.innerHTML = `
|
|
<div class="box-item-info">
|
|
<div class="box-number">${box.box_number}</div>
|
|
</div>
|
|
<span class="box-status ${box.status}">${box.status.toUpperCase()}</span>
|
|
`;
|
|
container.appendChild(boxEl);
|
|
});
|
|
}
|
|
|
|
function clearTab1() {
|
|
document.getElementById('location-code-input-tab1').value = '';
|
|
document.getElementById('location-info-tab1').classList.remove('show');
|
|
document.getElementById('boxes-list-tab1').classList.remove('show');
|
|
document.getElementById('clear-tab1-btn').style.display = 'none';
|
|
hideAlert('alert-tab1');
|
|
}
|
|
|
|
// ========== TAB 2: MOVE BOX TO NEW LOCATION ==========
|
|
|
|
async function searchLocationTab2() {
|
|
const locationCode = document.getElementById('location-code-input-tab2').value.trim();
|
|
|
|
if (!locationCode) {
|
|
showAlert('alert-tab2', 'Please enter a location code', 'error');
|
|
return;
|
|
}
|
|
|
|
hideAlert('alert-tab2');
|
|
showSpinner('spinner-search-tab2', 'btn-search-tab2-text', 'Searching...');
|
|
|
|
try {
|
|
const response = await fetch('{{ url_for("warehouse.api_search_location") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ location_code: locationCode })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
document.getElementById('display-location-code-tab2').textContent = data.location.location_code;
|
|
document.getElementById('display-boxes-count-tab2').textContent = data.boxes.length;
|
|
|
|
document.getElementById('location-info-tab2').classList.add('show');
|
|
|
|
if (data.boxes.length > 0) {
|
|
displayBoxesTab2(data.boxes);
|
|
document.getElementById('boxes-list-tab2').classList.add('show');
|
|
showAlert('alert-tab2', `Found ${data.boxes.length} box(es). Click to select.`, 'info');
|
|
} else {
|
|
document.getElementById('boxes-list-tab2').classList.remove('show');
|
|
showAlert('alert-tab2', `Location "${locationCode}" is empty`, 'warning');
|
|
}
|
|
} else {
|
|
document.getElementById('location-info-tab2').classList.remove('show');
|
|
document.getElementById('boxes-list-tab2').classList.remove('show');
|
|
showAlert('alert-tab2', data.error || 'Location not found', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showAlert('alert-tab2', 'An error occurred', 'error');
|
|
} finally {
|
|
hideSpinner('spinner-search-tab2', 'btn-search-tab2-text', '🔍 Search Location');
|
|
}
|
|
}
|
|
|
|
function displayBoxesTab2(boxes) {
|
|
const container = document.getElementById('boxes-container-tab2');
|
|
container.innerHTML = '';
|
|
|
|
boxes.forEach(box => {
|
|
const boxEl = document.createElement('div');
|
|
boxEl.className = 'box-item';
|
|
boxEl.onclick = () => selectBoxTab2(box.id, box.box_number);
|
|
boxEl.innerHTML = `
|
|
<div class="box-item-info">
|
|
<div class="box-number">${box.box_number}</div>
|
|
</div>
|
|
<span class="box-status ${box.status}">${box.status.toUpperCase()}</span>
|
|
`;
|
|
container.appendChild(boxEl);
|
|
});
|
|
}
|
|
|
|
function selectBoxTab2(boxId, boxNumber) {
|
|
currentBoxId_Tab2 = boxId;
|
|
document.getElementById('selected-box-display-tab2').textContent = boxNumber;
|
|
document.getElementById('move-section-tab2').style.display = 'block';
|
|
|
|
// Highlight selected box
|
|
document.querySelectorAll('#boxes-container-tab2 .box-item').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
event.currentTarget.classList.add('selected');
|
|
}
|
|
|
|
async function moveBoxTab2() {
|
|
const locationCode = document.getElementById('location-code-input-tab2').value.trim();
|
|
const newLocationCode = document.getElementById('new-location-input-tab2').value.trim();
|
|
|
|
if (!currentBoxId_Tab2) {
|
|
showAlert('alert-tab2', 'Please select a box first', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!newLocationCode) {
|
|
showAlert('alert-tab2', 'Please enter a new location code', 'error');
|
|
return;
|
|
}
|
|
|
|
hideAlert('alert-tab2');
|
|
showSpinner('spinner-move-tab2', 'btn-move-tab2-text', 'Moving...');
|
|
|
|
try {
|
|
const response = await fetch('{{ url_for("warehouse.api_move_box_to_location") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
box_id: currentBoxId_Tab2,
|
|
new_location_code: newLocationCode
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showAlert('alert-tab2', data.message + ' - Refreshing location...', 'success');
|
|
// Clear the selection but keep the location code to show updated list
|
|
document.getElementById('new-location-input-tab2').value = '';
|
|
document.getElementById('move-section-tab2').style.display = 'none';
|
|
document.getElementById('selected-box-display-tab2').textContent = '-';
|
|
currentBoxId_Tab2 = null;
|
|
|
|
// Automatically refresh and show the updated location list after a brief delay
|
|
setTimeout(async () => {
|
|
try {
|
|
const searchResponse = await fetch('{{ url_for("warehouse.api_search_location") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ location_code: locationCode })
|
|
});
|
|
|
|
const searchData = await searchResponse.json();
|
|
|
|
if (searchData.success) {
|
|
document.getElementById('display-location-code-tab2').textContent = searchData.location.location_code;
|
|
document.getElementById('display-boxes-count-tab2').textContent = searchData.boxes.length;
|
|
|
|
document.getElementById('location-info-tab2').classList.add('show');
|
|
|
|
if (searchData.boxes.length > 0) {
|
|
displayBoxesTab2(searchData.boxes);
|
|
document.getElementById('boxes-list-tab2').classList.add('show');
|
|
showAlert('alert-tab2', `Location updated - ${searchData.boxes.length} box(es) found`, 'success');
|
|
} else {
|
|
document.getElementById('boxes-list-tab2').classList.remove('show');
|
|
showAlert('alert-tab2', `Location "${locationCode}" is now empty`, 'warning');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error refreshing location:', error);
|
|
}
|
|
}, 800);
|
|
} else {
|
|
showAlert('alert-tab2', data.message || 'Error moving box', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
showAlert('alert-tab2', 'An error occurred', 'error');
|
|
} finally {
|
|
hideSpinner('spinner-move-tab2', 'btn-move-tab2-text', '🚚 Move to New Location');
|
|
}
|
|
}
|
|
|
|
function clearTab2() {
|
|
document.getElementById('location-code-input-tab2').value = '';
|
|
document.getElementById('new-location-input-tab2').value = '';
|
|
document.getElementById('location-info-tab2').classList.remove('show');
|
|
document.getElementById('boxes-list-tab2').classList.remove('show');
|
|
document.getElementById('move-section-tab2').style.display = 'none';
|
|
document.getElementById('selected-box-display-tab2').textContent = '-';
|
|
hideAlert('alert-tab2');
|
|
currentBoxId_Tab2 = null;
|
|
}
|
|
|
|
// Auto-submit on Enter key
|
|
document.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
const activeTab = document.querySelector('.tab-content.active');
|
|
if (activeTab.id === 'tab-0') {
|
|
if (document.activeElement.id === 'box-number-input-tab0') {
|
|
searchBoxTab0();
|
|
} else if (document.activeElement.id === 'location-code-input-tab0' && currentBoxId_Tab0) {
|
|
assignBoxToLocationTab0();
|
|
}
|
|
} else if (activeTab.id === 'tab-1') {
|
|
if (document.activeElement.id === 'location-code-input-tab1') {
|
|
searchLocationTab1();
|
|
}
|
|
} else if (activeTab.id === 'tab-2') {
|
|
if (document.activeElement.id === 'location-code-input-tab2') {
|
|
searchLocationTab2();
|
|
} else if (document.activeElement.id === 'new-location-input-tab2' && currentBoxId_Tab2) {
|
|
moveBoxTab2();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|