Compare commits
No commits in common. "main" and "master" have entirely different histories.
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.db
|
||||
*.sqlite
|
||||
.env
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
14
.idea/discord.xml
generated
14
.idea/discord.xml
generated
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT" />
|
||||
<option name="description" value="" />
|
||||
<option name="applicationTheme" value="default" />
|
||||
<option name="iconsTheme" value="default" />
|
||||
<option name="button1Title" value="" />
|
||||
<option name="button1Url" value="" />
|
||||
<option name="button2Title" value="" />
|
||||
<option name="button2Url" value="" />
|
||||
<option name="customApplicationId" value="" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/do-tracker.iml
generated
8
.idea/do-tracker.iml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/do-tracker.iml" filepath="$PROJECT_DIR$/.idea/do-tracker.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
167
.idea/workspace.xml
generated
167
.idea/workspace.xml
generated
@ -1,167 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="5112d569-3a3f-4a44-9bd9-2c9295e7fe64" name="Changes" comment="added readme">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="HTML File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="main" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/run.py" root0="FORCE_HIGHLIGHTING" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 2
|
||||
}</component>
|
||||
<component name="ProjectId" id="2xzZKYTQ3ONQyWMQvvsDEzRY2pu" />
|
||||
<component name="ProjectLevelVcsManager">
|
||||
<ConfirmationsSetting value="2" id="Add" />
|
||||
</component>
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"DefaultHtmlFileTemplate": "HTML File",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"settings.editor.selected.configurable": "discord-application"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="5112d569-3a3f-4a44-9bd9-2c9295e7fe64" name="Changes" comment="" />
|
||||
<created>1748942798494</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1748942798494</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="First Commit">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748943061863</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748943061863</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="First Commit">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748943110291</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748943110291</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="First Commit">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748943342764</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748943342764</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="Web front enabled basic flask setup">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748943934876</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748943934876</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="Login and Dashboard added">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748944937448</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748944937448</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="Tracking DOs and other stuff added">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748945875048</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748945875048</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="added readme">
|
||||
<option name="closed" value="true" />
|
||||
<created>1748946041819</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1748946041819</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="8" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="RECENT_FILTERS">
|
||||
<map>
|
||||
<entry key="Branch">
|
||||
<value>
|
||||
<list>
|
||||
<RecentGroup>
|
||||
<option name="FILTER_VALUES">
|
||||
<option value="origin/main" />
|
||||
</option>
|
||||
</RecentGroup>
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="main" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="First Commit" />
|
||||
<MESSAGE value="Web front enabled basic flask setup" />
|
||||
<MESSAGE value="Login and Dashboard added" />
|
||||
<MESSAGE value="Tracking DOs and other stuff added" />
|
||||
<MESSAGE value="added readme" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="added readme" />
|
||||
</component>
|
||||
</project>
|
||||
112
README.md
112
README.md
@ -1,112 +0,0 @@
|
||||
# 🧾 DO Tracker & Manifest System
|
||||
|
||||
A lightweight Flask-based delivery order tracking and manifest system for inter-branch logistics. Built for internal use in multi-branch warehouse/distribution networks, such as plumbing or trade supply chains.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- 🔐 Store-based login system (per-branch access control)
|
||||
- 📦 Manual DO entry (DO Number + Delivery Number)
|
||||
- 🚚 Track each DO across multiple store stopovers
|
||||
- ✍️ Logs who handled the DO at each location
|
||||
- 📅 Timestamps all arrivals and departures
|
||||
- ✅ Final destination marking
|
||||
- 🧼 Admin portal (WIP) for fixing errors
|
||||
- 🔍 DO lookup by either DO Number or Delivery Number
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Tech Stack
|
||||
|
||||
- Python 3
|
||||
- Flask (Web Framework)
|
||||
- SQLAlchemy (ORM)
|
||||
- SQLite (default backend DB)
|
||||
- Jinja2 (templating)
|
||||
- PyCharm (dev environment)
|
||||
- Gitea (self-hosted Git)
|
||||
|
||||
---
|
||||
|
||||
## 🗂 Folder Structure
|
||||
|
||||
```
|
||||
do-tracker/
|
||||
├── app/
|
||||
│ ├── __init__.py
|
||||
│ ├── routes.py
|
||||
│ ├── models.py
|
||||
│ ├── auth.py
|
||||
│ ├── templates/
|
||||
│ └── static/
|
||||
├── run.py
|
||||
├── requirements.txt
|
||||
├── README.md
|
||||
└── do_tracker.db (generated after first run)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Setup Instructions
|
||||
|
||||
1. Clone repo:
|
||||
```bash
|
||||
git clone https://git.anubisdevelopments.com/buster_dylan/do-tracker.git
|
||||
cd do-tracker
|
||||
```
|
||||
|
||||
2. Create a Python virtual environment:
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. Initialize the database:
|
||||
```bash
|
||||
python
|
||||
>>> from app import create_app, db
|
||||
>>> app = create_app()
|
||||
>>> app.app_context().push()
|
||||
>>> db.create_all()
|
||||
>>> exit()
|
||||
```
|
||||
|
||||
5. Run the app:
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
Visit: [http://127.0.0.1:5000](http://127.0.0.1:5000)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Login Details (Example)
|
||||
|
||||
| Store | ID | Password |
|
||||
|----------------|-----|-----------|
|
||||
| West Gosford | 210 | gosford |
|
||||
| Woy Woy | 230 | woywoy |
|
||||
| Charmhaven | 220 | charms |
|
||||
| Long Jetty | 250 | jetty |
|
||||
| RDD Erina | 240 | erina |
|
||||
| Commercial | 610 | com610 |
|
||||
|
||||
---
|
||||
|
||||
## 📌 TODO / Roadmap
|
||||
|
||||
- [ ] Admin panel to edit movements
|
||||
- [ ] CSV export of DO logs
|
||||
- [ ] Signature capture for deliveries
|
||||
- [ ] Access logging & audit trail
|
||||
- [ ] Move to Postgres for production
|
||||
|
||||
---
|
||||
|
||||
Built by Dylan Wright for operational and portfolio use.
|
||||
@ -1,25 +0,0 @@
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
##from flask_login import LoginManager
|
||||
|
||||
db = SQLAlchemy()
|
||||
#login_manager = LoginManager()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'devkey' # Change this later
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///do_tracker.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
db.init_app(app)
|
||||
# login_manager.init_app(app)
|
||||
|
||||
from .routes import main as main_blueprint
|
||||
app.register_blueprint(main_blueprint)
|
||||
|
||||
from .auth import auth as auth_blueprint
|
||||
app.register_blueprint(auth_blueprint)
|
||||
|
||||
from . import models # <-- 🔥 this is what was missing
|
||||
|
||||
return app
|
||||
26
app/auth.py
26
app/auth.py
@ -1,26 +0,0 @@
|
||||
from flask import Blueprint, render_template, redirect, request, session, url_for, flash
|
||||
from .models import Store
|
||||
from . import db
|
||||
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
@auth.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
store_id = request.form.get('store_id')
|
||||
password = request.form.get('password')
|
||||
store = Store.query.filter_by(id=store_id, password=password).first()
|
||||
if store:
|
||||
session['store_id'] = store.id
|
||||
session['store_name'] = store.name
|
||||
flash(f"Logged in as {store.name}", "success")
|
||||
return redirect(url_for('main.store_dashboard'))
|
||||
else:
|
||||
flash("Invalid store ID or password", "danger")
|
||||
return render_template('login.html')
|
||||
|
||||
@auth.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
flash("Logged out successfully", "info")
|
||||
return redirect(url_for('auth.login'))
|
||||
@ -1,28 +0,0 @@
|
||||
from datetime import datetime
|
||||
from . import db
|
||||
|
||||
class Store(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True) # Store number like 210
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
password = db.Column(db.String(128), nullable=False) # plaintext for now (hash later)
|
||||
|
||||
class DeliveryOrder(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
do_number = db.Column(db.String(50), unique=True, nullable=False)
|
||||
delivery_number = db.Column(db.String(50), nullable=False)
|
||||
final_location = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False)
|
||||
created_by = db.Column(db.String(100), nullable=False)
|
||||
status = db.Column(db.String(50), default='Ready for Collection')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
collected_by = db.Column(db.String(100), nullable=True)
|
||||
|
||||
movements = db.relationship('Movement', backref='delivery_order', cascade='all, delete-orphan')
|
||||
|
||||
class Movement(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
do_id = db.Column(db.Integer, db.ForeignKey('delivery_order.id'), nullable=False)
|
||||
branch_id = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False)
|
||||
arrived_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
departed_at = db.Column(db.DateTime, nullable=True)
|
||||
handled_by = db.Column(db.String(100), nullable=False)
|
||||
comment = db.Column(db.Text, nullable=True)
|
||||
152
app/routes.py
152
app/routes.py
@ -1,152 +0,0 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, session, flash
|
||||
from .models import DeliveryOrder
|
||||
|
||||
main = Blueprint('main', __name__)
|
||||
|
||||
|
||||
@main.route('/')
|
||||
def home():
|
||||
return """
|
||||
<h1>DO Tracker Online</h1>
|
||||
<p><a href='/login'>Login as Store</a></p>
|
||||
<p><a href='/store'>Store Dashboard</a></p>
|
||||
"""
|
||||
|
||||
|
||||
@main.route('/store')
|
||||
def store_dashboard():
|
||||
if 'store_id' not in session:
|
||||
flash("You must be logged in to access the store dashboard.", "warning")
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
store_name = session.get('store_name')
|
||||
|
||||
return f"""
|
||||
<h1>Welcome, {store_name}</h1>
|
||||
<p><a href='/logout'>Logout</a></p>
|
||||
<ul>
|
||||
<li><a href='/store/do-entry'>Enter New DO</a> (coming next)</li>
|
||||
<li><a href='/store/track'>Track DOs</a> (coming soon)</li>
|
||||
</ul>
|
||||
"""
|
||||
from flask import request
|
||||
from .models import DeliveryOrder, Store
|
||||
from . import db
|
||||
|
||||
@main.route('/store/do-entry', methods=['GET', 'POST'])
|
||||
def do_entry():
|
||||
if 'store_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
stores = Store.query.order_by(Store.name).all()
|
||||
|
||||
if request.method == 'POST':
|
||||
do_number = request.form.get('do_number')
|
||||
delivery_number = request.form.get('delivery_number')
|
||||
final_location = int(request.form.get('final_location'))
|
||||
created_by = session.get('store_name')
|
||||
|
||||
# Prevent duplicate DO numbers
|
||||
existing = DeliveryOrder.query.filter_by(do_number=do_number).first()
|
||||
if existing:
|
||||
flash("DO already exists!", "danger")
|
||||
return redirect(url_for('main.do_entry'))
|
||||
|
||||
new_do = DeliveryOrder(
|
||||
do_number=do_number,
|
||||
delivery_number=delivery_number,
|
||||
final_location=final_location,
|
||||
created_by=created_by,
|
||||
status="Ready for Collection"
|
||||
)
|
||||
|
||||
db.session.add(new_do)
|
||||
db.session.commit()
|
||||
flash("DO created successfully.", "success")
|
||||
return redirect(url_for('main.store_dashboard'))
|
||||
|
||||
return render_template("do_entry.html", stores=stores)
|
||||
@main.route('/store/track', methods=['GET', 'POST'])
|
||||
def track_do():
|
||||
if 'store_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
do = None
|
||||
movements = []
|
||||
|
||||
if request.method == 'POST':
|
||||
search = request.form.get('search')
|
||||
do = DeliveryOrder.query.filter(
|
||||
(DeliveryOrder.do_number == search) |
|
||||
(DeliveryOrder.delivery_number == search)
|
||||
).first()
|
||||
|
||||
if do:
|
||||
movements = do.movements
|
||||
|
||||
return render_template('track_do.html', do=do, movements=movements)
|
||||
from datetime import datetime
|
||||
from .models import Movement
|
||||
|
||||
@main.route('/store/move', methods=['GET', 'POST'])
|
||||
def move_do():
|
||||
if 'store_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
message = None
|
||||
if request.method == 'POST':
|
||||
search = request.form.get('do_search')
|
||||
do = DeliveryOrder.query.filter(
|
||||
(DeliveryOrder.do_number == search) |
|
||||
(DeliveryOrder.delivery_number == search)
|
||||
).first()
|
||||
|
||||
if not do:
|
||||
flash("DO not found", "danger")
|
||||
return redirect(url_for('main.move_do'))
|
||||
|
||||
handled_by = request.form.get('handled_by')
|
||||
comment = request.form.get('comment')
|
||||
mark_departed = request.form.get('departed')
|
||||
|
||||
branch_id = session.get('store_id')
|
||||
|
||||
# Check if this store has already logged a movement
|
||||
existing = Movement.query.filter_by(do_id=do.id, branch_id=branch_id).first()
|
||||
|
||||
if not existing:
|
||||
# First time it arrived at this store
|
||||
move = Movement(
|
||||
do_id=do.id,
|
||||
branch_id=branch_id,
|
||||
handled_by=handled_by,
|
||||
comment=comment
|
||||
)
|
||||
db.session.add(move)
|
||||
else:
|
||||
# Already exists → optionally mark departure
|
||||
if mark_departed == "on" and not existing.departed_at:
|
||||
existing.departed_at = datetime.utcnow()
|
||||
existing.comment = (existing.comment or '') + f" | {comment}"
|
||||
|
||||
db.session.commit()
|
||||
flash("Movement updated successfully.", "success")
|
||||
return redirect(url_for('main.track_do'))
|
||||
|
||||
return render_template("move_do.html")
|
||||
@main.route('/store/complete/<int:do_id>', methods=['POST'])
|
||||
def mark_completed(do_id):
|
||||
if 'store_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
do = DeliveryOrder.query.get_or_404(do_id)
|
||||
|
||||
# Ensure only the final destination can mark complete
|
||||
if session['store_id'] != do.final_location:
|
||||
flash("You are not authorized to mark this DO as completed.", "danger")
|
||||
return redirect(url_for('main.track_do'))
|
||||
|
||||
do.status = "Completed"
|
||||
db.session.commit()
|
||||
flash("DO marked as completed.", "success")
|
||||
return redirect(url_for('main.track_do'))
|
||||
@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>New DO Entry</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Enter a New Delivery Order</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label>DO Number:</label><br>
|
||||
<input type="text" name="do_number" required><br>
|
||||
|
||||
<label>Delivery Number:</label><br>
|
||||
<input type="text" name="delivery_number" required><br>
|
||||
|
||||
<label>Final Destination:</label><br>
|
||||
<select name="final_location">
|
||||
{% for store in stores %}
|
||||
<option value="{{ store.id }}">{{ store.name }} ({{ store.id }})</option>
|
||||
{% endfor %}
|
||||
</select><br><br>
|
||||
|
||||
<button type="submit">Create DO</button>
|
||||
</form>
|
||||
|
||||
<p><a href="/store">Back to Dashboard</a></p>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<p style="color:red;">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Store Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Store Login</h1>
|
||||
<form method="POST">
|
||||
<label>Store ID:</label><br>
|
||||
<input type="text" name="store_id"><br>
|
||||
<label>Password:</label><br>
|
||||
<input type="password" name="password"><br><br>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<p style="color:red;">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Mark DO Movement</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Mark Arrival/Departure</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label>DO Number or Delivery Number:</label><br>
|
||||
<input type="text" name="do_search" required><br>
|
||||
|
||||
<label>Your Name (who handled it):</label><br>
|
||||
<input type="text" name="handled_by" required><br>
|
||||
|
||||
<label>Comment (optional):</label><br>
|
||||
<input type="text" name="comment"><br>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="departed">
|
||||
Mark as Departed
|
||||
</label><br><br>
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<p><a href="/store">Back to Dashboard</a></p>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<p style="color:red;">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,46 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Track Delivery Order</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Track a Delivery Order</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label>Enter DO Number or Delivery Number:</label><br>
|
||||
<input type="text" name="search" required>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
|
||||
{% if do %}
|
||||
<h2>DO: {{ do.do_number }} / {{ do.delivery_number }}</h2>
|
||||
<p>Status: <strong>{{ do.status }}</strong></p>
|
||||
<p>Created by: {{ do.created_by }}</p>
|
||||
<p>Final Destination: {{ do.final_location }}</p>
|
||||
<p>Collected By: {{ do.collected_by if do.collected_by else "Not collected yet" }}</p>
|
||||
<p>Created: {{ do.created_at }}</p>
|
||||
{% if do.status != "Completed" and session['store_id'] == do.final_location %}
|
||||
<form action="/store/complete/{{ do.id }}" method="POST">
|
||||
<button type="submit">✅ Mark as Delivered</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<h3>Movement History:</h3>
|
||||
{% if movements %}
|
||||
<ul>
|
||||
{% for m in movements %}
|
||||
<li>
|
||||
📍 {{ m.branch_id }} | Handled by {{ m.handled_by }} | Arrived: {{ m.arrived_at }}
|
||||
{% if m.departed_at %}| Departed: {{ m.departed_at }}{% endif %}
|
||||
{% if m.comment %}<br>📝 {{ m.comment }}{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No movement data yet.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p><a href="/store">Back to Dashboard</a></p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user