5b7b41aa50
- 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>
192 lines
9.2 KiB
Python
192 lines
9.2 KiB
Python
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')
|