コード例 #1
0
def build_single_movement_query(dbsession, owner, period):
    """Build a query that lists the unreconciled movements in open periods.

    Return a query providing these columns:

    - transfer_id
    - date
    - delta
    - movement_ids
    """
    movement_date_c = func.date(
        func.timezone(get_tzname(owner), func.timezone('UTC',
                                                       FileMovement.ts)))

    return (dbsession.query(
        TransferRecord.transfer_id,
        movement_date_c.label('date'),
        file_movement_delta.label('delta'),
        array([FileMovement.movement_id]).label('movement_ids'),
    ).select_from(FileMovement).join(
        TransferRecord,
        TransferRecord.id == FileMovement.transfer_record_id).join(
            Period, Period.id == FileMovement.period_id).filter(
                FileMovement.owner_id == owner.id,
                FileMovement.file_id == period.file_id,
                FileMovement.reco_id == null,
                file_movement_delta != 0,
                ~Period.closed,
            ))
コード例 #2
0
 def __init__(self, owner):
     self.table = FileMovement
     self.date_c = func.date(
         func.timezone(get_tzname(owner),
                       func.timezone('UTC', FileMovement.ts)))
     self.id_c = FileMovement.movement_id
     self.plural = 'movements'
コード例 #3
0
    def list_bundle_records(self):
        """List bundle transfers that contain unreconciled bundled movements.
        """
        dbsession = self.dbsession
        owner = self.owner
        period = self.period

        # bundle_transfer_ids_cte lists the bundle transfer IDs
        # of the unreconciled bundled movements for this file.
        bundle_transfer_ids_cte = (dbsession.query(
            TransferRecord.bundle_transfer_id).select_from(FileMovement).join(
                TransferRecord,
                TransferRecord.id == FileMovement.transfer_record_id).join(
                    Period, Period.id == FileMovement.period_id).filter(
                        FileMovement.owner_id == owner.id,
                        FileMovement.file_id == period.file_id,
                        FileMovement.reco_id == null,
                        file_movement_delta != 0,
                        TransferRecord.bundle_transfer_id != null,
                        ~Period.closed,
                    ).distinct().cte('bundle_transfer_ids_cte'))

        record_date_c = func.date(
            func.timezone(get_tzname(owner),
                          func.timezone('UTC', TransferRecord.start)))

        bundle_records = (dbsession.query(
            TransferRecord.transfer_id,
            TransferRecord.bundled_transfers,
            record_date_c.label('date'),
        ).filter(
            TransferRecord.owner_id == owner.id,
            TransferRecord.transfer_id.in_(bundle_transfer_ids_cte),
            TransferRecord.bundled_transfers != null,
            func.jsonb_array_length(TransferRecord.bundled_transfers) > 0,
        ).order_by(TransferRecord.start, TransferRecord.transfer_id).all())

        log.info("BundleFinder: %s unreconciled bundle(s) for period %s",
                 len(bundle_records), period.id)

        return bundle_records
コード例 #4
0
def push_recos(request, period):
    """Push all the recos in a period to other open periods.

    Create a new period if necessary.

    This is done in preparation for deleting the period.
    """
    dbsession = request.dbsession
    owner = request.owner
    owner_id = owner.id
    assert period.owner_id == owner_id

    entry_date_c = (dbsession.query(func.min(AccountEntry.entry_date)).filter(
        AccountEntry.reco_id == Reco.id).correlate(Reco).as_scalar())

    movement_date_c = (dbsession.query(
        func.date(
            func.timezone(get_tzname(owner),
                          func.timezone('UTC', func.min(FileMovement.ts))))
    ).select_from(FileMovement).filter(
        FileMovement.reco_id == Reco.id).correlate(Reco).as_scalar())

    future = datetime.date.today() + datetime.timedelta(days=366 * 100)

    # reco_date_c provides the date of each reco.
    # Recos with no entries or movements have no date, so fall back to
    # an arbitrary date 100+ years in the future as the reco date.
    # The arbitrary date does not get stored (unless the user has created
    # a period for 100+ years in the future.)
    reco_date_c = func.coalesce(entry_date_c, movement_date_c, future)

    # List the dates of all recos in this period.
    reco_rows = (dbsession.query(
        reco_date_c,
        Reco.id).select_from(Reco).filter(Reco.period_id == period.id).all())

    if not reco_rows:
        # There are no reconciliations in the period.
        return 0

    # List the other open periods for the peer.
    period_list = (dbsession.query(Period).filter(
        Period.owner_id == owner_id, Period.file_id == period.file_id,
        ~Period.closed, Period.id != period.id).all())

    # List the recos to reassign.
    days = set()
    reco_ids = []
    for day, reco_id in reco_rows:
        days.add(day)
        if reco_id is not None:
            reco_ids.append(reco_id)

    # Map the days to periods.
    day_periods, day_period_cte, missing_period = make_day_period_cte(
        days=sorted(days), period_list=period_list)

    # If no period is available for some of the recos,
    # create a new period.
    if missing_period:
        new_period = add_open_period(request=request,
                                     file_id=period.file_id,
                                     event_type='add_period_for_push_reco',
                                     has_vault=period.has_vault)
        new_period_id = new_period.id
    else:
        new_period_id = None

    # Reassign the recos.
    subq = (dbsession.query(day_period_cte.c.period_id).filter(
        day_period_cte.c.day == reco_date_c).as_scalar())
    (dbsession.query(Reco).filter(Reco.id.in_(reco_ids)).update(
        {'period_id': func.coalesce(subq, new_period_id)},
        synchronize_session='fetch'))

    # Reassign the period_id of affected movements.
    subq = (dbsession.query(
        Reco.period_id).filter(Reco.id == FileMovement.reco_id).as_scalar())
    (dbsession.query(FileMovement).filter(
        FileMovement.reco_id.in_(reco_ids)).update(
            {'period_id': subq}, synchronize_session='fetch'))

    # Reassign the period_id of affected account entries.
    subq = (dbsession.query(
        Reco.period_id).filter(Reco.id == AccountEntry.reco_id).as_scalar())
    (dbsession.query(AccountEntry).filter(
        AccountEntry.reco_id.in_(reco_ids)).update(
            {'period_id': subq}, synchronize_session='fetch'))

    dbsession.add(
        OwnerLog(owner_id=owner_id,
                 personal_id=request.personal_id,
                 event_type='push_recos',
                 content={
                     'period_id': period.id,
                     'file_id': period.file_id,
                     'reco_ids': reco_ids,
                     'day_periods': day_periods,
                     'new_period_id': new_period_id,
                 }))

    return len(reco_ids)
コード例 #5
0
def pull_recos(request, period):
    """Pull recos from other open periods into this period.
    """
    dbsession = request.dbsession
    owner = request.owner
    owner_id = owner.id
    assert period.owner_id == owner_id

    # reco_filter finds the recos that might need to be pulled.
    reco_filter = and_(
        Reco.owner_id == owner_id,
        Reco.period_id != period.id,
        Period.file_id == period.file_id,
        ~Period.closed,
    )

    entry_date_c = (dbsession.query(func.min(AccountEntry.entry_date)).filter(
        AccountEntry.reco_id == Reco.id).correlate(Reco).as_scalar())

    movement_date_c = (dbsession.query(
        func.date(
            func.timezone(get_tzname(owner),
                          func.timezone('UTC', func.min(FileMovement.ts))))
    ).filter(FileMovement.reco_id == Reco.id).correlate(Reco).as_scalar())

    # reco_date_c provides the date of each reco. Note that
    # some recos have no account entries or movements; they have a date
    # of None. We don't want to move those recos into this period.
    reco_date_c = func.coalesce(entry_date_c, movement_date_c)

    # List the dates of all recos in other open periods
    # for the same peer loop.
    day_rows = (dbsession.query(reco_date_c).select_from(Reco).join(
        Period,
        Period.id == Reco.period_id).filter(reco_filter).distinct().all())

    if not day_rows:
        # There are no recos to pull in.
        return 0

    # List the dates of the recos to pull in.
    reassign_days = []
    period_list = [period]
    for (day, ) in day_rows:
        if day is not None and get_period_for_day(
                period_list, day, default_endless=False) is period:
            reassign_days.append(day)

    if not reassign_days:
        # None of the recos found should be pulled in to this period.
        return 0

    # Map the reassignable recos to this period.
    # (Recos for other periods will not be listed in day_period_cte.)
    day_periods, day_period_cte, missing_period = make_day_period_cte(
        days=sorted(reassign_days),
        period_list=period_list,
        default_endless=False)

    # List the recos to reassign.
    reco_id_rows = (dbsession.query(Reco.id).join(
        Period, Period.id == Reco.period_id).join(
            day_period_cte,
            day_period_cte.c.day == reco_date_c).filter(reco_filter).all())

    reco_ids = [reco_id for (reco_id, ) in reco_id_rows]

    # Reassign recos.
    (dbsession.query(Reco).filter(Reco.id.in_(reco_ids)).update(
        {'period_id': period.id}, synchronize_session='fetch'))

    # Reassign the period_id of affected movements.
    (dbsession.query(FileMovement).filter(
        FileMovement.reco_id.in_(reco_ids)).update(
            {'period_id': period.id}, synchronize_session='fetch'))

    # Reassign the period_id of affected account entries.
    (dbsession.query(AccountEntry).filter(
        AccountEntry.reco_id.in_(reco_ids)).update(
            {'period_id': period.id}, synchronize_session='fetch'))

    dbsession.add(
        OwnerLog(owner_id=owner_id,
                 personal_id=request.personal_id,
                 event_type='pull_recos',
                 content={
                     'period_id': period.id,
                     'file_id': period.file_id,
                     'reco_ids': reco_ids,
                     'day_periods': day_periods,
                 }))

    return len(reco_ids)