Client billing code push

This commit is contained in:
Pooja Fulari
2026-04-15 10:32:46 +05:30
commit 92670665c3
599 changed files with 9348 additions and 0 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/excel_upload_app.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (excel_upload_app)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (excel_upload_app)" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/excel_upload_app.iml" filepath="$PROJECT_DIR$/.idea/excel_upload_app.iml" />
</modules>
</component>
</project>

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt gunicorn
COPY . .
EXPOSE 5000
ENV FLASK_APP=run:app
ENV FLASK_ENV=development
CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:5000", "--timeout", "120", "run:app"]

Binary file not shown.

Binary file not shown.

42
alembic.ini Normal file
View File

@@ -0,0 +1,42 @@
# alembic.ini
[alembic]
# Path to migration scripts
script_location = migrations
# Database URL (replace with your MySQL credentials)
sqlalchemy.url = mysql+pymysql://root:tiger@mysql:3306/excel_data7
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
propagate = 0
[logger_alembic]
level = INFO
handlers =
qualname = alembic
propagate = 0
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s

108
app/__init__.py Normal file
View File

@@ -0,0 +1,108 @@
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager, UserMixin
from flask_ldap3_login import LDAP3LoginManager
import os
from werkzeug.middleware.proxy_fix import ProxyFix
# ---------------------------
# Initialize extensions
# ---------------------------
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
ldap_manager = LDAP3LoginManager()
# ---------------------------
# LDAP User class for Flask-Login
# ---------------------------
class LDAPUser(UserMixin):
def __init__(self, dn, username, data):
self.id = username # Flask-Login requires .id
self.username = username
self.dn = dn
self.data = data
# ---------------------------
# Create Flask app factory
# ---------------------------
def create_app():
app = Flask(__name__) # simplified, no APPLICATION_ROOT
app.secret_key = "o17d88dba8ebd13565e862c752bf017b7"
# ---------------------------
# Upload folder config
# ---------------------------
app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), 'uploads')
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# ---------------------------
# Database config
# ---------------------------
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:admin@mysql:3306/excel_data7"
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:root@localhost:3306/excel_data7"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# ---------------------------
# LDAP config
# ---------------------------
# app.config['LDAP_HOST'] = 'openldap'
app.config['LDAP_HOST'] = 'localhost'
# app.config['LDAP_PORT'] = 389
app.config['LDAP_PORT'] = 389
app.config['LDAP_USE_SSL'] = False
# Base DN for your directory
app.config['LDAP_BASE_DN'] = 'dc=lcepl,dc=org'
# Admin bind credentials
app.config['LDAP_BIND_USER_DN'] = 'uid=admin,dc=lcepl,dc=org'
app.config['LDAP_BIND_USER_PASSWORD'] = 'admin123'
# User search config
app.config['LDAP_USER_SEARCH_BASE'] = 'dc=lcepl,dc=org'
app.config['LDAP_USER_SEARCH_FILTER'] = '(uid=%s)'
app.config['LDAP_USER_RDN_ATTR'] = 'uid'
app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
# ---------------------------
# Initialize extensions
# ---------------------------
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
ldap_manager.init_app(app)
# ---------------------------
# Login view
# ---------------------------
login_manager.login_view = 'main.login' # blueprint + endpoint
login_manager.login_message_category = 'info'
# ---------------------------
# Flask-Login user loader
# ---------------------------
@login_manager.user_loader
def load_user(user_id):
return LDAPUser(dn=None, username=user_id, data={})
# ---------------------------
# Register blueprints
# ---------------------------
try:
from app.routes.main import main as main_bp
from app.routes.reports import reports as reports_bp
app.register_blueprint(main_bp)
app.register_blueprint(reports_bp)
except ImportError as e:
print(f"Error importing blueprints: {e}")
# ---------------------------
# Fix proxy headers for reverse proxy (Nginx)
# ---------------------------
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
return app

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4286
app/activity.log Normal file

File diff suppressed because it is too large Load Diff

74
app/models.py Normal file
View File

@@ -0,0 +1,74 @@
from . import db
from flask_login import UserMixin
from datetime import datetime
String_size = db.String(255)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(String_size, unique=True, nullable=False)
email = db.Column(String_size, unique=True, nullable=False)
password_hash = db.Column(String_size, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password):
from werkzeug.security import generate_password_hash
self.password_hash = generate_password_hash(password)
def check_password(self, password):
from werkzeug.security import check_password_hash
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f'<User {self.username}>'
# ==========================
# Your existing models below
# ==========================
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
district = db.Column(String_size)
block_name = db.Column(String_size)
village_name = db.Column(String_size)
serial_number = db.Column(String_size)
parent_id = db.Column(String_size, nullable=True)
parent_task_name = db.Column(db.Text)
task_name = db.Column(db.Text)
unit = db.Column(String_size)
qty = db.Column(String_size)
rate = db.Column(String_size)
boq_amount = db.Column(String_size)
previous_billed_qty = db.Column(String_size)
previous_billing_amount = db.Column(String_size)
remaining_amount = db.Column(String_size)
in_this_ra_bill_qty = db.Column(String_size)
in_this_ra_billing_amount = db.Column(String_size)
cumulative_billed_qty = db.Column(String_size)
cumulative_billed_amount = db.Column(String_size)
variation_qty = db.Column(String_size)
variation_amount = db.Column(String_size)
remark = db.Column(String_size, nullable=True)
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
class WorkDetail(db.Model):
id = db.Column(db.Integer, primary_key=True)
name_of_work = db.Column(String_size)
cover_agreement_no = db.Column(String_size)
name_of_contractor = db.Column(String_size)
name_of_tpi_agency = db.Column(String_size)
name_of_division = db.Column(String_size)
name_of_village = db.Column(String_size)
block = db.Column(String_size)
scheme_id = db.Column(String_size)
measurement_book = db.Column(String_size)
date_of_billing = db.Column(String_size)
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
district = db.Column(db.String(255))
class ActivityLog(db.Model): # ✅ OUTSIDE WorkDetail
id = db.Column(db.Integer, primary_key=True)
user = db.Column(db.String(100)) # can link with User model later
action = db.Column(db.String(255))
details = db.Column(db.Text)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)

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

@@ -0,0 +1,68 @@
# from flask import Flask
# from flask_sqlalchemy import SQLAlchemy
# from flask_migrate import Migrate
# from flask_login import LoginManager
# from flask_ldap3_login import LDAP3LoginManager
# import os
# db = SQLAlchemy()
# migrate = Migrate()
# login_manager = LoginManager()
# ldap_manager = LDAP3LoginManager()
# from app.models import User
# # Flask-Login user loader
# @login_manager.user_loader
# def load_user(user_id):
# return User.query.get(int(user_id))
# def create_app():
# app = Flask(__name__)
# # --------------------
# # App config
# # --------------------
# app.config['SECRET_KEY'] = 'dev-secret-key' # 🔐 change this in production
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:admin@localhost/excel_data7'
# # 🔽 Add upload folder config
# app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'upload')
# # Make sure folder exists
# os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# # --------------------
# # LDAP config
# # --------------------
# app.config['LDAP_HOST'] = 'openldap'
# app.config['LDAP_PORT'] = 389
# app.config['LDAP_BASE_DN'] = 'dc=lcepl,dc=org'
# app.config['LDAP_BIND_USER_DN'] = 'cn=admin,dc=lcepl,dc=org'
# app.config['LDAP_BIND_USER_PASSWORD'] = 'admin123'
# app.config['LDAP_USER_DN'] = 'ou=users'
# app.config['LDAP_GROUP_DN'] = 'ou=groups'
# app.config['LDAP_USER_RDN_ATTR'] = 'uid'
# app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
# # Extra to avoid BasicAuth popup
# app.config['USE_LDAP_AUTH'] = True
# app.config['LDAP_REQUIRE_CERT'] = False
# app.config['LDAP_LOGIN_VIEW'] = 'main.login' # your login route
# # --------------------
# # Init extensions
# # --------------------
# db.init_app(app)
# migrate.init_app(app, db)
# login_manager.init_app(app)
# ldap_manager.init_app(app)
# # Redirect to login if not authenticated
# login_manager.login_view = "main.login"
# # --------------------
# # Register blueprints
# # --------------------
# from app.routes.main import main
# app.register_blueprint(main)
# return app

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

10
app/routes/ldap_user.py Normal file
View File

@@ -0,0 +1,10 @@
# from flask_login import UserMixin
# # --------------------
# # LDAP User class (not stored in DB)
# # --------------------
# class LDAPUser(UserMixin):
# """Represents an authenticated LDAP user (kept in session)."""
# def __init__(self, dn, username):
# self.dn = dn
# self.username = username
# self.id = username # 🔑 Use username for session id (stable)

837
app/routes/main.py Normal file
View File

@@ -0,0 +1,837 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash,jsonify
from flask_login import login_user, logout_user, login_required, current_user
from sqlalchemy import func, cast, Float
from app.models import Task,WorkDetail
import pandas as pd
from werkzeug.security import generate_password_hash
import os
from app import LDAPUser
import numpy as np
from flask import current_app
from datetime import datetime
from app import db
from app.models import User
from ldap3 import Server, Connection, ALL # ✅ make sure this is at the top
main = Blueprint("main", __name__)
# @main.route("/login", methods=["GET", "POST"])
# def login():
# # Redirect if already logged in
# if current_user.is_authenticated:
# return redirect(url_for("main.dashboard"))
# if request.method == "POST":
# username = request.form.get("username", "").strip()
# password = request.form.get("password", "")
# if not username or not password:
# flash("Username and password are required.", "danger")
# return render_template("login.html")
# ldap_user_dn = f"uid={username},ou=users,dc=lcepl,dc=org"
# try:
# # Connect to LDAP server
# # server = Server("openldap", port=389, get_info=ALL)
# server = Server("localhost", port=389, get_info=ALL)
# conn = Connection(server, user=ldap_user_dn, password=password)
# if conn.bind():
# # Pass the required 'data' argument
# user = LDAPUser(dn=ldap_user_dn, username=username, data={})
# login_user(user)
# flash(f"Welcome, {username}!", "success")
# return redirect(url_for("main.dashboard"))
# else:
# flash("Invalid LDAP credentials", "danger")
# except Exception as e:
# flash(f"LDAP connection error: {e}", "danger")
# # GET request or failed login
# return render_template("login.html")
@main.route("/login", methods=["GET", "POST"])
def login():
# Redirect if already logged in
if current_user.is_authenticated:
return redirect(url_for("main.dashboard"))
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
if not username or not password:
flash("Username and password are required.", "danger")
return render_template("login.html")
ldap_user_dn = f"uid={username},ou=users,dc=lcepl,dc=org"
try:
# Try LDAP authentication first
server = Server("localhost", port=389, get_info=ALL)
conn = Connection(server, user=ldap_user_dn, password=password)
if conn.bind():
user = LDAPUser(dn=ldap_user_dn, username=username, data={})
login_user(user)
flash(f"Welcome, {username}! (LDAP)", "success")
return redirect(url_for("main.dashboard"))
else:
flash("Invalid LDAP credentials", "danger")
except Exception as e:
# Fallback to LOCAL login if LDAP not available
if username == "admin" and password == "admin":
user = LDAPUser(dn=None, username=username, data={})
login_user(user)
flash(f"Welcome, {username}! (Local Login)", "success")
return redirect(url_for("main.dashboard"))
else:
flash("LDAP unavailable and local login failed", "danger")
return render_template("login.html")
@main.route('/logout')
@login_required
def logout():
logout_user()
# flash("You have been logged out.", "info")
return redirect(url_for("main.login"))
# Home route
@main.route('/upload_excel')
@login_required
def upload_excel():
log_activity(current_user.username, "Page Load", "Upload Excel page accessed")
return render_template('upload.html')
# @main.route('/dashboard')
# def upload_file():
# return render_template('dashboard.html')
# # File upload route
# @main.route('/upload', methods=['POST'])
# @login_required
# def upload():
# if 'file' not in request.files:
# return "No file part"
# file = request.files['file']
# if file.filename == '':
# return "No selected file"
# if file:
# filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], file.filename)
# file.save(filepath)
# log_activity(current_user.username, "File Upload", f"Uploaded file: {file.filename}")
# # Read work details (first 11 rows)
# work_details_data = pd.read_excel(filepath, nrows=11, header=None)
# work_details_dict = {
# "name_of_work": work_details_data.iloc[0, 1],
# "cover_agreement_no": work_details_data.iloc[1, 1],
# "name_of_contractor": work_details_data.iloc[2, 1],
# "name_of_tpi_agency": work_details_data.iloc[3, 1],
# "name_of_division": work_details_data.iloc[4, 1],
# "name_of_village": work_details_data.iloc[5, 1],
# "block": work_details_data.iloc[6, 1],
# "scheme_id": work_details_data.iloc[7, 1],
# "date_of_billing": work_details_data.iloc[8, 1],
# "measurement_book": work_details_data.iloc[9, 1],
# "district": work_details_data.iloc[10, 1] # Example: row 11 (index 10), column 2 (index 1)
# }
# work_details_dict = {key: (None if pd.isna(value) else value) for key, value in work_details_dict.items()}
# work_detail = WorkDetail(**work_details_dict)
# db.session.add(work_detail)
# # Read task data starting from row 12
# data = pd.read_excel(filepath, skiprows=10)
# data = data.astype(str).replace({"nan": None, "NaT": None, "None": None})
# expected_columns = [
# "serial_number", "task_name", "unit", "qty", "rate", "boq_amount",
# "previous_billed_qty", "previous_billing_amount",
# "in_this_ra_bill_qty", "in_this_ra_billing_amount",
# "cumulative_billed_qty", "cumulative_billed_amount",
# "variation_qty", "variation_amount", "remark"
# ]
# if data.shape[1] == len(expected_columns):
# data.columns = expected_columns
# else:
# data.columns = expected_columns[:data.shape[1]] # Truncate
# current_main_task_serial = None
# current_main_task_name = None
# for _, row in data.iterrows():
# task_name = str(row["task_name"]) if row["task_name"] else ""
# serial_number = row["serial_number"]
# if serial_number: # Main task
# current_main_task_serial = serial_number
# current_main_task_name = task_name
# parent_id = None
# else: # Subtask
# parent_id = current_main_task_serial
# task = Task(
# district=work_details_dict.get("district"),
# block_name=work_details_dict["block"],
# village_name=work_details_dict["name_of_village"],
# serial_number=serial_number,
# task_name=task_name,
# unit=row["unit"],
# qty=row["qty"],
# rate=row["rate"],
# boq_amount=row["boq_amount"],
# previous_billed_qty=row["previous_billed_qty"],
# previous_billing_amount=row["previous_billing_amount"],
# in_this_ra_bill_qty=row["in_this_ra_bill_qty"],
# in_this_ra_billing_amount=row["in_this_ra_billing_amount"],
# cumulative_billed_qty=row["cumulative_billed_qty"],
# cumulative_billed_amount=row["cumulative_billed_amount"],
# variation_qty=row["variation_qty"],
# variation_amount=row["variation_amount"],
# parent_id=parent_id,
# parent_task_name=current_main_task_name if not serial_number else None,
# remark=row["remark"]
# )
# db.session.add(task)
# db.session.commit()
# log_activity(current_user.username, "Database Insert", f"Inserted work details and tasks from {file.filename}")
# return redirect(url_for('main.display_tasks'))
def to_2_decimal(value):
try:
if value is None or value == "":
return None
return round(float(value), 2)
except (TypeError, ValueError):
return None
@main.route('/upload', methods=['POST'])
@login_required
def upload():
if 'file' not in request.files:
return "No file part"
file = request.files['file']
if file.filename == '':
return "No selected file"
if file:
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], file.filename)
file.save(filepath)
log_activity(current_user.username, "File Upload", f"Uploaded file: {file.filename}")
# Read work details (first 11 rows)
# work_details_data = pd.read_excel(filepath, nrows=11, header=None)
work_details_data = pd.read_excel(filepath, nrows=11, header=None, dtype=str)
work_details_dict = {
"name_of_work": work_details_data.iloc[0, 1],
"cover_agreement_no": work_details_data.iloc[1, 1],
"name_of_contractor": work_details_data.iloc[2, 1],
"name_of_tpi_agency": work_details_data.iloc[3, 1],
"name_of_division": work_details_data.iloc[4, 1],
"name_of_village": work_details_data.iloc[5, 1],
"block": work_details_data.iloc[6, 1],
"scheme_id": work_details_data.iloc[7, 1],
"date_of_billing": work_details_data.iloc[8, 1],
"measurement_book": work_details_data.iloc[9, 1],
"district": work_details_data.iloc[10, 1]
}
# Clean work details NaN
work_details_dict = {k: (None if pd.isna(v) else v) for k, v in work_details_dict.items()}
work_detail = WorkDetail(**work_details_dict)
db.session.add(work_detail)
# Read task data
# data = pd.read_excel(filepath, skiprows=10)
data = pd.read_excel(filepath, skiprows=10)
# ✅ Convert all NaN → None (critical for MySQL)
data = data.astype(object).where(pd.notna(data), None)
expected_columns = [
"serial_number", "task_name", "unit", "qty", "rate", "boq_amount",
"previous_billed_qty", "previous_billing_amount",
"in_this_ra_bill_qty", "in_this_ra_billing_amount",
"cumulative_billed_qty", "cumulative_billed_amount",
"variation_qty", "variation_amount", "remark"
]
if data.shape[1] == len(expected_columns):
data.columns = expected_columns
else:
data.columns = expected_columns[:data.shape[1]]
current_main_task_serial = None
current_main_task_name = None
for _, row in data.iterrows():
task_name = str(row["task_name"]) if row["task_name"] else ""
serial_number = str(row["serial_number"]) if row["serial_number"] else None
if serial_number:
current_main_task_serial = serial_number
current_main_task_name = task_name
parent_id = None
else:
parent_id = current_main_task_serial
task = Task(
district=work_details_dict.get("district"),
block_name=work_details_dict["block"],
village_name=work_details_dict["name_of_village"],
serial_number=serial_number,
task_name=task_name,
unit=row["unit"],
qty=to_2_decimal(row["qty"]),
rate=to_2_decimal(row["rate"]),
boq_amount=to_2_decimal(row["boq_amount"]),
previous_billed_qty=to_2_decimal(row["previous_billed_qty"]),
previous_billing_amount=to_2_decimal(row["previous_billing_amount"]),
in_this_ra_bill_qty=to_2_decimal(row["in_this_ra_bill_qty"]),
in_this_ra_billing_amount=to_2_decimal(row["in_this_ra_billing_amount"]),
cumulative_billed_qty=to_2_decimal(row["cumulative_billed_qty"]),
cumulative_billed_amount=to_2_decimal(row["cumulative_billed_amount"]),
variation_qty=to_2_decimal(row["variation_qty"]),
variation_amount=to_2_decimal(row["variation_amount"]),
parent_id=parent_id,
parent_task_name=current_main_task_name if not serial_number else None,
remark=row["remark"]
)
db.session.add(task)
db.session.commit()
log_activity(current_user.username, "Database Insert", f"Inserted work details and tasks from {file.filename}")
return redirect(url_for('main.display_tasks'))
# # Update tasks route
# @main.route('/update_tasks', methods=['POST'])
# @login_required
# def update_tasks():
# try:
# updates = request.get_json()
# update_count = 0
# for key, new_value in updates.items():
# if '_' not in key:
# continue
# field_name, task_id_str = key.rsplit('_', 1)
# if not task_id_str.isdigit():
# continue
# task_id = int(task_id_str)
# task = db.session.query(Task).filter_by(id=task_id).first()
# if task:
# current_value = getattr(task, field_name, None)
# if current_value != new_value:
# setattr(task, field_name, new_value)
# update_count += 1
# log_activity(current_user.username, "Task Update", f"Task ID {task.id} - {field_name} changed to {new_value}")
# if update_count > 0:
# db.session.commit()
# log_activity(current_user.username, "Database Commit", f"{update_count} task field(s) updated")
# return jsonify({'message': f'count: {update_count} field(s) updated.'})
# else:
# return jsonify({'message': 'No fields were updated.'})
# except Exception as e:
# log_activity(current_user.username, "Error", f"Update tasks error: {str(e)}")
# return jsonify({'error': 'An error occurred while updating tasks.'}), 500
def recalc_task(task):
rate = float(task.rate or 0)
qty = float(task.qty or 0)
prev_qty = float(task.previous_billed_qty or 0)
ra_qty = float(task.in_this_ra_bill_qty or 0)
# H = G * E
task.previous_billing_amount = round(prev_qty * rate, 2)
# J = I * E
task.in_this_ra_billing_amount = round(ra_qty * rate, 2)
# K = I + G
task.cumulative_billed_qty = round(prev_qty + ra_qty, 2)
# L = K * E
task.cumulative_billed_amount = round(task.cumulative_billed_qty * rate, 2)
# M = IF(K > D , K - D , 0)
if task.cumulative_billed_qty > qty:
task.variation_qty = round(task.cumulative_billed_qty - qty, 2)
else:
task.variation_qty = 0
# N = M * E
task.variation_amount = round(task.variation_qty * rate, 2)
@main.route('/update_tasks', methods=['POST'])
@login_required
def update_tasks():
try:
updates = request.get_json()
update_count = 0
# fields that should NOT be manually edited
formula_fields = [
"previous_billing_amount",
"in_this_ra_billing_amount",
"cumulative_billed_qty",
"cumulative_billed_amount",
"variation_qty",
"variation_amount"
]
for key, new_value in updates.items():
if '_' not in key:
continue
field_name, task_id_str = key.rsplit('_', 1)
if not task_id_str.isdigit():
continue
task_id = int(task_id_str)
task = db.session.query(Task).filter_by(id=task_id).first()
if task:
# ❌ Skip manual update for formula fields
if field_name in formula_fields:
continue
current_value = getattr(task, field_name, None)
if str(current_value) != str(new_value):
setattr(task, field_name, new_value)
# 🔥 auto recalc after any change
recalc_task(task)
update_count += 1
log_activity(
current_user.username,
"Task Update",
f"Task ID {task.id} - {field_name} changed to {new_value}"
)
if update_count > 0:
db.session.commit()
log_activity(
current_user.username,
"Database Commit",
f"{update_count} task field(s) updated"
)
return jsonify({'message': f'count: {update_count} field(s) updated.'})
else:
return jsonify({'message': 'No fields were updated.'})
except Exception as e:
log_activity(current_user.username, "Error", f"Update tasks error: {str(e)}")
return jsonify({'error': 'An error occurred while updating tasks.'}), 500
# Display tasks route
@main.route('/tasks')
@login_required
def display_tasks():
work_details = WorkDetail.query.order_by(WorkDetail.uploaded_at.desc()).first()
if not work_details:
log_activity(current_user.username, "Tasks View", "No work details available")
return "No work details available.", 404
tasks = Task.query.filter_by(
district=work_details.district,
village_name=work_details.name_of_village,
block_name=work_details.block
).order_by(Task.uploaded_at.desc()).all()
grouped_tasks = []
current_main_task = None
for task in tasks:
task_data = {
"id": task.id,
"task_name": task.task_name,
"unit": task.unit,
"qty": task.qty,
"rate": task.rate,
"boq_amount": task.boq_amount,
"previous_billed_qty": task.previous_billed_qty,
"previous_billing_amount": task.previous_billing_amount,
"in_this_ra_bill_qty": task.in_this_ra_bill_qty,
"in_this_ra_billing_amount": task.in_this_ra_billing_amount,
"cumulative_billed_qty": task.cumulative_billed_qty,
"cumulative_billed_amount": task.cumulative_billed_amount,
"variation_qty": task.variation_qty,
"variation_amount": task.variation_amount,
"remark": task.remark,
"district": task.district
}
if task.serial_number:
task_data["subtasks"] = []
grouped_tasks.append(task_data)
current_main_task = task_data
elif current_main_task:
current_main_task["subtasks"].append(task_data)
log_activity(current_user.username, "Tasks View", f"Displayed tasks for {work_details.name_of_village}, {work_details.block}")
return render_template('tasks_display.html', work_details=work_details, grouped_tasks=grouped_tasks)
@main.route('/')
@login_required
def dashboard():
selected_block = request.args.getlist('block[]', None)
rate_col = cast(Task.rate, Float)
qty_col = cast(Task.in_this_ra_bill_qty, Float)
query = db.session.query(
Task.block_name.label("block_name"),
Task.village_name.label("village_name"),
func.sum(cast(Task.boq_amount, Float)).label("total_boq_amount"),
func.sum(cast(Task.previous_billing_amount, Float)).label("prev_billed_amount"),
func.sum(cast(Task.variation_amount, Float)).label("total_variation_amount"),
func.sum(cast(Task.cumulative_billed_qty, Float)).label("cumulative_billed_qty"),
func.sum(cast(Task.cumulative_billed_qty, Float) * cast(Task.rate, Float)).label("cumulative_billed_amount"),
func.sum(qty_col).label("in_this_ra_bill_qty"),
func.sum(rate_col * qty_col).label("to_be_claimed_amount")
)
if selected_block and "All" not in selected_block:
query = query.filter(Task.block_name.in_(selected_block))
query = query.group_by(Task.block_name, Task.village_name)
villages = query.all()
village_data = []
for village in villages:
village_data.append({
"block_name": village.block_name,
"village_name": village.village_name,
"total_boq_amount": village.total_boq_amount or 0,
"rate": "-",
"prev_billed_amount": village.prev_billed_amount or 0,
"total_variation_amount": village.total_variation_amount or 0,
"cumulative_billed_qty": village.cumulative_billed_qty or 0,
"cumulative_billed_amount": village.cumulative_billed_amount or 0,
"in_this_ra_bill_qty": village.in_this_ra_bill_qty or 0,
"to_be_claimed_amount": round(village.to_be_claimed_amount or 0, 2)
})
blocks = db.session.query(Task.block_name).distinct().all()
block_list = ["All"] + [block[0] for block in blocks]
# log_activity(current_user.username, "Dashboard View", "Dashboard accessed")
return render_template('index.html', villages=village_data, blocks=block_list, selected_block=selected_block)
# @main.route('/generate_report_page')
# @login_required
# def generate_report_page():
# blocks = db.session.query(Task.block_name).distinct().all()
# blocks = [block.block_name for block in blocks]
# selected_block = request.args.get('block')
# if selected_block:
# main_tasks = db.session.query(Task.task_name).filter(
# Task.serial_number.isnot(None),
# Task.block_name == selected_block
# ).distinct().all()
# main_tasks = [task.task_name.strip().replace(",", "").replace("(", "").replace(")", "").replace(".", "").replace("&", "").replace("\n", "") for task in main_tasks]
# else:
# main_tasks = db.session.query(Task.task_name).filter(Task.serial_number.isnot(None)).distinct().all()
# main_tasks = [task.task_name.strip().replace(",", "").replace("(", "").replace(")", "").replace(".", "").replace("&", "").replace("\n", "") for task in main_tasks]
# log_activity(current_user.username, "Report Page", f"Report generation page accessed (block={selected_block})")
# return render_template('task_report.html', main_tasks=main_tasks, blocks=blocks)
@main.route('/get_blocks_by_district')
@login_required
def get_blocks_by_district():
district = request.args.get('district')
if not district:
return jsonify({'blocks': []})
blocks = db.session.query(Task.block_name)\
.filter(Task.district == district)\
.distinct().all()
return jsonify({'blocks': [b[0] for b in blocks]})
@main.route('/generate_report_page')
@login_required
def generate_report_page():
selected_district = request.args.get('district')
selected_block = request.args.get('block')
# ✅ Get all districts
districts = [d[0] for d in db.session.query(Task.district).distinct().all()]
# ✅ Get blocks based on district
if selected_district:
blocks = [b[0] for b in db.session.query(Task.block_name)
.filter(Task.district == selected_district)
.distinct().all()]
else:
blocks = []
# ✅ Get main tasks based on block
if selected_block:
main_tasks = db.session.query(Task.task_name).filter(
Task.serial_number.isnot(None),
Task.block_name == selected_block
).distinct().all()
main_tasks = [
task[0].strip()
.replace(",", "")
.replace("(", "")
.replace(")", "")
.replace(".", "")
.replace("&", "")
.replace("\n", "")
for task in main_tasks
]
else:
main_tasks = []
log_activity(
current_user.username,
"Report Page",
f"Report page accessed district={selected_district}, block={selected_block}"
)
return render_template(
'task_report.html',
districts=districts,
blocks=blocks,
main_tasks=main_tasks,
selected_district=selected_district,
selected_block=selected_block
)
@main.route('/get_tasks_by_block')
@login_required
def get_tasks_by_block():
block = request.args.get('block')
if not block:
return jsonify({'tasks': []})
tasks = db.session.query(Task.task_name)\
.filter(Task.block_name == block)\
.distinct()\
.all()
task_list = [task[0].strip()
.replace(",", "")
.replace("(", "")
.replace(")", "")
.replace(".", "")
.replace("&", "")
.replace("\n", "") for task in tasks]
log_activity(current_user.username, "Fetch Tasks", f"Fetched tasks for block {block}")
return jsonify({'tasks': task_list})
def get_villages_for_block(block_name):
villages = (
db.session.query(WorkDetail.name_of_village)
.filter(WorkDetail.block == block_name)
.distinct()
.order_by(WorkDetail.name_of_village)
.all()
)
return [v[0] for v in villages if v[0]]
@main.route('/get_villages_by_block', methods=['GET'])
@login_required
def get_villages_by_block():
block = request.args.get('block')
villages = get_villages_for_block(block)
log_activity(current_user.username, "Fetch Villages", f"Fetched villages for block {block}")
return jsonify({'villages': villages})
@main.route('/filter_tasks', methods=['GET'])
@login_required
def filter_tasks():
district = request.args.get('district')
block = request.args.get('block')
village = request.args.get('village')
# ✅ Fetch distinct districts
districts = [d[0] for d in db.session.query(WorkDetail.district).distinct()]
# ✅ Fetch blocks filtered by district
if district:
blocks = [b[0] for b in db.session.query(WorkDetail.block)
.filter(WorkDetail.district == district).distinct()]
else:
blocks = []
# ✅ Fetch villages filtered by district + block
if district and block:
villages = [v[0] for v in db.session.query(WorkDetail.name_of_village)
.filter(WorkDetail.district == district,
WorkDetail.block == block)
.distinct()]
else:
villages = []
grouped_tasks = []
# ✅ Only fetch tasks if all three (district, block, village) are selected
if district and block and village:
query = (db.session.query(Task)
.join(WorkDetail, Task.village_name == WorkDetail.name_of_village)
.filter(WorkDetail.district == district,
WorkDetail.block == block,
WorkDetail.name_of_village == village))
tasks = query.order_by(Task.uploaded_at.desc()).all()
current_main_task = None
for task in tasks:
task_data = {
"id": task.id,
"task_name": task.task_name,
"unit": task.unit,
"qty": task.qty,
"rate": task.rate,
"boq_amount": task.boq_amount,
"previous_billed_qty": task.previous_billed_qty,
"previous_billing_amount": task.previous_billing_amount,
"in_this_ra_bill_qty": task.in_this_ra_bill_qty,
"in_this_ra_billing_amount": task.in_this_ra_billing_amount,
"cumulative_billed_qty": task.cumulative_billed_qty,
"cumulative_billed_amount": task.cumulative_billed_amount,
"variation_qty": task.variation_qty,
"variation_amount": task.variation_amount,
"remark": task.remark
}
# ✅ Group main tasks (with serial_number) and subtasks
if task.serial_number:
task_data["subtasks"] = []
grouped_tasks.append(task_data)
current_main_task = task_data
elif current_main_task:
current_main_task["subtasks"].append(task_data)
log_activity(current_user.username, "Filter Tasks",
f"Filtered tasks for district={district}, block={block}, village={village}")
# ✅ Render with both filtering + grouped tasks
return render_template(
'filter_tasks.html',
grouped_tasks=grouped_tasks,
districts=districts,
blocks=blocks,
villages=villages,
selected_district=district,
selected_block=block,
selected_village=village
)
# ✅ Helper function for logging user activity
def log_activity(user, action, details=""):
try:
log_file = os.path.join(current_app.root_path, "activity.log")
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(log_file, "a") as f:
f.write(f"Timestamp: {timestamp} | User: {user} | Action: {action} | Details: {details}\n")
except Exception as e:
print(f"Logging failed: {e}")
from flask import request
from datetime import datetime
@main.route('/activity_log', methods=['GET', 'POST'])
@login_required
def activity_log():
logs = []
log_file = os.path.join(current_app.root_path, 'activity.log')
if os.path.exists(log_file):
with open(log_file, 'r') as f:
for line in f:
parts = line.strip().split(" | ")
if len(parts) == 4:
logs.append({
"timestamp": parts[0].replace("Timestamp:", "").strip(),
"user": parts[1].replace("User:", "").strip(),
"action": parts[2].replace("Action:", "").strip(),
"details": parts[3].replace("Details:", "").strip()
})
# Filters
start_date = request.args.get("start_date")
end_date = request.args.get("end_date")
username = request.args.get("username")
filtered_logs = logs
# Date filter (inclusive)
if start_date or end_date:
try:
if start_date:
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
else:
start_dt = datetime.min
if end_date:
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
else:
end_dt = datetime.max
filtered_logs = [
log for log in filtered_logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt.replace(hour=23, minute=59, second=59)
]
except Exception as e:
print("Date filter error:", e)
# Username filter
if username:
filtered_logs = [log for log in filtered_logs if log["user"].lower() == username.lower()]
return render_template(
"activity_log.html",
logs=filtered_logs,
start_date=start_date,
end_date=end_date,
username=username
)

210
app/routes/reports.py Normal file
View File

@@ -0,0 +1,210 @@
from flask import Blueprint, request, render_template, send_from_directory, redirect, url_for, current_app, flash
from app.__init__ import db
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
import os
import re
from datetime import datetime
from app.models import Task
reports = Blueprint('reports', __name__)
import logging
def clean_text(text):
if not isinstance(text, str):
return ""
return text.strip().replace(",", "").replace("(", "").replace(")", "")\
.replace(".", "").replace("&", "").replace("\n", "").lower()
@reports.route('/report_excel', methods=['GET'])
def generate_report():
main_task_rexp = r'[\\/*?:"<>|]'
block = request.args.get('block', '')
main_task = request.args.get('main_task', '')
block_clean = clean_text(block)
main_task_clean = clean_text(main_task)
if not block_clean:
return "Please select a Block.", 400
if not main_task_clean:
return "Please select a Main Task.", 400
print(f"Block selected: {block}")
print(f"Main task selected: {main_task}")
# Filter main task records based on cleaned block and task name
main_task_records = [task for task in Task.query.filter_by(block_name=block).all()
if clean_text(task.task_name) == main_task_clean]
print(f"Found {len(main_task_records)} main task records")
# if not main_task_records:
# return f"Main Task '{main_task}' not found in the selected block '{block}'.", 404
if not main_task_records:
flash("No data found for selected Block and Main Task", "error")
# return redirect(url_for('generate_report_page'))
return redirect(url_for('main.generate_report_page'))
report_data = {}
def safe_float(value):
try:
return round(float(value), 2)
except (ValueError, TypeError):
return 0.0
# Process subtasks for each main task
for main_task_record in main_task_records:
main_task_serial_number = main_task_record.serial_number
# Get all subtasks under the selected main task (match cleaned names)
subtasks_query = [
task for task in Task.query.filter(Task.block_name == block).all()
if clean_text(task.parent_task_name) == main_task_clean
]
print(f"Found {len(subtasks_query)} subtasks for main task '{main_task}'")
for task in subtasks_query:
key = task.village_name.strip() if task.village_name else ""
totalElemList = 26
if key not in report_data:
report_data[key] = [None] * totalElemList
report_data[key][0] = task.id
report_data[key][1] = task.village_name
boq_amount = safe_float(task.boq_amount)
previous_billing_amount = safe_float(task.previous_billing_amount)
remaining_amount = safe_float(boq_amount - previous_billing_amount)
tender_amount = safe_float(task.qty) * safe_float(task.rate)
values = [
safe_float(task.qty),
safe_float(task.rate),
tender_amount,
safe_float(task.previous_billed_qty),
previous_billing_amount,
remaining_amount
]
# Determine task type section
task_name_clean = task.task_name.lower() if task.task_name else ""
start, end = (
(2, 8) if "supply" in task_name_clean else
(8, 14) if "erection" in task_name_clean else
(14, 20) if "testing" in task_name_clean else
(20, 26) if "commissioning" in task_name_clean else
(None, None)
)
if start is not None and end is not None:
for i in range(start, end):
current_value = safe_float(report_data[key][i]) if report_data[key][i] is not None else 0
report_data[key][i] = current_value + values[i - start]
print(f"Number of villages in report data: {len(report_data)}")
# if not report_data:
# return f"No matching data found for the selected block '{block}' and main task '{main_task}'.", 404
if not report_data:
flash("Sub task data not found", "error")
# return redirect(url_for('generate_report_page'))
return redirect(url_for('main.generate_report_page'))
# Generate Excel report
sanitized_main_task = re.sub(main_task_rexp, "", main_task)
max_length = 30
if len(sanitized_main_task) > max_length:
sanitized_main_task = sanitized_main_task[:max_length]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# file_name = f"{sanitized_main_task}_Report.xlsx"
file_name = f"{sanitized_main_task}_{timestamp}.xlsx"
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)
wb = Workbook()
ws = wb.active
ws.title = "Subtask Report"
# Excel formatting
thin_border = Border(left=Side(style="thin"), right=Side(style="thin"),
top=Side(style="thin"), bottom=Side(style="thin"))
header_fill = PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid")
subheader_fill = PatternFill(start_color="92D050", end_color="92D050", fill_type="solid")
data_row_fill1 = PatternFill(start_color="FFFFFF", end_color="FFFFFF", fill_type="solid")
data_row_fill2 = PatternFill(start_color="D9EAD3", end_color="D9EAD3", fill_type="solid")
total_row_fill = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid")
ws.merge_cells(start_row=1, start_column=3, end_row=1, end_column=8)
ws["A1"] = "Task ID"
ws["B1"] = "Village Name"
ws["C1"] = "Supply (70%)"
ws.merge_cells(start_row=1, start_column=9, end_row=1, end_column=14)
ws["I1"] = "Erection (20%)"
ws.merge_cells(start_row=1, start_column=15, end_row=1, end_column=20)
ws["O1"] = "Testing (5%)"
ws.merge_cells(start_row=1, start_column=21, end_row=1, end_column=26)
ws["U1"] = "Commissioning (5%)"
for start_col in [3, 9, 15, 21]:
ws.cell(row=2, column=start_col).value = "Tender Qty"
ws.cell(row=2, column=start_col + 1).value = "Tender Rate"
ws.cell(row=2, column=start_col + 2).value = "Tender Amount"
ws.cell(row=2, column=start_col + 3).value = "Previous Bill QTY"
ws.cell(row=2, column=start_col + 4).value = "Previous Bill Amount"
ws.cell(row=2, column=start_col + 5).value = "Remaining Amount"
# Style header and subheaders
for col in range(1, 27):
col_letter = ws.cell(row=2, column=col).column_letter
ws[f"{col_letter}1"].font = Font(bold=True, size=12)
ws[f"{col_letter}1"].alignment = Alignment(horizontal="center", vertical="center")
ws[f"{col_letter}1"].fill = header_fill
ws[f"{col_letter}1"].border = thin_border
ws[f"{col_letter}2"].font = Font(bold=True, size=11)
ws[f"{col_letter}2"].alignment = Alignment(horizontal="center", vertical="center")
ws[f"{col_letter}2"].fill = subheader_fill
ws[f"{col_letter}2"].border = thin_border
# Fill data
row_index = 3
totals = ["Total", ""] + [0] * 24
sum_columns = [4, 6, 7, 10, 12, 13, 16, 18, 19, 22, 24, 25]
for row_data in report_data.values():
ws.append(row_data)
fill = data_row_fill1 if row_index % 2 != 0 else data_row_fill2
for col in range(1, 27):
ws.cell(row=row_index, column=col).fill = fill
ws.cell(row=row_index, column=col).border = thin_border
for i in sum_columns:
totals[i] += safe_float(row_data[i])
row_index += 1
# Add totals
ws.append(totals)
for col in range(1, 27):
ws.cell(row=row_index, column=col).font = Font(bold=True)
ws.cell(row=row_index, column=col).fill = total_row_fill
ws.cell(row=row_index, column=col).alignment = Alignment(horizontal="center")
ws.cell(row=row_index, column=col).border = thin_border
for i in range(1, 27):
ws.column_dimensions[ws.cell(row=2, column=i).column_letter].width = 20
wb.save(file_path)
print(f"Report generated: {file_name}")
return redirect(url_for('reports.download_report', filename=file_name))
@reports.route('/download/<filename>')
def download_report(filename):
return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename, as_attachment=True)

View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Activity Logs</title>
<style>
body {
font-family: "Segoe UI", Tahoma, sans-serif;
background-color: #f8f9fa;
margin: 20px;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
.top-buttons {
text-align: center;
margin-bottom: 20px;
}
form {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
}
input, button {
padding: 8px;
border-radius: 6px;
border: 1px solid #ccc;
}
button {
background-color: #007bff;
color: white;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
table {
width: 90%;
margin: auto;
border-collapse: collapse;
background: white;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
th, td {
padding: 12px;
border: 1px solid #ddd;
text-align: center;
}
th {
background-color: #007bff;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<h2>Activity Logs</h2>
<div class="top-buttons">
<a href="{{ url_for('main.dashboard') }}">
<button type="button">⬅ Back to Dashboard</button>
</a>
</div>
<form method="get" action="{{ url_for('main.activity_log') }}" class="filter-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter username" value="{{ username or '' }}">
<label for="start_date">Start Date:</label>
<input type="date" id="start_date" name="start_date" value="{{ start_date or '' }}">
<label for="end_date">End Date:</label>
<input type="date" id="end_date" name="end_date" value="{{ end_date or '' }}">
<button type="submit" class="btn btn-primary">Filter</button>
</form>
<table>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Details</th>
</tr>
{% for log in logs %}
<tr>
<td>{{ log.timestamp }}</td>
<td>{{ log.user }}</td>
<td>{{ log.action }}</td>
<td>{{ log.details }}</td>
</tr>
{% endfor %}
{% if logs|length == 0 %}
<tr>
<td colspan="4">No logs found</td>
</tr>
{% endif %}
</table>
<script>
function resetFilter() {
window.location.href = "{{ url_for('main.activity_log') }}";
}
</script>
</body>
</html>

View File

@@ -0,0 +1,439 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Filtered Task Display</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f7f9fc;
margin: 20px;
position: relative;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
form {
display: flex;
justify-content: center;
align-items: flex-end;
gap: 20px;
margin-bottom: 20px;
}
label {
font-weight: bold;
}
select,
button {
padding: 5px 10px;
font-size: 16px;
}
button[type="submit"] {
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
}
button[type="submit"]:hover {
background-color: #45a049;
}
.button-container1 {
position: absolute;
top: 20px;
left: 20px;
}
.button-container1 button {
background-color: #4CAF50;
color: white;
font-size: 14px;
border: none;
border-radius: 5px;
padding: 8px 16px;
cursor: pointer;
}
.button-container1 button:hover {
background-color: #45a049;
}
.button-container {
display: flex;
justify-content: center;
gap: 15px;
margin: 20px 0;
}
.edit-mode-button {
padding: 8px 16px;
font-size: 14px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.edit-mode-button:hover {
background-color: #45a049;
}
#selectedFilters {
font-size: 18px;
text-align: center;
margin-top: 10px;
margin-bottom: 20px;
}
strong {
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 30px;
margin-left: 275px;
}
th,
td {
border: 1px solid #ccc;
padding: 10px;
text-align: center;
}
th {
background-color: #007bff;
color: white;
}
.main-task-row {
background-color: #d9eaf7;
font-weight: bold;
}
.subtask-row {
background-color: #ffffff;
}
.edit-field {
display: none;
width: 100%;
box-sizing: border-box;
}
.no-tasks-message {
text-align: center;
font-size: 18px;
color: #666666;
}
select {
min-width: 200px;
}
/* Sidebar Styling */
.sidebar {
width: 250px;
background-color: #f4f4f4;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 20px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1001;
}
.sidebar .logo {
width: 60px;
height: auto;
margin-bottom: 20px;
}
.sidebar a {
display: block;
color: #333;
text-decoration: none;
padding: 10px 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.sidebar a:hover {
background-color: #007bff;
color: white;
}
.header {
width: 100%;
background-color: #007bff;
color: white;
text-align: center;
padding: 15px 0;
font-size: 24px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
h1.report_title {
text-align: center;
margin-top: 100px;
color: #333;
font-size: 28px;
}
.table-container {
overflow-y: auto;
}
th {
position: sticky; /* Ensures the header stays on top */
top: 58px; /* Keeps the header fixed at the top */
z-index: 1; /* Ensures it's above the content */
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
alt="LCEPL Logo"
/>
<a href="/">Dashboard</a>
<a href="/upload_excel">Upload Excel</a>
<a href="/generate_report_page">Print Report</a>
<a href="/filter_tasks">Filter Tasks</a>
</div>
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
<h1 class="report_title">Filtered Tasks</h1>
<form method="GET" action="{{ url_for('main.filter_tasks') }}">
<!-- District Dropdown -->
<label for="district">District:</label>
<select name="district" id="district" onchange="this.form.submit()">
<option value="">-- Select District --</option>
{% for d in districts %}
<option value="{{ d }}" {% if d == selected_district %}selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<!-- Block Dropdown -->
<label for="block">Block:</label>
<select name="block" id="block" onchange="this.form.submit()">
<option value="">-- Select Block --</option>
{% for b in blocks %}
<option value="{{ b }}" {% if b == selected_block %}selected{% endif %}>{{ b }}</option>
{% endfor %}
</select>
<!-- Village Dropdown -->
<label for="village">Village:</label>
<select name="village" id="village" onchange="this.form.submit()">
<option value="">-- Select Village --</option>
{% for v in villages %}
<option value="{{ v }}" {% if v == selected_village %}selected{% endif %}>{{ v }}</option>
{% endfor %}
</select>
</form>
<div id="selectedFilters">
{% if selected_district or selected_block or selected_village %}
<p>
{% if selected_district %}<strong>District:</strong> {{ selected_district }}{% endif %}
{% if selected_block %} <strong> | Block:</strong> {{ selected_block }}{% endif %}
{% if selected_village %} <strong> | Village:</strong> {{ selected_village }}{% endif %}
</p>
{% else %}
<p><strong>No filters applied. Showing all tasks.</strong></p>
{% endif %}
</div>
<div class="button-container1">
<button onclick="window.location.href='/'">Home Page</button>
</div>
<div class="button-container">
<button class="edit-mode-button" type="button" onclick="toggleEditMode()">Edit Tasks</button>
<button class="edit-mode-button" type="button" id="cancelBtn" onclick="cancelEditMode()" style="display: none;">Cancel</button>
<button class="edit-mode-button" type="button" onclick="submitChangedFields()">Submit Updates</button>
</div>
{% if grouped_tasks %}
<table class="table-container">
<thead>
<tr>
<th>Task Name</th>
<th>Unit</th>
<th>Qty</th>
<th>Rate</th>
<th>BOQ Amt</th>
<th>Prev Billed Qty</th>
<th>Prev Bill Amt</th>
<th>RA Bill Qty</th>
<th>RA Bill Amt</th>
<th>Cum Billed Qty</th>
<th>Cum Billed Amt</th>
<th>Var Qty</th>
<th>Var Amt</th>
</tr>
</thead>
<tbody>
{% set formula_fields = [
"previous_billing_amount",
"in_this_ra_billing_amount",
"cumulative_billed_qty",
"cumulative_billed_amount",
"variation_qty",
"variation_amount"
] %}
{% for group in grouped_tasks %}
<tr class="main-task-row">
<td colspan="1">{{ group.task_name }}</td>
</tr>
{% for sub in group.subtasks %}
<tr class="subtask-row">
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
<td>
<span class="static-text">{{ sub[field] }}</span>
<input
type="text"
class="edit-field"
name="{{ field }}_{{ sub.id }}"
value="{{ sub[field] }}"
data-original-value="{{ sub[field] | trim }}"
{% if field in formula_fields %} readonly style="background:#f5f5f5;" {% endif %}
>
</td>
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
{% else %}
<p class="no-tasks-message">No tasks found for the selected filters.</p>
{% endif %}
<!-- ✅ Scripts -->
<script>
// Toggle edit mode
function toggleEditMode() {
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'none');
document.querySelectorAll('.edit-field').forEach(input => input.style.display = 'inline-block');
document.getElementById('cancelBtn').style.display = 'inline-block';
}
// Cancel edit mode
function cancelEditMode() {
document.querySelectorAll('.edit-field').forEach(input => {
input.style.display = 'none';
input.value = input.dataset.originalValue;
});
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'inline-block');
document.getElementById('cancelBtn').style.display = 'none';
}
// Submit changed fields only
function submitChangedFields() {
const updates = {};
document.querySelectorAll('.edit-field').forEach(input => {
const original = input.dataset.originalValue;
const current = input.value;
if (current !== original) {
updates[input.name] = current;
}
});
if (Object.keys(updates).length === 0) {
alert('No changes detected.');
return;
}
fetch('/update_tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})
.then(response => response.json())
.then(data => {
alert(data.message || 'Tasks updated successfully.');
window.location.reload();
})
.catch(error => {
console.error('Error updating tasks:', error);
alert('Failed to update tasks.');
});
}
// ✅ Filtering dependent dropdowns
document.addEventListener("DOMContentLoaded", function () {
const districtDropdown = document.getElementById("district");
const blockDropdown = document.getElementById("block");
const villageDropdown = document.getElementById("village");
districtDropdown.addEventListener("change", function () {
let district = this.value;
blockDropdown.innerHTML = '<option value="">Select Block</option>';
villageDropdown.innerHTML = '<option value="">Select Village</option>';
if (district) {
fetch(`/get_blocks?district=${district}`)
.then(response => response.json())
.then(data => {
data.forEach(block => {
let option = document.createElement("option");
option.value = block;
option.textContent = block;
blockDropdown.appendChild(option);
});
});
}
});
blockDropdown.addEventListener("change", function () {
let block = this.value;
villageDropdown.innerHTML = '<option value="">Select Village</option>';
if (block) {
fetch(`/get_villages?block=${block}`)
.then(response => response.json())
.then(data => {
data.forEach(village => {
let option = document.createElement("option");
option.value = village;
option.textContent = village;
villageDropdown.appendChild(option);
});
});
}
});
});
</script>
</body>
</html>

516
app/templates/index.html Normal file
View File

@@ -0,0 +1,516 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
/* display: flex; */
flex-direction: row;
height: 100vh;
}
.sidebar {
width: 250px;
background-color: #f4f4f4;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 20px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;
}
.sidebar .logo {
width: 150px;
height: auto;
margin-bottom: 20px;
}
.sidebar a {
display: block;
color: #333;
text-decoration: none;
padding: 10px 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.sidebar a:hover {
background-color: #007bff;
color: white;
}
.header {
width: 100%;
background-color: #007bff;
color: white;
text-align: center;
padding: 15px 0;
font-size: 24px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
.container {
margin: 100px 20px 20px 330px;
padding: 20px;
background-color: #f9f9f9;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
width: calc(100% - 270px);
max-width: 1500px;
}
.table-container {
margin: 20px auto;
width: 100%;
max-height: calc(18 * 40px);
overflow-y: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 16px;
text-align: left;
}
th,
td {
padding: 12px;
border: 1px solid #ddd;
}
th {
background-color: #007bff;
color: white;
text-align: center;
position: sticky; /* Ensures the header stays on top */
top: 0; /* Keeps the header fixed at the top */
z-index: 1; /* Ensures it's above the content */
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
.total-row {
position: -webkit-sticky; /* For Safari */
position: sticky;
bottom: 0;
background-color: #007bff !important;
font-weight: bold;
border-top: 2px solid #ddd;
z-index: 10;
color: #ddd;
display: table-row; /* Ensures row alignment is maintained */
}
.amount {
text-align: left;
}
.summary-title {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
/* Style for the filter and search inputs */
.filter-container,
.search-container {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.filter-container label,
.search-container label {
font-size: 16px;
margin-bottom: 5px;
font-weight: bold;
}
#blockSelect,
#search-input {
width: 100%;
padding: 10px;
font-size: 14px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box; /* Ensures padding is included in width */
}
#blockSelect {
max-width: 300px;
}
#search-input {
max-width: 400px;
}
#blockSelect:focus,
#search-input:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
@media screen and (max-width: 768px) {
/* Adjust layout for mobile responsiveness */
.filter-container,
.search-container {
margin-bottom: 15px;
}
#blockSelect,
#search-input {
width: 100%;
max-width: none;
}
}
/* Smaller dropdown container */
.dropdown {
position: relative;
display: inline-block;
width: 180px; /* reduced width */
font-size: 14px; /* smaller font */
}
/* Smaller dropdown button */
.dropdown-btn {
background-color: #ffffff;
color: black;
padding: 6px 8px; /* smaller padding */
border: 1px solid #ccc;
width: 100%;
text-align: left;
font-size: 14px; /* smaller font */
cursor: pointer;
}
/* Smaller dropdown content */
.dropdown-content {
display: none;
position: absolute;
background-color: #ffffff;
min-width: 180px; /* match button width */
border: 1px solid #ccc;
z-index: 1;
max-height: 150px; /* slightly smaller max height */
overflow-y: auto;
font-size: 13px; /* smaller font */
}
/* Smaller checkbox labels */
.dropdown-content label {
display: block;
padding: 4px 8px; /* reduced padding */
cursor: pointer;
font-size: 13px; /* reduced font size */
}
/* Hover effect */
.dropdown-content label:hover {
background-color: #f1f1f1;
}
/* Show the dropdown content when clicked */
.show {
display: block;
}
.submit-btn {
display: inline-block;
margin-left: 20px;
color:#ddd;
background-color: #007bff;
padding: 5px 8px 5px 8px;
border-radius: 7px;
border: 1px solid #007bff;
height: 30px;
width: 100px;
}
</style>
</head>
<body>
<div class="sidebar">
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
alt="LCEPL Logo"
/>
<a href="/">Dashboard</a>
<a href="/upload_excel">Upload Excel</a>
<a href="/generate_report_page">Print Report</a>
<!--<a href="/tasks">Show Tasks</a>-->
<a href="/filter_tasks">Filter Tasks</a>
<a href="/activity_log">Activity Logs</a>
<a href="{{ url_for('main.logout') }}" style="margin-top:auto; color: #fff; background-color: #dc3545;">Logout</a>
</div>
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
<div class="container">
<h1 class="summary-title">Dashboard</h1>
<!-- Block Selection Dropdown -->
<form method="get" action="{{ url_for('main.dashboard') }}">
<div class="filter-container">
<label>Select Blocks:</label>
<div class="dropdown-container">
<div class="dropdown">
<div class="dropdown-btn" onclick="toggleDropdown()">Select Blocks</div>
<div id="dropdownContent" class="dropdown-content">
{% for block in blocks %}
<label>
<input class="input-style" type="checkbox" name="block[]" value="{{ block }}"
{% if block in selected_blocks %}checked{% endif %}>
{{ block }}
</label>
{% endfor %}
</div>
</div>
<button type="submit" class="submit-btn">Submit</button>
</div>
</div>
</form>
<div class="search-container">
<br><label for="search-input">Search Village:</label>
<input class="input-style" type="text" id="search-input" placeholder="Enter village name..." onkeyup="filterVillages()">
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Sr. No.</th>
<th>Block</th>
<th>Village Name</th>
<th>Total BOQ Amount</th>
<th>Claimed Amount (Rs.)</th>
<th>To be Claimed Amount (Rs.)</th>
<th>Cumulative Amount (Rs.)</th>
<th>Total Variation Amount</th>
<th>Balance against BOQ</th>
</tr>
</thead>
<tbody id="village-table-body">
{% for village in villages %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ village.block_name }}</td>
<td>{{ village.village_name }}</td>
<td class="amount" data-amount="{{ village.total_boq_amount | default(0) }}">
{{ village.total_boq_amount | round(2) }}
</td>
<!-- Claimed Amount -->
<td class="amount" data-amount="{{ village.prev_billed_amount | default(0) }}">
{{ village.prev_billed_amount | round(2) }}
</td>
<!-- To Be Claimed Amount -->
<td class="amount" data-amount="{{ village.to_be_claimed_amount }}">
{{ village.to_be_claimed_amount | round(2) }}
</td>
<!-- Cumulative = prev billed + to be claimed -->
<td class="amount" data-amount="{{ village.cumulative_billed_amount | default(0) | float }}">
{{ village.cumulative_billed_amount | default(0) | float | round(2) }}
</td>
<!-- Total Variation Amount -->
<td class="amount" data-amount="{{ village.total_variation_amount | default(0) }}">
{{ village.total_variation_amount | round(2) }}
</td>
<!-- Balance against BOQ -->
<td class="amount" data-amount="{{ (village.total_boq_amount | default(0) | float) - (village.cumulative_billed_amount | default(0) | float) - (village.total_variation_amount | default(0) | float) }}">
{{ ((village.total_boq_amount | default(0) | float) - (village.cumulative_billed_amount | default(0) | float) - (village.total_variation_amount | default(0) | float)) | round(2) }}
</td>
</td>
</tr>
{% else %}
<tr>
<td colspan="9" style="text-align: center">No data available.</td>
</tr>
{% endfor %}
<tr class="total-row">
<td colspan="3"><strong>Total</strong></td>
<td class="amount" id="total-boq"></td>
<td class="amount" id="total-billed"></td>
<td class="amount" id="total-to-be-claimed"></td>
<td class="amount" id="total-cumulative"></td>
<td class="amount" id="total-variation"></td>
<td class="amount" id="total-balance-against-boq"></td>
</tr>
</tbody>
</table>
<script>
function formatAmount(amount) {
return parseFloat(amount).toFixed(2);
}
// Apply formatting to all cells on load
window.onload = function () {
// Format all amount cells
document.querySelectorAll('.amount').forEach(function (cell) {
var amount = parseFloat(cell.getAttribute('data-amount'));
if (!isNaN(amount)) {
cell.textContent = formatAmount(amount);
}
});
// Totals
let totalBOQ = 0;
let totalBilled = 0;
let totalToBeClaimed = 0;
let totalCumulative = 0;
let totalVariation = 0;
let totalBalanceAgainstBOQ = 0;
const rows = document.querySelectorAll('tbody tr:not(.total-row)');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
const boq = parseFloat(cells[3]?.getAttribute('data-amount') || 0); // Total BOQ Amount
const billed = parseFloat(cells[4]?.getAttribute('data-amount') || 0); // Claimed Amount
const toBeClaimed = parseFloat(cells[5]?.getAttribute('data-amount') || 0); // To Be Claimed
const cumulative = parseFloat(cells[6]?.getAttribute('data-amount') || 0); // Cumulative
const variation = parseFloat(cells[7]?.getAttribute('data-amount') || 0); // Total Variation
const balanceAgainstBOQ = parseFloat(cells[8]?.getAttribute('data-amount') || 0); // Balance BOQ
totalBOQ += boq;
totalBilled += billed;
totalToBeClaimed += toBeClaimed;
totalCumulative += cumulative;
totalVariation += variation;
totalBalanceAgainstBOQ += balanceAgainstBOQ;
});
document.getElementById('total-boq').textContent = formatAmount(totalBOQ);
document.getElementById('total-billed').textContent = formatAmount(totalBilled);
document.getElementById('total-to-be-claimed').textContent = formatAmount(totalToBeClaimed);
document.getElementById('total-cumulative').textContent = formatAmount(totalCumulative);
document.getElementById('total-variation').textContent = formatAmount(totalVariation);
document.getElementById('total-balance-against-boq').textContent = formatAmount(totalBalanceAgainstBOQ);
};
</script>
<script>
function filterVillages() {
const searchInput = document.getElementById("search-input").value.toLowerCase();
const tableBody = document.getElementById("village-table-body");
const rows = tableBody.getElementsByTagName("tr");
Array.from(rows).forEach((row) => {
const villageNameCell = row.getElementsByTagName("td")[2]; // 3rd column (Village Name)
if (villageNameCell) {
const villageName = villageNameCell.textContent || villageNameCell.innerText;
row.style.display = villageName.toLowerCase().indexOf(searchInput) > -1 ? "" : "none";
}
});
}
function toggleDropdown() {
var dropdown = document.getElementById("dropdownContent");
dropdown.style.display = dropdown.style.display === "block" ? "none" : "block";
}
document.getElementById("dropdownContent").addEventListener("click", function (event) {
event.stopPropagation();
});
// Convert number to Cr-Lakh-Rs format
function formatAmountCRL(amount) {
if (amount >= 10000000) { // 1 Crore
let crore = Math.floor(amount / 10000000);
let lakh = Math.floor((amount % 10000000) / 100000);
let rupees = Math.floor(amount % 100000);
return `${crore} Cr ${lakh} Lakh ${rupees} Rs`;
} else if (amount >= 100000) { // 1 Lakh
let lakh = Math.floor(amount / 100000);
let rupees = Math.floor(amount % 100000);
return `${lakh} Lakh ${rupees} Rs`;
} else {
return `${amount.toFixed(2)} Rs`;
}
}
// Convert number to Cr or Lakh dynamically
function formatAmountCRL(amount) {
if (amount >= 10000000) { // 1 Crore
return (amount / 10000000).toFixed(2) + ' Cr';
} else if (amount >= 100000) { // 1 Lakh
return (amount / 100000).toFixed(2) + ' Lakh';
} else {
return amount.toFixed(2);
}
}
// Apply formatting to total row on load
window.onload = function () {
// Calculate totals
let totalBOQ = 0;
let totalBilled = 0;
let totalToBeClaimed = 0;
let totalCumulative = 0;
let totalVariation = 0;
let totalBalanceAgainstBOQ = 0;
const rows = document.querySelectorAll('tbody tr:not(.total-row)');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
totalBOQ += parseFloat(cells[3]?.getAttribute('data-amount') || 0);
totalBilled += parseFloat(cells[4]?.getAttribute('data-amount') || 0);
totalToBeClaimed += parseFloat(cells[5]?.getAttribute('data-amount') || 0);
totalCumulative += parseFloat(cells[6]?.getAttribute('data-amount') || 0);
totalVariation += parseFloat(cells[7]?.getAttribute('data-amount') || 0);
totalBalanceAgainstBOQ += parseFloat(cells[8]?.getAttribute('data-amount') || 0);
});
document.getElementById('total-boq').textContent = formatAmountCRL(totalBOQ);
document.getElementById('total-billed').textContent = formatAmountCRL(totalBilled);
document.getElementById('total-to-be-claimed').textContent = formatAmountCRL(totalToBeClaimed);
document.getElementById('total-cumulative').textContent = formatAmountCRL(totalCumulative);
document.getElementById('total-variation').textContent = formatAmountCRL(totalVariation);
document.getElementById('total-balance-against-boq').textContent = formatAmountCRL(totalBalanceAgainstBOQ);
};
window.onclick = function (e) {
if (!e.target.matches('.dropdown-btn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

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

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>LCEPL Client Billing Software</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f3f3f3;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
h2 {
text-align: center;
margin-bottom: 1.5rem;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
width: 100%;
padding: 0.5rem;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.flash {
color: red;
text-align: center;
margin-bottom: 1rem;
}
.title {
text-align: center;
color: #007bff;
}
.subtitle {
text-align: center;
}
</style>
</head>
<body>
<div class="login-container">
<h1 class="title">Laxmi Civil Engineering Services</h1>
<h4 class="subtitle">LOGIN</h4>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<input type="text" name="username" placeholder="Username" required />
<input
type="password"
name="password"
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Task Report Generator</title>
<!-- Select2 CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Arial', sans-serif;
background-color: #f4f7fc;
padding: 0 20px;
margin-left: 275px;
}
h1.report_title {
text-align: center;
margin-top: 100px;
color: #333;
font-size: 28px;
}
form {
max-width: 500px;
margin: 60px auto;
padding: 30px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border: 1px solid #e0e0e0;
}
form label {
display: block;
margin-bottom: 10px;
font-size: 16px;
font-weight: bold;
color: #333;
}
select, button {
width: 100%;
padding: 12px 16px;
font-size: 16px;
border-radius: 5px;
border: 1px solid #ddd;
margin-bottom: 20px;
background-color: #fafafa;
}
button {
background-color: #28a745;
color: white;
border: none;
cursor: pointer;
}
button:hover { background-color: #218838; }
.form-group { margin-bottom: 15px; }
.sidebar {
width: 295px;
background-color: #f4f4f4;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
padding: 20px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1001;
}
.sidebar .logo { width: 60px; margin-bottom: 20px; }
.sidebar a {
display: block;
color: #333;
text-decoration: none;
padding: 10px 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.sidebar a:hover {
background-color: #007bff;
color: white;
}
.header {
width: 100%;
background-color: #007bff;
color: white;
text-align: center;
padding: 15px 0;
font-size: 24px;
font-weight: bold;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<!-- <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA..." class="logo"/> -->
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
alt="LCEPL Logo"
/>
<a href="/">Dashboard</a>
<a href="/upload_excel">Upload Excel</a>
<a href="/generate_report_page">Print Report</a>
<a href="/filter_tasks">Filter Tasks</a>
</div>
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
<h1 class="report_title">Generate Task Report</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endif %}
{% endwith %}
<form action="/report_excel" method="GET">
<!-- District -->
<div class="form-group">
<label for="district">District</label>
<select name="district" id="district" required>
<option value="" disabled selected>Select District</option>
{% for d in districts %}
<option value="{{ d }}">{{ d }}</option>
{% endfor %}
</select>
</div>
<!-- Block -->
<div class="form-group">
<label for="block">Block</label>
<select name="block" id="block" required>
<option value="" disabled selected>Select Block</option>
</select>
</div>
<!-- Main Task -->
<div class="form-group">
<label for="main_task">Main Task</label>
<select name="main_task" id="main_task" required>
<option value="" disabled selected>Select Main Task</option>
</select>
</div>
<button type="submit">Generate Report</button>
</form>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Select2 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
<script>
$(document).ready(function () {
$('#district').select2({ width: '100%' });
$('#block').select2({ width: '100%' });
$('#main_task').select2({ width: '100%' });
// District → Blocks
$('#district').on('change', function () {
const district = $(this).val();
$('#block').empty().append('<option value="">Select Block</option>');
$('#main_task').empty().append('<option value="">Select Main Task</option>');
if (!district) return;
$.ajax({
url: '/get_blocks_by_district',
method: 'GET',
data: { district: district },
success: function (response) {
response.blocks.forEach(block => {
$('#block').append(`<option value="${block}">${block}</option>`);
});
}
});
});
// Block → Tasks
$('#block').on('change', function () {
const block = $(this).val();
$('#main_task').empty().append('<option value="">Select Main Task</option>');
if (!block) return;
$.ajax({
url: '/get_tasks_by_block',
method: 'GET',
data: { block: block },
success: function (response) {
response.tasks.forEach(task => {
$('#main_task').append(`<option value="${task}">${task}</option>`);
});
}
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,366 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Tasks</title>
<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f8f9fa;
color: #333;
}
h1, h2 {
text-align: center;
color: #007bff;
}
table {
width: 90%;
margin: 10px auto;
border-collapse: collapse;
background-color: #fff;
font-size: 0.85em;
transition: opacity 0.3s ease-in-out;
margin-left: 285px;
}
th, td {
padding: 8px 10px;
border: 1px solid #ddd;
}
th {
background-color: #007bff;
color: white;
}
.main-task-row {
background-color: #e3f2fd;
font-weight: bold;
}
.subtask-row td {
background-color: #fefefe;
padding-left: 20px;
}
.button-container {
display: flex;
justify-content: center;
padding: 10px;
gap: 10px;
flex-wrap: wrap;
}
.button-container a,
.button-container button {
padding: 10px 20px;
color: white;
border: none;
border-radius: 5px;
font-weight: bold;
text-decoration: none;
background-color: #007bff;
cursor: pointer;
}
.button-container a {
background-color: green;
}
.top-buttons {
position: absolute;
top: 20px;
left: 20px;
}
.top-buttons button {
padding: 8px;
font-size: 12px;
background-color: #28a745;
color: white;
border: none;
border-radius: 5px;
}
.top-buttons button:hover {
background-color: #218838;
}
.static-text {
display: inline-block;
width: 100%;
}
.edit-field {
width: 100%;
box-sizing: border-box;
}
#searchBox {
width: 300px;
padding: 8px;
margin: 0 auto 10px;
display: block;
font-size: 14px;
}
#taskTableWrapper {
transition: opacity 0.3s ease-in-out;
}
.spinner {
margin: 15px auto;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
width: 36px;
height: 36px;
animation: spin 0.8s linear infinite;
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Sidebar Styling */
.sidebar {
width: 250px;
background-color: #f4f4f4;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 20px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1001;
}
.sidebar .logo {
width: 60px;
height: auto;
margin-bottom: 20px;
}
.sidebar a {
display: block;
color: #333;
text-decoration: none;
padding: 10px 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.sidebar a:hover {
background-color: #007bff;
color: white;
}
.header {
width: 100%;
background-color: #007bff;
color: white;
text-align: center;
padding: 15px 0;
font-size: 24px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
alt="LCEPL Logo"
class="logo"
/>
<a href="/">Dashboard</a>
<a href="/upload_excel">Upload Excel</a>
<a href="/generate_report_page">Print Report</a>
<!--<a href="/tasks">Show Tasks</a>-->
<a href="/filter_tasks">Filter Tasks</a>
</div>
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
<h1>Uploaded Tasks</h1>
<div class="top-buttons">
<button onclick="window.location.href='/'">Home Page</button>
</div>
<div class="button-container">
<a href="/generate_report_page">Print Task Wise Report</a>
<button onclick="toggleEditMode()">Edit Tasks</button>
<button onclick="cancelEditMode()" id="cancelBtn" style="display: none; background-color: #dc3545;">Cancel Edit</button>
<button onclick="submitChangedFields()">Submit Updates</button>
</div>
<div>
<input type="text" id="searchBox" placeholder="Search tasks..." />
</div>
{% if work_details %}
<h2>Work Details</h2>
<table class="work-details">
<tr><th>Name of Work</th><td>{{ work_details.name_of_work }}</td></tr>
<tr><th>Cover Agreement No</th><td>{{ work_details.cover_agreement_no }}</td></tr>
<tr><th>Name of Contractor</th><td>{{ work_details.name_of_contractor }}</td></tr>
<tr><th>Name of TPI Agency</th><td>{{ work_details.name_of_tpi_agency }}</td></tr>
<tr><th>Name of Division</th><td>{{ work_details.name_of_division }}</td></tr>
<tr><th>Name of District</th><td>{{ work_details.district }}</td></tr>
<tr><th>Village</th><td>{{ work_details.name_of_village }}</td></tr>
<tr><th>Block</th><td>{{ work_details.block }}</td></tr>
<tr><th>Scheme ID</th><td>{{ work_details.scheme_id }}</td></tr>
<tr><th>Measurement Book</th><td>{{ work_details.measurement_book }}</td></tr>
<tr><th>Date of Billing</th><td>{{ work_details.date_of_billing }}</td></tr>
</table>
{% endif %}
<h2>Task Details</h2>
<div class="spinner" id="loader"></div>
<div id="taskTableWrapper" style="opacity: 1;">
<form id="task-form">
<table>
<thead>
<tr>
<th>Main Task</th>
<th>Unit</th>
<th>Qty</th>
<th>Rate</th>
<th>BOQ Amt</th>
<th>Prev Billed Qty</th>
<th>Prev Bill Amt</th>
<th>RA Bill Qty</th>
<th>RA Bill Amt</th>
<th>Cum Billed Qty</th>
<th>Cum Billed Amt</th>
<th>Var Qty</th>
<th>Var Amt</th>
</tr>
</thead>
<tbody>
{% for group in grouped_tasks %}
<tr class="main-task-row">
{% for field in ["task_name", "unit", "qty", "rate", "boq_amount", "previous_billed_qty", "previous_billing_amount", "in_this_ra_bill_qty", "in_this_ra_billing_amount", "cumulative_billed_qty", "cumulative_billed_amount", "variation_qty", "variation_amount"] %}
<td>
<span class="static-text">{{ group[field] }}</span>
<input type="text" class="edit-field" name="{{ field }}_{{ group.id }}" value="{{ group[field] }}" data-original-value="{{ group[field] | trim }}" style="display: none;" />
</td>
{% endfor %}
</tr>
{% for sub in group.subtasks %}
<tr class="subtask-row">
{% for field in ["task_name", "unit", "qty", "rate", "boq_amount", "previous_billed_qty", "previous_billing_amount", "in_this_ra_bill_qty", "in_this_ra_billing_amount", "cumulative_billed_qty", "cumulative_billed_amount", "variation_qty", "variation_amount"] %}
<td>
<span class="static-text">{{ sub[field] }}</span>
<input type="text" class="edit-field" name="{{ field }}_{{ sub.id }}" value="{{ sub[field] }}" data-original-value="{{ sub[field] | trim }}" style="display: none;" />
</td>
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</form>
</div>
<script>
function toggleEditMode() {
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'none');
document.querySelectorAll('.edit-field').forEach(input => input.style.display = 'inline-block');
document.getElementById('cancelBtn').style.display = 'inline-block';
}
function cancelEditMode() {
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'inline-block');
document.querySelectorAll('.edit-field').forEach(input => {
input.style.display = 'none';
input.value = input.getAttribute('data-original-value');
});
document.getElementById('cancelBtn').style.display = 'none';
}
function submitChangedFields() {
const updates = {};
document.querySelectorAll('.edit-field').forEach(input => {
const original = input.getAttribute('data-original-value') || '';
const current = input.value.trim();
if (current !== original.trim()) {
updates[input.name] = current;
}
});
if (Object.keys(updates).length === 0) {
alert("No changes made.");
return;
}
fetch('/update_tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})
.then(response => response.json())
.then(data => {
alert(data.message || 'Update complete!');
window.location.reload();
})
.catch(error => {
console.error('Update error:', error);
alert('Error updating tasks');
});
}
document.addEventListener("DOMContentLoaded", () => {
const searchBox = document.getElementById("searchBox");
const tableWrapper = document.getElementById("taskTableWrapper");
const loader = document.getElementById("loader");
document.querySelectorAll("tbody tr").forEach(row => {
const text = Array.from(row.querySelectorAll("td")).map(cell => {
const span = cell.querySelector(".static-text")?.innerText || '';
const input = cell.querySelector(".edit-field")?.value || '';
return (span + " " + input).toLowerCase();
}).join(" ");
row.setAttribute("data-search-text", text);
});
let timeout;
searchBox.addEventListener("input", function () {
clearTimeout(timeout);
loader.style.display = "block";
tableWrapper.style.opacity = "0.3";
const term = this.value.toLowerCase();
timeout = setTimeout(() => {
document.querySelectorAll("tbody tr").forEach(row => {
const searchable = row.getAttribute("data-search-text");
row.style.display = searchable.includes(term) ? "" : "none";
});
loader.style.display = "none";
tableWrapper.style.opacity = "1";
}, 150);
});
});
</script>
</body>
</html>

166
app/templates/upload.html Normal file
View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Upload Excel</title>
<style>
/* General Styling */
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
height: 50vh;
background-color: #f4f7f9;
margin-top: 0px;
}
/* Sidebar Styling */
.sidebar {
width: 250px;
background-color: #f4f4f4;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 20px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1001;
}
.sidebar .logo {
width: 60px;
height: auto;
margin-bottom: 20px;
}
.sidebar a {
display: block;
color: #333;
text-decoration: none;
padding: 10px 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.sidebar a:hover {
background-color: #007bff;
color: white;
}
/* Main Container Styling */
.main-content {
margin: 400px auto;
/* margin-left: 270px; */
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: calc(100% - 270px);
}
form {
background-color: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 400px;
}
h1 {
text-align: center;
font-size: 2rem;
color: #007bff;
margin-bottom: 20px;
}
input[type="file"] {
display: block;
width: 100%;
padding: 10px;
font-size: 1rem;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
transition: border-color 0.3s ease;
}
input[type="file"]:focus {
border-color: #007bff;
outline: none;
}
.upload {
display: block;
width: 100%;
padding: 10px;
background-color: #007bff;
color: #ffffff;
border: none;
border-radius: 5px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
}
button:hover {
background-color: #0056b3;
}
button:active {
transform: scale(0.98);
}
.header {
width: 100%;
background-color: #007bff;
color: white;
text-align: center;
padding: 15px 0;
font-size: 24px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
alt="LCEPL Logo"
class="logo"
/>
<a href="/">Dashboard</a>
<a href="/upload_excel">Upload Excel</a>
<a href="/generate_report_page">Print Report</a>
<!--<a href="/tasks">Show Tasks</a>-->
<a href="/filter_tasks">Filter Tasks</a>
</div>
<!-- Main Content -->
<div class="main-content">
<form action="/upload" method="post" enctype="multipart/form-data">
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
<h1>Upload Excel File</h1>
<input type="file" name="file" required />
<button class="upload" type="submit">Upload</button>
</form>
</div>
</body>
</html>

Binary file not shown.

110
docker-compose.yml Normal file
View File

@@ -0,0 +1,110 @@
version: "3.9"
services:
traefik:
image: traefik:v2.11.28
command:
- "--api.insecure=true"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80" # container internal port
ports:
- "8085:80" # host port changed to 8085
- "8090:8080" # Traefik dashboard
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
- billing-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://127.0.0.1:8080/api/overview || exit 1"]
interval: 15s
timeout: 5s
retries: 5
flask-app:
build: .
ports:
- "5003:5002" # internal container port
depends_on:
mysql:
condition: service_healthy
openldap:
condition: service_healthy
environment:
FLASK_APP: run:app
FLASK_ENV: development
FLASK_RUN_HOST: 0.0.0.0
FLASK_RUN_PORT: 5002
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_USER: root
MYSQL_PASSWORD: admin
MYSQL_DB: excel_data7
labels:
- "traefik.enable=true"
- "traefik.http.routers.client-billing-flask.rule=Host(`flask.lcepl.local`)"
- "traefik.http.routers.client-billing-flask.entrypoints=web"
- "traefik.http.services.client-billing-flask.loadbalancer.server.port=5002"
networks:
- billing-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:5002/"]
interval: 15s
timeout: 5s
retries: 10
start_period: 20s
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: admin
MYSQL_DATABASE: excel_data7
ports:
- "3307:3306" # external access if needed
volumes:
- mysql-data:/var/lib/mysql
networks:
- billing-network
restart: always
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -padmin --silent"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
openldap:
image: osixia/openldap:1.5.0
environment:
LDAP_ORGANISATION: "LCEPL"
LDAP_DOMAIN: "lcepl.org"
LDAP_ADMIN_PASSWORD: admin
ports:
- "1389:389" # optional external access
- "1636:636"
volumes:
- openldap-data:/var/lib/ldap
- openldap-config:/etc/ldap/slapd.d
- ./ldifs:/container/service/slapd/assets/bootstrap/ldifs:rw
networks:
- billing-network
restart: always
healthcheck:
test: ["CMD-SHELL", "ldapsearch -x -H ldap://127.0.0.1:389 -D 'cn=admin,dc=lcepl,dc=org' -w admin -b 'dc=lcepl,dc=org' >/dev/null 2>&1 || exit 1"]
interval: 20s
timeout: 10s
retries: 20
start_period: 60s
volumes:
mysql-data:
openldap-data:
openldap-config:
networks:
billing-network:
driver: bridge

BIN
instance/default.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,23 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 573b04a1
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /var/run/slapd/slapd.args
olcLogLevel: none
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
structuralObjectClass: olcGlobal
entryUUID: 40b79a56-16b1-1040-911b-a716cb68c9fb
creatorsName: cn=config
createTimestamp: 20250826101453Z
olcTLSCipherSuite: SECURE256:+SECURE128:-VERS-TLS-ALL:+VERS-TLS1.2:-RSA:-DHE
-DSS:-CAMELLIA-128-CBC:-CAMELLIA-256-CBC
olcTLSCACertificateFile: /container/service/slapd/assets/certs/ca.crt
olcTLSCertificateFile: /container/service/slapd/assets/certs/ldap.crt
olcTLSCertificateKeyFile: /container/service/slapd/assets/certs/ldap.key
olcTLSDHParamFile: /container/service/slapd/assets/certs/dhparam.pem
olcTLSVerifyClient: demand
entryCSN: 20250828121450.388111Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250828121450Z

View File

@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 05e25886
dn: cn=module{0}
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}memberof
olcModuleLoad: {2}refint
structuralObjectClass: olcModuleList
entryUUID: 40d348aa-16b1-1040-9123-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101454.917289Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,12 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 3c314b20
dn: cn=schema
objectClass: olcSchemaConfig
cn: schema
structuralObjectClass: olcSchemaConfig
entryUUID: 40c25608-16b1-1040-911e-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.598216Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,249 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 663efec9
dn: cn={0}core
objectClass: olcSchemaConfig
cn: {0}core
olcAttributeTypes: {0}( 2.5.4.2 NAME 'knowledgeInformation' DESC 'RFC2256: k
nowledge information' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{32768} )
olcAttributeTypes: {1}( 2.5.4.4 NAME ( 'sn' 'surname' ) DESC 'RFC2256: last
(family) name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {2}( 2.5.4.5 NAME 'serialNumber' DESC 'RFC2256: serial nu
mber of the entity' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} )
olcAttributeTypes: {3}( 2.5.4.6 NAME ( 'c' 'countryName' ) DESC 'RFC4519: tw
o-letter ISO-3166 country code' SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.
11 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.5.4.7 NAME ( 'l' 'localityName' ) DESC 'RFC2256: l
ocality which this object resides in' SUP name )
olcAttributeTypes: {5}( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' ) DESC 'RF
C2256: state or province which this object resides in' SUP name )
olcAttributeTypes: {6}( 2.5.4.9 NAME ( 'street' 'streetAddress' ) DESC 'RFC2
256: street address of this object' EQUALITY caseIgnoreMatch SUBSTR caseIgn
oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {7}( 2.5.4.10 NAME ( 'o' 'organizationName' ) DESC 'RFC22
56: organization this object belongs to' SUP name )
olcAttributeTypes: {8}( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) DESC
'RFC2256: organizational unit this object belongs to' SUP name )
olcAttributeTypes: {9}( 2.5.4.12 NAME 'title' DESC 'RFC2256: title associate
d with the entity' SUP name )
olcAttributeTypes: {10}( 2.5.4.14 NAME 'searchGuide' DESC 'RFC2256: search g
uide, deprecated by enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
25 )
olcAttributeTypes: {11}( 2.5.4.15 NAME 'businessCategory' DESC 'RFC2256: bus
iness category' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {12}( 2.5.4.16 NAME 'postalAddress' DESC 'RFC2256: postal
address' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {13}( 2.5.4.17 NAME 'postalCode' DESC 'RFC2256: postal co
de' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {14}( 2.5.4.18 NAME 'postOfficeBox' DESC 'RFC2256: Post O
ffice Box' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15{40} )
olcAttributeTypes: {15}( 2.5.4.19 NAME 'physicalDeliveryOfficeName' DESC 'RF
C2256: Physical Delivery Office Name' EQUALITY caseIgnoreMatch SUBSTR caseI
gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {16}( 2.5.4.20 NAME 'telephoneNumber' DESC 'RFC2256: Tele
phone Number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )
olcAttributeTypes: {17}( 2.5.4.21 NAME 'telexNumber' DESC 'RFC2256: Telex Nu
mber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )
olcAttributeTypes: {18}( 2.5.4.22 NAME 'teletexTerminalIdentifier' DESC 'RFC
2256: Teletex Terminal Identifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )
olcAttributeTypes: {19}( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' )
DESC 'RFC2256: Facsimile (Fax) Telephone Number' SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.22 )
olcAttributeTypes: {20}( 2.5.4.24 NAME 'x121Address' DESC 'RFC2256: X.121 Ad
dress' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.36{15} )
olcAttributeTypes: {21}( 2.5.4.25 NAME 'internationaliSDNNumber' DESC 'RFC22
56: international ISDN number' EQUALITY numericStringMatch SUBSTR numericSt
ringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} )
olcAttributeTypes: {22}( 2.5.4.26 NAME 'registeredAddress' DESC 'RFC2256: re
gistered postal address' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.
1.41 )
olcAttributeTypes: {23}( 2.5.4.27 NAME 'destinationIndicator' DESC 'RFC2256:
destination indicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} )
olcAttributeTypes: {24}( 2.5.4.28 NAME 'preferredDeliveryMethod' DESC 'RFC22
56: preferred delivery method' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-
VALUE )
olcAttributeTypes: {25}( 2.5.4.29 NAME 'presentationAddress' DESC 'RFC2256:
presentation address' EQUALITY presentationAddressMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.43 SINGLE-VALUE )
olcAttributeTypes: {26}( 2.5.4.30 NAME 'supportedApplicationContext' DESC 'R
FC2256: supported application context' EQUALITY objectIdentifierMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {27}( 2.5.4.31 NAME 'member' DESC 'RFC2256: member of a g
roup' SUP distinguishedName )
olcAttributeTypes: {28}( 2.5.4.32 NAME 'owner' DESC 'RFC2256: owner (of the
object)' SUP distinguishedName )
olcAttributeTypes: {29}( 2.5.4.33 NAME 'roleOccupant' DESC 'RFC2256: occupan
t of role' SUP distinguishedName )
olcAttributeTypes: {30}( 2.5.4.36 NAME 'userCertificate' DESC 'RFC2256: X.50
9 user certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.8 )
olcAttributeTypes: {31}( 2.5.4.37 NAME 'cACertificate' DESC 'RFC2256: X.509
CA certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.8 )
olcAttributeTypes: {32}( 2.5.4.38 NAME 'authorityRevocationList' DESC 'RFC22
56: X.509 authority revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.9 )
olcAttributeTypes: {33}( 2.5.4.39 NAME 'certificateRevocationList' DESC 'RFC
2256: X.509 certificate revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.9 )
olcAttributeTypes: {34}( 2.5.4.40 NAME 'crossCertificatePair' DESC 'RFC2256:
X.509 cross certificate pair, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121
.1.10 )
olcAttributeTypes: {35}( 2.5.4.42 NAME ( 'givenName' 'gn' ) DESC 'RFC2256: f
irst name(s) for which the entity is known by' SUP name )
olcAttributeTypes: {36}( 2.5.4.43 NAME 'initials' DESC 'RFC2256: initials of
some or all of names, but not the surname(s).' SUP name )
olcAttributeTypes: {37}( 2.5.4.44 NAME 'generationQualifier' DESC 'RFC2256:
name qualifier indicating a generation' SUP name )
olcAttributeTypes: {38}( 2.5.4.45 NAME 'x500UniqueIdentifier' DESC 'RFC2256:
X.500 unique identifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.6 )
olcAttributeTypes: {39}( 2.5.4.46 NAME 'dnQualifier' DESC 'RFC2256: DN quali
fier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {40}( 2.5.4.47 NAME 'enhancedSearchGuide' DESC 'RFC2256:
enhanced search guide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )
olcAttributeTypes: {41}( 2.5.4.48 NAME 'protocolInformation' DESC 'RFC2256:
protocol information' EQUALITY protocolInformationMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.42 )
olcAttributeTypes: {42}( 2.5.4.50 NAME 'uniqueMember' DESC 'RFC2256: unique
member of a group' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.34 )
olcAttributeTypes: {43}( 2.5.4.51 NAME 'houseIdentifier' DESC 'RFC2256: hous
e identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )
olcAttributeTypes: {44}( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'RFC2256:
supported algorithms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )
olcAttributeTypes: {45}( 2.5.4.53 NAME 'deltaRevocationList' DESC 'RFC2256:
delta revocation list; use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )
olcAttributeTypes: {46}( 2.5.4.54 NAME 'dmdName' DESC 'RFC2256: name of DMD'
SUP name )
olcAttributeTypes: {47}( 2.5.4.65 NAME 'pseudonym' DESC 'X.520(4th): pseudon
ym for the object' SUP name )
olcAttributeTypes: {48}( 0.9.2342.19200300.100.1.3 NAME ( 'mail' 'rfc822Mail
box' ) DESC 'RFC1274: RFC822 Mailbox' EQUALITY caseIgnoreIA5Match SUBST
R caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
)
olcAttributeTypes: {49}( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domainCompo
nent' ) DESC 'RFC1274/2247: domain component' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SIN
GLE-VALUE )
olcAttributeTypes: {50}( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain'
DESC 'RFC1274: domain associated with object' EQUALITY caseIgnoreIA5Match S
UBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {51}( 1.2.840.113549.1.9.1 NAME ( 'email' 'emailAddress'
'pkcs9email' ) DESC 'RFC3280: legacy attribute for email addresses in DNs'
EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.26{128} )
olcObjectClasses: {0}( 2.5.6.2 NAME 'country' DESC 'RFC2256: a country' SUP
top STRUCTURAL MUST c MAY ( searchGuide $ description ) )
olcObjectClasses: {1}( 2.5.6.3 NAME 'locality' DESC 'RFC2256: a locality' SU
P top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ descriptio
n ) )
olcObjectClasses: {2}( 2.5.6.4 NAME 'organization' DESC 'RFC2256: an organiz
ation' SUP top STRUCTURAL MUST o MAY ( userPassword $ searchGuide $ seeAlso
$ businessCategory $ x121Address $ registeredAddress $ destinationIndicato
r $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ tel
ephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street
$ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l $ description ) )
olcObjectClasses: {3}( 2.5.6.5 NAME 'organizationalUnit' DESC 'RFC2256: an o
rganizational unit' SUP top STRUCTURAL MUST ou MAY ( userPassword $ searchG
uide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ desti
nationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalId
entifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNu
mber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDelive
ryOfficeName $ st $ l $ description ) )
olcObjectClasses: {4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP to
p STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAls
o $ description ) )
olcObjectClasses: {5}( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an
organizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ r
egisteredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNu
mber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumbe
r $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ posta
lAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )
olcObjectClasses: {6}( 2.5.6.8 NAME 'organizationalRole' DESC 'RFC2256: an o
rganizational role' SUP top STRUCTURAL MUST cn MAY ( x121Address $ register
edAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $
teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ fac
simileTelephoneNumber $ seeAlso $ roleOccupant $ preferredDeliveryMethod $
street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOffic
eName $ ou $ st $ l $ description ) )
olcObjectClasses: {7}( 2.5.6.9 NAME 'groupOfNames' DESC 'RFC2256: a group of
names (DNs)' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategor
y $ seeAlso $ owner $ ou $ o $ description ) )
olcObjectClasses: {8}( 2.5.6.10 NAME 'residentialPerson' DESC 'RFC2256: an r
esidential person' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x1
21Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMet
hod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internati
onaliSDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ stree
t $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName
$ st $ l ) )
olcObjectClasses: {9}( 2.5.6.11 NAME 'applicationProcess' DESC 'RFC2256: an
application process' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ de
scription ) )
olcObjectClasses: {10}( 2.5.6.12 NAME 'applicationEntity' DESC 'RFC2256: an
application entity' SUP top STRUCTURAL MUST ( presentationAddress $ cn ) MA
Y ( supportedApplicationContext $ seeAlso $ ou $ o $ l $ description ) )
olcObjectClasses: {11}( 2.5.6.13 NAME 'dSA' DESC 'RFC2256: a directory syste
m agent (a server)' SUP applicationEntity STRUCTURAL MAY knowledgeInformati
on )
olcObjectClasses: {12}( 2.5.6.14 NAME 'device' DESC 'RFC2256: a device' SUP
top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $
description ) )
olcObjectClasses: {13}( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'RFC22
56: a strong authentication user' SUP top AUXILIARY MUST userCertificate )
olcObjectClasses: {14}( 2.5.6.16 NAME 'certificationAuthority' DESC 'RFC2256
: a certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList
$ certificateRevocationList $ cACertificate ) MAY crossCertificatePair )
olcObjectClasses: {15}( 2.5.6.17 NAME 'groupOfUniqueNames' DESC 'RFC2256: a
group of unique names (DN and Unique Identifier)' SUP top STRUCTURAL MUST (
uniqueMember $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ de
scription ) )
olcObjectClasses: {16}( 2.5.6.18 NAME 'userSecurityInformation' DESC 'RFC225
6: a user security information' SUP top AUXILIARY MAY ( supportedAlgorithms
) )
olcObjectClasses: {17}( 2.5.6.16.2 NAME 'certificationAuthority-V2' SUP cert
ificationAuthority AUXILIARY MAY ( deltaRevocationList ) )
olcObjectClasses: {18}( 2.5.6.19 NAME 'cRLDistributionPoint' SUP top STRUCTU
RAL MUST ( cn ) MAY ( certificateRevocationList $ authorityRevocationList $
deltaRevocationList ) )
olcObjectClasses: {19}( 2.5.6.20 NAME 'dmd' SUP top STRUCTURAL MUST ( dmdNam
e ) MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Add
ress $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $
telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationali
SDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode
$ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )
olcObjectClasses: {20}( 2.5.6.21 NAME 'pkiUser' DESC 'RFC2587: a PKI user' S
UP top AUXILIARY MAY userCertificate )
olcObjectClasses: {21}( 2.5.6.22 NAME 'pkiCA' DESC 'RFC2587: PKI certificate
authority' SUP top AUXILIARY MAY ( authorityRevocationList $ certificateRe
vocationList $ cACertificate $ crossCertificatePair ) )
olcObjectClasses: {22}( 2.5.6.23 NAME 'deltaCRL' DESC 'RFC2587: PKI user' SU
P top AUXILIARY MAY deltaRevocationList )
olcObjectClasses: {23}( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'R
FC2079: object that contains the URI attribute type' MAY ( labeledURI ) SUP
top AUXILIARY )
olcObjectClasses: {24}( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObjec
t' DESC 'RFC1274: simple security object' SUP top AUXILIARY MUST userPasswo
rd )
olcObjectClasses: {25}( 1.3.6.1.4.1.1466.344 NAME 'dcObject' DESC 'RFC2247:
domain component object' SUP top AUXILIARY MUST dc )
olcObjectClasses: {26}( 1.3.6.1.1.3.1 NAME 'uidObject' DESC 'RFC2377: uid ob
ject' SUP top AUXILIARY MUST uid )
structuralObjectClass: olcSchemaConfig
entryUUID: 40c42136-16b1-1040-911f-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.609974Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,178 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 c41ea9d7
dn: cn={1}cosine
objectClass: olcSchemaConfig
cn: {1}cosine
olcAttributeTypes: {0}( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress
' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {1}( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274:
general information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )
olcAttributeTypes: {2}( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteD
rink' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {3}( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RF
C1274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {4}( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274
: photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC
1274: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {6}( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274:
host computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {7}( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC1
274: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466
.115.121.1.12 )
olcAttributeTypes: {8}( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier'
DESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUB
STR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {9}( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC
'RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSub
stringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {10}( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' D
ESC 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {11}( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DE
SC 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {12}( 0.9.2342.19200300.100.1.15 NAME 'documentLocation'
DESC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBS
TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {13}( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'home
TelephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephone
NumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.50 )
olcAttributeTypes: {14}( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'R
FC1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.12 )
olcAttributeTypes: {15}( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNT
AX 1.3.6.1.4.1.1466.115.121.1.39 )
olcAttributeTypes: {16}( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {17}( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {18}( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {19}( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY
caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {20}( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALIT
Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {21}( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUAL
ITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {22}( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DE
SC 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedName
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {23}( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress'
DESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR ca
seIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
olcAttributeTypes: {24}( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DES
C 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubst
ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {25}( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileT
elephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephon
eNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.50 )
olcAttributeTypes: {26}( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTel
ephoneNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNu
mberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115
.121.1.50 )
olcAttributeTypes: {27}( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCou
ntryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier'
DESC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.15{256} )
olcAttributeTypes: {29}( 0.9.2342.19200300.100.1.45 NAME 'organizationalStat
us' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR c
aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC
'RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S
ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {31}( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOpti
on' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 )
olcAttributeTypes: {32}( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC
'RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {33}( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC '
RFC1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE )
olcAttributeTypes: {34}( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality
' DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13
SINGLE-VALUE )
olcAttributeTypes: {35}( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQual
ity' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {36}( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQual
ity' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.13 SINGLE-VALUE )
olcAttributeTypes: {37}( 0.9.2342.19200300.100.1.53 NAME 'personalSignature'
DESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.23 )
olcAttributeTypes: {38}( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC
'RFC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.12 )
olcAttributeTypes: {39}( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC12
74: audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} )
olcAttributeTypes: {40}( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher'
DESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: {0}( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPi
lotPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rf
c822Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber
$ homePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod
$ businessCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $
pagerTelephoneNumber $ organizationalStatus $ mailPreferenceOption $ person
alSignature ) )
olcObjectClasses: {1}( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRU
CTURAL MUST userid MAY ( description $ seeAlso $ localityName $ organizatio
nName $ organizationalUnitName $ host ) )
olcObjectClasses: {2}( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STR
UCTURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $
localityName $ organizationName $ organizationalUnitName $ documentTitle $
documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) )
olcObjectClasses: {3}( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTU
RAL MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNum
ber ) )
olcObjectClasses: {4}( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP t
op STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber
$ localityName $ organizationName $ organizationalUnitName ) )
olcObjectClasses: {5}( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRU
CTURAL MUST domainComponent MAY ( associatedName $ organizationName $ descr
iption $ businessCategory $ seeAlso $ searchGuide $ userPassword $ locality
Name $ stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ p
ostalAddress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTeleph
oneNumber $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIden
tifier $ telexNumber $ preferredDeliveryMethod $ destinationIndicator $ reg
isteredAddress $ x121Address ) )
olcObjectClasses: {6}( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP
domain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ tel
ephoneNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ po
stOfficeBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNN
umber $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferr
edDeliveryMethod $ destinationIndicator $ registeredAddress $ x121Address )
)
olcObjectClasses: {7}( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domai
n STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ C
NAMERecord ) )
olcObjectClasses: {8}( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject'
DESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST asso
ciatedDomain )
olcObjectClasses: {9}( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP
country STRUCTURAL MUST friendlyCountryName )
olcObjectClasses: {10}( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization'
SUP ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName )
olcObjectClasses: {11}( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa S
TRUCTURAL MAY dSAQuality )
olcObjectClasses: {12}( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData
' SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMa
ximumQuality ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 40c836c2-16b1-1040-9120-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.636741Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,108 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 3b70abfe
dn: cn={2}nis
objectClass: olcSchemaConfig
cn: {2}nis
olcAttributeTypes: {0}( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field;
the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Substrings
Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absol
ute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4
.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to
the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121
.1.26 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExac
tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26 )
olcAttributeTypes: {11}( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY
caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.26 )
olcAttributeTypes: {12}( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Net
group triple' SYNTAX 1.3.6.1.1.1.0.0 )
olcAttributeTypes: {13}( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY inte
gerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name
)
olcAttributeTypes: {15}( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP addre
ss' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {18}( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP ne
twork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP ne
tmask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128
} SINGLE-VALUE )
olcAttributeTypes: {20}( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC addres
s' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
olcAttributeTypes: {21}( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.boo
tparamd parameter' SYNTAX 1.3.6.1.1.1.0.1 )
olcAttributeTypes: {22}( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image n
ame' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {23}( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name )
olcAttributeTypes: {24}( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseEx
actIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.26{1024} SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction
of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ u
idNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ ge
cos $ description ) )
olcObjectClasses: {1}( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional
attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPass
word $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowIna
ctive $ shadowExpire $ shadowFlag $ description ) )
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction o
f a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( use
rPassword $ memberUid $ description ) )
olcObjectClasses: {3}( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an
Internet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $
ipServiceProtocol ) MAY description )
olcObjectClasses: {4}( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction o
f an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ descrip
tion ) MAY description )
olcObjectClasses: {5}( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an
ONC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description
) MAY description )
olcObjectClasses: {6}( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a
host, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $
description $ manager ) )
olcObjectClasses: {7}( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of
an IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNe
tmaskNumber $ l $ description $ manager ) )
olcObjectClasses: {8}( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction
of a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberN
isNetgroup $ description ) )
olcObjectClasses: {9}( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstrac
tion of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )
olcObjectClasses: {10}( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in
a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY de
scription )
olcObjectClasses: {11}( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device
with a MAC address' SUP top AUXILIARY MAY macAddress )
olcObjectClasses: {12}( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A devic
e with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter )
)
structuralObjectClass: olcSchemaConfig
entryUUID: 40cc26a6-16b1-1040-9121-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.662538Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,49 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 de7f4198
dn: cn={3}inetorgperson
objectClass: olcSchemaConfig
cn: {3}inetorgperson
olcAttributeTypes: {0}( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC2
798: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {1}( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC
'RFC2798: identifies a department within an organization' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 )
olcAttributeTypes: {2}( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'R
FC2798: preferred name to be used when displaying entries' EQUALITY caseIgn
oreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.15 SINGLE-VALUE )
olcAttributeTypes: {3}( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC '
RFC2798: numerically identifies an employee within an organization' EQUALIT
Y caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RF
C2798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR cas
eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {5}( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RF
C2798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
olcAttributeTypes: {6}( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DE
SC 'RFC2798: preferred written or spoken language for a person' EQUALITY ca
seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15 SINGLE-VALUE )
olcAttributeTypes: {7}( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate'
DESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.
1.1466.115.121.1.5 )
olcAttributeTypes: {8}( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RF
C2798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.14
66.115.121.1.5 )
olcObjectClasses: {0}( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RF
C2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL
MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayNam
e $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddre
ss $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ page
r $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIden
tifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 40d0893a-16b1-1040-9122-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.691278Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,65 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 078a540a
dn: cn={4}ppolicy
objectClass: olcSchemaConfig
cn: {4}ppolicy
olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.8.1.1 NAME 'pwdAttribute' EQUALI
TY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.8.1.2 NAME 'pwdMinAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.8.1.3 NAME 'pwdMaxAge' EQUALITY
integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.8.1.4 NAME 'pwdInHistory' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.8.1.5 NAME 'pwdCheckQuality' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.8.1.6 NAME 'pwdMinLength' EQUALI
TY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.8.1.7 NAME 'pwdExpireWarning' EQ
UALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.42.2.27.8.1.8 NAME 'pwdGraceAuthNLimit'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.42.2.27.8.1.9 NAME 'pwdLockout' EQUALITY
booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.42.2.27.8.1.10 NAME 'pwdLockoutDuration'
EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.42.2.27.8.1.11 NAME 'pwdMaxFailure' EQU
ALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.42.2.27.8.1.12 NAME 'pwdFailureCountInt
erval' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.42.2.27.8.1.13 NAME 'pwdMustChange' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.42.2.27.8.1.14 NAME 'pwdAllowUserChange
' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQU
ALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC
'Loadable module that instantiates "check_password() function' EQUALITY cas
eExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.42.2.27.8.1.30 NAME 'pwdMaxRecordedFail
ure' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP t
op AUXILIARY MAY pwdCheckModule )
olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AU
XILIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdC
heckQuality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLoc
kout $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMu
stChange $ pwdAllowUserChange $ pwdSafeModify $ pwdMaxRecordedFailure ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 411f1b86-16b1-1040-995d-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.206194Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,175 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 0214d98f
dn: cn={5}kopano
objectClass: olcSchemaConfig
cn: {5}kopano
olcAttributeTypes: {0}( 1.3.6.1.4.1.47732.1.1.1.1 NAME 'kopanoQuotaOverride'
DESC 'KOPANO: Override child quota' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.47732.1.1.1.2 NAME 'kopanoQuotaWarn' DES
C 'KOPANO: Warning quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.47732.1.1.1.3 NAME 'kopanoQuotaSoft' DES
C 'KOPANO: Soft quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.47732.1.1.1.4 NAME 'kopanoQuotaHard' DES
C 'KOPANO: Hard quota size in MB' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.47732.1.1.1.5 NAME 'kopanoUserDefaultQuo
taOverride' DESC 'KOPANO: Override User default quota for children' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.47732.1.1.1.6 NAME 'kopanoUserDefaultQuo
taWarn' DESC 'KOPANO: User default warning quota size in MB' EQUALITY integ
erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.47732.1.1.1.7 NAME 'kopanoUserDefaultQuo
taSoft' DESC 'KOPANO: User default soft quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.47732.1.1.1.8 NAME 'kopanoUserDefaultQuo
taHard' DESC 'KOPANO: User default hard quota size in MB' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.47732.1.1.2.1 NAME 'kopanoAdmin' DESC 'K
OPANO: Administrator of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.14
66.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.47732.1.1.2.2 NAME 'kopanoSharedStoreOnl
y' DESC 'KOPANO: is store a shared store' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.47732.1.1.2.3 NAME 'kopanoAccount' DESC
'KOPANO: entry is a part of kopano' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.47732.1.1.2.4 NAME 'kopanoSendAsPrivile
ge' DESC 'KOPANO: Users may directly send email as this user' EQUALITY case
IgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.15 )
olcAttributeTypes: {12}( 1.3.6.1.4.1.47732.1.1.2.5 NAME 'kopanoMrAccept' DES
C 'KOPANO: user should auto-accept meeting requests' EQUALITY integerMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.47732.1.1.2.6 NAME 'kopanoMrDeclineConf
lict' DESC 'KOPANO: user should automatically decline conflicting meeting r
equests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-
VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.47732.1.1.2.7 NAME 'kopanoMrDeclineRecu
rring' DESC 'KOPANO: user should automatically decline recurring meeting re
quests' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.47732.1.1.2.8 NAME 'kopanoId' DESC 'KOP
ANO: Generic unique ID' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.47732.1.1.2.9 NAME 'kopanoResourceType'
DESC 'KOPANO: for shared stores, resource is type Room or Equipment' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {17}( 1.3.6.1.4.1.47732.1.1.2.10 NAME 'kopanoResourceCapa
city' DESC 'KOPANO: number of rooms or equipment available' EQUALITY intege
rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {18}( 1.3.6.1.4.1.47732.1.1.2.11 NAME 'kopanoHidden' DESC
'KOPANO: This object should be hidden from address book' EQUALITY integerM
atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {19}( 1.3.6.1.4.1.47732.1.1.2.13 NAME 'kopanoEnabledFeatu
res' DESC 'KOPANO: This user has these features explicitly enabled' EQUALIT
Y caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {20}( 1.3.6.1.4.1.47732.1.1.2.14 NAME 'kopanoDisabledFeat
ures' DESC 'KOPANO: This user has these features explicitly disabled' EQUAL
ITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )
olcAttributeTypes: {21}( 1.3.6.1.4.1.47732.1.1.3.1 NAME 'kopanoAliases' DESC
'KOPANO: All other email addresses for this user' EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {22}( 1.3.6.1.4.1.47732.1.1.4.1 NAME 'kopanoUserServer' D
ESC 'KOPANO: Home server for the user' EQUALITY caseIgnoreMatch SUBSTR case
IgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {23}( 1.3.6.1.4.1.47732.1.1.6.1 NAME 'kopanoUserArchiveSe
rvers' DESC 'KOPANO: List of server names that contain an archive store for
the user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {24}( 1.3.6.1.4.1.47732.1.1.6.2 NAME 'kopanoUserArchiveCo
uplings' DESC 'KOPANO: List of username:foldername pairs that specify many-
to-one archive locations' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri
ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {25}( 1.3.6.1.4.1.47732.1.2.2.1 NAME 'kopanoSecurityGroup
' DESC 'KOPANO: group has security possibilities' EQUALITY integerMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.47732.1.3.2.4 NAME 'kopanoViewPrivilege
' DESC 'KOPANO: Companies with view privileges over selected company' EQUAL
ITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.146
6.115.121.1.15 )
olcAttributeTypes: {27}( 1.3.6.1.4.1.47732.1.3.2.5 NAME 'kopanoAdminPrivileg
e' DESC 'KOPANO: Users from different companies which are administrator ove
r selected company' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {28}( 1.3.6.1.4.1.47732.1.3.2.6 NAME 'kopanoSystemAdmin'
DESC 'KOPANO: The user who is the system administrator for this company' EQ
UALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.
1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.47732.1.3.1.5 NAME 'kopanoQuotaUserWarn
ingRecipients' DESC 'KOPANO: Users who will receive a notification email wh
en a user exceeds his quota' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubs
tringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {30}( 1.3.6.1.4.1.47732.1.3.1.6 NAME 'kopanoQuotaCompanyW
arningRecipients' DESC 'KOPANO: Users who will receive a notification email
when a company exceeds its quota' EQUALITY caseIgnoreMatch SUBSTR caseIgno
reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {31}( 1.3.6.1.4.1.47732.1.3.4.1 NAME 'kopanoCompanyServer
' DESC 'KOPANO: Home server for the public folders for a company' EQUALITY
caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.47732.1.4.4.1 NAME 'kopanoHttpPort' DES
C 'KOPANO: Port for the http connection' EQUALITY integerMatch SYNTAX 1.3.6
.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.47732.1.4.4.2 NAME 'kopanoSslPort' DESC
'KOPANO: Port for the ssl connection' EQUALITY integerMatch SYNTAX 1.3.6.1
.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {34}( 1.3.6.1.4.1.47732.1.4.4.3 NAME 'kopanoFilePath' DES
C 'KOPANO: The Unix socket or named pipe to the server' EQUALITY caseIgnore
Match SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {35}( 1.3.6.1.4.1.47732.1.4.4.4 NAME 'kopanoContainsPubli
c' DESC 'KOPANO: This server contains the public store' EQUALITY integerMat
ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.47732.1.4.4.6 NAME 'kopanoProxyURL' DES
C 'KOPANO: Full proxy URL for this server' EQUALITY caseIgnoreMatch SUBSTR
caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
)
olcAttributeTypes: {37}( 1.3.6.1.4.1.47732.1.5.5.1 NAME 'kopanoFilter' DESC
'KOPANO: LDAP Filter to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.47732.1.5.5.2 NAME 'kopanoBase' DESC 'K
OPANO: LDAP Search base to apply' EQUALITY caseIgnoreMatch SUBSTR caseIgnor
eSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.47732.1.1.0.0 NAME 'kopano-user' DESC 'KO
PANO: an user of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoQuotaOverrid
e $ kopanoQuotaWarn $ kopanoQuotaSoft $ kopanoSendAsPrivilege $ kopanoQuota
Hard $ kopanoAdmin $ kopanoSharedStoreOnly $ kopanoResourceType $ kopanoRes
ourceCapacity $ kopanoAccount $ kopanoHidden $ kopanoAliases $ kopanoUserSe
rver $ kopanoEnabledFeatures $ kopanoDisabledFeatures $ kopanoUserArchiveSe
rvers $ kopanoUserArchiveCouplings $ uidNumber ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.47732.1.6.0.0 NAME 'kopano-contact' DESC
'KOPANO: a contact of Kopano' SUP top AUXILIARY MUST ( cn $ uidNumber ) MAY
( kopanoSendAsPrivilege $ kopanoHidden $ kopanoAliases $ kopanoAccount ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.47732.1.2.0.0 NAME 'kopano-group' DESC 'K
OPANO: a group of Kopano' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ k
opanoHidden $ mail $ kopanoAliases $ kopanoSecurityGroup $ kopanoSendAsPriv
ilege $ gidNumber ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.47732.1.3.0.0 NAME 'kopano-company' DESC
'KOPANO: a company of Kopano' SUP top AUXILIARY MUST ou MAY ( kopanoAccount
$ kopanoHidden $ kopanoViewPrivilege $ kopanoAdminPrivilege $ kopanoSystem
Admin $ kopanoQuotaOverride $ kopanoQuotaWarn $ kopanoUserDefaultQuotaOverr
ide $ kopanoUserDefaultQuotaWarn $ kopanoUserDefaultQuotaSoft $ kopanoUserD
efaultQuotaHard $ kopanoQuotaUserWarningRecipients $ kopanoQuotaCompanyWarn
ingRecipients $ kopanoCompanyServer ) )
olcObjectClasses: {4}( 1.3.6.1.4.1.47732.1.4.0.0 NAME 'kopano-server' DESC '
KOPANO: a Kopano server' SUP top AUXILIARY MUST cn MAY ( kopanoAccount $ ko
panoHidden $ kopanoHttpPort $ kopanoSslPort $ kopanoFilePath $ kopanoContai
nsPublic $ kopanoProxyURL ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.47732.1.5.0.0 NAME 'kopano-addresslist' D
ESC 'KOPANO: a Kopano Addresslist' SUP top STRUCTURAL MUST cn MAY ( kopanoA
ccount $ kopanoHidden $ kopanoFilter $ kopanoBase ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.47732.1.7.0.0 NAME 'kopano-dynamicgroup'
DESC 'KOPANO: a Kopano dynamic group' SUP top STRUCTURAL MUST cn MAY ( kopa
noAccount $ kopanoHidden $ mail $ kopanoAliases $ kopanoFilter $ kopanoBase
) )
structuralObjectClass: olcSchemaConfig
entryUUID: 4144ac84-16b1-1040-995e-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.452403Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 c0c96705
dn: cn={6}openssh-lpk
objectClass: olcSchemaConfig
cn: {6}openssh-lpk
olcAttributeTypes: {0}( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' D
ESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.40 )
olcObjectClasses: {0}( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' DE
SC 'MANDATORY: OpenSSH LPK objectclass' SUP top AUXILIARY MAY ( sshPublicKe
y $ uid ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 414c3832-16b1-1040-995f-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.501855Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,50 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 c7358a52
dn: cn={7}postfix-book
objectClass: olcSchemaConfig
cn: {7}postfix-book
olcAttributeTypes: {0}( 1.3.6.1.4.1.29426.1.10.1 NAME 'mailHomeDirectory' DE
SC 'The absolute path to the mail user home directory' EQUALITY caseExactIA
5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.29426.1.10.2 NAME 'mailAlias' DESC 'RFC8
22 Mailbox - mail alias' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Su
bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
olcAttributeTypes: {2}( 1.3.6.1.4.1.29426.1.10.3 NAME 'mailUidNumber' DESC '
UID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.29426.1.10.4 NAME 'mailGidNumber' DESC '
GID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4.
1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.29426.1.10.5 NAME 'mailEnabled' DESC 'TR
UE to enable, FALSE to disable account' EQUALITY booleanMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.29426.1.10.6 NAME 'mailGroupMember' DESC
'Name of a mail distribution list' EQUALITY caseExactIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {6}( 1.3.6.1.4.1.29426.1.10.7 NAME 'mailQuota' DESC 'Mail
quota limit in kilobytes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.14
66.115.121.1.26 )
olcAttributeTypes: {7}( 1.3.6.1.4.1.29426.1.10.8 NAME 'mailStorageDirectory'
DESC 'The absolute path to the mail users mailbox' EQUALITY caseExactIA5Ma
tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.29426.1.10.9 NAME 'mailSieveRuleSource'
DESC 'Sun ONE Messaging Server defined attribute' SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26 X-ORIGIN 'Sun ONE Messaging Server' )
olcAttributeTypes: {9}( 1.3.6.1.4.1.29426.1.10.10 NAME 'mailForwardingAddres
s' DESC 'Address(es) to forward all incoming messages to.' EQUALITY caseIgn
oreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{320} )
olcObjectClasses: {0}( 1.3.6.1.4.1.29426.1.2.2.1 NAME 'PostfixBookMailAccoun
t' DESC 'Mail account used in Postfix Book' SUP top AUXILIARY MUST mail MAY
( mailHomeDirectory $ mailAlias $ mailGroupMember $ mailUidNumber $ mailGi
dNumber $ mailEnabled $ mailQuota $ mailStorageDirectory $ mailSieveRuleSou
rce ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.29426.1.2.2.2 NAME 'PostfixBookMailForwar
d' DESC 'Mail forward used in Postfix Book' SUP top AUXILIARY MUST ( mail $
mailAlias ) MAY mailForwardingAddress )
structuralObjectClass: olcSchemaConfig
entryUUID: 41542d80-16b1-1040-9960-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.554011Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,235 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 e6f2f44f
dn: cn={8}samba
objectClass: olcSchemaConfig
cn: {8}samba
olcAttributeTypes: {0}( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC
'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {1}( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC
'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6
.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE )
olcAttributeTypes: {2}( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC '
Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{16} SINGLE-VALUE )
olcAttributeTypes: {3}( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC
'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {4}( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DES
C 'Timestamp of when the user is allowed to update the password' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {5}( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DE
SC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTA
X 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC '
Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC
'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.11
5.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC
'Timestamp of when the user will be logged off automatically' EQUALITY int
egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {9}( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount'
DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1
.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {10}( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime'
DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {11}( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC
'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.
1.26{42} SINGLE-VALUE )
olcAttributeTypes: {12}( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC
'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTA
X 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE )
olcAttributeTypes: {13}( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DES
C 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.
121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {14}( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DES
C 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {15}( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations
' DESC 'List of user workstations the user is allowed to logon to' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE )
olcAttributeTypes: {16}( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC '
Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.15{128} )
olcAttributeTypes: {17}( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC
'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYN
TAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {18}( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC
'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {19}( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory'
DESC 'Concatenated MD5 hashes of the salted NT passwords used on this acco
unt' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
olcAttributeTypes: {20}( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Secur
ity ID' EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch SYNT
AX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {21}( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID'
DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.
1.4.1.1466.115.121.1.26{64} SINGLE-VALUE )
olcAttributeTypes: {22}( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'S
ecurity ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.12
1.1.26{64} )
olcAttributeTypes: {23}( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC
'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {24}( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DES
C 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.
4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {25}( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DE
SC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.
1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'N
ext NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4
.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {27}( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBa
se' DESC 'Base at which the samba RID generation algorithm should operate'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {28}( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC
'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {29}( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC
'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SY
NTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
olcAttributeTypes: {30}( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC
'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
.7 SINGLE-VALUE )
olcAttributeTypes: {31}( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' D
ESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.1
21.1.27 SINGLE-VALUE )
olcAttributeTypes: {32}( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DE
SC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115
.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {33}( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption
' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.15 )
olcAttributeTypes: {34}( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC
'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466
.115.121.1.26 )
olcAttributeTypes: {35}( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DE
SC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.
6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength
' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY in
tegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {37}( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' D
ESC 'Force Users to logon for password change (default: 0 => off, 2 => on)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC
'Maximum password age, in seconds (default: -1 => never expire passwords)'
EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {39}( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC
'Minimum password age, in seconds (default: 0 => allow immediate password c
hange)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
ALUE )
olcAttributeTypes: {40}( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration'
DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY i
ntegerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {41}( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservati
onWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {42}( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold
' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALIT
Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {43}( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DES
C 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUA
LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {44}( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwd
Change' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY
integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {45}( 1.3.6.1.4.1.7165.2.1.68 NAME 'sambaClearTextPasswor
d' DESC 'Clear text password (used for trusted domain passwords)' EQUALITY
octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {46}( 1.3.6.1.4.1.7165.2.1.69 NAME 'sambaPreviousClearTex
tPassword' DESC 'Previous clear text password (used for trusted domain pass
words)' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {47}( 1.3.6.1.4.1.7165.2.1.70 NAME 'sambaTrustType' DESC
'Type of trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE )
olcAttributeTypes: {48}( 1.3.6.1.4.1.7165.2.1.71 NAME 'sambaTrustAttributes'
DESC 'Trust attributes for a trusted domain' EQUALITY integerMatch SYNTAX
1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {49}( 1.3.6.1.4.1.7165.2.1.72 NAME 'sambaTrustDirection'
DESC 'Direction of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.1
15.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {50}( 1.3.6.1.4.1.7165.2.1.73 NAME 'sambaTrustPartner' DE
SC 'Fully qualified name of the domain with which a trust exists' EQUALITY
caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
olcAttributeTypes: {51}( 1.3.6.1.4.1.7165.2.1.74 NAME 'sambaFlatName' DESC '
NetBIOS name of a domain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.
115.121.1.15{128} )
olcAttributeTypes: {52}( 1.3.6.1.4.1.7165.2.1.75 NAME 'sambaTrustAuthOutgoin
g' DESC 'Authentication information for the outgoing portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {53}( 1.3.6.1.4.1.7165.2.1.76 NAME 'sambaTrustAuthIncomin
g' DESC 'Authentication information for the incoming portion of a trust' EQ
UALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {54}( 1.3.6.1.4.1.7165.2.1.77 NAME 'sambaSecurityIdentifi
er' DESC 'SID of a trusted domain' EQUALITY caseIgnoreIA5Match SUBSTR caseE
xactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALU
E )
olcAttributeTypes: {55}( 1.3.6.1.4.1.7165.2.1.78 NAME 'sambaTrustForestTrust
Info' DESC 'Forest trust information for a trusted domain object' EQUALITY
caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} )
olcAttributeTypes: {56}( 1.3.6.1.4.1.7165.2.1.79 NAME 'sambaTrustPosixOffset
' DESC 'POSIX offset of a trust' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1
466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {57}( 1.3.6.1.4.1.7165.2.1.80 NAME 'sambaSupportedEncrypt
ionTypes' DESC 'Supported encryption types of a trust' EQUALITY integerMatc
h SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcObjectClasses: {0}( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' DESC 'S
amba 3.0 Auxilary SAM Account' SUP top AUXILIARY MUST ( uid $ sambaSID ) MA
Y ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTi
me $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustC
hange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sam
baLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sa
mbaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCo
unt $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours ) )
olcObjectClasses: {1}( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' DESC
'Samba Group Mapping' SUP top AUXILIARY MUST ( gidNumber $ sambaSID $ samba
GroupType ) MAY ( displayName $ description $ sambaSIDList ) )
olcObjectClasses: {2}( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' DES
C 'Samba Trust Password' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaN
TPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet ) )
olcObjectClasses: {3}( 1.3.6.1.4.1.7165.2.2.15 NAME 'sambaTrustedDomainPassw
ord' DESC 'Samba Trusted Domain Password' SUP top STRUCTURAL MUST ( sambaDo
mainName $ sambaSID $ sambaClearTextPassword $ sambaPwdLastSet ) MAY sambaP
reviousClearTextPassword )
olcObjectClasses: {4}( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' DESC 'Samba
Domain Information' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaSID )
MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithm
icRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd
$ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObse
rvationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachi
nePwdChange ) )
olcObjectClasses: {5}( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' DESC 'P
ool for allocating UNIX uids/gids' SUP top AUXILIARY MUST ( uidNumber $ gid
Number ) )
olcObjectClasses: {6}( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' DESC 'M
apping from a SID to an ID' SUP top AUXILIARY MUST sambaSID MAY ( uidNumber
$ gidNumber ) )
olcObjectClasses: {7}( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' DESC 'Str
uctural Class for a SID' SUP top STRUCTURAL MUST sambaSID )
olcObjectClasses: {8}( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' DESC 'Samb
a Configuration Section' SUP top AUXILIARY MAY description )
olcObjectClasses: {9}( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' DESC 'Samba
Share Section' SUP top STRUCTURAL MUST sambaShareName MAY description )
olcObjectClasses: {10}( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' DES
C 'Samba Configuration Option' SUP top STRUCTURAL MUST sambaOptionName MAY
( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringLis
toption $ description ) )
olcObjectClasses: {11}( 1.3.6.1.4.1.7165.2.2.16 NAME 'sambaTrustedDomain' DE
SC 'Samba Trusted Domain Object' SUP top STRUCTURAL MUST cn MAY ( sambaTrus
tType $ sambaTrustAttributes $ sambaTrustDirection $ sambaTrustPartner $ sa
mbaFlatName $ sambaTrustAuthOutgoing $ sambaTrustAuthIncoming $ sambaSecuri
tyIdentifier $ sambaTrustForestTrustInfo $ sambaTrustPosixOffset $ sambaSup
portedEncryptionTypes ) )
structuralObjectClass: olcSchemaConfig
entryUUID: 415c59ce-16b1-1040-9961-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.607572Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 fe90ccf1
dn: olcDatabase={-1}frontend
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcAccess: {1}to dn.exact="" by * read
olcAccess: {2}to dn.base="cn=Subschema" by * read
olcSizeLimit: 500
structuralObjectClass: olcDatabaseConfig
entryUUID: 40bc7da0-16b1-1040-911c-a716cb68c9fb
creatorsName: cn=config
createTimestamp: 20250826101453Z
entryCSN: 20250826101453.559911Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20250826101453Z

View File

@@ -0,0 +1,16 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 c15d64f1
dn: olcDatabase={0}config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcRootDN: cn=admin,cn=config
structuralObjectClass: olcDatabaseConfig
entryUUID: 40c0138e-16b1-1040-911d-a716cb68c9fb
creatorsName: cn=config
createTimestamp: 20250826101453Z
olcRootPW:: e1NTSEF9ZkdLK0pYSC9xVDk3eEtWMWVHTmsrTHRkZlpxYTFwamk=
entryCSN: 20250828121450.398436Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250828121450Z

View File

@@ -0,0 +1,32 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 7b4c0c45
dn: olcDatabase={1}mdb
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=lcepl,dc=org
olcLastMod: TRUE
olcRootDN: cn=admin,dc=lcepl,dc=org
olcDbCheckpoint: 512 30
olcDbMaxSize: 1073741824
structuralObjectClass: olcMdbConfig
entryUUID: 40d5b3a6-16b1-1040-9124-a716cb68c9fb
creatorsName: cn=admin,cn=config
createTimestamp: 20250826101453Z
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=extern
al,cn=auth manage by * break
olcAccess: {1}to attrs=userPassword,shadowLastChange by self write by dn="cn
=admin,dc=lcepl,dc=org" write by anonymous auth by * none
olcAccess: {2}to * by self read by dn="cn=admin,dc=lcepl,dc=org" write by *
none
olcDbIndex: uid eq
olcDbIndex: mail eq
olcDbIndex: memberOf eq
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
olcDbIndex: objectClass eq
olcRootPW:: e1NTSEF9UXh4Qi95TkhZc25LNzJ1NGRoWERhSFljRlEzaWdWc1Y=
entryCSN: 20250828121450.413834Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250828121450Z

View File

@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 c294553c
dn: olcOverlay={0}memberof
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: {0}memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfUniqueNames
olcMemberOfMemberAD: uniqueMember
olcMemberOfMemberOfAD: memberOf
structuralObjectClass: olcMemberOf
entryUUID: 4182ac28-16b1-1040-9962-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.858707Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,18 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 79898312
dn: olcOverlay={1}refint
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: owner
olcRefintAttribute: manager
olcRefintAttribute: uniqueMember
olcRefintAttribute: member
olcRefintAttribute: memberOf
structuralObjectClass: olcRefintConfig
entryUUID: 418d5344-16b1-1040-9963-2797d7152165
creatorsName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
createTimestamp: 20250826101454Z
entryCSN: 20250826101454.928532Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20250826101454Z

View File

@@ -0,0 +1,4 @@
export PREVIOUS_LDAP_TLS_CA_CRT_PATH=/container/service/slapd/assets/certs/ca.crt
export PREVIOUS_LDAP_TLS_CRT_PATH=/container/service/slapd/assets/certs/ldap.crt
export PREVIOUS_LDAP_TLS_KEY_PATH=/container/service/slapd/assets/certs/ldap.key
export PREVIOUS_LDAP_TLS_DH_PARAM_PATH=/container/service/slapd/assets/certs/dhparam.pem

BIN
ldap-data/data.mdb Normal file

Binary file not shown.

BIN
ldap-data/lock.mdb Normal file

Binary file not shown.

6
ldifs/base.ldif Normal file
View File

@@ -0,0 +1,6 @@
dn: dc=lcepl,dc=org
objectClass: top
objectClass: dcObject
objectClass: organization
o: LCEPL Organization
dc: lcepl

2
ldifs/desktop.ini Normal file
View File

@@ -0,0 +1,2 @@
[LocalizedFileNames]
ou.ldif=@ou,0

13
ldifs/laxmi_add.ldif Normal file
View File

@@ -0,0 +1,13 @@
dn: uid=laxmi,ou=users,dc=lcepl,dc=org
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
cn: Laxmi Bamnale
sn: Bamnale
uid: laxmi
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/laxmi
loginShell: /bin/bash
mail: laxmi@lcepl.org
userPassword: {SSHA}Vg2xl+biEhgR9DQPmCUuPJ/e70ECgND7

3
ldifs/ou-users.ldif Normal file
View File

@@ -0,0 +1,3 @@
dn: ou=users,dc=lcepl,dc=org
objectClass: organizationalUnit
ou: users

3
ldifs/ou.ldif Normal file
View File

@@ -0,0 +1,3 @@
dn: ou=users,dc=lcepl,dc=org
objectClass: organizationalUnit
ou: users

6
ldifs/prajakta.ldif Normal file
View File

@@ -0,0 +1,6 @@
dn: uid=prajakta,ou=users,dc=lcepl,dc=org
objectClass: inetOrgPerson
cn: Prajakta
sn: S
uid: prajakta
userPassword: prajakta123

0
logs/activity.log Normal file
View File

1
migrations/README Normal file
View File

@@ -0,0 +1 @@
Single-database configuration for Flask.

Binary file not shown.

51
migrations/alembic.ini Normal file
View File

@@ -0,0 +1,51 @@
# A generic, single database configuration.
[alembic]
sqlalchemy.url = mysql+pymysql://root:tiger@mysql:3306/excel_data7
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

113
migrations/env.py Normal file
View File

@@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,90 @@
"""initial migrations
Revision ID: fc9ad8ba55e5
Revises:
Create Date: 2025-08-28 09:19:51.758029
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fc9ad8ba55e5'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('activity_log',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user', sa.String(length=100), nullable=True),
sa.Column('action', sa.String(length=255), nullable=True),
sa.Column('details', sa.Text(), nullable=True),
sa.Column('timestamp', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('task',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('district', sa.String(length=255), nullable=True),
sa.Column('block_name', sa.String(length=255), nullable=True),
sa.Column('village_name', sa.String(length=255), nullable=True),
sa.Column('serial_number', sa.String(length=255), nullable=True),
sa.Column('parent_id', sa.String(length=255), nullable=True),
sa.Column('parent_task_name', sa.Text(), nullable=True),
sa.Column('task_name', sa.Text(), nullable=True),
sa.Column('unit', sa.String(length=255), nullable=True),
sa.Column('qty', sa.String(length=255), nullable=True),
sa.Column('rate', sa.String(length=255), nullable=True),
sa.Column('boq_amount', sa.String(length=255), nullable=True),
sa.Column('previous_billed_qty', sa.String(length=255), nullable=True),
sa.Column('previous_billing_amount', sa.String(length=255), nullable=True),
sa.Column('remaining_amount', sa.String(length=255), nullable=True),
sa.Column('in_this_ra_bill_qty', sa.String(length=255), nullable=True),
sa.Column('in_this_ra_billing_amount', sa.String(length=255), nullable=True),
sa.Column('cumulative_billed_qty', sa.String(length=255), nullable=True),
sa.Column('cumulative_billed_amount', sa.String(length=255), nullable=True),
sa.Column('variation_qty', sa.String(length=255), nullable=True),
sa.Column('variation_amount', sa.String(length=255), nullable=True),
sa.Column('remark', sa.String(length=255), nullable=True),
sa.Column('uploaded_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=255), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('password_hash', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
op.create_table('work_detail',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name_of_work', sa.String(length=255), nullable=True),
sa.Column('cover_agreement_no', sa.String(length=255), nullable=True),
sa.Column('name_of_contractor', sa.String(length=255), nullable=True),
sa.Column('name_of_tpi_agency', sa.String(length=255), nullable=True),
sa.Column('name_of_division', sa.String(length=255), nullable=True),
sa.Column('name_of_village', sa.String(length=255), nullable=True),
sa.Column('block', sa.String(length=255), nullable=True),
sa.Column('scheme_id', sa.String(length=255), nullable=True),
sa.Column('measurement_book', sa.String(length=255), nullable=True),
sa.Column('date_of_billing', sa.String(length=255), nullable=True),
sa.Column('uploaded_at', sa.DateTime(), nullable=True),
sa.Column('district', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('work_detail')
op.drop_table('user')
op.drop_table('task')
op.drop_table('activity_log')
# ### end Alembic commands ###

18
requirements.txt Normal file
View File

@@ -0,0 +1,18 @@
# Flask
# Flask-SQLAlchemy
# pymysql
# openpyxl
# pandas
# python-dotenv
# cryptography
# Flask-Migrate
Flask
flask-login
Flask-SQLAlchemy
pymysql
openpyxl
pandas
python-dotenv
cryptography
Flask-Migrate
flask_ldap3_login

10
run.py Normal file
View File

@@ -0,0 +1,10 @@
from app import create_app, db
app = create_app()
app.config['MAX_CONTENT_LENGTH'] = 64 * 1024 * 1024
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=5002, debug=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More