Merge branch 'dev-anish' of http://gitea.lcepl.org/pjpatil12/Comparison_Project into dev-anish

This commit is contained in:
2026-02-02 14:57:20 +05:30
commit 37dd118cbd
57 changed files with 4063 additions and 0 deletions

19
.dockerignore Normal file
View File

@@ -0,0 +1,19 @@
.git
.gitignore
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
*.egg-info/
dist/
build/
.vscode/
.idea/
*.log
.env
instance/
.pytest_cache/
.coverage

26
.env Normal file
View File

@@ -0,0 +1,26 @@
# -----------------------------
# Flask App Configuration
# -----------------------------
FLASK_ENV=development
FLASK_DEBUG=True
FLASK_HOST=0.0.0.0
FLASK_PORT=5001
# -----------------------------
# Security
# -----------------------------
SECRET_KEY=change-this-to-strong-secret-key
# -----------------------------
# Database Configuration
# -----------------------------
DB_DIALECT=mysql
DB_DRIVER=pymysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_NAME=comparisondb
DB_USER=root
DB_PASSWORD=admin
# DATABASE_URL=mysql+pymysql://root:root@localhost/comparisondb

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Python
app/__pycache__/
*.pyc
*.pyo
*.pyd
*.__pycache__
# Ingnor upload files
app/static/uploads/
# Ignore env files
venv
# Ignore Log files ss
logs/
# Ignore db folders
instance/

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create necessary directories
RUN mkdir -p app/logs app/static/uploads app/static/downloads
# Expose port
EXPOSE 5001
# Run the application
CMD ["python", "run.py"]

583
README.md Normal file
View File

@@ -0,0 +1,583 @@
# Comparison Project - Backend Documentation
A Flask-based web application for comparing and managing construction project data (excavation and manhole work) between subcontractors and clients. The system imports data from Excel files, stores it in a database, and generates comparison reports.
## Table of Contents
- [Project Overview](#project-overview)
- [Tech Stack](#tech-stack)
- [Project Structure](#project-structure)
- [Database Schema](#database-schema)
- [Application Flow](#application-flow)
- [API Routes & Endpoints](#api-routes--endpoints)
- [Setup & Installation](#setup--installation)
---
## Project Overview
The Comparison Project is designed to:
- **Manage subcontractors** and their excavation data (Manhole Excavation, Trench Excavation, Domestic Chambers)
- **Import client-side project data** for comparison
- **Compare subcontractor work** against client specifications
- **Generate detailed reports** highlighting differences between client and subcontractor data
- **User authentication** with login/registration system
### Key Features
✅ User Authentication (Register/Login/Logout)
✅ Subcontractor Management (Add/Edit/List/Delete)
✅ File Import (Excel/CSV) for both Client and Subcontractor data
✅ Data validation and duplicate detection
✅ Comparison Reports with Excel export
✅ Dashboard with file management
---
## Tech Stack
**Backend Framework**: Flask
**Database**: SQL Database (MySQL/PostgreSQL/SQLite configured via environment variables)
**ORM**: SQLAlchemy
**File Processing**: Pandas, OpenPyXL, XlsxWriter
**Authentication**: Werkzeug (Password hashing)
**Configuration**: Python Dotenv
### Dependencies
```
Flask
pandas
openpyxl
xlrd
Werkzeug
python-dotenv
cryptography
xlsxwriter
matplotlib
```
---
## Project Structure
```
Comparison_Project/
├── run.py # Application entry point
├── requirements.txt # Python dependencies
├── .env # Environment variables (DB, secrets)
├── README.md # This file
├── app/
│ ├── __init__.py # Flask app initialization
│ ├── config.py # Configuration settings
│ │
│ ├── models/ # Database models (SQLAlchemy ORM)
│ │ ├── user_model.py # User table schema
│ │ ├── subcontractor_model.py # Subcontractor table schema
│ │ ├── manhole_excavation_model.py # Subcontractor MH excavation
│ │ ├── trench_excavation_model.py # Subcontractor Trench excavation
│ │ ├── manhole_domestic_chamber_model.py # Subcontractor Domestic chamber
│ │ ├── mh_ex_client_model.py # Client MH excavation
│ │ ├── tr_ex_client_model.py # Client Trench excavation
│ │ └── mh_dc_client_model.py # Client Domestic chamber
│ │
│ ├── routes/ # Flask Blueprints (API endpoints)
│ │ ├── auth.py # Login, Register, Logout
│ │ ├── dashboard.py # Dashboard page
│ │ ├── file_import.py # File upload endpoints
│ │ ├── file_report.py # Fetch and format data for reports
│ │ ├── generate_comparison_report.py # Comparison logic & Excel export
│ │ ├── subcontractor_routes.py # CRUD for subcontractors
│ │ ├── user.py # User management routes
│ │ └── file_format.py # File format validation
│ │
│ ├── services/ # Business logic layer
│ │ ├── db_service.py # Database connection & SQLAlchemy init
│ │ ├── file_service.py # File parsing & data insertion
│ │ └── user_service.py # User authentication logic
│ │
│ ├── utils/ # Helper functions
│ │ ├── file_utils.py # File path utilities
│ │ ├── helpers.py # Decorators, common helpers
│ │ └── __pycache__/
│ │
│ ├── static/ # Static assets
│ │ ├── css/ # Stylesheets
│ │ ├── images/ # Images
│ │ ├── uploads/ # Uploaded files directory
│ │ │ ├── Client_Bill_1/
│ │ │ └── sub_1/
│ │ └── downloads/ # Generated reports
│ │
│ ├── templates/ # Jinja2 HTML templates
│ │ ├── base.html # Base template
│ │ ├── login.html
│ │ ├── register.html
│ │ ├── dashboard.html
│ │ ├── file_import.html
│ │ ├── file_import_client.html
│ │ ├── file_format.html
│ │ ├── file_report.html
│ │ ├── generate_comparison_report.html
│ │ ├── generate_comparison_client_vs_subcont.html
│ │ ├── list_user.html
│ │ ├── report.html
│ │ ├── users.htm
│ │ └── subcontractor/
│ │ ├── add.html
│ │ ├── edit.html
│ │ └── list.html
│ │
│ └── logs/ # Application logs
├── instance/ # Instance folder (DB, temp files)
└── logs/ # Root level logs
```
---
## Database Schema
### 1. **Users Table** (`users`)
Stores user authentication data.
| Column | Type | Constraints | Purpose |
|--------|------|-------------|---------|
| `id` | Integer | Primary Key | User identifier |
| `name` | String(120) | NOT NULL | User's full name |
| `email` | String(120) | UNIQUE, NOT NULL | User's email |
| `password_hash` | String(255) | NOT NULL | Hashed password (Werkzeug) |
**Key Methods:**
- `set_password(password)` - Hash and store password
- `check_password(password)` - Verify password
---
### 2. **Subcontractors Table** (`subcontractors`)
Stores subcontractor company information.
| Column | Type | Constraints | Purpose |
|--------|------|-------------|---------|
| `id` | Integer | Primary Key | Subcontractor ID |
| `subcontractor_name` | String(255) | NOT NULL | Company name |
| `address` | String(500) | - | Company address |
| `gst_no` | String(50) | - | GST number |
| `pan_no` | String(50) | - | PAN number |
| `mobile_no` | String(20) | - | Contact phone |
| `email_id` | String(150) | - | Contact email |
| `contact_person` | String(150) | - | Contact person name |
| `status` | String(20) | Default: "Active" | Active/Inactive status |
| `created_at` | DateTime | Default: today | Record creation date |
**Relationships:**
- One-to-Many with `manhole_excavation`
- One-to-Many with `trench_excavation`
- One-to-Many with `manhole_domestic_chamber`
---
### 3. **Manhole Excavation (Subcontractor)** (`manhole_excavation`)
Stores manhole excavation work data from subcontractors.
| Column Type | Purpose |
|-----|---------|
| `id` | Primary Key |
| `subcontractor_id` | Foreign Key → Subcontractor |
| `Location` | Worksite location |
| `MH_NO` | Manhole number |
| `RA_Bill_No` | RA Bill reference (for grouping) |
| `Upto_IL_Depth`, `Cutting_Depth`, `ID_of_MH_m`, `Ex_Dia_of_Manhole`, `Area_of_Manhole` | Dimension fields |
| **Excavation Categories** (by depth ranges): | |
| Soft_Murum_0_to_1_5, Soft_Murum_1_5_to_3_0, etc. | Material type + depth range |
| Hard_Murum_0_to_1_5, Hard_Murum_1_5_to_3_0, etc. | Hard mudstone layers |
| Soft_Rock_0_to_1_5, Soft_Rock_1_5_to_3_0, etc. | Soft rock layers |
| Hard_Rock_0_to_1_5 through Hard_Rock_6_0_to_7_5 | Hard rock layers |
| **Totals** | Sum per category |
| `Total` | Grand total excavation |
| `Remarks`, `created_at` | Notes and timestamp |
---
### 4. **Trench Excavation (Subcontractor)** (`trench_excavation`)
Stores trench/sewer line excavation work data from subcontractors.
**Similar structure to Manhole Excavation with additional fields:**
- Width categories: `Width_0_to_2_5`, `Width_2_5_to_3_0`, etc.
- `Avg_Depth`, `Actual_Trench_Length`, `Pipe_Dia_mm`
---
### 5. **Manhole Domestic Chamber (Subcontractor)** (`manhole_domestic_chamber`)
Stores domestic chamber construction data.
| Key Fields | Purpose |
|-----------|---------|
| `id`, `subcontractor_id` | Primary & Foreign Key |
| `Location`, `MH_NO`, `RA_Bill_No` | Identification |
| `Depth_of_MH`, `MH_TOP_LEVEL`, `MH_IL_LEVEL` | Dimensions |
| `d_0_to_1_5` through `d_6_0_to_6_5` | Depth-based excavation categories |
| `Domestic_Chambers` | Count of chambers |
| `DWC_Pipe_Length`, `UPVC_Pipe_Length` | Pipe lengths |
---
### 6-8. **Client Data Tables** (Similar structure to subcontractor models)
- **`mh_ex_client`** - Manhole Excavation (Client specifications)
- **`tr_ex_client`** - Trench Excavation (Client specifications)
- **`mh_dc_client`** - Manhole Domestic Chamber (Client specifications)
**Note:** Client tables do NOT have `subcontractor_id` as they represent client baseline data.
---
## Application Flow
### 1. **User Authentication Flow**
```
User Access (/)
Redirect to /login
[Login Page]
├─ POST /auth/login
│ ↓
│ UserService.validate_login(email, password)
│ ↓
│ Query User table, verify password
│ ↓
│ Set session["user_id"], session["user_name"]
Redirect to /dashboard
```
### 2. **Subcontractor File Import Flow**
```
User → /file/import [GET]
Display form with subcontractor dropdown
User selects subcontractor & uploads Excel file
POST /file/import
FileService.handle_file_upload()
├─ Validate file type (xlsx, xls, csv)
├─ Create upload folder: static/uploads/sub_{id}/
├─ Save file
├─ Read Excel sheets:
│ ├─ "Tr.Ex." (Trench Excavation)
│ ├─ "MH Ex." (Manhole Excavation)
│ └─ "MH & DC" (Manhole & Domestic Chamber)
├─ Process each sheet:
│ ├─ Normalize column names
│ ├─ Clean data (handle NaN, empty values)
│ ├─ Check for duplicates by (Location, MH_NO, RA_Bill_No, subcontractor_id)
│ ├─ Insert records into respective tables
│ └─ Commit to database
└─ Return success/error message
```
### 3. **Client File Import Flow**
```
User → /file/import_client [GET]
Display form for client file upload
User uploads Excel file with client specifications
POST /file/import_client
FileService.handle_client_file_upload()
├─ Validate & save file
├─ Read sheets: "Tr.Ex.", "MH Ex.", "MH & DC"
├─ Process and insert into:
│ ├─ mh_ex_client
│ ├─ tr_ex_client
│ └─ mh_dc_client
└─ Return status
```
### 4. **Comparison Report Generation Flow**
```
User → /report/generate_comparison [GET]
Display form to select RA Bill No.
User selects bill number
POST /report/generate_comparison
generate_report_bp routes request
├─ Fetch client data (mh_ex_client, tr_ex_client, mh_dc_client)
├─ Fetch subcontractor data (by RA_Bill_No)
├─ For each record type:
│ ├─ Create lookup table by (Location, MH_NO)
│ ├─ Match client records with subcontractor records
│ ├─ Calculate totals for each category
│ ├─ Compute differences/variances
│ └─ Format for Excel output
├─ Generate Excel file with comparison results:
│ ├─ Summary sheet
│ ├─ Detailed Trench Excavation comparison
│ ├─ Detailed Manhole Excavation comparison
│ └─ Detailed Domestic Chamber comparison
└─ Send file to client as download
```
### 5. **Dashboard & Data Retrieval Flow**
```
User → /dashboard [GET]
Check session["user_id"] (authentication)
Render dashboard.html
├─ Display available reports
├─ List recent RA Bills
├─ Show subcontractor list
└─ Provide navigation to:
├─ File import
├─ Reports
├─ Subcontractor management
└─ Logout
```
---
## API Routes & Endpoints
### Authentication Routes (`/auth`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET/POST | `/login` | Login form & validation | ❌ |
| GET/POST | `/register` | User registration | ❌ |
| GET | `/logout` | Clear session & logout | ✅ |
### Dashboard Route (`/dashboard`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET | `/dashboard/` | Main dashboard | ✅ |
### Subcontractor Routes (`/subcontractor`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET | `/subcontractor/add` | Add form | ✅ |
| POST | `/subcontractor/save` | Save new subcontractor | ✅ |
| GET | `/subcontractor/list` | List all subcontractors | ✅ |
| GET | `/subcontractor/edit/<id>` | Edit form | ✅ |
| POST | `/subcontractor/update/<id>` | Update subcontractor | ✅ |
| GET | `/subcontractor/delete/<id>` | Delete subcontractor | ✅ |
### File Import Routes (`/file`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET/POST | `/file/import` | Subcontractor file upload | ✅ |
| GET/POST | `/file/import_client` | Client file upload | ✅ |
| GET/POST | `/file/report` | View imported data report | ✅ |
### Report Routes (`/report`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET/POST | `/report/generate_comparison` | Generate comparison report | ✅ |
| GET/POST | `/report/generate_comparison_client_vs_subcont` | Alternative comparison | ✅ |
### User Routes (`/user`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET | `/user/list` | List all users | ✅ |
| Other | (Check routes) | User management | ✅ |
### File Format Route (`/format`)
| Method | Endpoint | Purpose | Auth |
|--------|----------|---------|------|
| GET/POST | `/format/...` | File format reference | ✅ |
---
## Setup & Installation
### Prerequisites
- Python 3.7+
- MySQL/PostgreSQL (or SQLite for development)
- pip (Python package manager)
### 1. Clone & Install Dependencies
```bash
cd d:\New folder\Comparison\Comparison_Project
pip install -r requirements.txt
```
### 2. Configure Environment Variables
Create a `.env` file in the project root:
```env
# -----------------------------
# Flask App Configuration
# -----------------------------
FLASK_ENV=development
FLASK_DEBUG=True
FLASK_HOST='0.0.0.0'
FLASK_PORT=5001
# -----------------------------
# Security
# -----------------------------
SECRET_KEY=change-this-to-strong-secret-key
# -----------------------------
# Database Configuration
# -----------------------------
DB_DIALECT=mysql
DB_DRIVER=pymysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=comparisondb
DB_USER=root
DB_PASSWORD=your_password
```
### 3. Initialize Database
```bash
python run.py
```
This will:
- Load environment variables
- Create Flask app with configuration
- Initialize SQLAlchemy
- Create all tables (if not exist)
- Start Flask development server
### 4. Access Application
Open browser: `http://127.0.0.1:5000/`
- First time: Redirect to `/login`
- Register a new account
- Login and start using the application
---
## Key Services & Utilities
### FileService (`app/services/file_service.py`)
**Purpose:** Handles file uploads, parsing, and data validation
**Key Methods:**
- `handle_file_upload(file, subcontractor_id, RA_Bill_No)` - Upload & process subcontractor file
- `handle_client_file_upload(file, RA_Bill_No)` - Upload & process client file
- `process_trench_excavation(df, subcontractor_id, RA_Bill_No)` - Parse & insert trench data
- `process_manhole_excavation(df, subcontractor_id, RA_Bill_No)` - Parse & insert manhole data
- `process_manhole_domestic_chamber(df, subcontractor_id, RA_Bill_No)` - Parse & insert chamber data
### UserService (`app/services/user_service.py`)
**Purpose:** User authentication & management
**Key Methods:**
- `register_user(name, email, password)` - Register new user
- `validate_login(email, password)` - Authenticate user
- `get_all_users()` - Fetch all users
### DBService (`app/services/db_service.py`)
**Purpose:** Database connection & SQLAlchemy initialization
**Exports:**
- `db` - SQLAlchemy instance for ORM operations
- `migrate` - Flask-Migrate for schema migrations
---
## Authentication & Security
### Session Management
- Uses Flask `session` object
- Stores `user_id` and `user_name` after successful login
- `@login_required` decorator validates authenticated requests
### Password Security
- Passwords hashed using Werkzeug's `generate_password_hash()`
- Verification via `check_password_hash()`
- No plaintext passwords stored in database
### File Security
- File uploads sanitized with `secure_filename()`
- Only allowed extensions: `.xlsx`, `.xls`, `.csv`
- Files stored in user-specific subfolders
---
## Error Handling & Validation
### File Import Validation
1. **File Type Check** - Only CSV/XLS/XLSX allowed
2. **Sheet Validation** - Required sheets must exist
3. **Column Normalization** - Auto-fixes column name inconsistencies
4. **Data Type Conversion** - Converts to appropriate types
5. **Duplicate Detection** - Prevents duplicate records by (Location, MH_NO, RA_Bill_No)
6. **Null Handling** - Converts empty/NaN values to None
### Database Constraints
- Foreign key relationships enforced
- Unique email constraint on users
- NOT NULL constraints on critical fields
---
## Example: Typical User Workflow
```
1. User registers/logs in
→ POST /auth/register (new user) or /auth/login
2. User adds subcontractors
→ GET /subcontractor/add
→ POST /subcontractor/save
3. User uploads subcontractor data (Excel file with 3 sheets)
→ GET /file/import
→ POST /file/import (file, subcontractor_id, RA_Bill_No)
→ Database populated with excavation data
4. User uploads client specifications (Excel file with same 3 sheets)
→ GET /file/import_client
→ POST /file/import_client (file, RA_Bill_No)
→ Database populated with client data
5. User generates comparison report
→ GET /report/generate_comparison
→ POST /report/generate_comparison (RA_Bill_No)
→ System compares client vs subcontractor data
→ Generates Excel file showing differences
→ User downloads report
6. User logs out
→ GET /auth/logout
→ Session cleared, redirected to login
```
---
## Future Enhancements
- [ ] Email notifications for report generation
- [ ] Data visualization dashboards
- [ ] Role-based access control (Admin, User, Viewer)
- [ ] Bulk report generation
- [ ] Data export to PDF format
- [ ] Audit logs for data modifications
- [ ] API documentation (Swagger/OpenAPI)
- [ ] Unit & integration tests
---
## Support & Contribution
For issues, feature requests, or contributions, please contact the development team.
**Last Updated:** January 2026

52
app/__init__.py Normal file
View File

@@ -0,0 +1,52 @@
from flask import Flask, redirect, url_for
from app.config import Config
from app.services.db_service import db
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
# Initialize extensions
db.init_app(app)
# Register blueprints
register_blueprints(app)
# Register error handlers
register_error_handlers(app)
# ROOT → LOGIN
@app.route("/")
def index():
return redirect(url_for("auth.login"))
return app
def register_blueprints(app):
from app.routes.auth import auth_bp
from app.routes.user import user_bp
from app.routes.dashboard import dashboard_bp
from app.routes.subcontractor_routes import subcontractor_bp
from app.routes.file_import import file_import_bp
from app.routes.file_report import file_report_bp
from app.routes.generate_comparison_report import generate_report_bp
from app.routes.file_format import file_format_bp
app.register_blueprint(auth_bp)
app.register_blueprint(user_bp)
app.register_blueprint(dashboard_bp)
app.register_blueprint(subcontractor_bp)
app.register_blueprint(file_import_bp)
app.register_blueprint(file_report_bp)
app.register_blueprint(generate_report_bp)
app.register_blueprint(file_format_bp )
def register_error_handlers(app):
@app.errorhandler(404)
def page_not_found(e):
return "Page Not Found", 404
@app.errorhandler(500)
def internal_error(e):
return "Internal Server Error", 500

29
app/config.py Normal file
View File

@@ -0,0 +1,29 @@
import os
# project base url
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
class Config:
# secret key
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key")
# Database varibles
DB_DIALECT = os.getenv("DB_DIALECT")
DB_DRIVER = os.getenv("DB_DRIVER")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
# database connection url
SQLALCHEMY_DATABASE_URI = (
f"{DB_DIALECT}+{DB_DRIVER}://"
f"{DB_USER}:{DB_PASSWORD}@"
f"{DB_HOST}:{DB_PORT}/"
f"{DB_NAME}"
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
# uploads folder path
UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads")
# file extension
ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"}

0
app/models/__init__.py Normal file
View File

View File

@@ -0,0 +1,58 @@
from app import db
from datetime import datetime
class LayingClient(db.Model):
__tablename__ = "laying_client"
id = db.Column(db.Integer, primary_key=True)
# Basic Fields
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
CC_length = db.Column(db.Float)
# Bedding Qty.
Outer_dia_of_MH_m = db.Column(db.Float)
Bedding_Length = db.Column(db.Float)
Width = db.Column(db.Float)
Depth = db.Column(db.Float)
Qty = db.Column(db.Float)
# PIPE LAYING Qty.
Pipe_Dia_mm = db.Column(db.Float)
ID_of_MH_m = db.Column(db.Float)
Laying_Length = db.Column(db.Float)
pipe_150_mm = db.Column(db.Float)
pipe_200_mm = db.Column(db.Float)
pipe_250_mm = db.Column(db.Float)
pipe_300_mm = db.Column(db.Float)
pipe_350_mm = db.Column(db.Float)
pipe_400_mm = db.Column(db.Float)
pipe_450_mm = db.Column(db.Float)
pipe_500_mm = db.Column(db.Float)
pipe_600_mm = db.Column(db.Float)
pipe_700_mm = db.Column(db.Float)
pipe_900_mm = db.Column(db.Float)
pipe_1200_mm = db.Column(db.Float)
Total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
RA_Bill_No=db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<LayingModel {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def sum_laying_fields():
return [
"pipe_150_mm", "pipe_200_mm", "pipe_250_mm",
"pipe_300_mm", "pipe_350_mm", "pipe_400_mm",
"pipe_450_mm", "pipe_500_mm", "pipe_600_mm",
"pipe_700_mm", "pipe_900_mm", "pipe_1200_mm"
]

View File

@@ -0,0 +1,54 @@
from app import db
from datetime import datetime
class Laying(db.Model):
__tablename__ = "laying"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor tables
subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
subcontractor = db.relationship("Subcontractor", backref="laying_records")
# Pipe Laying Fields
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
CC_length = db.Column(db.Float)
Pipe_Dia_mm = db.Column(db.Float)
ID_of_MH_m = db.Column(db.Float)
Laying_Length = db.Column(db.Float)
pipe_150_mm = db.Column(db.Float)
pipe_200_mm = db.Column(db.Float)
pipe_250_mm = db.Column(db.Float)
pipe_300_mm = db.Column(db.Float)
pipe_350_mm = db.Column(db.Float)
pipe_400_mm = db.Column(db.Float)
pipe_450_mm = db.Column(db.Float)
pipe_500_mm = db.Column(db.Float)
pipe_600_mm = db.Column(db.Float)
pipe_700_mm = db.Column(db.Float)
pipe_900_mm = db.Column(db.Float)
pipe_1200_mm = db.Column(db.Float)
Total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
RA_Bill_No=db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<LayingModel {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def sum_laying_fields():
return [
"pipe_150_mm", "pipe_200_mm", "pipe_250_mm",
"pipe_300_mm", "pipe_350_mm", "pipe_400_mm",
"pipe_450_mm", "pipe_500_mm", "pipe_600_mm",
"pipe_700_mm", "pipe_900_mm", "pipe_1200_mm"
]

View File

@@ -0,0 +1,63 @@
from app import db
from datetime import datetime
class ManholeDomesticChamber(db.Model):
__tablename__ = "manhole_domestic_chamber"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
subcontractor = db.relationship("Subcontractor", backref="manhole_domestic_chamber_records")
# Basic Fields
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
Depth_of_MH = db.Column(db.Float)
# Excavation categories
d_0_to_0_75 = db.Column(db.Float)
d_0_76_to_1_05 = db.Column(db.Float)
d_1_06_to_1_65 = db.Column(db.Float)
d_1_66_to_2_15 = db.Column(db.Float)
d_2_16_to_2_65 = db.Column(db.Float)
d_2_66_to_3_15 = db.Column(db.Float)
d_3_16_to_3_65= db.Column(db.Float)
d_3_66_to_4_15 = db.Column(db.Float)
d_4_16_to_4_65 = db.Column(db.Float)
d_4_66_to_5_15 = db.Column(db.Float)
d_5_16_to_5_65 = db.Column(db.Float)
d_5_66_to_6_15 = db.Column(db.Float)
d_6_16_to_6_65 = db.Column(db.Float)
d_6_66_to_7_15 = db.Column(db.Float)
d_7_16_to_7_65 = db.Column(db.Float)
d_7_66_to_8_15 = db.Column(db.Float)
d_8_16_to_8_65 = db.Column(db.Float)
d_8_66_to_9_15 = db.Column(db.Float)
d_9_16_to_9_65 = db.Column(db.Float)
Domestic_Chambers = db.Column(db.Float)
DWC_Pipe_Length = db.Column(db.Float)
UPVC_Pipe_Length = db.Column(db.Float)
RA_Bill_No=db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<HanholeDomesticChamberConstruction {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def sum_mh_dc_fields():
return [
"d_0_to_0_75", "d_0_76_to_1_05", "d_1_06_to_1_65",
"d_1_66_to_2_15", "d_2_16_to_2_65", "d_2_66_to_3_15",
"d_3_16_to_3_65", "d_3_66_to_4_15", "d_4_16_to_4_65",
"d_4_66_to_5_15", "d_5_16_to_5_65", "d_5_66_to_6_15",
"d_6_16_to_6_65", "d_6_66_to_7_15", "d_7_16_to_7_65",
"d_7_66_to_8_15", "d_8_16_to_8_65", "d_8_66_to_9_15",
"d_9_16_to_9_65"]

View File

@@ -0,0 +1,67 @@
from app import db
from datetime import datetime
class ManholeExcavation(db.Model):
__tablename__ = "manhole_excavation"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
subcontractor = db.relationship("Subcontractor", backref="manhole_records")
# Basic Fields
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
Upto_IL_Depth = db.Column(db.Float)
Cutting_Depth = db.Column(db.Float)
ID_of_MH_m = db.Column(db.Float)
Ex_Dia_of_Manhole = db.Column(db.Float)
Area_of_Manhole = db.Column(db.Float)
# Excavation categories
Soft_Murum_0_to_1_5 = db.Column(db.Float)
Soft_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Murum_3_0_to_4_5 = db.Column(db.Float)
Hard_Murum_0_to_1_5 = db.Column(db.Float)
Hard_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Rock_0_to_1_5 = db.Column(db.Float)
Soft_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_0_to_1_5 = db.Column(db.Float)
Hard_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_4_5_to_6_0 = db.Column(db.Float)
Hard_Rock_6_0_to_7_5 = db.Column(db.Float)
# Totals
Soft_Murum_0_to_1_5_total = db.Column(db.Float)
Soft_Murum_1_5_to_3_0_total = db.Column(db.Float)
Soft_Murum_3_0_to_4_5_total = db.Column(db.Float)
Hard_Murum_0_to_1_5_total = db.Column(db.Float)
Hard_Murum_1_5_and_above_total = db.Column(db.Float)
Soft_Rock_0_to_1_5_total = db.Column(db.Float)
Soft_Rock_1_5_and_above_total = db.Column(db.Float)
Hard_Rock_0_to_1_5_total = db.Column(db.Float)
Hard_Rock_1_5_to_3_0_total = db.Column(db.Float)
Hard_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_4_5_to_6_0_total = db.Column(db.Float)
Hard_Rock_6_0_to_7_5_total = db.Column(db.Float)
Total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
RA_Bill_No=db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<HanholeExcavation {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@@ -0,0 +1,57 @@
from app import db
from datetime import datetime
class ManholeDomesticChamberClient(db.Model):
__tablename__ = "mh_dc_client"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
# subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
# subcontractor = db.relationship("Subcontractor", backref="mh_dc_records")
# Basic Fields
RA_Bill_No=db.Column(db.String(500))
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
MH_TOP_LEVEL = db.Column(db.Float)
MH_IL_LEVEL = db.Column(db.Float)
Depth_of_MH = db.Column(db.Float)
# Excavation categories
d_0_to_1_5 = db.Column(db.Float)
d_1_5_to_2_0 = db.Column(db.Float)
d_2_0_to_2_5 = db.Column(db.Float)
d_2_5_to_3_0 = db.Column(db.Float)
d_3_0_to_3_5 = db.Column(db.Float)
d_3_5_to_4_0 = db.Column(db.Float)
d_4_0_to_4_5= db.Column(db.Float)
d_4_5_to_5_0 = db.Column(db.Float)
d_5_0_to_5_5 = db.Column(db.Float)
d_5_5_to_6_0 = db.Column(db.Float)
d_6_0_to_6_5 = db.Column(db.Float)
Domestic_Chambers = db.Column(db.Float)
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<HanholeDomesticChamberConstruction {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def sum_mh_dc_fields():
return [
"d_0_to_0_75", "d_0_76_to_1_05", "d_1_06_to_1_65",
"d_1_66_to_2_15", "d_2_16_to_2_65", "d_2_66_to_3_15",
"d_3_16_to_3_65", "d_3_66_to_4_15", "d_4_16_to_4_65",
"d_4_66_to_5_15", "d_5_16_to_5_65", "d_5_66_to_6_15",
"d_6_16_to_6_65", "d_6_66_to_7_15", "d_7_16_to_7_65",
"d_7_66_to_8_15", "d_8_16_to_8_65", "d_8_66_to_9_15",
"d_9_16_to_9_65" ]

View File

@@ -0,0 +1,83 @@
from app import db
from datetime import datetime
class ManholeExcavationClient(db.Model):
__tablename__ = "mh_ex_client"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
# subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
# subcontractor = db.relationship("Subcontractor", backref="mh_ex_records")
# Basic Fields
RA_Bill_No=db.Column(db.String(500))
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
Ground_Level = db.Column(db.Float)
MH_Invert_Level = db.Column(db.Float)
MH_Top_Level = db.Column(db.Float)
Ex_Level = db.Column(db.Float)
Cutting_Depth = db.Column(db.Float)
MH_Depth = db.Column(db.Float)
ID_of_MH_m = db.Column(db.Float)
Dia_of_MH_Cutting = db.Column(db.Float)
Area_of_Manhole = db.Column(db.Float)
# Excavation categories
Marshi_Muddy_Slushy_0_to_1_5 = db.Column(db.Float)
Marshi_Muddy_Slushy_1_5_to_3_0 = db.Column(db.Float)
Marshi_Muddy_Slushy_3_0_to_4_5 = db.Column(db.Float)
Soft_Murum_0_to_1_5 = db.Column(db.Float)
Soft_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Murum_3_0_to_4_5 = db.Column(db.Float)
Hard_Murum_0_to_1_5 = db.Column(db.Float)
Hard_Murum_1_5_to_3_0 = db.Column(db.Float)
Hard_Murum_3_0_to_4_5 = db.Column(db.Float)
Soft_Rock_0_to_1_5 = db.Column(db.Float)
Soft_Rock_1_5_to_3_0 = db.Column(db.Float)
Soft_Murum_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_0_to_1_5 = db.Column(db.Float)
Hard_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_4_5_to_6_0 = db.Column(db.Float)
Hard_Rock_6_0_to_7_5 = db.Column(db.Float)
# Totals
Marshi_Muddy_Slushy_0_to_1_5_total = db.Column(db.Float)
Marshi_Muddy_Slushy_1_5_to_3_0_total = db.Column(db.Float)
Marshi_Muddy_Slushy_3_0_to_4_5_total = db.Column(db.Float)
Soft_Murum_0_to_1_5_total = db.Column(db.Float)
Soft_Murum_1_5_to_3_0_total = db.Column(db.Float)
Soft_Murum_3_0_to_4_5_total = db.Column(db.Float)
Hard_Murum_0_to_1_5_total = db.Column(db.Float)
Hard_Murum_1_5_to_3_0_total = db.Column(db.Float)
Hard_Murum_3_0_to_4_5_total = db.Column(db.Float)
Soft_Rock_0_to_1_5_total = db.Column(db.Float)
Soft_Rock_1_5_to_3_0_total = db.Column(db.Float)
Soft_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_0_to_1_5_total = db.Column(db.Float)
Hard_Rock_1_5_to_3_0_total = db.Column(db.Float)
Hard_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_4_5_to_6_0_total = db.Column(db.Float)
Hard_Rock_6_0_to_7_5_total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
Total = db.Column(db.Float)
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<HanholeExcavation {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@@ -0,0 +1,21 @@
from app import db
# from app.services.db_service import db
from datetime import datetime
class Subcontractor(db.Model):
__tablename__ = "subcontractors"
id = db.Column(db.Integer, primary_key=True)
subcontractor_name = db.Column(db.String(255), nullable=False)
address = db.Column(db.String(500))
gst_no = db.Column(db.String(50))
pan_no = db.Column(db.String(50))
mobile_no = db.Column(db.String(20))
email_id = db.Column(db.String(150))
contact_person = db.Column(db.String(150))
status = db.Column(db.String(20), default="Active")
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<Subcontractor {self.subcontractor_name}>"

View File

@@ -0,0 +1,88 @@
from app import db
from datetime import datetime
class TrenchExcavationClient(db.Model):
__tablename__ = "tr_ex_client"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
# subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
# subcontractor = db.relationship("Subcontractor", backref="tr_ex_records")
# Basic Fields
RA_Bill_No=db.Column(db.String(500))
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
CC_length = db.Column(db.Float)
Actual_Trench_Length = db.Column(db.Float)
Ground_Level = db.Column(db.Float)
Invert_Level = db.Column(db.Float)
Excavated_level = db.Column(db.Float)
Cutting_Depth = db.Column(db.Float)
Avg_Depth = db.Column(db.Float)
Pipe_Dia_mm = db.Column(db.Float)
# width
Width_0_to_1_5_m = db.Column(db.Float)
Width_1_5_to_3_0_m = db.Column(db.Float)
Width_3_0_to_4_5_m = db.Column(db.Float)
Width_4_5_to_6_0_m = db.Column(db.Float)
Width_6_0_to_7_5_m = db.Column(db.Float)
# Excavation categories
Marshi_Muddy_Slushy_0_to_1_5 = db.Column(db.Float)
Marshi_Muddy_Slushy_1_5_to_3_0 = db.Column(db.Float)
Marshi_Muddy_Slushy_3_0_to_4_5 = db.Column(db.Float)
Soft_Murum_0_to_1_5 = db.Column(db.Float)
Soft_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Murum_3_0_to_4_5 = db.Column(db.Float)
Hard_Murum_0_to_1_5 = db.Column(db.Float)
Hard_Murum_1_5_to_3_0 = db.Column(db.Float)
Hard_Murum_3_0_to_4_5 = db.Column(db.Float)
Soft_Rock_0_to_1_5 = db.Column(db.Float)
Soft_Rock_1_5_to_3_0 = db.Column(db.Float)
Soft_Rock_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_0_to_1_5 = db.Column(db.Float)
Hard_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_4_5_to_6_0 = db.Column(db.Float)
Hard_Rock_6_0_to_7_5 = db.Column(db.Float)
# Totals
Marshi_Muddy_Slushy_0_to_1_5_total = db.Column(db.Float)
Marshi_Muddy_Slushy_1_5_to_3_0_total = db.Column(db.Float)
Marshi_Muddy_Slushy_3_0_to_4_5_total = db.Column(db.Float)
Soft_Murum_0_to_1_5_total = db.Column(db.Float)
Soft_Murum_1_5_to_3_0_total = db.Column(db.Float)
Soft_Murum_3_0_to_4_5_total = db.Column(db.Float)
Hard_Murum_0_to_1_5_total = db.Column(db.Float)
Hard_Murum_1_5_to_3_0_total = db.Column(db.Float)
Hard_Murum_3_0_to_4_5_total = db.Column(db.Float)
Soft_Rock_0_to_1_5_total = db.Column(db.Float)
Soft_Rock_1_5_to_3_0_total = db.Column(db.Float)
Soft_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_0_to_1_5_total = db.Column(db.Float)
Hard_Rock_1_5_to_3_0_total = db.Column(db.Float)
Hard_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_4_5_to_6_0_total = db.Column(db.Float)
Hard_Rock_6_0_to_7_5_total = db.Column(db.Float)
Total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<TrenchExcavation {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@@ -0,0 +1,110 @@
from app import db
from datetime import datetime
class TrenchExcavation(db.Model):
__tablename__ = "trench_excavation"
id = db.Column(db.Integer, primary_key=True)
# Foreign Key to Subcontractor table
subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False)
# Relationship for easy access (subcontractor.subcontractor_name)
subcontractor = db.relationship("Subcontractor", backref="trench_records")
# Basic Fields
Location = db.Column(db.String(500))
MH_NO = db.Column(db.String(100))
CC_length = db.Column(db.Float)
Invert_Level = db.Column(db.Float)
MH_Top_Level = db.Column(db.Float)
Ground_Level = db.Column(db.Float)
ID_of_MH_m = db.Column(db.Float)
Actual_Trench_Length = db.Column(db.Float)
Pipe_Dia_mm = db.Column(db.Float)
# width
Width_0_to_2_5 = db.Column(db.Float)
Width_2_5_to_3_0 = db.Column(db.Float)
Width_3_0_to_4_5 = db.Column(db.Float)
Width_4_5_to_6_0 = db.Column(db.Float)
Upto_IL_Depth = db.Column(db.Float)
Cutting_Depth = db.Column(db.Float)
Avg_Depth = db.Column(db.Float)
# Excavation categories
Soft_Murum_0_to_1_5 = db.Column(db.Float)
Soft_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Murum_3_0_to_4_5 = db.Column(db.Float)
Hard_Murum_0_to_1_5 = db.Column(db.Float)
Hard_Murum_1_5_to_3_0 = db.Column(db.Float)
Soft_Rock_0_to_1_5 = db.Column(db.Float)
Soft_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_0_to_1_5 = db.Column(db.Float)
Hard_Rock_1_5_to_3_0 = db.Column(db.Float)
Hard_Rock_3_0_to_4_5 = db.Column(db.Float)
Hard_Rock_4_5_to_6_0 = db.Column(db.Float)
Hard_Rock_6_0_to_7_5 = db.Column(db.Float)
# Totals
Soft_Murum_0_to_1_5_total = db.Column(db.Float)
Soft_Murum_1_5_to_3_0_total = db.Column(db.Float)
Soft_Murum_3_0_to_4_5_total = db.Column(db.Float)
Hard_Murum_0_to_1_5_total = db.Column(db.Float)
Hard_Murum_1_5_and_above_total = db.Column(db.Float)
Soft_Rock_0_to_1_5_total = db.Column(db.Float)
Soft_Rock_1_5_and_above_total = db.Column(db.Float)
Hard_Rock_0_to_1_5_total = db.Column(db.Float)
Hard_Rock_1_5_to_3_0_total = db.Column(db.Float)
Hard_Rock_3_0_to_4_5_total = db.Column(db.Float)
Hard_Rock_4_5_to_6_0_total = db.Column(db.Float)
Hard_Rock_6_0_to_7_5_total = db.Column(db.Float)
Total = db.Column(db.Float)
Remarks = db.Column(db.String(500))
RA_Bill_No=db.Column(db.String(500))
created_at = db.Column(db.DateTime, default=datetime.today)
def __repr__(self):
return f"<TrenchExcavation {self.Location}>"
def serialize(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
def excavation_category_sums(self):
def safe(val):
return val or 0
return {
"Soft_Murum_Total": (
safe(self.Soft_Murum_0_to_1_5)
+ safe(self.Soft_Murum_1_5_to_3_0)
+ safe(self.Soft_Murum_3_0_to_4_5)
),
"Hard_Murum_Total": (
safe(self.Hard_Murum_0_to_1_5)
+ safe(self.Hard_Murum_1_5_to_3_0)
),
"Soft_Rock_Total": (
safe(self.Soft_Rock_0_to_1_5)
+ safe(self.Soft_Rock_1_5_to_3_0)
),
"Hard_Rock_Total": (
safe(self.Hard_Rock_0_to_1_5)
+ safe(self.Hard_Rock_1_5_to_3_0)
+ safe(self.Hard_Rock_3_0_to_4_5)
+ safe(self.Hard_Rock_4_5_to_6_0)
+ safe(self.Hard_Rock_6_0_to_7_5)
),
}

16
app/models/user_model.py Normal file
View File

@@ -0,0 +1,16 @@
from app.services.db_service import db
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)

0
app/routes/__init__.py Normal file
View File

48
app/routes/auth.py Normal file
View File

@@ -0,0 +1,48 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from app.services.user_service import UserService
auth_bp = Blueprint("auth", __name__)
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
if session.get("user_id"):
return redirect(url_for("dashboard.dashboard"))
if request.method == "POST":
email = request.form.get("email")
password = request.form.get("password")
user = UserService.validate_login(email, password)
if user:
session["user_id"] = user.id
session["user_name"] = user.name
flash("Login successful", "success")
return redirect(url_for("dashboard.dashboard"))
flash("Invalid email or password", "danger")
return render_template("login.html", title="Login")
@auth_bp.route("/logout")
def logout():
session.clear()
flash("Logged out successfully", "info")
return redirect(url_for("auth.login"))
@auth_bp.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
name = request.form.get("name")
email = request.form.get("email")
password = request.form.get("password")
user = UserService.register_user(name, email, password)
if not user:
flash("Email already exists", "danger")
return redirect(url_for("auth.register"))
flash("User registered successfully", "success")
return redirect(url_for("auth.login"))
return render_template("register.html", title="Register")

136
app/routes/dashboard.py Normal file
View File

@@ -0,0 +1,136 @@
# import matplotlib
# matplotlib.use("Agg")
# from flask import Blueprint, render_template, session, redirect, url_for
# import matplotlib.pyplot as plt
# import io
# import base64
# from app.utils.plot_utils import plot_to_base64
# from app.services.dashboard_service import DashboardService
# dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
# # dashboard_bp = Blueprint("dashboard", __name__)
# # charts
# # def plot_to_base64():
# # img = io.BytesIO()
# # plt.savefig(img, format="png", bbox_inches="tight")
# # plt.close()
# # img.seek(0)
# # return base64.b64encode(img.getvalue()).decode()
# # bar chart
# def bar_chart():
# categories = ["Trench", "Manhole", "Pipe Laying", "Restoration"]
# values = [120, 80, 150, 60]
# plt.figure()
# plt.bar(categories, values)
# plt.title("Work Category Report")
# plt.xlabel("test Category")
# plt.ylabel("test Quantity")
# return plot_to_base64(plt)
# # Pie chart
# def pie_chart():
# labels = ["Completed", "In Progress", "Pending"]
# sizes = [55, 20, 25]
# plt.figure()
# plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=140)
# plt.title("Project Status")
# return plot_to_base64(plt)
# # Histogram chart
# def histogram_chart():
# daily_work = [5, 10, 15, 20, 20, 25, 30, 35, 40, 45, 50]
# plt.figure()
# plt.hist(daily_work, bins=5)
# plt.title("Daily Work Distribution")
# plt.xlabel("Work Units")
# plt.ylabel("Frequency")
# return plot_to_base64(plt)
# # Dashboaed page
# @dashboard_bp.route("/")
# def dashboard():
# if not session.get("user_id"):
# return redirect(url_for("auth.login"))
# return render_template(
# "dashboard.html",
# title="Dashboard",
# bar_chart=bar_chart(),
# pie_chart=pie_chart(),
# histogram=histogram_chart()
# )
# # subcontractor dashboard
# @dashboard_bp.route("/subcontractor_dashboard", methods=["GET", "POST"])
# def subcontractor_dashboard():
# if not session.get("user_id"):
# return redirect(url_for("auth.login"))
# tr_dash = DashboardService().bar_chart_of_tr_ex
# return render_template(
# "subcontractor_dashboard.html",
# title="Dashboard",
# bar_chart=tr_dash
# )
from flask import Blueprint, render_template, session, redirect, url_for, jsonify
from sqlalchemy import func
from app import db
from app.models.trench_excavation_model import TrenchExcavation
from app.models.manhole_excavation_model import ManholeExcavation
from app.models.laying_model import Laying
dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard")
@dashboard_bp.route("/api/live-stats")
def live_stats():
try:
# 1. Overall Volume
t_count = TrenchExcavation.query.count()
m_count = ManholeExcavation.query.count()
l_count = Laying.query.count()
# 2. Location Distribution (Business reach)
loc_results = db.session.query(
TrenchExcavation.Location,
func.count(TrenchExcavation.id)
).group_by(TrenchExcavation.Location).all()
# 3. Work Timeline (Business productivity trend)
# Assuming your models have a 'created_at' field
timeline_results = db.session.query(
func.date(TrenchExcavation.created_at),
func.count(TrenchExcavation.id)
).group_by(func.date(TrenchExcavation.created_at)).order_by(func.date(TrenchExcavation.created_at)).all()
return jsonify({
"summary": {
"trench": t_count,
"manhole": m_count,
"laying": l_count,
"total": t_count + m_count + l_count
},
"locations": {row[0]: row[1] for row in loc_results if row[0]},
"timeline": {str(row[0]): row[1] for row in timeline_results}
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@dashboard_bp.route("/")
def dashboard():
if not session.get("user_id"):
return redirect(url_for("auth.login"))
return render_template("dashboard.html", title="Business Intelligence Dashboard")

30
app/routes/file_format.py Normal file
View File

@@ -0,0 +1,30 @@
from flask import Blueprint, render_template, send_from_directory, abort, current_app
from app.utils.helpers import login_required
import os
file_format_bp = Blueprint("file_format", __name__)
@file_format_bp .route("/file_format")
@login_required
def download_format():
return render_template("file_format.html", title="Download File Formats")
@file_format_bp .route("/file_format/download/<filename>")
@login_required
def download_excel_format(filename):
download_folder = os.path.join(
current_app.root_path, "static", "downloads/format"
)
file_path = os.path.join(download_folder, filename)
if not os.path.exists(file_path):
abort(404)
return send_from_directory(
directory=download_folder,
path=filename,
as_attachment=True
)

46
app/routes/file_import.py Normal file
View File

@@ -0,0 +1,46 @@
from flask import Blueprint, render_template, request, flash
from app.services.file_service import FileService
from app.models.subcontractor_model import Subcontractor
from app.utils.helpers import login_required
file_import_bp = Blueprint("file_import", __name__, url_prefix="/file")
# this is contractractor immport routes
@file_import_bp.route("/import_Subcontractor", methods=["GET", "POST"])
@login_required
def import_file():
subcontractors = Subcontractor.query.all()
if request.method == "POST":
file = request.files.get("file")
subcontractor_id = request.form.get("subcontractor_id")
RA_Bill_No = request.form.get("RA_Bill_No")
service = FileService()
success, msg = service.handle_file_upload(file, subcontractor_id, RA_Bill_No)
flash(msg, "success" if success else "danger")
return render_template(
"file_import_subcontractor.html",
title="Sub-cont. File Import",
subcontractors=subcontractors
)
# this is client import routes
@file_import_bp.route("/import_client", methods=["GET", "POST"])
@login_required
def client_import_file():
subcontractors = Subcontractor.query.all()
if request.method == "POST":
file = request.files.get("file")
RA_Bill_No = request.form.get("RA_Bill_No")
service = FileService()
success, msg = service.handle_client_file_upload(file, RA_Bill_No)
flash(msg, "success" if success else "danger")
return render_template("file_import_client.html", title="Client File Import", subcontractors=subcontractors)

214
app/routes/file_report.py Normal file
View File

@@ -0,0 +1,214 @@
import pandas as pd
import io
from flask import Blueprint, render_template, request, send_file, flash
from app.utils.helpers import login_required
from app.models.subcontractor_model import Subcontractor
from app.models.manhole_excavation_model import ManholeExcavation
from app.models.trench_excavation_model import TrenchExcavation
from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber
from app.models.laying_model import Laying
from app.models.mh_ex_client_model import ManholeExcavationClient
from app.models.tr_ex_client_model import TrenchExcavationClient
from app.models.mh_dc_client_model import ManholeDomesticChamberClient
from app.models.laying_client_model import LayingClient
# --- BLUEPRINT DEFINITION ---
file_report_bp = Blueprint("file_report", __name__, url_prefix="/file")
# --- Client class ---
class ClientBill:
def __init__(self):
self.df_tr = pd.DataFrame()
self.df_mh = pd.DataFrame()
self.df_dc = pd.DataFrame()
self.df_laying = pd.DataFrame()
def Fetch(self, RA_Bill_No):
trench = TrenchExcavationClient.query.filter_by(RA_Bill_No=RA_Bill_No).all()
mh = ManholeExcavationClient.query.filter_by(RA_Bill_No=RA_Bill_No).all()
dc = ManholeDomesticChamberClient.query.filter_by(RA_Bill_No=RA_Bill_No).all()
lay = LayingClient.query.filter_by(RA_Bill_No=RA_Bill_No).all()
self.df_tr = pd.DataFrame([c.serialize() for c in trench])
self.df_mh = pd.DataFrame([c.serialize() for c in mh])
self.df_dc = pd.DataFrame([c.serialize() for c in dc])
self.df_laying = pd.DataFrame([c.serialize() for c in lay])
drop_cols = ["id", "created_at", "_sa_instance_state"]
for df in [self.df_tr, self.df_mh, self.df_dc, self.df_laying]:
if not df.empty:
df.drop(columns=drop_cols, errors="ignore", inplace=True)
# --- Subcontractor class ---
class SubcontractorBill:
def __init__(self):
self.df_tr = pd.DataFrame()
self.df_mh = pd.DataFrame()
self.df_dc = pd.DataFrame()
self.df_laying = pd.DataFrame()
def Fetch(self, RA_Bill_No=None, subcontractor_id=None):
filters = {}
if subcontractor_id:
filters["subcontractor_id"] = subcontractor_id
if RA_Bill_No:
filters["RA_Bill_No"] = RA_Bill_No
trench = TrenchExcavation.query.filter_by(**filters).all()
mh = ManholeExcavation.query.filter_by(**filters).all()
dc = ManholeDomesticChamber.query.filter_by(**filters).all()
lay = Laying.query.filter_by(**filters).all()
self.df_tr = pd.DataFrame([c.serialize() for c in trench])
self.df_mh = pd.DataFrame([c.serialize() for c in mh])
self.df_dc = pd.DataFrame([c.serialize() for c in dc])
self.df_laying = pd.DataFrame([c.serialize() for c in lay])
drop_cols = ["id", "created_at", "_sa_instance_state"]
for df in [self.df_tr, self.df_mh, self.df_dc, self.df_laying]:
if not df.empty:
df.drop(columns=drop_cols, errors="ignore", inplace=True)
# --- subcontractor report only ---
@file_report_bp.route("/Subcontractor_report", methods=["GET", "POST"])
@login_required
def report_file():
subcontractors = Subcontractor.query.all()
tables = None
selected_sc_id = None
ra_bill_no = None
download_all = False
if request.method == "POST":
subcontractor_id = request.form.get("subcontractor_id")
ra_bill_no = request.form.get("ra_bill_no")
download_all = request.form.get("download_all") == "true"
action = request.form.get("action")
if not subcontractor_id:
flash("Please select a subcontractor.", "danger")
return render_template("subcontractor_report.html", subcontractors=subcontractors)
subcontractor = Subcontractor.query.get(subcontractor_id)
bill_gen = SubcontractorBill()
if download_all:
bill_gen.Fetch(subcontractor_id=subcontractor_id)
file_name = f"{subcontractor.subcontractor_name}_ALL_BILLS.xlsx"
else:
if not ra_bill_no:
flash("Please enter an RA Bill Number.", "danger")
return render_template("subcontractor_report.html", subcontractors=subcontractors)
bill_gen.Fetch(RA_Bill_No=ra_bill_no, subcontractor_id=subcontractor_id)
file_name = f"{subcontractor.subcontractor_name}_RA_{ra_bill_no}_Report.xlsx"
if bill_gen.df_tr.empty and bill_gen.df_mh.empty and bill_gen.df_dc.empty:
flash("No data found for this selection.", "warning")
return render_template("subcontractor_report.html", subcontractors=subcontractors)
# If download is clicked, return file immediately
if action == "download":
output = io.BytesIO()
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
bill_gen.df_tr.to_excel(writer, index=False, sheet_name="Tr.Ex.")
bill_gen.df_mh.to_excel(writer, index=False, sheet_name="MH.Ex.")
bill_gen.df_dc.to_excel(writer, index=False, sheet_name="MH & DC")
bill_gen.df_laying.to_excel(writer, index=False, sheet_name="Laying")
output.seek(0)
return send_file(output, download_name=file_name, as_attachment=True)
# We add bootstrap classes directly to the pandas output
table_classes = "table table-bordered table-striped table-hover table-sm mb-0"
tables = {
"tr": bill_gen.df_tr.to_html(classes=table_classes, index=False),
"mh": bill_gen.df_mh.to_html(classes=table_classes, index=False),
"dc": bill_gen.df_dc.to_html(classes=table_classes, index=False),
"laying": bill_gen.df_laying.to_html(classes=table_classes, index=False)
}
selected_sc_id = subcontractor_id
return render_template(
"subcontractor_report.html",
subcontractors=subcontractors,
tables=tables,
selected_sc_id=selected_sc_id,
ra_bill_no=ra_bill_no,
download_all=download_all
)
# --- client report only ---
@file_report_bp.route("/client_report", methods=["GET", "POST"])
@login_required
def client_vs_all_subcontractor():
tables = {"tr": None, "mh": None, "dc": None}
ra_val = ""
if request.method == "POST":
RA_Bill_No = request.form.get("RA_Bill_No")
ra_val = RA_Bill_No
if not RA_Bill_No:
flash("Please enter RA Bill No.", "danger")
return render_template("client_report.html", tables=tables, ra_val=ra_val)
clientBill = ClientBill()
clientBill.Fetch(RA_Bill_No=RA_Bill_No)
contractorBill = SubcontractorBill()
contractorBill.Fetch(RA_Bill_No=RA_Bill_No)
# --- SAFETY CHECK: Verify data exists before merging ---
if clientBill.df_tr.empty and clientBill.df_mh.empty:
flash(f"No Client records found for RA Bill {RA_Bill_No}", "warning")
return render_template("client_report.html", tables=tables, ra_val=ra_val)
qty_cols = [...] # (Keep your existing list)
mh_dc_qty_cols = [...] # (Keep your existing list)
mh_lay_qty_cols =[...]
def aggregate_df(df, group_cols, sum_cols):
if df.empty:
# Create an empty DF with the correct columns to avoid Merge/Key Errors
return pd.DataFrame(columns=group_cols + sum_cols)
existing_cols = [c for c in sum_cols if c in df.columns]
# Ensure group_cols exist in the DF
for col in group_cols:
if col not in df.columns:
df[col] = "N/A" # Fill missing join keys
return df.groupby(group_cols, as_index=False)[existing_cols].sum()
# Aggregate data
df_sub_tr_grp = aggregate_df(contractorBill.df_tr, ["Location", "MH_NO"], qty_cols)
df_sub_mh_grp = aggregate_df(contractorBill.df_mh, ["Location", "MH_NO"], qty_cols)
df_sub_dc_grp = aggregate_df(contractorBill.df_dc, ["Location", "MH_NO"], mh_dc_qty_cols)
df_sub_lay_grp = aggregate_df(contractorBill.df_dc, ["Location", "MH_NO"], mh_lay_qty_cols)
# --- FINAL MERGE LOGIC ---
# We check if "Location" exists in the client data. If not, we add it to prevent the KeyError.
for df_client in [clientBill.df_tr, clientBill.df_mh, clientBill.df_dc, clientBill.df_laying ]:
if not df_client.empty and "Location" not in df_client.columns:
df_client["Location"] = "Unknown"
try:
df_tr_cmp = clientBill.df_tr.merge(df_sub_tr_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub"))
df_mh_cmp = clientBill.df_mh.merge(df_sub_mh_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub"))
df_dc_cmp = clientBill.df_dc.merge(df_sub_dc_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub"))
df_lay_cmp = clientBill.df_laying.merge(df_sub_lay_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub"))
except KeyError as e:
flash(f"Merge Error: Missing column {str(e)}. Check if 'Location' is defined in your database models.", "danger")
return render_template("client_report.html", tables=tables, ra_val=ra_val)
# Convert to HTML for preview
tables["tr"] = df_tr_cmp.to_html(classes='table table-striped table-hover table-sm', index=False)
tables["mh"] = df_mh_cmp.to_html(classes='table table-striped table-hover table-sm', index=False)
tables["dc"] = df_dc_cmp.to_html(classes='table table-striped table-hover table-sm', index=False)
tables["laying"] = df_lay_cmp.to_html(classes='table table-striped table-hover table-sm', index=False)
return render_template("client_report.html", tables=tables, ra_val=ra_val)

View File

@@ -0,0 +1,352 @@
from flask import Blueprint, render_template, request, send_file, flash
from collections import defaultdict
import pandas as pd
import io
from app.models.subcontractor_model import Subcontractor
from app.models.trench_excavation_model import TrenchExcavation
from app.models.manhole_excavation_model import ManholeExcavation
from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber
from app.models.laying_model import Laying
from app.models.tr_ex_client_model import TrenchExcavationClient
from app.models.mh_ex_client_model import ManholeExcavationClient
from app.models.mh_dc_client_model import ManholeDomesticChamberClient
from app.models.laying_client_model import LayingClient
from app.utils.helpers import login_required
import re
generate_report_bp = Blueprint("generate_report", __name__, url_prefix="/report")
# sum field of pipe laying (pipe_150_mm)
PIPE_MM_PATTERN = re.compile(r"^pipe_\d+_mm$")
# sum fields of MH dc (d_0_to_0_75)
D_RANGE_PATTERN = re.compile( r"^d_\d+(?:_\d+)?_to_\d+(?:_\d+)?$")
# NORMALIZER
def normalize_key(value):
if value is None:
return None
return str(value).strip().upper()
# HEADER FORMATTER
def format_header(header):
if "-" in header:
prefix, rest = header.split("-", 1)
prefix = prefix.title()
else:
prefix, rest = None, header
parts = rest.split("_")
result = []
i = 0
while i < len(parts):
if i + 1 < len(parts) and parts[i].isdigit() and parts[i + 1].isdigit():
result.append(f"{parts[i]}.{parts[i + 1]}")
i += 2
else:
result.append(parts[i].title())
i += 1
final_text = " ".join(result)
return f"{prefix}-{final_text}" if prefix else final_text
# LOOKUP CREATOR
def make_lookup(rows, key_field):
lookup = {}
for r in rows:
location = normalize_key(r.get("Location"))
key_val = normalize_key(r.get(key_field))
if location and key_val:
lookup.setdefault((location, key_val), []).append(r)
return lookup
# COMPARISON BUILDER
def build_comparison(client_rows, contractor_rows, key_field):
contractor_lookup = make_lookup(contractor_rows, key_field)
output = []
used_index = defaultdict(int) # 🔥 THIS FIXES YOUR ISSUE
for c in client_rows:
client_location = normalize_key(c.get("Location"))
client_key = normalize_key(c.get(key_field))
if not client_location or not client_key:
continue
subs = contractor_lookup.get((client_location, client_key))
if not subs:
continue
idx = used_index[(client_location, client_key)]
# ❗ If subcontractor rows are exhausted, skip
if idx >= len(subs):
continue
s = subs[idx] # ✅ take NEXT subcontractor row
used_index[(client_location, client_key)] += 1
# ---- totals ----
client_total = sum(
float(v or 0)
for k, v in c.items()
if k.endswith("_total")
or D_RANGE_PATTERN.match(k)
or PIPE_MM_PATTERN.match(k)
)
sub_total = sum(
float(v or 0)
for k, v in s.items()
if k.endswith("_total")
or D_RANGE_PATTERN.match(k)
or PIPE_MM_PATTERN.match(k)
)
row = {
"Location": client_location,
key_field.replace("_", " "): client_key
}
for k, v in c.items():
if k not in ["id", "created_at"]:
row[f"Client-{k}"] = v
row["Client-Total"] = round(client_total, 2)
row[" "] = ""
for k, v in s.items():
if k not in ["id", "created_at", "subcontractor_id"]:
row[f"Subcontractor-{k}"] = v
row["Subcontractor-Total"] = round(sub_total, 2)
row["Diff"] = round(client_total - sub_total, 2)
output.append(row)
df = pd.DataFrame(output)
df.columns = [format_header(col) for col in df.columns]
return df
# EXCEL SHEET WRITER
def write_sheet(writer, df, sheet_name, subcontractor_name):
workbook = writer.book
df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=3)
ws = writer.sheets[sheet_name]
title_fmt = workbook.add_format({"bold": True, "font_size": 14})
client_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#B6DAED"})
sub_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#F3A081"})
total_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#F7D261"})
diff_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#82DD49"})
default_header_fmt = workbook.add_format({"bold": True,"border": 1,"bg_color": "#E7E6E6","align": "center","valign": "vcenter"})
ws.merge_range(
0, 0, 0, len(df.columns) - 1,
"CLIENT vs SUBCONTRACTOR",
title_fmt
)
ws.merge_range(
1, 0, 1, len(df.columns) - 1,
f"Subcontractor Name - {subcontractor_name}",
title_fmt
)
for col_num, col_name in enumerate(df.columns):
if col_name.startswith("Client-"):
ws.write(3, col_num, col_name, client_fmt)
elif col_name.startswith("Subcontractor-"):
ws.write(3, col_num, col_name, sub_fmt)
elif col_name.endswith("_total") or col_name.endswith("_total") :
ws.write(3, col_num, col_name, total_fmt)
elif col_name == "Diff":
ws.write(3, col_num, col_name, diff_fmt)
else:
ws.write(3, col_num, col_name, default_header_fmt)
ws.set_column(col_num, col_num, 20)
# REPORT ROUTE
@generate_report_bp.route("/comparison_report", methods=["GET", "POST"])
@login_required
def comparison_report():
subcontractors = Subcontractor.query.all()
if request.method == "POST":
subcontractor_id = request.form.get("subcontractor_id")
if not subcontractor_id:
flash("Please select subcontractor", "danger")
return render_template("generate_comparison_report.html",subcontractors=subcontractors)
subcontractor = Subcontractor.query.get_or_404(subcontractor_id)
# -------- DATA --------
tr_client = [r.serialize() for r in TrenchExcavationClient.query.all()]
tr_sub = [r.serialize() for r in TrenchExcavation.query.filter_by(
subcontractor_id=subcontractor_id
).all()]
df_tr = build_comparison(tr_client, tr_sub, "MH_NO")
mh_client = [r.serialize() for r in ManholeExcavationClient.query.all()]
mh_sub = [r.serialize() for r in ManholeExcavation.query.filter_by(
subcontractor_id=subcontractor_id
).all()]
df_mh = build_comparison(mh_client, mh_sub, "MH_NO")
dc_client = [r.serialize() for r in ManholeDomesticChamberClient.query.all()]
dc_sub = [r.serialize() for r in ManholeDomesticChamber.query.filter_by(
subcontractor_id=subcontractor_id
).all()]
df_dc = build_comparison(dc_client, dc_sub, "MH_NO")
# df_dc = build_comparison_mh_dc(dc_client, dc_sub, "MH_NO")
lay_client = [r.serialize() for r in LayingClient.query.all()]
lay_sub = [r.serialize() for r in Laying.query.filter_by(
subcontractor_id=subcontractor_id
).all()]
df_lay = build_comparison(lay_client, lay_sub, "MH_NO")
# df_lay = build_comparison_laying(lay_client, lay_sub, "MH_NO")
# -------- EXCEL --------
output = io.BytesIO()
filename = f"{subcontractor.subcontractor_name}_Comparison_Report.xlsx"
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
write_sheet(writer, df_tr, "Tr.Ex", subcontractor.subcontractor_name)
write_sheet(writer, df_mh, "Mh.Ex", subcontractor.subcontractor_name)
write_sheet(writer, df_dc, "MH & DC", subcontractor.subcontractor_name)
write_sheet(writer, df_lay, "Laying", subcontractor.subcontractor_name)
output.seek(0)
return send_file(
output,
as_attachment=True,
download_name=filename,
mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
return render_template("generate_comparison_report.html",subcontractors=subcontractors)
# def build_comparison_mh_dc(client_rows, contractor_rows, key_field):
# contractor_lookup = make_lookup(contractor_rows, key_field)
# mh_dc_fields = ManholeDomesticChamberClient.sum_mh_dc_fields()
# output = []
# for c in client_rows:
# loc = normalize_key(c.get("Location"))
# key = normalize_key(c.get(key_field))
# if not loc or not key:
# continue
# s = contractor_lookup.get((loc, key))
# if not s:
# continue
# client_total = sum(float(c.get(f, 0) or 0) for f in mh_dc_fields)
# sub_total = sum(float(s.get(f, 0) or 0) for f in mh_dc_fields)
# row = {
# "Location": loc,
# key_field.replace("_", " "): key
# }
# # CLIENT ALL FIELDS
# for k, v in c.items():
# if k in ["id", "created_at"]:
# continue
# row[f"Client-{k}"] = v
# row["Client-Total"] = round(client_total, 2)
# row[" "] = ""
# # SUBCONTRACTOR ALL FIELDS
# for k, v in s.items():
# if k in ["id", "created_at", "subcontractor_id"]:
# continue
# row[f"Subcontractor-{k}"] = v
# row["Subcontractor-Total"] = round(sub_total, 2)
# row["Diff"] = round(client_total - sub_total, 2)
# output.append(row)
# df = pd.DataFrame(output)
# df.columns = [format_header(col) for col in df.columns]
# return df
# def build_comparison_laying(client_rows, contractor_rows, key_field):
# contractor_lookup = make_lookup(contractor_rows, key_field)
# laying_fields = Laying.sum_laying_fields()
# output = []
# for c in client_rows:
# loc = normalize_key(c.get("Location"))
# key = normalize_key(c.get(key_field))
# if not loc or not key:
# continue
# s = contractor_lookup.get((loc, key))
# if not s:
# continue
# client_total = sum(float(c.get(f, 0) or 0) for f in laying_fields)
# sub_total = sum(float(s.get(f, 0) or 0) for f in laying_fields)
# print("--------------",key,"----------")
# print("sum -client_total ",client_total)
# print("sum -sub_total ",sub_total)
# print("Diff ---- ",client_total - sub_total)
# print("------------------------")
# row = {
# "Location": loc,
# key_field.replace("_", " "): key
# }
# # CLIENT ALL FIELDS
# for k, v in c.items():
# if k in ["id", "created_at"]:
# continue
# row[f"Client-{k}"] = v
# row["Client-Total"] = round(client_total, 2)
# row[" "] = ""
# # SUBCONTRACTOR ALL FIELDS
# for k, v in s.items():
# if k in ["id", "created_at", "subcontractor_id"]:
# continue
# row[f"Subcontractor-{k}"] = v
# row["Subcontractor-Total"] = round(sub_total, 2)
# row["Diff"] = round(client_total - sub_total, 2)
# output.append(row)
# df = pd.DataFrame(output)
# df.columns = [format_header(col) for col in df.columns]
# return df

View File

@@ -0,0 +1,191 @@
from flask import Blueprint, render_template, request, redirect, flash
from app import db
from app.models.subcontractor_model import Subcontractor
from app.utils.helpers import login_required
subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor")
# ---------------- ADD -----------------
@subcontractor_bp.route("/add")
@login_required
def add_subcontractor():
return render_template("subcontractor/add.html")
@subcontractor_bp.route("/save", methods=["POST"])
@login_required
def save_subcontractor():
# 1. Get and clean the name from the form
name = request.form.get("subcontractor_name", "").strip()
# 2. Basic validation: Ensure the name isn't empty
if not name:
flash("Subcontractor name cannot be empty.", "danger")
return redirect("/subcontractor/add")
# 3. Check if a subcontractor with this name already exists
existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first()
if existing_sub:
flash(f"Subcontractor with name '{name}' already exists!", "danger")
return redirect("/subcontractor/add")
# 4. If no duplicate is found, proceed to save
try:
subcontractor = Subcontractor(
subcontractor_name=name,
contact_person=request.form.get("contact_person"),
mobile_no=request.form.get("mobile_no"),
email_id=request.form.get("email_id"),
gst_no=request.form.get("gst_no")
)
db.session.add(subcontractor)
db.session.commit()
flash("Subcontractor added successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while saving. Please try again.", "danger")
return redirect("/subcontractor/list")
# ---------------- LIST -----------------
@subcontractor_bp.route("/list")
@login_required
def subcontractor_list():
subcontractors = Subcontractor.query.all()
return render_template("subcontractor/list.html", subcontractors=subcontractors)
# ---------------- EDIT -----------------
@subcontractor_bp.route("/edit/<int:id>")
@login_required
def edit_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
return render_template("subcontractor/edit.html", subcontractor=subcontractor)
# ---------------- UPDATE -----------------
@subcontractor_bp.route("/update/<int:id>", methods=["POST"])
@login_required
def update_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
new_name = request.form.get("subcontractor_name")
# Check if the new name is taken by someone ELSE (not this current ID)
duplicate = Subcontractor.query.filter(
Subcontractor.subcontractor_name == new_name,
Subcontractor.id != id
).first()
if duplicate:
flash("Another subcontractor already uses this name.", "danger")
return redirect(f"/subcontractor/edit/{id}")
subcontractor.subcontractor_name = new_name
db.session.commit()
flash("Subcontractor updated successfully!", "success")
return redirect("/subcontractor/list")
# ---------------- DELETE -----------------
@subcontractor_bp.route("/delete/<int:id>")
@login_required
def delete_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
db.session.delete(subcontractor)
db.session.commit()
flash("Subcontractor deleted successfully!", "success")
return redirect("/subcontractor/list")
from flask import Blueprint, render_template, request, redirect, flash
from app import db
from app.models.subcontractor_model import Subcontractor
from app.utils.helpers import login_required
subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor")
# ---------------- ADD -----------------
@subcontractor_bp.route("/add")
@login_required
def add_subcontractor():
return render_template("subcontractor/add.html")
@subcontractor_bp.route("/save", methods=["POST"])
@login_required
def save_subcontractor():
name = request.form.get("subcontractor_name", "").strip()
if not name:
flash("Subcontractor name cannot be empty.", "danger")
return redirect("/subcontractor/add")
existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first()
if existing_sub:
flash(f"Subcontractor with name '{name}' already exists!", "danger")
return redirect("/subcontractor/add")
try:
subcontractor = Subcontractor(
subcontractor_name=name,
contact_person=request.form.get("contact_person"),
mobile_no=request.form.get("mobile_no"),
email_id=request.form.get("email_id"),
gst_no=request.form.get("gst_no")
)
db.session.add(subcontractor)
db.session.commit()
flash("Subcontractor added successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while saving. Please try again.", "danger")
return redirect("/subcontractor/list")
# ---------------- LIST -----------------
@subcontractor_bp.route("/list")
@login_required
def subcontractor_list():
subcontractors = Subcontractor.query.all()
return render_template("subcontractor/list.html", subcontractors=subcontractors)
# ---------------- EDIT -----------------
@subcontractor_bp.route("/edit/<int:id>")
@login_required
def edit_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
return render_template("subcontractor/edit.html", subcontractor=subcontractor)
# ---------------- UPDATE -----------------
@subcontractor_bp.route("/update/<int:id>", methods=["POST"])
@login_required
def update_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
new_name = request.form.get("subcontractor_name")
# Check if the new name is taken by someone ELSE (not this current ID)
duplicate = Subcontractor.query.filter(
Subcontractor.subcontractor_name == new_name,
Subcontractor.id != id
).first()
if duplicate:
flash("Another subcontractor already uses this name.", "danger")
return redirect(f"/subcontractor/edit/{id}")
subcontractor.subcontractor_name = new_name
db.session.commit()
flash("Subcontractor updated successfully!", "success")
return redirect("/subcontractor/list")
# ---------------- DELETE -----------------
@subcontractor_bp.route("/delete/<int:id>")
@login_required
def delete_subcontractor(id):
subcontractor = Subcontractor.query.get_or_404(id)
db.session.delete(subcontractor)
db.session.commit()
flash("Subcontractor deleted successfully!", "success")
return redirect("/subcontractor/list")

11
app/routes/user.py Normal file
View File

@@ -0,0 +1,11 @@
from flask import Blueprint, render_template
from app.services.user_service import UserService
from app.utils.helpers import login_required
user_bp = Blueprint("user", __name__, url_prefix="/user")
@user_bp.route("/list")
@login_required
def list_users():
users = UserService.get_all_users()
return render_template("users.html", users=users, title="Users")

0
app/services/__init__.py Normal file
View File

View File

@@ -0,0 +1,49 @@
from app.config import Config
from app import db
import matplotlib
matplotlib.use("Agg")
from app.routes.dashboard import plot_to_base64
import matplotlib.pyplot as plt
import io
import base64
from app.utils.plot_utils import plot_to_base64
# Subcontractor models import
from app.models.trench_excavation_model import TrenchExcavation
# Client models import
# from app.models.tr_ex_client_model import TrenchExcavationClient
class DashboardService:
# bar chart
def bar_chart_of_tr_ex():
categories = ["Soft Murum", "Hard Murum", "Soft Rock", "Hard Rock"]
values = [120, 80, 150, 60]
tr = TrenchExcavation()
record = TrenchExcavation.query.first()
print(" RA_Bill_No::",record["RA_Bill_No"])
totals = tr.excavation_category_sums()
# print(totals["Soft_Murum_Total"])
# print(totals["Hard_Rock_Total"])
plt.figure()
plt.bar(categories, values)
plt.title("Trench Excavation Work Category Report")
plt.xlabel("Excavation category")
plt.ylabel("Quantity")
return plot_to_base64(plt)
def subcontractor_dash():
return True

View File

@@ -0,0 +1,19 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
# import mysql.connector
# from app.config import Config
# class DBService:
# def connect(self):
# return mysql.connector.connect(
# host=Config.DB_HOST,
# user=Config.DB_USER,
# password=Config.DB_PASSWORD,
# database=Config.DB_NAME
# )

View File

@@ -0,0 +1,375 @@
import os
import pandas as pd
from werkzeug.utils import secure_filename
from app.utils.file_utils import ensure_upload_folder
from app.config import Config
from app import db
# Subcontractor models import
from app.models.trench_excavation_model import TrenchExcavation
from app.models.manhole_excavation_model import ManholeExcavation
from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber
from app.models.laying_model import Laying
# Client models import
from app.models.tr_ex_client_model import TrenchExcavationClient
from app.models.mh_ex_client_model import ManholeExcavationClient
from app.models.mh_dc_client_model import ManholeDomesticChamberClient
from app.models.laying_client_model import LayingClient
class FileService:
# ---------------- COMMON HELPERS ----------------
def allowed_file(self, filename):
return ("." in filename and filename.rsplit(".", 1)[1].lower() in Config.ALLOWED_EXTENSIONS)
def normalize(self, val):
if val is None or pd.isna(val):
return None
val = str(val).strip()
if val.lower() in ["", "nan", "none", "-", ""]:
return None
return val.upper()
# ---------------- SUBCONTRACTOR FILE UPLOAD ----------------
def handle_file_upload(self, file, subcontractor_id, RA_Bill_No):
if not subcontractor_id:
return False, "Please select subcontractor."
if not RA_Bill_No:
return False, "Please Enter RA Bill No."
if not file or file.filename == "":
return False, "No file selected."
if not self.allowed_file(file.filename):
return False, "Invalid file type! Allowed: CSV, XLSX, XLS"
ensure_upload_folder()
folder = os.path.join(Config.UPLOAD_FOLDER, f"sub_{subcontractor_id}")
os.makedirs(folder, exist_ok=True)
filename = secure_filename(file.filename)
filepath = os.path.join(folder, filename)
file.save(filepath)
try:
df_tr_ex = pd.read_excel(filepath, sheet_name="Tr.Ex.", header=12, dtype={"MH No": str})
df_mh_ex = pd.read_excel(filepath, sheet_name="MH Ex.", header=12, dtype={"MH No": str})
df_mh_dc = pd.read_excel(filepath, sheet_name="MH & DC", header=11, dtype={"MH No": str})
df_laying = pd.read_excel(filepath, sheet_name="Laying", header=11, dtype={"MH No": str})
self.process_trench_excavation(df_tr_ex, subcontractor_id, RA_Bill_No)
self.process_manhole_excavation(df_mh_ex, subcontractor_id, RA_Bill_No)
self.process_manhole_domestic_chamber(df_mh_dc, subcontractor_id, RA_Bill_No)
self.process_laying(df_laying, subcontractor_id, RA_Bill_No)
return True, "SUBCONTRACTOR File uploaded successfully."
except Exception as e:
db.session.rollback()
return False, f"Import failed: {e}"
# ---------------- Trench Excavation (Subcontractor) ----------------
def process_trench_excavation(self, df, subcontractor_id, RA_Bill_No):
df.columns = (
df.columns.astype(str)
.str.strip()
.str.replace(r"[^\w]", "_", regex=True)
.str.replace("__+", "_", regex=True)
.str.strip("_")
)
df = df.dropna(how="all")
if "Location" in df.columns:
df["Location"] = df["Location"].ffill()
errors = []
for idx, row in df.iterrows():
location = self.normalize(row.get("Location"))
mh_no = self.normalize(row.get("MH_NO"))
if not location or not mh_no:
continue
# exists = TrenchExcavation.query.filter_by(
# subcontractor_id=subcontractor_id,
# RA_Bill_No=RA_Bill_No,
# Location=location,
# MH_NO=mh_no,
# ).first()
# if exists:
# errors.append(
# f"Model-Tr.Ex. (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}"
# )
# continue
record_data = {}
for col in df.columns:
if hasattr(TrenchExcavation, col):
val = row[col]
if pd.isna(val) or str(val).strip() in ["", "-", "", "nan"]:
val = None
record_data[col] = val
record = TrenchExcavation(
subcontractor_id=subcontractor_id,
RA_Bill_No=RA_Bill_No,
**record_data,
)
db.session.add(record)
if errors:
raise Exception(" | ".join(errors))
db.session.commit()
# ---------------- Manhole Excavation (Subcontractor) ----------------
def process_manhole_excavation(self, df, subcontractor_id, RA_Bill_No):
df.columns = (
df.columns.astype(str)
.str.strip()
.str.replace(r"[^\w]", "_", regex=True)
.str.replace("__+", "_", regex=True)
.str.strip("_")
)
df = df.dropna(how="all")
if "Location" in df.columns:
df["Location"] = df["Location"].ffill()
errors = []
for idx, row in df.iterrows():
location = self.normalize(row.get("Location"))
mh_no = self.normalize(row.get("MH_NO"))
if not location or not mh_no:
continue
# exists = ManholeExcavation.query.filter_by(
# subcontractor_id=subcontractor_id,
# RA_Bill_No=RA_Bill_No,
# Location=location,
# MH_NO=mh_no,
# ).first()
# if exists:
# errors.append(
# f"Model-MH Ex. (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}"
# )
# continue
record_data = {}
for col in df.columns:
if hasattr(ManholeExcavation, col):
val = row[col]
if pd.isna(val) or str(val).strip() in ["", "-", "", "nan"]:
val = None
record_data[col] = val
record = ManholeExcavation(
subcontractor_id=subcontractor_id,
RA_Bill_No=RA_Bill_No,
**record_data,
)
db.session.add(record)
if errors:
raise Exception(" | ".join(errors))
db.session.commit()
# ---------------- Manhole & Domestic Chamber (Subcontractor) ----------------
def process_manhole_domestic_chamber(self, df, subcontractor_id, RA_Bill_No):
df.columns = (
df.columns.astype(str)
.str.strip()
.str.replace(r"[^\w]", "_", regex=True)
.str.replace("__+", "_", regex=True)
.str.strip("_")
)
df = df.dropna(how="all")
if "Location" in df.columns:
df["Location"] = df["Location"].ffill()
errors = []
for idx, row in df.iterrows():
location = self.normalize(row.get("Location"))
mh_no = self.normalize(row.get("MH_NO"))
if not location or not mh_no:
continue
# exists = ManholeDomesticChamber.query.filter_by(
# subcontractor_id=subcontractor_id,
# RA_Bill_No=RA_Bill_No,
# Location=location,
# MH_NO=mh_no,
# ).first()
# if exists:
# errors.append(
# f"Model-MH & DC (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}"
# )
# continue
record_data = {}
for col in df.columns:
if hasattr(ManholeDomesticChamber, col):
val = row[col]
if pd.isna(val) or str(val).strip() in ["", "-", "", "nan"]:
val = None
record_data[col] = val
record = ManholeDomesticChamber(
subcontractor_id=subcontractor_id,
RA_Bill_No=RA_Bill_No,
**record_data,
)
db.session.add(record)
if errors:
raise Exception(" | ".join(errors))
db.session.commit()
# ---------------- Laying (Subcontractor) ----------------
def process_laying(self, df, subcontractor_id, RA_Bill_No):
df.columns = (
df.columns.astype(str)
.str.strip()
.str.replace(r"[^\w]", "_", regex=True)
.str.replace("__+", "_", regex=True)
.str.strip("_")
)
df = df.dropna(how="all")
if "Location" in df.columns:
df["Location"] = df["Location"].ffill()
errors = []
for idx, row in df.iterrows():
location = self.normalize(row.get("Location"))
mh_no = self.normalize(row.get("MH_NO"))
if not location or not mh_no:
continue
# exists = ManholeDomesticChamber.query.filter_by(
# subcontractor_id=subcontractor_id,
# RA_Bill_No=RA_Bill_No,
# Location=location,
# MH_NO=mh_no,
# ).first()
# if exists:
# errors.append(
# f"Model-MH & DC (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}"
# )
# continue
record_data = {}
for col in df.columns:
if hasattr(Laying, col):
val = row[col]
if pd.isna(val) or str(val).strip() in ["", "-", "", "nan"]:
val = None
record_data[col] = val
record = Laying(
subcontractor_id=subcontractor_id,
RA_Bill_No=RA_Bill_No,
**record_data,
)
db.session.add(record)
if errors:
raise Exception(" | ".join(errors))
db.session.commit()
# ---------------- CLIENT FILE UPLOAD ----------------
def handle_client_file_upload(self, file, RA_Bill_No):
if not RA_Bill_No:
return False, "Please Enter RA Bill No."
if not file or file.filename == "":
return False, "No file selected."
if not self.allowed_file(file.filename):
return False, "Invalid file type! Allowed: CSV, XLSX, XLS"
ensure_upload_folder()
folder = os.path.join(Config.UPLOAD_FOLDER, f"Client_Bill_{RA_Bill_No}")
os.makedirs(folder, exist_ok=True)
filename = secure_filename(file.filename)
filepath = os.path.join(folder, filename)
file.save(filepath)
try:
df_tr_ex = pd.read_excel(filepath, sheet_name="Tr.Ex.", header=4)
df_mh_ex = pd.read_excel(filepath, sheet_name="MH Ex.", header=4)
df_mh_dc = pd.read_excel(filepath, sheet_name="MH & DC", header=3)
df_lay = pd.read_excel(filepath, sheet_name="Laying & Bedding", header=3)
self.save_client_data(df_tr_ex, TrenchExcavationClient, RA_Bill_No)
self.save_client_data(df_mh_ex, ManholeExcavationClient, RA_Bill_No)
self.save_client_data(df_mh_dc, ManholeDomesticChamberClient, RA_Bill_No)
self.save_client_data(df_lay, LayingClient, RA_Bill_No)
db.session.commit()
return True, "Client file uploaded successfully."
except Exception as e:
db.session.rollback()
return False, f"Client import failed: {e}"
# ---------------- CLIENT SAVE METHOD ----------------
def save_client_data(self, df, model, RA_Bill_No):
df.columns = [str(c).strip() for c in df.columns]
if "Location" in df.columns:
df["Location"] = df["Location"].ffill()
df = df.dropna(how="all")
for idx, row in df.iterrows():
record_data = {}
for col in df.columns:
if hasattr(model, col):
val = row[col]
if pd.isna(val) or str(val).strip() in ["", "-", "", "nan"]:
val = None
record_data[col] = val
record = model(RA_Bill_No=RA_Bill_No, **record_data)
db.session.add(record)

View File

@@ -0,0 +1,27 @@
from app.models.user_model import User
from app.services.db_service import db
class UserService:
@staticmethod
def register_user(name, email, password):
if User.query.filter_by(email=email).first():
return None
user = User(name=name, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
return user
@staticmethod
def validate_login(email, password):
user = User.query.filter_by(email=email).first()
if user and user.check_password(password):
return user
return None
@staticmethod
def get_all_users():
return User.query.all()

Binary file not shown.

Binary file not shown.

BIN
app/static/images/lcepl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

217
app/templates/base.html Normal file
View File

@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title if title else "Comparison Software" }}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
</head>
<body class="bg-light">
<!-- NAVBAR -->
<!-- <nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm"> -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm fixed-top">
<div class="container-fluid">
<!-- Brand -->
<a class="navbar-brand fw-bold" href="/">
<i class="bi bi-buildings me-1"></i> LCEPL
</a>
<!-- Mobile toggle -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Menu -->
<div class="collapse navbar-collapse" id="navMenu">
<ul class="navbar-nav ms-auto align-items-lg-center">
<!-- Dashboard -->
<li class="nav-item">
<a class="nav-link" href="/dashboard">
<<<<<<< HEAD
<i class="bi bi-speedometer2 me-1"></i> Dashboard-Anish
=======
<i class="bi bi-speedometer2 me-1"></i> Dashboard - Anish
>>>>>>> de8e68aff2a986814f5958a1f2faecb61acfd502
</a>
</li>
<!-- Subcontractor Model -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<i class="bi bi-people-fill me-1"></i> Subcontractor Model
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li>
<a class="dropdown-item" href="/subcontractor/add">
<i class="bi bi-plus-circle me-2"></i> Add Subcontractor
</a>
</li>
<li>
<a class="dropdown-item" href="/subcontractor/list">
<i class="bi bi-list-ul me-2"></i> Subcontractor List
</a>
</li>
</ul>
</li>
<!-- Subcontractor File System -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<i class="bi bi-folder-fill me-1"></i>Subcontractor File System
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li>
<a class="dropdown-item" href="/file/import_Subcontractor">
<i class="bi bi-upload me-2"></i> Import File
</a>
</li>
<li>
<a class="dropdown-item" href="/file/Subcontractor_report">
<i class="bi bi-download me-2"></i> Show Reports
</a>
</li>
<li>
<a class="dropdown-item" href="/dashboard/subcontractor_dashboard">
<i class="bi bi-speedometer2 me-2"></i> Subcontractor Dashboard
</a>
</li>
</ul>
</li>
<!-- Client System -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<i class="bi bi-building me-1"></i> Client File System
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li>
<a class="dropdown-item" href="/file/import_client">
<i class="bi bi-upload me-2"></i> Import Client File
</a>
</li>
<li>
<a class="dropdown-item" href="/file/client_report">
<i class="bi bi-arrow-left-right me-2"></i> Show Report
</a>
</li>
</ul>
</li>
<!-- Reports -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<i class="bi bi-building me-1"></i> Reports
</a>
<ul class="dropdown-menu dropdown-menu-dark">
<li>
<a class="dropdown-item" href="/report/comparison_report">
<i class="bi bi-arrow-left-right me-2"></i> client vs sub-cont. Comparison Report
</a>
</li>
<!-- <li>
<a class="dropdown-item" href="/file/client_vs_subcont">
<i class="bi bi-arrow-left-right me-2"></i> Comparison Report
</a>
</li> -->
</ul>
</li>
<!-- Formats -->
<li class="nav-item">
<a class="nav-link" href="/file_format">
<i class="bi bi-file-earmark-text me-1"></i> Formats
</a>
</li>
<!-- USER DROPDOWN -->
{% if session.get("user_id") %}
<li class="nav-item dropdown ms-lg-3">
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" href="#"
data-bs-toggle="dropdown">
<i class="bi bi-person-circle fs-5"></i>
<span class="d-none d-lg-inline">
{{ session.get("user_name") }}
</span>
</a>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark shadow">
<!-- User card -->
<li class="px-3 py-3 text-center border-bottom">
<i class="bi bi-person-circle fs-1"></i>
<div class="fw-semibold mt-1">
{{ session.get("user_name") }}
</div>
<small class="text-muted">Logged in user</small>
</li>
<li>
<a class="dropdown-item" href="/dashboard">
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
</li>
<li>
<a class="dropdown-item text-warning" href="/logout">
<i class="bi bi-box-arrow-right me-2"></i> Logout
</a>
</li>
</ul>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- PAGE CONTENT -->
<div class="container-fluid vh-100 pt-5 overflow-hidden">
<!-- FLASH MESSAGES -->
<div class="container mt-3">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show">
{{ message }}
<button class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div class="overflow-auto h-100">
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,74 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid mt-4">
<h2 class="mb-4">Client File Reports</h2>
<div class="card p-4 shadow-sm mb-5">
<form method="POST">
<label class="form-label fw-bold">RA Bill No</label>
<input type="text" name="RA_Bill_No" class="form-control mb-3" value="{{ ra_val }}" required>
<div class="row">
<div class="col-md-6">
<button type="submit" name="action" value="preview" class="btn btn-secondary w-100">Preview
Data</button>
</div>
<div class="col-md-6">
<button type="submit" name="action" value="download" class="btn btn-primary w-100">Download Excel
Report</button>
</div>
</div>
</form>
</div>
{% if tables.tr or tables.mh or tables.dc or tables.laying %}
<div class="card shadow-sm p-3">
<h4 class="mb-3">Comparison Preview</h4>
<ul class="nav nav-tabs" id="reportTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active" id="tr-tab" data-bs-toggle="tab" data-bs-target="#tr"
type="button">Tr.Ex Comparison</button>
</li>
<li class="nav-item">
<button class="nav-link" id="mh-tab" data-bs-toggle="tab" data-bs-target="#mh" type="button">Mh.Ex
Comparison</button>
</li>
<li class="nav-item">
<button class="nav-link" id="dc-tab" data-bs-toggle="tab" data-bs-target="#dc" type="button">MH & DC
Comparison</button>
</li>
<li class="nav-item">
<button class="nav-link" id="laying-tab" data-bs-toggle="tab" data-bs-target="#laying"
type="button">Laying
& Bedding Comparison</button>
</ul>
<div class="tab-content mt-3" id="reportTabsContent">
<div class="tab-pane fade show active" id="tr" role="tabpanel">
<div class="table-responsive" style="max-height: 500px;">
{{ tables.tr|safe }}
</div>
</div>
<div class="tab-pane fade" id="mh" role="tabpanel">
<div class="table-responsive" style="max-height: 500px;">
{{ tables.mh|safe }}
</div>
</div>
<div class="tab-pane fade" id="dc" role="tabpanel">
<div class="table-responsive" style="max-height: 500px;">
{{ tables.dc|safe }}
</div>
</div>
<div class="tab-pane fade" id="laying" role="tabpanel">
<div class="table-responsive" style="max-height: 500px;">
{{ tables.laying|safe }}
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,118 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid px-2 px-md-4">
<h4 class="mb-3 text-center text-md-start">Comparison Software Solapur (UGD) - Live Dashboard</h4>
<div class="row g-3 mb-4">
<div class="col-12 col-md-4">
<div class="card text-white bg-primary shadow h-100">
<div class="card-body text-center text-md-start">
<h6>Trenching Units</h6>
<h3 class="fw-bold" id="card-trench">0</h3>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="card text-white bg-success shadow h-100">
<div class="card-body text-center text-md-start">
<h6>Manhole Units</h6>
<h3 class="fw-bold" id="card-manhole">0</h3>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="card text-dark bg-warning shadow h-100">
<div class="card-body text-center text-md-start">
<h6>Laying Units</h6>
<h3 class="fw-bold" id="card-laying">0</h3>
</div>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-12 col-md-6">
<div class="card shadow-sm h-100">
<div class="card-header bg-dark text-white">Live Category Bar Chart</div>
<div class="card-body">
<canvas id="liveBarChart" style="max-height:300px;"></canvas>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card shadow-sm h-100">
<div class="card-header bg-dark text-white">Location Distribution Pie Chart</div>
<div class="card-body">
<canvas id="livePieChart" style="max-height:300px;"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 2. Initialize the Bar Chart
const barCtx = document.getElementById('liveBarChart').getContext('2d');
let liveBarChart = new Chart(barCtx, {
type: 'bar',
data: {
labels: ['Trenching', 'Manholes', 'Laying'],
datasets: [{
label: 'Units Completed',
data: [0, 0, 0],
backgroundColor: ['#0d6efd', '#198754', '#ffc107']
}]
},
options: { responsive: true, maintainAspectRatio: false }
});
// 3. Initialize the Pie Chart
const pieCtx = document.getElementById('livePieChart').getContext('2d');
let livePieChart = new Chart(pieCtx, {
type: 'pie',
data: {
labels: [], // Will be filled from SQL
datasets: [{
data: [],
backgroundColor: ['#0d6efd', '#198754', '#ffc107', '#6f42c1', '#fd7e14']
}]
},
options: { responsive: true, maintainAspectRatio: false }
});
// 4. Function to Fetch Live Data from your Python API
function fetchLiveData() {
fetch('/dashboard/api/live-stats') // This matches the route we created in the "Kitchen"
.then(response => response.json())
.then(data => {
// Update the Summary Cards
document.getElementById('card-trench').innerText = data.summary.trench;
document.getElementById('card-manhole').innerText = data.summary.manhole;
document.getElementById('card-laying').innerText = data.summary.laying;
// Update Bar Chart
liveBarChart.data.datasets[0].data = [
data.summary.trench,
data.summary.manhole,
data.summary.laying
];
liveBarChart.update();
// Update Pie Chart (Location stats)
livePieChart.data.labels = Object.keys(data.locations);
livePieChart.data.datasets[0].data = Object.values(data.locations);
livePieChart.update();
})
.catch(err => console.error("Error fetching live data:", err));
}
// 5. Check for updates every 10 seconds (Real-time effect)
setInterval(fetchLiveData, 10000);
fetchLiveData(); // Load immediately on page open
</script>
{% endblock %}

View File

@@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block content %}
<h2 class="mb-4">Download File Formats</h2>
<div class="row g-4">
<!-- Subcontractor Format -->
<div class="col-md-4">
<div class="card text-center shadow-sm h-100">
<div class="card-body d-flex flex-column justify-content-center">
<div class="mb-3 fs-1 text-primary">
⬇️
</div>
<h5 class="card-title">Subcontractor Upload Format</h5>
<p class="text-muted mb-2">Excel (.xlsx)</p>
<p class="small text-secondary">File size: 245 KB</p>
<a href="{{ url_for('file_format.download_excel_format', filename='subcontractor_format.xlsx') }}"
class="btn btn-outline-primary mt-auto">
Download
</a>
</div>
</div>
</div>
<!-- Client Format -->
<div class="col-md-4">
<div class="card text-center shadow-sm h-100">
<div class="card-body d-flex flex-column justify-content-center">
<div class="mb-3 fs-1 text-success">
⬇️
</div>
<h5 class="card-title">Client Upload Format</h5>
<p class="text-muted mb-2">Excel (.xlsx)</p>
<p class="small text-secondary">File size: 310 KB</p>
<a href="{{ url_for('file_format.download_excel_format', filename='client_format.xlsx') }}"
class="btn btn-outline-success mt-auto">
Download
</a>
</div>
</div>
</div>
<!-- MH & DC Format -->
<!-- <div class="col-md-4">
<div class="card text-center shadow-sm h-100">
<div class="card-body d-flex flex-column justify-content-center">
<div class="mb-3 fs-1 text-warning">
⬇️
</div>
<h5 class="card-title">Manhole & DC Format</h5>
<p class="text-muted mb-2">Excel (.xlsx)</p>
<p class="small text-secondary">File size: 190 KB</p>
<a href="{{ url_for('file_format.download_excel_format', filename='mh_dc_format.xlsx') }}"
class="btn btn-outline-warning mt-auto">
Download
</a>
</div>
</div>
</div> -->
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block content %}
<h2 class="mb-4">Client File Import</h2>
<div class="card p-4 shadow-sm">
<form method="POST" enctype="multipart/form-data">
<!-- 1. SELECT SUBCONTRACTOR -->
<!-- <label class="form-label">Select Subcontractor vs Client</label>
<select name="subcontractor_id" id="subcontractor_id" class="form-select mb-3" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractors %}
<option value="{{ sc.id }}">{{ sc.subcontractor_name }}</option>
{% endfor %}
</select> -->
<!-- 2. FILE TYPE (MODEL NAME) -->
<!-- <label class="form-label">Select File Type</label>
<select name="file_type" id="file_type" class="form-select mb-3" required>
<option value="">-- Select File Type --</option>
<option value="">Sheet</option>
<option value="tr_ex_client">Tr. Ex</option>
<option value="mh_ex_client">Mh. Ex </option>
<option value="mh_dc_client">MH & DC </option>
<option value="">Laying Sheet</option>
</select> -->
<label class="form-label">RA Bill No</label>
<input type="text" name="RA_Bill_No" class="form-control mb-3" required>
<!-- 3. FILE UPLOAD -->
<label class="form-label">Choose File</label>
<input type="file" name="file" class="form-control mb-3" required>
<button class="btn btn-primary w-100">Upload</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block content %}
<h2 class="mb-4">Sub-Contractor File Import</h2>
<div class="card p-4 shadow-sm">
<form method="POST" enctype="multipart/form-data">
<!-- 1. SELECT SUBCONTRACTOR -->
<label class="form-label">Select Subcontractor</label>
<select name="subcontractor_id" id="subcontractor_id" class="form-select mb-3" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractors %}
<option value="{{ sc.id }}">{{ sc.subcontractor_name }}</option>
{% endfor %}
</select>
<!-- 2. RA bill no -->
<label class="form-label">RA Bill No</label>
<input type="text" name="RA_Bill_No" class="form-control mb-3" required>
<!-- 3. FILE UPLOAD -->
<label class="form-label">Choose File</label>
<input type="file" name="file" class="form-control mb-3" required>
<button class="btn btn-primary w-100">Upload</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<h2 class="mb-4">Subcontractor vs Client Comparison</h2>
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="card p-4 shadow-sm">
<form method="POST">
<!-- SELECT SUBCONTRACTOR -->
<label class="form-label fw-semibold">Select Subcontractor</label>
<select name="subcontractor_id" id="subcontractor_id" class="form-select mb-3" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractors %}
<option value="{{ sc.id }}">{{ sc.subcontractor_name }}</option>
{% endfor %}
</select>
<button class="btn btn-primary w-100">Generate Report</button>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block content %}
<h2 class="mb-4">Users List</h2>
<div class="card p-4 shadow-sm">
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

82
app/templates/login.html Normal file
View File

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LCEPL | Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container-fluid vh-100">
<div class="row h-100 justify-content-center align-items-center">
<!-- Increased column width -->
<div class="col-12 col-sm-10 col-md-8 col-lg-5 col-xl-4">
<div class="card shadow-lg border-0">
<!-- Increased padding -->
<div class="card-body p-5">
<!-- Branding -->
<div class="text-center mb-4">
<img src="{{ url_for('static', filename='images/lcepl.png') }}" alt="LCEPL Logo"
class="img-fluid mb-3" style="max-height:80px;">
<h4 class="fw-bold mb-1">
Laxmi Civil Engineering Services Pvt Ltd
</h4>
<p class="text-muted mb-0">
Data Comparison Software Solapur(UGD)
</p>
</div>
<!-- Flash messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Login Form -->
<form method="POST">
<div class="mb-4">
<label class="form-label fw-semibold">User Name</label>
<input type="email" name="email" class="form-control " placeholder="Enter email"
required>
</div>
<div class="mb-4">
<label class="form-label fw-semibold">Password</label>
<input type="password" name="password" class="form-control" placeholder="Enter password"
required>
</div>
<button class="btn btn-success btn-lg w-100">
Login
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block content %}
<h2>Register User</h2>
<div class="card p-4 shadow-sm">
<form method="POST">
<label>Name</label>
<input class="form-control mb-3" name="name" required>
<label>Email</label>
<input type="email" class="form-control mb-3" name="email" required>
<label>Password</label>
<input type="password" class="form-control mb-3" name="password" required>
<button class="btn btn-primary">Register</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<div class="card shadow-sm p-4">
<h4 class="mb-3">Add New Subcontractor</h4>
<form action="/subcontractor/save" method="POST">
<div class="mb-3">
<label class="form-label">Subcontractor Name</label>
<input type="text" class="form-control" name="subcontractor_name" required>
</div>
<div class="mb-3">
<label class="form-label">Contact Person Name</label>
<input type="text" class="form-control" name="contact_person">
</div>
<div class="mb-3">
<label class="form-label">Mobile</label>
<input type="text" class="form-control" name="mobile_no">
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email_id">
</div>
<div class="mb-3">
<label class="form-label">GST No</label>
<input type="text" class="form-control" name="gst_no">
</div>
<button class="btn btn-success">Save</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block content %}
<div class="card shadow-sm p-4">
<h4 class="mb-3">Edit Subcontractor</h4>
<form action="/subcontractor/update/{{ subcontractor.id }}" method="POST">
<div class="mb-3">
<label class="form-label">Subcontractor Name</label>
<input type="text" class="form-control" name="subcontractor_name"
value="{{ subcontractor.subcontractor_name }}" required>
</div>
<div class="mb-3">
<label class="form-label">Contact Person</label>
<input type="text" class="form-control" name="contact_person" value="{{ subcontractor.contact_person }}">
</div>
<div class="mb-3">
<label class="form-label">Mobile</label>
<input type="text" class="form-control" name="mobile_no" value="{{ subcontractor.mobile_no }}">
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email_id" value="{{ subcontractor.email_id }}">
</div>
<div class="mb-3">
<label class="form-label">GST No</label>
<input type="text" class="form-control" name="gst_no" value="{{ subcontractor.gst_no }}">
</div>
<button class="btn btn-success">Update</button>
<a href="/subcontractor/list" class="btn btn-secondary">Back</a>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,91 @@
{% extends "base.html" %}
{% block content %}
<div class="card shadow-sm p-3 p-md-4">
<!-- Header -->
<div class="row mb-3 align-items-center">
<div class="col-12 col-md-6 ">
<h4 class="mb-2 mb-md-0 text-center text-md-start">
Subcontractor List
</h4>
</div>
<div class="col-12 col-md-3 text-center text-md-end">
<a href="/subcontractor/add" class="btn btn-success w-100 w-md-auto">
Add Subcontractor
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped align-middle text-nowrap">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th class="d-none d-md-table-cell">Mobile</th>
<th class="d-none d-lg-table-cell">Email</th>
<th class="d-none d-lg-table-cell">GST No</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
{% for s in subcontractors %}
<tr>
<td>{{ s.id }}</td>
<td class="fw-semibold text-wrap">
{{ s.subcontractor_name }}
</td>
<td class="d-none d-md-table-cell">
{{ s.mobile_no }}
</td>
<td class="d-none d-lg-table-cell">
{{ s.email_id }}
</td>
<td class="d-none d-lg-table-cell">
{{ s.gst_no }}
</td>
<!-- Action Buttons -->
<td class="text-center">
<div class="d-flex flex-column gap-1">
<a href="/subcontractor/edit/{{ s.id }}" class="btn btn-sm btn-warning">
Edit
</a>
<a href="/subcontractor/delete/{{ s.id }}" class="btn btn-sm btn-danger"
onclick="return confirm('Are you sure?')">
Delete
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
<!-- TOTAL ROW -->
<tfoot>
<tr class="table-secondary fw-bold">
<td colspan="2" class="text-end"> Total Subcontractors</td>
<td class="d-none d-lg-table-cell">-</td>
<td class="d-none d-lg-table-cell">-</td>
<td class="d-none d-lg-table-cell">-</td>
<td class="text-center">{{ subcontractors|length }}</td>
</tr>
</tfoot>
</table>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid px-2 px-md-4">
<h4 class="mb-3 text-center text-md-start">Subcontractor Dashboard </h4>
<!-- Charts -->
<div class="row g-3">
<!-- Bar Chart -->
<div class="col-12 col-md-6">
<div class="card shadow-sm h-100">
<div class="card-header bg-dark text-white text-center text-md-start">
Work Category Bar Chart
</div>
<div class="card-body text-center">
<img src="data:image/png;base64,{{ bar_chart }}" class="img-fluid" style="max-height:300px;">
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,142 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-4">
<h2 class="text-center mb-4">Generate Subcontractor Report</h2>
<!-- FORM -->
<div class="card shadow-sm p-3 p-md-4 mx-auto" style="max-width:600px;">
<form method="POST">
<div class="mb-3">
<label class="form-label fw-semibold">Select Subcontractor</label>
<select name="subcontractor_id" class="form-select" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractors %}
<option value="{{ sc.id }}" {% if selected_sc_id==sc.id|string %}selected{% endif %}>
{{ sc.subcontractor_name }}
</option>
{% endfor %}
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="downloadAllSwitch" name="download_all" value="true"
{% if download_all %}checked{% endif %}>
<label class="form-check-label fw-bold text-primary">
Download All RA Bills
</label>
</div>
<div class="mb-4" id="ra_bill_container">
<label class="form-label fw-semibold">RA Bill Number</label>
<input type="text" name="ra_bill_no" id="ra_bill_input" class="form-control"
value="{{ ra_bill_no or '' }}">
</div>
<div class="row g-2">
<div class="col-12 col-md-6">
<button class="btn btn-outline-primary w-100" name="action" value="preview">
Preview Data
</button>
</div>
<div class="col-12 col-md-6">
<button class="btn btn-primary w-100" name="action" value="download">
Download Excel
</button>
</div>
</div>
</form>
</div>
{% if tables %}
<!-- REPORT PREVIEW -->
<div class="mt-5">
<h3 class="text-center mb-3">Report Preview</h3>
<div class="card shadow-sm">
<!-- MOBILE SCROLLABLE TABS -->
<div class="card-header p-0">
<ul class="nav nav-tabs flex-nowrap overflow-auto">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tr">
Trench Excavation
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#mh">
Manhole Excavation
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#dc">
Manhole & Domestic Chambers
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#laying">
Pipe Laying
</button>
</li>
</ul>
</div>
<!-- TAB CONTENT -->
<div class="card-body tab-content">
<div class="tab-pane fade show active" id="tr">
<div class="table-responsive overflow-auto">
{{ tables.tr | safe }}
</div>
</div>
<div class="tab-pane fade" id="mh">
<div class="table-responsive overflow-auto">
{{ tables.mh | safe }}
</div>
</div>
<div class="tab-pane fade" id="dc">
<div class="table-responsive overflow-auto">
{{ tables.dc | safe }}
</div>
</div>
<div class="tab-pane fade" id="laying">
<div class="table-responsive overflow-auto">
{{ tables.laying | safe }}
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- BOOTSTRAP-ONLY JS -->
<script>
function toggleRAInput() {
const checkbox = document.getElementById("downloadAllSwitch");
const input = document.getElementById("ra_bill_input");
const container = document.getElementById("ra_bill_container");
if (checkbox.checked) {
input.value = "";
input.disabled = true;
container.classList.add("opacity-50");
} else {
input.disabled = false;
container.classList.remove("opacity-50");
}
}
document.addEventListener("DOMContentLoaded", toggleRAInput);
document.getElementById("downloadAllSwitch").addEventListener("change", toggleRAInput);
</script>
{% endblock %}

28
app/templates/users.htm Normal file
View File

@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block content %}
<h2 class="mb-4">Users List</h2>
<div class="card p-4 shadow-sm">
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

6
app/utils/file_utils.py Normal file
View File

@@ -0,0 +1,6 @@
import os
from app.config import Config
def ensure_upload_folder():
if not os.path.exists(Config.UPLOAD_FOLDER):
os.makedirs(Config.UPLOAD_FOLDER)

14
app/utils/helpers.py Normal file
View File

@@ -0,0 +1,14 @@
# def is_logged_in(session):
# return session.get("user_id") is not None
from functools import wraps
from flask import session, redirect, url_for, flash
def login_required(view):
@wraps(view)
def wrapped_view(*args, **kwargs):
if "user_id" not in session:
flash("Please login first", "warning")
return redirect(url_for("auth.login"))
return view(*args, **kwargs)
return wrapped_view

10
app/utils/plot_utils.py Normal file
View File

@@ -0,0 +1,10 @@
import base64
from io import BytesIO
import matplotlib
matplotlib.use("Agg")
def plot_to_base64(plt):
img = BytesIO()
plt.savefig(img, format="png", bbox_inches="tight")
img.seek(0)
return base64.b64encode(img.read()).decode("utf-8")

46
docker-compose.yml Normal file
View File

@@ -0,0 +1,46 @@
version: '3.8'
services:
db:
image: mysql:8.0
container_name: comparison_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: admin
MYSQL_DATABASE: comparisondb
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
app:
build: .
container_name: comparison_app
restart: always
environment:
FLASK_ENV: development
FLASK_DEBUG: "True"
FLASK_HOST: "0.0.0.0"
FLASK_PORT: "5001"
DB_DIALECT: mysql
DB_DRIVER: pymysql
DB_HOST: db
DB_PORT: 3306
DB_NAME: comparisondb
DB_USER: root
DB_PASSWORD: admin
ports:
- "5001:5001"
depends_on:
- db
volumes:
- ./app/logs:/app/app/logs
- ./app/static/uploads:/app/app/static/uploads
- ./app/static/downloads:/app/app/static/downloads
volumes:
mysql_data:

15
requirements.txt Normal file
View File

@@ -0,0 +1,15 @@
Flask
pandas
openpyxl
xlrd
Werkzeug
python-dotenv
pymysql
sqlalchemy
flask
cryptography
xlsxwriter
matplotlib
flask_sqlalchemy
flask_migrate

17
run.py Normal file
View File

@@ -0,0 +1,17 @@
from dotenv import load_dotenv
load_dotenv()
from app import create_app
from app.services.db_service import db
import os
app = create_app()
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(
host=os.getenv("FLASK_HOST"),
port=int(os.getenv("FLASK_PORT")),
debug=os.getenv("FLASK_DEBUG") == "True"
)