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>
597 lines
23 KiB
Python
597 lines
23 KiB
Python
"""
|
||
seed_data.py — Datos de demo completos para Fleet Management
|
||
Ejecutar: python seed_data.py
|
||
|
||
Cuentas creadas:
|
||
Admin: admin@fleet.com / admin123
|
||
Owner 1: rmitchell@email.com / owner123 (Robert Mitchell - 2 botes)
|
||
Owner 2: msantos@email.com / owner123 (Maria Santos - 2 botes)
|
||
Owner 3: cvega@email.com / owner123 (Carlos Vega - 1 bote)
|
||
Owner 4: jwilliams@email.com / owner123 (Jennifer Williams - 1 bote)
|
||
"""
|
||
|
||
from app import create_app, db
|
||
from app.models import Company, User, Vessel, Captain, Charter, WorkOrder, Voucher, AccountingEntry, FuelEntry
|
||
from werkzeug.security import generate_password_hash
|
||
from datetime import date
|
||
from datetime import datetime, timedelta
|
||
import random
|
||
|
||
app = create_app()
|
||
|
||
def get_or_create(model, defaults=None, **kwargs):
|
||
instance = model.query.filter_by(**kwargs).first()
|
||
if instance:
|
||
return instance, False
|
||
params = {**kwargs, **(defaults or {})}
|
||
instance = model(**params)
|
||
db.session.add(instance)
|
||
db.session.flush()
|
||
return instance, True
|
||
|
||
with app.app_context():
|
||
print("=" * 55)
|
||
print(" Fleet Management — Cargando datos de demo")
|
||
print("=" * 55)
|
||
|
||
# ============================================================
|
||
# MANAGEMENT COMPANY + ADMIN USER
|
||
# ============================================================
|
||
mgmt, _ = get_or_create(Company,
|
||
defaults={'type': 'management', 'phone': '305-900-0001'},
|
||
name='Al & Al Management LLC', email='admin@fleet.com')
|
||
|
||
admin_user, created = get_or_create(User,
|
||
defaults={
|
||
'name': 'Admin Fleet',
|
||
'password_hash': generate_password_hash('admin123'),
|
||
'company_id': mgmt.id,
|
||
'role': 'admin',
|
||
'is_active': True,
|
||
'is_super_admin': True
|
||
},
|
||
email='admin@fleet.com')
|
||
# Ensure existing admin is marked as super_admin
|
||
if not admin_user.is_super_admin:
|
||
admin_user.is_super_admin = True
|
||
if created:
|
||
print(" [+] Admin creado: admin@fleet.com / admin123")
|
||
else:
|
||
print(" [=] Admin ya existe")
|
||
|
||
db.session.commit()
|
||
|
||
# ============================================================
|
||
# OWNERS (companies + portal users)
|
||
# ============================================================
|
||
owners_data = [
|
||
{
|
||
'name': 'Robert Mitchell',
|
||
'email': 'rmitchell@email.com',
|
||
'phone': '305-555-2001',
|
||
},
|
||
{
|
||
'name': 'Maria Santos',
|
||
'email': 'msantos@email.com',
|
||
'phone': '305-555-2002',
|
||
},
|
||
{
|
||
'name': 'Carlos Vega',
|
||
'email': 'cvega@email.com',
|
||
'phone': '786-555-2003',
|
||
},
|
||
{
|
||
'name': 'Jennifer Williams',
|
||
'email': 'jwilliams@email.com',
|
||
'phone': '786-555-2004',
|
||
},
|
||
]
|
||
|
||
owner_companies = {}
|
||
for od in owners_data:
|
||
company, created = get_or_create(Company,
|
||
defaults={'type': 'owner', 'phone': od['phone']},
|
||
name=od['name'], email=od['email'])
|
||
owner_companies[od['name']] = company
|
||
|
||
user, ucreated = get_or_create(User,
|
||
defaults={
|
||
'name': od['name'],
|
||
'password_hash': generate_password_hash('owner123'),
|
||
'company_id': company.id,
|
||
'role': 'owner',
|
||
'is_active': True
|
||
},
|
||
email=od['email'])
|
||
if ucreated:
|
||
print(f" [+] Owner: {od['email']} / owner123")
|
||
else:
|
||
print(f" [=] Owner ya existe: {od['email']}")
|
||
|
||
db.session.commit()
|
||
|
||
# ============================================================
|
||
# CAPTAINS
|
||
# ============================================================
|
||
captains_data = [
|
||
{'name': 'Carlos Pérez', 'phone': '305-555-5001', 'license_number': 'USCG-REC-12345', 'hourly_rate': 55},
|
||
{'name': 'Miguel Torres', 'phone': '305-555-5002', 'license_number': 'USCG-REC-67890', 'hourly_rate': 60},
|
||
{'name': 'Roberto Díaz', 'phone': '786-555-5003', 'license_number': 'USCG-REC-11223', 'hourly_rate': 50},
|
||
]
|
||
captains = {}
|
||
for cd in captains_data:
|
||
cap, created = get_or_create(Captain,
|
||
defaults={
|
||
'phone': cd['phone'],
|
||
'license_number': cd['license_number'],
|
||
'hourly_rate': cd['hourly_rate'],
|
||
'company_id': mgmt.id
|
||
},
|
||
name=cd['name'])
|
||
captains[cd['name']] = cap
|
||
if created:
|
||
print(f" [+] Capitán: {cd['name']}")
|
||
|
||
db.session.commit()
|
||
|
||
# ============================================================
|
||
# VESSELS (6 embarcaciones)
|
||
# ============================================================
|
||
# plan_id: 1=Básico $199, 2=Estándar $399, 3=Mantenimiento $299, 4=Plus $599
|
||
vessels_data = [
|
||
{
|
||
'name': 'Blue Horizon',
|
||
'make': 'Sea Ray',
|
||
'model': '430 Sundancer',
|
||
'engines': '2 x Mercruiser 496 MAG',
|
||
'length': 43,
|
||
'fuel_consumption_14knots': 65,
|
||
'base_rate_4h': 1200,
|
||
'hourly_rate_extra': 300,
|
||
'charter_percentage': 25,
|
||
'plan_id': 4,
|
||
'owner': 'Robert Mitchell',
|
||
'hin': 'SRAY43001A222'
|
||
},
|
||
{
|
||
'name': 'Sol y Mar',
|
||
'make': 'Azimut',
|
||
'model': '38 Flybridge',
|
||
'engines': '2 x Volvo Penta D6-370',
|
||
'length': 38,
|
||
'fuel_consumption_14knots': 55,
|
||
'base_rate_4h': 950,
|
||
'hourly_rate_extra': 250,
|
||
'charter_percentage': 25,
|
||
'plan_id': 2,
|
||
'owner': 'Robert Mitchell',
|
||
'hin': 'AZMT38002B222'
|
||
},
|
||
{
|
||
'name': 'La Perla',
|
||
'make': 'Contender',
|
||
'model': '32 ST',
|
||
'engines': '2 x Yamaha F350',
|
||
'length': 32,
|
||
'fuel_consumption_14knots': 42,
|
||
'base_rate_4h': 750,
|
||
'hourly_rate_extra': 190,
|
||
'charter_percentage': 25,
|
||
'plan_id': 2,
|
||
'owner': 'Maria Santos',
|
||
'hin': 'CONT32003C222'
|
||
},
|
||
{
|
||
'name': 'Brisa Marina',
|
||
'make': 'Grady-White',
|
||
'model': 'Freedom 285',
|
||
'engines': '2 x Yamaha F150',
|
||
'length': 28,
|
||
'fuel_consumption_14knots': 30,
|
||
'base_rate_4h': 550,
|
||
'hourly_rate_extra': 140,
|
||
'charter_percentage': 25,
|
||
'plan_id': 1,
|
||
'owner': 'Maria Santos',
|
||
'hin': 'GRDY28004D222'
|
||
},
|
||
{
|
||
'name': 'Veloce',
|
||
'make': 'Scarab',
|
||
'model': '35 Sport',
|
||
'engines': '2 x Mercury Verado 400R',
|
||
'length': 35,
|
||
'fuel_consumption_14knots': 50,
|
||
'base_rate_4h': 850,
|
||
'hourly_rate_extra': 220,
|
||
'charter_percentage': 25,
|
||
'plan_id': 1,
|
||
'owner': 'Carlos Vega',
|
||
'hin': 'SCRB35005E222'
|
||
},
|
||
{
|
||
'name': 'Lady J',
|
||
'make': 'Sea Ray',
|
||
'model': '480 Sundancer',
|
||
'engines': '2 x Zeus Pod 600',
|
||
'length': 48,
|
||
'fuel_consumption_14knots': 75,
|
||
'base_rate_4h': 1500,
|
||
'hourly_rate_extra': 375,
|
||
'charter_percentage': 25,
|
||
'plan_id': 4,
|
||
'owner': 'Jennifer Williams',
|
||
'hin': 'SRAY48006F222'
|
||
},
|
||
]
|
||
|
||
vessels = {}
|
||
for vd in vessels_data:
|
||
owner_co = owner_companies[vd['owner']]
|
||
vessel, created = get_or_create(Vessel,
|
||
defaults={
|
||
'make': vd['make'],
|
||
'model': vd['model'],
|
||
'engines': vd['engines'],
|
||
'length': vd['length'],
|
||
'fuel_consumption_14knots': vd['fuel_consumption_14knots'],
|
||
'base_rate_4h': vd['base_rate_4h'],
|
||
'hourly_rate_extra': vd['hourly_rate_extra'],
|
||
'charter_percentage': vd['charter_percentage'],
|
||
'plan_id': vd['plan_id'],
|
||
'owner_company_id': owner_co.id,
|
||
'management_company_id': mgmt.id,
|
||
'hin': vd['hin'],
|
||
'is_active': True
|
||
},
|
||
name=vd['name'])
|
||
vessels[vd['name']] = vessel
|
||
if created:
|
||
print(f" [+] Bote: {vd['name']} ({vd['make']} {vd['model']}) — {vd['owner']}")
|
||
|
||
db.session.commit()
|
||
|
||
# ============================================================
|
||
# CHARTERS
|
||
# ============================================================
|
||
# Clientes reales con datos
|
||
charterers = [
|
||
{'name': 'Andrew Lawson', 'phone': '305-600-1001', 'email': 'alawson@gmail.com'},
|
||
{'name': 'Sophia Chen', 'phone': '305-600-1002', 'email': 'sophia.chen@mail.com'},
|
||
{'name': 'Marcus Johnson', 'phone': '786-600-1003', 'email': 'mjohnson@corp.com'},
|
||
{'name': 'Isabella Gomez', 'phone': '305-600-1004', 'email': 'igomez@gmail.com'},
|
||
{'name': 'David Park', 'phone': '786-600-1005', 'email': 'dpark@hotmail.com'},
|
||
{'name': 'Nicole Fontaine', 'phone': '305-600-1006', 'email': 'nfontaine@gmail.com'},
|
||
{'name': 'Tyler Brooks', 'phone': '786-600-1007', 'email': 'tbrooks@corp.com'},
|
||
{'name': 'Valentina Cruz', 'phone': '305-600-1008', 'email': 'vcruz@gmail.com'},
|
||
{'name': 'James Whitfield', 'phone': '305-600-1009', 'email': 'jwhitfield@mail.com'},
|
||
{'name': 'Camila Restrepo', 'phone': '786-600-1010', 'email': 'crestrepo@gmail.com'},
|
||
]
|
||
|
||
def calc_charter(vessel, hours):
|
||
base = vessel.base_rate_4h
|
||
extra = vessel.hourly_rate_extra
|
||
total = base if hours <= 4 else base + (hours - 4) * extra
|
||
pct = vessel.charter_percentage
|
||
mgmt_earn = round(total * pct / 100, 2)
|
||
owner_earn = round(total - mgmt_earn, 2)
|
||
return round(total, 2), mgmt_earn, owner_earn
|
||
|
||
now = datetime.utcnow()
|
||
charters_def = [
|
||
# Blue Horizon — 4 charters completados, 1 próximo
|
||
{'vessel': 'Blue Horizon', 'days_ago': 45, 'hours': 4, 'charterer': 0, 'status': 'completed'},
|
||
{'vessel': 'Blue Horizon', 'days_ago': 32, 'hours': 6, 'charterer': 3, 'status': 'completed'},
|
||
{'vessel': 'Blue Horizon', 'days_ago': 18, 'hours': 8, 'charterer': 6, 'status': 'completed'},
|
||
{'vessel': 'Blue Horizon', 'days_ago': 7, 'hours': 4, 'charterer': 9, 'status': 'completed'},
|
||
{'vessel': 'Blue Horizon', 'days_ago': -5, 'hours': 6, 'charterer': 1, 'status': 'signed'},
|
||
|
||
# Sol y Mar — 3 completados
|
||
{'vessel': 'Sol y Mar', 'days_ago': 38, 'hours': 5, 'charterer': 2, 'status': 'completed'},
|
||
{'vessel': 'Sol y Mar', 'days_ago': 21, 'hours': 4, 'charterer': 5, 'status': 'completed'},
|
||
{'vessel': 'Sol y Mar', 'days_ago': 10, 'hours': 6, 'charterer': 8, 'status': 'completed'},
|
||
|
||
# La Perla — 3 completados, 1 draft
|
||
{'vessel': 'La Perla', 'days_ago': 50, 'hours': 4, 'charterer': 1, 'status': 'completed'},
|
||
{'vessel': 'La Perla', 'days_ago': 29, 'hours': 4, 'charterer': 4, 'status': 'completed'},
|
||
{'vessel': 'La Perla', 'days_ago': 14, 'hours': 5, 'charterer': 7, 'status': 'completed'},
|
||
{'vessel': 'La Perla', 'days_ago': -3, 'hours': 4, 'charterer': 0, 'status': 'draft'},
|
||
|
||
# Brisa Marina — 2 completados
|
||
{'vessel': 'Brisa Marina', 'days_ago': 42, 'hours': 4, 'charterer': 3, 'status': 'completed'},
|
||
{'vessel': 'Brisa Marina', 'days_ago': 15, 'hours': 4, 'charterer': 6, 'status': 'completed'},
|
||
|
||
# Veloce — 3 completados, 1 signed
|
||
{'vessel': 'Veloce', 'days_ago': 55, 'hours': 4, 'charterer': 5, 'status': 'completed'},
|
||
{'vessel': 'Veloce', 'days_ago': 35, 'hours': 6, 'charterer': 2, 'status': 'completed'},
|
||
{'vessel': 'Veloce', 'days_ago': 12, 'hours': 4, 'charterer': 9, 'status': 'completed'},
|
||
{'vessel': 'Veloce', 'days_ago': -7, 'hours': 5, 'charterer': 4, 'status': 'signed'},
|
||
|
||
# Lady J — 4 completados, 1 próximo (el más caro)
|
||
{'vessel': 'Lady J', 'days_ago': 60, 'hours': 8, 'charterer': 7, 'status': 'completed'},
|
||
{'vessel': 'Lady J', 'days_ago': 40, 'hours': 6, 'charterer': 0, 'status': 'completed'},
|
||
{'vessel': 'Lady J', 'days_ago': 22, 'hours': 4, 'charterer': 3, 'status': 'completed'},
|
||
{'vessel': 'Lady J', 'days_ago': 8, 'hours': 10, 'charterer': 8, 'status': 'completed'},
|
||
{'vessel': 'Lady J', 'days_ago': -10,'hours': 6, 'charterer': 1, 'status': 'signed'},
|
||
]
|
||
|
||
charters_created = 0
|
||
vouchers_created = 0
|
||
for cd in charters_def:
|
||
vessel = vessels[cd['vessel']]
|
||
ch_data = charterers[cd['charterer']]
|
||
start_dt = now - timedelta(days=cd['days_ago'], hours=10)
|
||
total, mgmt_earn, owner_earn = calc_charter(vessel, cd['hours'])
|
||
|
||
existing = Charter.query.filter_by(
|
||
vessel_id=vessel.id,
|
||
charterer_name=ch_data['name'],
|
||
hours=cd['hours']
|
||
).filter(
|
||
Charter.start_datetime >= start_dt - timedelta(hours=2),
|
||
Charter.start_datetime <= start_dt + timedelta(hours=2)
|
||
).first()
|
||
|
||
if existing:
|
||
continue
|
||
|
||
charter = Charter(
|
||
vessel_id=vessel.id,
|
||
charterer_name=ch_data['name'],
|
||
charterer_phone=ch_data['phone'],
|
||
charterer_email=ch_data['email'],
|
||
start_datetime=start_dt,
|
||
hours=cd['hours'],
|
||
total_base_rate=total,
|
||
management_percentage=vessel.charter_percentage,
|
||
management_earnings=mgmt_earn,
|
||
owner_earnings=owner_earn,
|
||
status=cd['status'],
|
||
completed_at=start_dt + timedelta(hours=cd['hours']) if cd['status'] == 'completed' else None
|
||
)
|
||
db.session.add(charter)
|
||
db.session.flush()
|
||
charters_created += 1
|
||
|
||
# Generar voucher para charters completados
|
||
if cd['status'] == 'completed':
|
||
fuel = round(vessel.fuel_consumption_14knots * cd['hours'] * random.uniform(0.8, 1.0))
|
||
tip_pct = random.choice([15, 18, 20])
|
||
voucher = Voucher(
|
||
charter_id=charter.id,
|
||
total_charged=total,
|
||
fuel_actual_liters=fuel,
|
||
deviation_charged=0,
|
||
tip_amount=round(total * tip_pct / 100, 2),
|
||
tip_percentage=tip_pct,
|
||
issued_at=charter.completed_at,
|
||
paid_at=charter.completed_at + timedelta(days=1)
|
||
)
|
||
db.session.add(voucher)
|
||
vouchers_created += 1
|
||
|
||
db.session.commit()
|
||
print(f"\n [+] Charters creados: {charters_created} ({vouchers_created} con voucher)")
|
||
|
||
# ============================================================
|
||
# WORK ORDERS
|
||
# ============================================================
|
||
wo_data = [
|
||
{
|
||
'vessel': 'Blue Horizon',
|
||
'description': 'Cambio de aceite y filtros motores - servicio 200h',
|
||
'estimated_cost': 850, 'actual_cost': 820,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 40
|
||
},
|
||
{
|
||
'vessel': 'Blue Horizon',
|
||
'description': 'Revisión sistema de dirección hidráulica - pérdida de fluido detectada',
|
||
'estimated_cost': 450, 'actual_cost': None,
|
||
'status': 'approved', 'priority': 'urgente', 'days_ago': 5
|
||
},
|
||
{
|
||
'vessel': 'Sol y Mar',
|
||
'description': 'Limpieza de teca y aplicación de teak oil',
|
||
'estimated_cost': 600, 'actual_cost': 580,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 25
|
||
},
|
||
{
|
||
'vessel': 'Sol y Mar',
|
||
'description': 'Actualización software GPS Garmin y radar',
|
||
'estimated_cost': 200, 'actual_cost': None,
|
||
'status': 'pending', 'priority': 'normal', 'days_ago': 3
|
||
},
|
||
{
|
||
'vessel': 'La Perla',
|
||
'description': 'Reemplazo baterías principales (banco 4 x AGM)',
|
||
'estimated_cost': 1200, 'actual_cost': 1150,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 30
|
||
},
|
||
{
|
||
'vessel': 'La Perla',
|
||
'description': 'Pintura fondo antifouling - temporada anual',
|
||
'estimated_cost': 1800, 'actual_cost': None,
|
||
'status': 'pending', 'priority': 'normal', 'days_ago': 2
|
||
},
|
||
{
|
||
'vessel': 'Brisa Marina',
|
||
'description': 'Cambio impellers bombas de agua dulce y salada',
|
||
'estimated_cost': 320, 'actual_cost': 310,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 20
|
||
},
|
||
{
|
||
'vessel': 'Veloce',
|
||
'description': 'Servicio anual motores Mercury Verado (aceite, filtros, bujías)',
|
||
'estimated_cost': 1400, 'actual_cost': 1380,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 35
|
||
},
|
||
{
|
||
'vessel': 'Veloce',
|
||
'description': 'Falla en sistema eléctrico principal — luces de navegación inoperativas. BOTE NO PUEDE SALIR A CHARTER.',
|
||
'estimated_cost': 1100, 'actual_cost': None,
|
||
'status': 'pending', 'priority': 'emergencia', 'days_ago': 1
|
||
},
|
||
{
|
||
'vessel': 'Lady J',
|
||
'description': 'Detailing completo exterior e interior - plan Plus anual',
|
||
'estimated_cost': 2200, 'actual_cost': 2200,
|
||
'status': 'done', 'priority': 'normal', 'days_ago': 50
|
||
},
|
||
{
|
||
'vessel': 'Lady J',
|
||
'description': 'Revisión y ajuste sistema Zeus Pod - vibración anormal a más de 12 nudos',
|
||
'estimated_cost': 950, 'actual_cost': None,
|
||
'status': 'pending', 'priority': 'urgente', 'days_ago': 1
|
||
},
|
||
]
|
||
|
||
wo_created = 0
|
||
for wd in wo_data:
|
||
vessel = vessels[wd['vessel']]
|
||
existing = WorkOrder.query.filter_by(
|
||
vessel_id=vessel.id,
|
||
description=wd['description']
|
||
).first()
|
||
if existing:
|
||
continue
|
||
created_date = now - timedelta(days=wd['days_ago'])
|
||
wo = WorkOrder(
|
||
vessel_id=vessel.id,
|
||
requested_by_company_id=mgmt.id,
|
||
description=wd['description'],
|
||
estimated_cost=wd['estimated_cost'],
|
||
actual_cost=wd['actual_cost'],
|
||
status=wd['status'],
|
||
priority=wd.get('priority', 'normal'),
|
||
created_at=created_date,
|
||
completed_at=created_date + timedelta(days=3) if wd['status'] == 'done' else None,
|
||
approved_by_owner_id=1 if wd['status'] in ('approved', 'done') else None
|
||
)
|
||
db.session.add(wo)
|
||
wo_created += 1
|
||
|
||
db.session.commit()
|
||
print(f" [+] Work Orders creadas: {wo_created}")
|
||
|
||
# ============================================================
|
||
# ACCOUNTING ENTRIES (desde charters y WOs existentes)
|
||
# ============================================================
|
||
acc_created = 0
|
||
|
||
# Income: charters completados → 75% va al owner
|
||
all_charters = Charter.query.filter_by(status='completed').all()
|
||
for ch in all_charters:
|
||
exists = AccountingEntry.query.filter_by(
|
||
vessel_id=ch.vessel_id, reference_type='charter', reference_id=ch.id
|
||
).first()
|
||
if exists:
|
||
continue
|
||
if not ch.owner_earnings:
|
||
continue
|
||
entry_date = ch.completed_at.date() if ch.completed_at else (
|
||
ch.start_datetime.date() if ch.start_datetime else date.today())
|
||
db.session.add(AccountingEntry(
|
||
vessel_id=ch.vessel_id,
|
||
date=entry_date,
|
||
entry_type='income',
|
||
category='charter',
|
||
description=f'Charter – {ch.charterer_name} – {ch.hours}h',
|
||
amount=round(ch.owner_earnings, 2),
|
||
reference_type='charter',
|
||
reference_id=ch.id,
|
||
invoice_number=f'CHR-{ch.id:04d}'
|
||
))
|
||
acc_created += 1
|
||
|
||
# Expense: WOs completadas
|
||
all_wos = WorkOrder.query.filter_by(status='done').all()
|
||
for wo in all_wos:
|
||
exists = AccountingEntry.query.filter_by(
|
||
vessel_id=wo.vessel_id, reference_type='work_order', reference_id=wo.id
|
||
).first()
|
||
if exists:
|
||
continue
|
||
cost = wo.actual_cost or wo.estimated_cost or 0
|
||
if cost <= 0:
|
||
continue
|
||
entry_date = wo.completed_at.date() if wo.completed_at else date.today()
|
||
db.session.add(AccountingEntry(
|
||
vessel_id=wo.vessel_id,
|
||
date=entry_date,
|
||
entry_type='expense',
|
||
category='work_order',
|
||
description=f'Mantenimiento – {wo.description[:80]}',
|
||
amount=round(cost, 2),
|
||
reference_type='work_order',
|
||
reference_id=wo.id,
|
||
invoice_number=f'WO-{wo.id:04d}'
|
||
))
|
||
acc_created += 1
|
||
|
||
# Expense: combustible de vouchers (si tiene fuel_actual_liters)
|
||
fuel_price = 1.45 # $/litro aprox
|
||
all_vouchers = Voucher.query.filter(Voucher.fuel_actual_liters > 0).all()
|
||
for v in all_vouchers:
|
||
ch = Charter.query.get(v.charter_id)
|
||
if not ch:
|
||
continue
|
||
exists = AccountingEntry.query.filter_by(
|
||
vessel_id=ch.vessel_id, reference_type='fuel_entry',
|
||
reference_id=v.id
|
||
).first()
|
||
if exists:
|
||
continue
|
||
fuel_cost = round(v.fuel_actual_liters * fuel_price, 2)
|
||
entry_date = v.issued_at.date() if v.issued_at else date.today()
|
||
# FuelEntry record
|
||
fuel_rec = FuelEntry(
|
||
vessel_id=ch.vessel_id,
|
||
charter_id=ch.id,
|
||
date=entry_date,
|
||
liters=v.fuel_actual_liters,
|
||
price_per_liter=fuel_price,
|
||
total_cost=fuel_cost,
|
||
supplier='Marina Fuel Station',
|
||
invoice_number=f'FUEL-V{v.id:04d}'
|
||
)
|
||
db.session.add(fuel_rec)
|
||
db.session.flush()
|
||
db.session.add(AccountingEntry(
|
||
vessel_id=ch.vessel_id,
|
||
date=entry_date,
|
||
entry_type='expense',
|
||
category='fuel',
|
||
description=f'Combustible – {v.fuel_actual_liters:.0f}L charter {ch.charterer_name}',
|
||
amount=fuel_cost,
|
||
reference_type='fuel_entry',
|
||
reference_id=fuel_rec.id,
|
||
invoice_number=f'FUEL-V{v.id:04d}'
|
||
))
|
||
acc_created += 1
|
||
|
||
db.session.commit()
|
||
print(f" [+] Asientos contables generados: {acc_created}")
|
||
|
||
# ============================================================
|
||
# RESUMEN
|
||
# ============================================================
|
||
print()
|
||
print("=" * 55)
|
||
print(" DATOS CARGADOS EXITOSAMENTE")
|
||
print("=" * 55)
|
||
print()
|
||
print(" ACCESOS:")
|
||
print(" Admin: admin@fleet.com / admin123")
|
||
print(" Owner 1: rmitchell@email.com / owner123")
|
||
print(" Owner 2: msantos@email.com / owner123")
|
||
print(" Owner 3: cvega@email.com / owner123")
|
||
print(" Owner 4: jwilliams@email.com / owner123")
|
||
print()
|
||
print(" DATOS:")
|
||
print(f" - {len(owner_companies)} duenos | 6 embarcaciones | 3 capitanes")
|
||
print(f" - {Charter.query.count()} charters | {WorkOrder.query.count()} work orders | {Voucher.query.count()} vouchers")
|
||
print(f" - {AccountingEntry.query.count()} asientos contables | {FuelEntry.query.count()} registros de combustible")
|
||
print()
|
||
print(" URL: http://localhost:5010")
|
||
print("=" * 55)
|