# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + pycharm={"name": "#%%\n"}
"""install dependencies"""
# !pip install sqlalchemy
# for use in main.py
# !pip install click

# +
"""create in-memory database"""
from sqlalchemy import (Column, Integer, Boolean, Float, DateTime, MetaData, Table, create_engine, CheckConstraint)

engine = create_engine('sqlite://', echo=False) # in-memory database
meta = MetaData()

pago = Table('PAGO', meta,
    Column('id_pago', Integer, primary_key=True, nullable=False),
    Column('id_contrato', Integer,nullable=False),
    Column('id_cliente', Integer,nullable=False),
    CheckConstraint("""typeof(id_contrato) = "integer" & id_contrato >= 0
    & typeof(id_cliente) = "integer" & id_cliente >= 0
    & typeof(id_pago) = "integer" & id_pago >= 0
    & typeof(monto) = "float" & monto > 0""", name='is a natural number'),

    Column('fecha', DateTime,nullable=False),
    Column('monto', Float,nullable=False),
    Column('fecha_registro', DateTime,nullable=False),
    Column('activo', Boolean,nullable=False)
).create(engine)

# + pycharm={"name": "#%%\n"}
"""define ORM functionality"""

from typing import Optional
from sqlalchemy.sql import Update,Select
from sqlalchemy.orm import Session,declarative_base
from sqlalchemy import select,desc,update
from datetime import datetime

Base = declarative_base()
DATE_FORMAT = '%d-%m-%Y'

class Pago(Base):
    __tablename__: str = "PAGO"

    # Identificador del pago
    id_pago = Column(Integer, nullable=False, primary_key=True, autoincrement=True)

    id_contrato = Column(Integer, nullable=False)  # Identificador del contrato
    id_cliente = Column(Integer, nullable=False)  # Identificador del cliente
    fecha = Column(DateTime, nullable=False)  # Fecha de pago
    monto = Column(Float, nullable=False)  # Monto del pago
    # true si el registro está vigente, false si el registro ya no es válido (eliminado lógico)
    activo = Column(Boolean, default=True)
    fecha_registro = Column(DateTime, default=datetime.now())  # Fecha de registro del pago

    @classmethod
    def select_latest_pagos(cls, id_contrato: int, id_cliente: int, activo=True)->Select:
        return select(cls).where(
                cls.id_contrato == id_contrato,
                cls.id_cliente == id_cliente,
                cls.activo == activo,
            ).order_by(desc(cls.fecha))

    @classmethod
    def replace_consecutive_ids(cls, starting_id_pago:int, id_contrato: int) -> Update:
        """replace consecutive ids found, i.e rows with (id_pago > starting_id_pago)"""
        return update(cls).where(
                cls.id_pago > starting_id_pago,
                cls.id_contrato == id_contrato,
                cls.activo).values(id_pago=cls.id_pago + 1)

    @classmethod
    def add_registry(cls, _session, pago_to_add):
        """related logic for specific constraint and business logic
        05 august 2021.- Se pueden recibir pagos con fechas anteriores a los pagos ya registrados
        de un contrato, es decir, si ya existen N pagos con fechas F0,...,FN
        en la tabla de pagos y se recibe un pago con fecha F' donde
        F' < {Fk,...,Fm} (F' es anterior a 1 o varios pagos de un contrato),
        se desactivarán todos los pagos del contrato posteriores a F',
        se insertará el nuevo pago con fecha F' y se insertarán nuevos registros
        para los pagos que ya existían (posteriores a F'), de tal manera que
        para todos los pagos de un mismo contrato si Fi < Fj
        entonces id_pago[i] < id_pago[j]"""

        ID_CONTRATO = pago_to_add.id_contrato

        LATEST_PAGO_STM = cls.select_latest_pagos(
            id_contrato=pago_to_add.id_contrato, id_cliente=pago_to_add.id_cliente
        ).limit(1)
        # https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Result.scalars
        latest_pago: Optional[cls] = _session.execute(LATEST_PAGO_STM).scalars().first()

        if latest_pago is None: # no related info found
            _session.add(pago_to_add)
        else: # info found
            if pago_to_add.fecha > latest_pago.fecha:
                _session.add(pago_to_add)
            else:
                UPDATE_STM = cls.replace_consecutive_ids(pago_to_add. id_pago,ID_CONTRATO)
                _session.execute(UPDATE_STM)

    def __repr__(self):
        fecha,id_contrato,monto = self.fecha.strftime(DATE_FORMAT),self.id_contrato,self.monto
        return f"Pago con {fecha=}, {id_contrato=}, {monto=}"

"ok"

# + pycharm={"name": "#%%\n"}
"""a. Escribir una función en Python que reciba como parámetro los datos de un pago e inserte el pago en la tabla considerando la regla 2 anterior."""
PAGOS_TO_INSERT = [
    Pago(id_contrato=12, id_cliente=99, fecha=datetime(2021,8,5), monto=7000),
    Pago(id_contrato=12, id_cliente=99, fecha=datetime(2021,8,6), monto=1280),
    Pago(id_contrato=12, id_cliente=99, fecha=datetime(2021,8,7), monto=4900),
    Pago(id_contrato=12, id_cliente=99, fecha=datetime(2021,8,4), monto=4900)
]

if __name__ == '__main__':
    with Session(engine) as session:
        # table = list(session.execute(select(Pago)).scalars().all())
        table = list(session.execute(select(Pago)))
        for row in table:
            print(row)

        for _pago in PAGOS_TO_INSERT:
            Pago.add_registry(session, _pago)
            session.commit()

# + pycharm={"name": "#%%\n"}
with Session(engine) as session:
    found = list(session.execute(
    select(Pago).where(
        # Pago.id_pago > 4,
        Pago.id_contrato == 12,
        Pago.activo == True)
    ))

# + pycharm={"name": "#%%\n"}
"""b. Habilitar interfaz de cualquier tipo (web, terminal, etc.) para interactuar con la función descrita en el punto anterior"""
# el archivo fue llamado main.py
"""c. Usar sqlalchemy para modelar la tabla e interactuar con ella (insert, update, select, etc.)."""
# hecho
"""d. Estructura el código de tal manera que sea modular"""
# hecho
"""e. Agrega validaciones y manejo de errores, debe ser a prueba de todo."""
# hecho

StarcoderdataJupyterScriptsDedupFiltered