123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import itertools
- import sys
- from sqlite3 import Cursor
- from typing import Union
- import urwid
- from urwid import raw_display
- from app.activities import ActivityManager, show_or_exit
- from app.activities.NewProduct import NewProduct
- from app.activities.TransactionEditor import TransactionEditor
- from app.db_utils import (QueryManager, display_mapper,
- get_insert_product_statement)
- from app.widgets import AutoCompleteEdit, AutoCompleteFloatEdit
- try:
- from db_credentials import HOST, PASSWORD
- host = f'host={HOST}'
- password = f'password={PASSWORD}'
- except:
- host = ''
- password = ''
- try:
- import os
- import psycopg2
- user = os.getenv('USER')
- conn: Cursor = psycopg2.connect(f"{host} dbname=das user={user} {password}")
- cur = conn.cursor()
- except:
- print('Failed to set up db connection. Entering Mock mode')
- from mock import *
- dark_palette = [
- ("popup_focus", "light red", "light gray"),
- ('popup', 'light red', 'dark gray'),
- ('banner', 'light gray', 'dark red'),
- ('streak', 'light red', 'dark gray'),
- ('bg', 'light red', 'black'),
- ]
- light_palette = [
- ("popup_focus", "dark blue", "light gray"),
- ('popup', 'light blue', 'dark gray'),
- ('banner', 'light gray', 'dark red'),
- ('streak', 'white', 'dark gray'),
- ('bg', 'white', 'dark blue'),
- ]
- grid_layout = [
- [ 'ts', 'store', ],
- [ 'organic', 'product', ],
- [ 'category', 'group', ],
- ]
- side_pane = [
- 'unit',
- 'quantity',
- 'price',
- ]
- bottom_pane = [
- 'description',
- 'dbview',
- ]
- cols = [
- c for c in filter(
- lambda x: x is not None,
- itertools.chain(
- *grid_layout, side_pane, set(bottom_pane) - set(['dbview'])
- )
- )
- ]
- def _apply_choice_callback(
- activity_manager: ActivityManager,
- base: str, name: str, value: str
- ):
- base = activity_manager.get(base)
- base.apply_choice(name, value)
- activity_manager.show(base.update())
- def _insert_new_product_callback(activity_manager, query_manager, product, category, group):
- activity_manager.app.log.write(
- '{};\n'.format(get_insert_product_statement(product, category, group)))
- query_manager.insert_new_product(product, category, group)
- activity_manager.show(activity_manager.get('transaction').update())
- def _new_product_callback(
- activity_manager: ActivityManager,
- query_manager: QueryManager,
- name: str, data: dict
- ):
- cur = activity_manager.current()
- txn : TransactionEditor = activity_manager.get('transaction')
- activity_manager.show(
- activity_manager.create(NewProduct, 'new_product',
- query_manager, cur, name, txn.data,
- lambda w, t, data: _autocomplete_callback(activity_manager, query_manager, w, t, data),
- txn.apply_changes,
- lambda product, category, group: _insert_new_product_callback(
- activity_manager, query_manager, product, category, group),
- lambda: activity_manager.show(cur.update()),
- lambda name, value: _apply_choice_callback(activity_manager, 'new_product', name, value)
- )
- )
- def _autocomplete_callback(
- activity_manager: ActivityManager,
- query_manager: QueryManager,
- widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
- name: str, data: dict
- ):
- options = query_manager.unique_suggestions(name, **data)
- if len(options) > 0:
- widget._emit('open', options)
- elif len(options) == 0 and activity_manager.current() is not activity_manager.get('new_product'):
- if name in ('product', 'category', 'group'):
- _new_product_callback(activity_manager, query_manager, name, data)
- def _save_and_clear_callback(activity_manager):
- txn = activity_manager.get('transaction')
- activity_manager.app.save(txn.data)
- txn.clear()
- txn.focus_on_product()
- activity_manager.show(txn.update())
- args = sys.argv
- log = args[1]
- class GroceryTransactionEditor(urwid.WidgetPlaceholder):
- def __init__(self, activity_manager, cur, log):
- super().__init__(urwid.SolidFill(u'/'))
- self.activity_manager = activity_manager
- self.cur = cur
- txn: TransactionEditor = self.activity_manager.get('transaction')
- with open(log, 'r') as f:
- date = None
- store = None
- for line in f.readlines():
- if date is None and store is None:
- if '$store$' in line:
- date, store, _= line.split('$store$')
- date = date.split("'")[1]
- else:
- assert None not in (date, store,), \
- "Both date and store should be set or neither should be set"
- if '$store$' in line:
- assert date in line and f'$store${store}$store$' in line, \
- "Date ({date}) and store ({store}) not found in {line}."\
- " Mixing transactions from different dates and stores is not supported"
-
-
- self.cur.execute(line)
-
- if None not in (date, store):
- txn.apply_choice('ts', date)
- txn.apply_choice('store', store)
- self.activity_manager.show(self)
- self.activity_manager.show(txn.update())
- self.log = self.open(log)
-
- def _open(self, log):
- with open(log, 'a') as f:
- yield f
-
- def open(self, log):
- self._to_close = self._open(log)
- return next(self._to_close)
-
- def close(self):
- if self._to_close is not None:
- self._to_close.close()
- self._to_close = None
-
- def save(self, data):
- ts = data['ts']
- store = data['store']
- description = data['description']
- quantity = data['quantity']
- unit = data['unit']
- price = data['price']
- product = data['product']
- organic = data['organic'] if data['organic'] else 'false'
- statement = \
- f"CALL insert_transaction('{ts}', $store${store}$store$, " \
- f"$descr${description}$descr$, {quantity}, $unit${unit}$unit$, " \
- f"{price}, $produ${product}$produ$, {organic});\n"
- self.log.write(statement)
- self.cur.execute(statement)
- cur.execute("BEGIN")
- activity_manager = ActivityManager()
- query_manager = QueryManager(cur, display_mapper)
- activity_manager.create(TransactionEditor, 'transaction',
- query_manager, cols, grid_layout, side_pane, bottom_pane,
- lambda: _save_and_clear_callback(activity_manager),
- lambda widget, name, data: _autocomplete_callback(activity_manager, query_manager, widget, name, data),
- lambda name, value: _apply_choice_callback(activity_manager, 'transaction', name, value),
- )
- app = None
- def iter_palettes():
- palettes = [dark_palette, light_palette]
- while True:
- p = palettes.pop(0)
- palettes.append(p)
- yield p
- palettes = iter_palettes()
- palette = next(palettes)
- try:
- app = GroceryTransactionEditor(activity_manager, cur, log)
- screen = raw_display.Screen()
- loop = urwid.MainLoop(app, palette, screen=screen,
- unhandled_input=lambda k: show_or_exit(k, screen=screen, palettes=palettes),
- pop_ups=True
- )
- loop.run()
-
- finally:
- if app is not None:
- app.close()
- cur.close()
- conn.close()
|