Skip to content
This repository has been archived by the owner on Sep 5, 2022. It is now read-only.

Commit

Permalink
Add acc subtask 'show'
Browse files Browse the repository at this point in the history
* Add acc subtask 'show'
* Harmonize '__init__' hooks
* Rewrite 'filter' method to output object instead of list
* Move timestamp generation to utility functions & clean code
* Fix 'filterBy' method return typehint

Also, this fixes #42
  • Loading branch information
S1SYPHOS committed Jun 17, 2021
1 parent 314c9c7 commit 82359ab
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 93 deletions.
72 changes: 62 additions & 10 deletions knv_cli/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def rank(config, year, quarter, months, enable_chart, limit):
Rank sales
'''

# Fallback to current year
# Fall back to current year
if year is None: year = pendulum.today().year

# Make months into list if provided
Expand Down Expand Up @@ -415,7 +415,7 @@ def prepare(config, year, quarter, months):
Generate cheatsheet for accounting mode
'''

# Fallback to current year
# Fall back to current year
if year is None: year = pendulum.today().year

# Make months into list if provided
Expand Down Expand Up @@ -465,7 +465,7 @@ def run(config, year, quarter, months):

click.echo('Accounting mode ON')

# Fallback to current year
# Fall back to current year
if year is None: year = pendulum.today().year

# Make months into list if provided
Expand Down Expand Up @@ -507,7 +507,7 @@ def run(config, year, quarter, months):

# Go through all unmatched payments
# TODO: Substitute '_children'
for count, payment in enumerate(handler._children):
for index, payment in enumerate(handler._children):
# Skip payments already marked in previous session
if payment.assigned() or last_session.has(payment, identifier):
# Proceed to next payment
Expand All @@ -516,7 +516,7 @@ def run(config, year, quarter, months):
# Wrap logic in case session gets cut short ..
try:
# Print current payment identifier
click.echo('Payment No. {}:'.format(str(count + 1)))
click.echo('Payment No. {}:'.format(str(index + 1)))
pretty_print(payment.export())

# Declare payment hit accuracy as 'manually assigned'
Expand Down Expand Up @@ -656,7 +656,7 @@ def pdf(config, year, quarter, months):
Create merged PDF invoices
'''

# Fallback to current year
# Fall back to current year
if year is None: year = pendulum.today().year

# Make months into list if provided
Expand Down Expand Up @@ -739,6 +739,46 @@ def pdf(config, year, quarter, months):
click.echo(' done.')


@acc.command()
@pass_config
@click.option('-y', '--year', default=None, help='Year.')
@click.option('-m', '--month', default=None, help='Month.')
def show(config, year, month):
'''
Show pending invoices
'''

# Fall back to current year
if year is None: year = pendulum.today().year

# Validate month
# (1) Convert input to integer
month = 0 if month is None else int(month)

# (2) Ensure validity of provided value
while not 1 <= month <= 12: month = click.prompt('Please enter month (1-12)', type=int)

# Initialize database
db = Database(config)

# Initialize invoices
invoices = db.get_invoices().filterBy('month', year, month).unassigned()._children

# Print
try:
for index, invoice in enumerate(invoices):
if invoice.is_revenue():
click.echo('Invoice No. {}:'.format(str(index + 1)))
pretty_print(invoice.export())

if not click.confirm('Show next invoice? ({} left)'.format(len(invoices) - index + 1), default=True):
break

except click.Abort: pass

click.echo('Exiting ..')


@acc.command()
@pass_config
@click.option('-y', '--year', default=None, help='Year.')
Expand All @@ -750,9 +790,17 @@ def report(config, year, quarter, years_back, enable_chart):
Generate revenue report
'''

# Fallback to current year
# Fall back to current year
if year is None: year = pendulum.today().year

# Determine report period & time range
period = 'year'
months = range(1, 13)

if quarter is not None:
period = 'quarter'
months = [month + 3 * (int(quarter) - 1) for month in [1, 2, 3]]

# Initialize database
db = Database(config)

Expand All @@ -763,9 +811,13 @@ def report(config, year, quarter, years_back, enable_chart):

data = {}

for i in range(0, 1 + int(years_back)):
this_year = str(int(year) - i)
data[this_year] = handler.profit_report(this_year, quarter)
for gap in range(0, 1 + int(years_back)):
current_year = str(int(year) - gap)
data[current_year] = handler.filterBy(period, current_year, quarter).profit_report()

# Fill missing months with zeroes
for month in months:
if month not in data[current_year]: data[current_year][month] = float(0)

df = DataFrame(data, index=list(data.values())[0].keys())

Expand Down
6 changes: 3 additions & 3 deletions knv_cli/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def __init__(self):
'credentials': './login.log',
}

# Load config provided by user
# Load config provided by user ..
config_file = join(xdg_config_home(), 'knv-cli', 'config')

if isfile(config_file):
config.read(config_file)
# .. if it exists
if isfile(config_file): config.read(config_file)

# Apply resulting config
self.config = config
Expand Down
20 changes: 9 additions & 11 deletions knv_cli/cli/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from os.path import basename, join
from zipfile import ZipFile

import pendulum

from ..processors.gateways.paypal import Paypal
from ..processors.gateways.volksbank import Volksbank
from ..processors.knv.infos import InfoProcessor
Expand All @@ -19,7 +17,7 @@
from ..structures.payments.paypal import PaypalPayments
from ..structures.payments.volksbank import VolksbankPayments
from ..utils import load_json, dump_json
from ..utils import build_path, dedupe, group_data, sort_data
from ..utils import build_path, dedupe, group_data, sort_data, timestamp
from .session import Session


Expand Down Expand Up @@ -109,7 +107,7 @@ def rebuild_data(self):
handler.load_files(self.order_files, 'orders', True)

for code, data in group_data(handler.process().data).items():
dump_json(sort_data(data), join(self.config.database_dir, code + '.json'))
dump_json(sort_data(data), join(self.config.database_dir, '{}.json'.format(code)))


def rebuild_infos(self) -> None:
Expand All @@ -124,7 +122,7 @@ def rebuild_infos(self) -> None:

# Split infos per-month & export them
for code, data in group_data(handler.data).items():
dump_json(sort_data(data), join(self.config.info_dir, code + '.json'))
dump_json(sort_data(data), join(self.config.info_dir, '{}.json'.format(code)))


def rebuild_invoices(self) -> None:
Expand All @@ -149,7 +147,7 @@ def rebuild_invoices(self) -> None:

# Split invoice data per-month & export it
for code, data in group_data(handler.data).items():
dump_json(sort_data(data), join(invoice_dir, 'data', code + '.json'))
dump_json(sort_data(data), join(invoice_dir, 'data', '{}.json'.format(code)))


def rebuild_orders(self) -> None:
Expand All @@ -164,7 +162,7 @@ def rebuild_orders(self) -> None:

# Split orders per-month & export them
for code, data in group_data(handler.data).items():
dump_json(sort_data(data), join(self.config.order_dir, code + '.json'))
dump_json(sort_data(data), join(self.config.order_dir, '{}.json'.format(code)))


def rebuild_payments(self) -> None:
Expand All @@ -184,7 +182,7 @@ def rebuild_payments(self) -> None:

# Split payments per-month & export them
for code, data in group_data(handler.data).items():
dump_json(sort_data(data), join(self.config.payment_dir, identifier, code + '.json'))
dump_json(sort_data(data), join(self.config.payment_dir, identifier, '{}.json'.format(code)))


# GET methods
Expand Down Expand Up @@ -291,11 +289,11 @@ def import_session(self) -> None:
session_data = load_json(self.session_files[identifier])

# (2) Merge data with current session
session_data.update(load_json(build_path(self.sessions_dir, identifier + '_*.json')))
session_data.update(load_json(build_path(self.sessions_dir, '{}_*.json'.format(identifier))))

# (3) Write changes to disk
for code, data in group_data(session_data).items():
dump_json(sort_data(data), join(self.sessions_dir, identifier, code + '.json'))
dump_json(sort_data(data), join(self.sessions_dir, identifier, '{}.json'.format(code)))


def load_session(self) -> Session:
Expand All @@ -322,4 +320,4 @@ def save_session(self, data: list, identifier: str) -> None:
data = {item.identifier(): item.export() for item in data}

# Save results
dump_json(data, join(self.sessions_dir, identifier + '_' + pendulum.now().strftime('%Y-%m-%d_%I-%M-%S') + '.json'))
dump_json(data, join(self.sessions_dir, '{identifier}_{timestamp}.json'.format(identifier=identifier, timestamp=timestamp())))
4 changes: 0 additions & 4 deletions knv_cli/structures/endpoint.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from abc import abstractmethod

from .framework import Framework


class Endpoint(Framework):
# PROPS

def __init__(self, data: dict) -> None:
self.data = data

Expand Down
2 changes: 2 additions & 0 deletions knv_cli/structures/framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class Framework(metaclass=ABCMeta):
data = None


# ADMINISTRATION methods

@property
def parent(self) -> Framework:
return self._parent
Expand Down
33 changes: 26 additions & 7 deletions knv_cli/structures/invoices/invoices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from operator import itemgetter
# Works with Python v3.10+
# See https://stackoverflow.com/a/33533514
from __future__ import annotations

import pendulum
from operator import itemgetter

from ..waypoint import Waypoint
from .expense import Expense
Expand All @@ -9,10 +11,27 @@


class Invoices(Waypoint):
def __init__(self, invoices: dict) -> None:
# Initialize 'Molecule' props
super().__init__()
def load(self, data: tuple) -> None:
invoices, = data

# Build composite structure
for data in invoices.values():
self.add(self.invoice_types[data['Rechnungsart']](data))
for item in invoices.values():
self.add(self.invoice_types[item['Rechnungsart']](item))


def assigned(self) -> Invoices:
handler = Invoices()

for child in self._children:
if child.assigned(): handler.add(child)

return handler


def unassigned(self) -> Invoices:
handler = Invoices()

for child in self._children:
if not child.assigned(): handler.add(child)

return handler
9 changes: 4 additions & 5 deletions knv_cli/structures/orders/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@


class Order(Waypoint):
def __init__(self, data: dict) -> None:
# Initialize 'Waypoint' props
super().__init__()
# CORE methods

def load(self, data: tuple):
# Unpack tuple & load data
data, = data
self.data = data


# CORE methods

def export(self) -> list:
return self.data

Expand Down
34 changes: 13 additions & 21 deletions knv_cli/structures/orders/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,37 @@


class Orders(Waypoint):
def __init__(self, orders: dict, invoices: dict) -> None:
# Initialize 'Waypoint' props
super().__init__()
# CORE methods

# Build composite structure
for data in orders.values():
order = Order(data)
def load(self, data: tuple) -> None:
orders, invoices = data

if isinstance(data['Rechnungen'], dict):
for item in orders.values():
order = Order(item)

if isinstance(item['Rechnungen'], dict):
# Ensure validity & availability of each invoice
for invoice in [invoices[invoice] for invoice in data['Rechnungen'].keys() if invoice in invoices]:
for invoice in [invoices[invoice] for invoice in item['Rechnungen'].keys() if invoice in invoices]:
order.add(self.invoice_types[invoice['Rechnungsart']](invoice))

self.add(order)


# ACCOUNTING methods

def profit_report(self, year: str, quarter: str = None) -> dict:
def profit_report(self) -> dict:
data = {}

# Select orders matching given time period
for item in self.filter(year, quarter):
if item.month() not in data:
data[item.month()] = []
for child in self._children:
if child.month() not in data:
data[child.month()] = []

data[item.month()].append(float(item.amount()))
data[child.month()].append(float(child.amount()))

# Assign data to respective month
data = {int(month): sum(amount) for month, amount in data.items()}

# Determine appropriate month range
month_range = self.month_range(quarter)

# Fill missing months with zeroes
for i in month_range:
if i not in data:
data[i] = float(0)

# Sort results
return {k: data[k] for k in sorted(data)}

Expand Down
Loading

0 comments on commit 82359ab

Please sign in to comment.