Initial commit: Fleet Management app with security hardening and background launcher
- Flask app with SQLAlchemy, Flask-Login, Flask-Mail - Admin/owner roles, vessel management, charters, work orders - Background launcher (Iniciar.vbs) runs server without terminal window - Root redirect fixed: / → /login - debug=False, use_reloader=False for pythonw.exe compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+191
@@ -0,0 +1,191 @@
|
||||
from flask_login import UserMixin
|
||||
from app import db
|
||||
from datetime import datetime
|
||||
|
||||
class Company(db.Model):
|
||||
__tablename__ = 'companies'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(200), nullable=False)
|
||||
type = db.Column(db.String(20), nullable=False) # 'management', 'owner'
|
||||
email = db.Column(db.String(100))
|
||||
phone = db.Column(db.String(20))
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
__tablename__ = 'users'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
email = db.Column(db.String(100), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(200), nullable=False)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
|
||||
role = db.Column(db.String(20), default='admin') # 'admin', 'captain'
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
is_super_admin = db.Column(db.Boolean, default=False)
|
||||
company = db.relationship('Company', backref='users')
|
||||
|
||||
class Vessel(db.Model):
|
||||
__tablename__ = 'vessels'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
hin = db.Column(db.String(50))
|
||||
make = db.Column(db.String(50))
|
||||
model = db.Column(db.String(50))
|
||||
length = db.Column(db.Float)
|
||||
engines = db.Column(db.String(200))
|
||||
fuel_consumption_14knots = db.Column(db.Float) # L/h
|
||||
owner_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
|
||||
management_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
|
||||
management_company = db.relationship('Company', foreign_keys=[management_company_id], backref='managed_vessels')
|
||||
plan_id = db.Column(db.Integer)
|
||||
charter_percentage = db.Column(db.Float, default=25.0) # % para management
|
||||
base_rate_4h = db.Column(db.Float)
|
||||
hourly_rate_extra = db.Column(db.Float)
|
||||
max_passengers = db.Column(db.Integer, default=12)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
|
||||
class Captain(db.Model):
|
||||
__tablename__ = 'captains'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
phone = db.Column(db.String(20))
|
||||
license_number = db.Column(db.String(50))
|
||||
hourly_rate = db.Column(db.Float)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
license_type = db.Column(db.String(20), default='private') # 'private' | 'six_pack' | 'master'
|
||||
|
||||
class Route(db.Model):
|
||||
__tablename__ = 'routes'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
distance_nm = db.Column(db.Float)
|
||||
time_hours_14knots = db.Column(db.Float)
|
||||
points_of_interest = db.Column(db.Text)
|
||||
deviation_charge_usd = db.Column(db.Float, default=50)
|
||||
|
||||
class Charter(db.Model):
|
||||
__tablename__ = 'charters'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
|
||||
route_id = db.Column(db.Integer, db.ForeignKey('routes.id'))
|
||||
charterer_name = db.Column(db.String(100))
|
||||
charterer_phone = db.Column(db.String(20))
|
||||
charterer_email = db.Column(db.String(100))
|
||||
start_datetime = db.Column(db.DateTime)
|
||||
hours = db.Column(db.Float)
|
||||
total_base_rate = db.Column(db.Float)
|
||||
management_percentage = db.Column(db.Float)
|
||||
management_earnings = db.Column(db.Float)
|
||||
owner_earnings = db.Column(db.Float)
|
||||
status = db.Column(db.String(20), default='draft') # draft, signed, completed, paid
|
||||
contract_pdf = db.Column(db.String(200))
|
||||
completed_at = db.Column(db.DateTime)
|
||||
insurance_rider_number = db.Column(db.String(60))
|
||||
insurer_name = db.Column(db.String(100))
|
||||
coverage_amount = db.Column(db.Float)
|
||||
damage_waiver = db.Column(db.Float, default=0)
|
||||
captain_id = db.Column(db.Integer, db.ForeignKey('captains.id'))
|
||||
captain = db.relationship('Captain', backref='charters')
|
||||
vessel = db.relationship('Vessel', backref='charters')
|
||||
route = db.relationship('Route', backref='charters')
|
||||
|
||||
class WorkOrder(db.Model):
|
||||
__tablename__ = 'work_orders'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
|
||||
requested_by_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
|
||||
approved_by_owner_id = db.Column(db.Integer)
|
||||
description = db.Column(db.Text)
|
||||
estimated_cost = db.Column(db.Float)
|
||||
actual_cost = db.Column(db.Float)
|
||||
status = db.Column(db.String(20), default='pending') # pending, approved, done, rejected
|
||||
priority = db.Column(db.String(20), default='normal') # normal, urgente, emergencia
|
||||
invoice_number = db.Column(db.String(60)) # número de factura/invoice
|
||||
notified_at = db.Column(db.DateTime)
|
||||
approved_at = db.Column(db.DateTime) # timestamp de aprobacion del owner
|
||||
approved_by_name = db.Column(db.String(100)) # nombre del owner que aprobó
|
||||
rejected_at = db.Column(db.DateTime)
|
||||
rejection_reason = db.Column(db.String(300))
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
completed_at = db.Column(db.DateTime)
|
||||
vessel = db.relationship('Vessel', backref='work_orders')
|
||||
|
||||
class Voucher(db.Model):
|
||||
__tablename__ = 'vouchers'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
charter_id = db.Column(db.Integer, db.ForeignKey('charters.id'))
|
||||
total_charged = db.Column(db.Float)
|
||||
fuel_actual_liters = db.Column(db.Float)
|
||||
deviation_charged = db.Column(db.Float, default=0)
|
||||
speed_extra_charged = db.Column(db.Float, default=0)
|
||||
tip_amount = db.Column(db.Float)
|
||||
tip_percentage = db.Column(db.Float, default=18)
|
||||
issued_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
paid_at = db.Column(db.DateTime)
|
||||
charter = db.relationship('Charter', backref='voucher')
|
||||
|
||||
class AccountingVessel(db.Model):
|
||||
__tablename__ = 'accounting_vessel'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
|
||||
month = db.Column(db.Integer)
|
||||
year = db.Column(db.Integer)
|
||||
charter_revenue = db.Column(db.Float, default=0)
|
||||
plan_revenue = db.Column(db.Float, default=0)
|
||||
fuel_cost = db.Column(db.Float, default=0)
|
||||
maintenance_cost = db.Column(db.Float, default=0)
|
||||
cleaning_cost = db.Column(db.Float, default=0)
|
||||
detailing_cost = db.Column(db.Float, default=0)
|
||||
teak_cost = db.Column(db.Float, default=0)
|
||||
net_profit = db.Column(db.Float, default=0)
|
||||
|
||||
# ── Ledger de transacciones por embarcación ──────────────────────────
|
||||
class AccountingEntry(db.Model):
|
||||
__tablename__ = 'accounting_entries'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'), nullable=False)
|
||||
date = db.Column(db.Date, nullable=False)
|
||||
entry_type = db.Column(db.String(10), nullable=False) # 'income' | 'expense'
|
||||
category = db.Column(db.String(30), nullable=False)
|
||||
# income: 'charter' | 'plan_subscription' | 'other_income'
|
||||
# expense: 'work_order' | 'fuel' | 'cleaning' | 'detailing' | 'teak' | 'marina' | 'other_expense'
|
||||
description = db.Column(db.String(300))
|
||||
amount = db.Column(db.Float, nullable=False)
|
||||
invoice_number = db.Column(db.String(60))
|
||||
reference_type = db.Column(db.String(20)) # 'charter' | 'work_order' | 'fuel_entry' | None
|
||||
reference_id = db.Column(db.Integer)
|
||||
notes = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
vessel = db.relationship('Vessel', backref='accounting_entries')
|
||||
|
||||
# ── Registro de combustible ──────────────────────────────────────────
|
||||
class FuelEntry(db.Model):
|
||||
__tablename__ = 'fuel_entries'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'), nullable=False)
|
||||
charter_id = db.Column(db.Integer, db.ForeignKey('charters.id'), nullable=True)
|
||||
date = db.Column(db.Date, nullable=False)
|
||||
liters = db.Column(db.Float)
|
||||
price_per_liter = db.Column(db.Float)
|
||||
total_cost = db.Column(db.Float, nullable=False)
|
||||
supplier = db.Column(db.String(100))
|
||||
invoice_number = db.Column(db.String(60))
|
||||
notes = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
vessel = db.relationship('Vessel', backref='fuel_entries')
|
||||
charter = db.relationship('Charter', backref='fuel_entries')
|
||||
|
||||
# ── Documentos adjuntos (invoices, contratos, fotos) ─────────────────
|
||||
class Document(db.Model):
|
||||
__tablename__ = 'documents'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
|
||||
reference_type = db.Column(db.String(20)) # 'charter'|'work_order'|'fuel_entry'|'general'
|
||||
reference_id = db.Column(db.Integer)
|
||||
doc_type = db.Column(db.String(20)) # 'invoice'|'receipt'|'contract'|'photo'|'other'
|
||||
filename = db.Column(db.String(200))
|
||||
original_name = db.Column(db.String(200))
|
||||
file_size = db.Column(db.Integer)
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
notes = db.Column(db.Text)
|
||||
vessel = db.relationship('Vessel', backref='documents')
|
||||
Reference in New Issue
Block a user