Przeglądaj źródła

Add new dialog to facilitate creating new products

Daniel Sheffield 3 lat temu
rodzic
commit
5fb78759ff

+ 8 - 9
app/activities/PriceCheck.py

@@ -132,13 +132,6 @@ class PriceCheck(FocusWidget):
     def __init__(self, query_manager, fields,
         top_pane, left_pane, right_pane, bottom_pane, badge,
         autocomplete_cb):
-        super().__init__([2,0,1], [
-                [2,0,0,], [2,0,2], [2,2,0], [2,2,2],
-                [3,0,2,], [3,0,3,], [3,0,4,],
-                [3,1,3,],
-                [4,0,0,],
-            ]
-        )
         self.query_manager = query_manager
         self.top_pane = top_pane
         self.left_pane = left_pane
@@ -266,5 +259,11 @@ class PriceCheck(FocusWidget):
         ])
         widget = urwid.Filler(widget, 'top') 
         widget = urwid.AttrMap(widget, 'bg')
-        self.original_widget = widget
-        widget.original_widget.original_widget.set_focus_path([3,0,0])
+        widget.original_widget.original_widget.set_focus_path([3,0,0])
+        super().__init__(widget, [2,0,1], [
+                [2,0,0,], [2,0,2], [2,2,0], [2,2,2],
+                [3,0,2,], [3,0,3,], [3,0,4,],
+                [3,1,3,],
+                [4,0,0,],
+            ]
+        )

+ 5 - 6
app/activities/TransactionEditor.py

@@ -83,11 +83,6 @@ class TransactionEditor(FocusWidget):
     def __init__(self, query_manager, fields,
         layout, side_pane, bottom_pane,
         save_and_clear_cb, autocomplete_cb):
-        super().__init__([2,0,0,0], [
-            [4,],
-            [5,],
-            [7,],
-        ])
         self.organic_checkbox = NoTabCheckBox(
             u"Organic",
             on_state_change=self.apply_organic_state
@@ -163,4 +158,8 @@ class TransactionEditor(FocusWidget):
         ])
         widget = urwid.Filler(widget, 'top') 
         widget = urwid.AttrMap(widget, 'bg')
-        self.original_widget = widget
+        super().__init__(widget, [2,0,0,0], [
+            [4,],
+            [5,],
+            [7,],
+        ])

+ 32 - 0
app/activities/__init__.py

@@ -11,3 +11,35 @@ def show_or_exit(key):
     
     if key in ('esc',):
         raise urwid.ExitMainLoop()
+
+class ActivityManager(object):
+
+    def __init__(self):
+        self.widgets = dict()
+        self.app = None
+    
+    def add(self, widget, name):
+        self.widgets[name] = widget
+        return widget
+    
+    def get(self, name):
+        if name in self.widgets:
+            return self.widgets[name]
+        return None
+    
+    def create(self, cls, name, *args, **kwargs):
+        widget = cls(*args, **kwargs)
+        if name is not None:
+            self.add(widget, name)
+        return widget
+    
+    def show(self, widget):
+        if self.app is not None:
+            self.app.original_widget = widget
+            return
+        self.app = widget
+
+    def current(self):
+        if self.app is None:
+            return None
+        return self.app.original_widget

+ 7 - 2
app/db_utils.py

@@ -98,12 +98,14 @@ def suggestions(cur, statement, name, display, exclude=NON_IDENTIFIER_COLUMNS, *
         x, strict=[ k for k in kwargs if k != name ], **kwargs
     ), get_data(cur, statement, display))
 
+def get_insert_product_statement(product, category, group):
+    return f'CALL insert_product($prod${product}$prod$, $category${category}$category$, $group${group}$group$)'
+
 class QueryManager(object):
     
-    def __init__(self, activity_manager, cursor, display):
+    def __init__(self, cursor, display):
         self.display = display
         self.cursor = cursor
-        self.activity_manager = activity_manager
     
     def get_historic_prices(self, rating_cb, sort, product, unit, organic=None, limit='365 days'):
         statement = get_historic_prices_statement(sort, product, unit, organic=organic, limit=limit)
@@ -132,3 +134,6 @@ class QueryManager(object):
 
     def unique_suggestions(self, name, **kwargs):
         return unique_suggestions(self.cursor, get_transactions_statement(), name, self.display, **kwargs)
+
+    def insert_new_product(self, product, category, group):
+        self.cursor.execute(get_insert_product_statement(product, category, group))

+ 76 - 24
app/widgets.py

@@ -4,10 +4,14 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
+from collections import OrderedDict
 import urwid
 from decimal import Decimal
 from urwid import numedit
 
+import app
+from app.db_utils import NON_IDENTIFIER_COLUMNS
+
 class AutoCompleteEdit(urwid.Edit):
     def __init__(self, name, *args, apply_change_func=None, **kwargs):
         if isinstance(name, tuple):
@@ -121,8 +125,8 @@ class NoTabCheckBox(urwid.CheckBox):
 
 class FocusWidget(urwid.WidgetPlaceholder):
 
-    def __init__(self, initial_focus, skip_focus):
-        super().__init__(urwid.SolidFill(u'/'))
+    def __init__(self, widget, initial_focus, skip_focus):
+        super().__init__(widget)
         self._initial_focus = tuple([ i for i in initial_focus ])
         self._skip_focus = tuple([ i for i in skip_focus ])
     
@@ -226,29 +230,77 @@ class SuggestionPopup(urwid.Overlay):
 
         return super().keypress(size, key)
 
-class ActivityManager(object):
+class NewProduct(urwid.Overlay):
 
-    def __init__(self):
-        self.widgets = dict()
-        self.app = None
-    
-    def add(self, widget, name):
-        self.widgets[name] = widget
-        return widget
-    
-    def get(self, name):
-        if name in self.widgets:
-            return self.widgets[name]
-        raise Exception("Widget {name} not found")
+    def __init__(self, query_manager, under, name, data, autocomplete_cb, change_cb, apply_cb, esc_cb):
+        self.esc_cb = esc_cb
+        self.under = under
+        self._data = data
+        self.query_manager = query_manager
+        self.name = name
+        
+        title = urwid.Text('Enter Product Info')
+        self.fields = OrderedDict()
+        for f in ('product', 'category', 'group'):
+            w = AutoCompleteEdit(
+                    ('bg', f),
+                    apply_change_func=lambda name: autocomplete_cb(name)
+                )
+            self.fields[f] = w
+            w.set_edit_text(data[f])
+            urwid.connect_signal(w, 'change', change_cb(f))
+
+        ok = urwid.Button('Done', on_press=lambda w: apply_cb(**self.data))
+
+        body = urwid.AttrMap(urwid.ListBox(urwid.SimpleListWalker([
+                urwid.Pile([
+                    urwid.AttrMap(title, 'banner'),
+                    urwid.Divider(),
+                    *[
+                        urwid.AttrMap(
+                            urwid.LineBox(v, title=k.title(), title_align='left'), 'streak'
+                        ) for k,v in self.fields.items()
+                    ],
+                    ok,
+                ], focus_item=2)
+            ])
+        ), 'streak')
+        super().__init__(body, under,
+            align='center', width=('relative',40),
+            valign='middle', height=('relative',30),
+            min_width=20, min_height=9)
+
+    @property
+    def data(self):
+        return dict([(k,v) for k,v in self._data.items() if k in ('product', 'category', 'group')])
     
-    def create(self, cls, name, *args, **kwargs):
-        widget = cls(*args, **kwargs)
-        if name is not None:
-            self.add(widget, name)
-        return widget
+    def _apply_choice(self, name, value):
+        self._data.update({
+            name: value
+        })
+        for k,v in self.data.items():
+            if k == name or v:
+                continue
+            options = self.query_manager.unique_suggestions(k, exclude=[self.name, *NON_IDENTIFIER_COLUMNS], **self.data)
+            if len(options) == 1 and k != 'ts':
+                self._data.update({
+                    k: list(options)[0]
+                })
     
-    def show(self, widget):
-        if self.app is not None:
-            self.app.original_widget = widget
+    def apply_choice(self, name):
+        return  lambda w,x: self._apply_choice(name, x)
+
+    def update(self):
+        for k, v in self.data.items():
+            self.fields[k].set_edit_text(v)
+        return self
+
+    def keypress(self, size, key):
+        if key == 'esc':
+            self.esc_cb()
             return
-        self.app = widget
+
+        if key == 'tab':
+            return
+
+        return super().keypress(size, key)

+ 53 - 15
grocery_transactions.py

@@ -9,13 +9,18 @@ import time
 import itertools
 import sys
 import urwid
-from app.db_utils import QueryManager
+from app.db_utils import (
+    NON_IDENTIFIER_COLUMNS,
+    QueryManager,
+    get_insert_product_statement
+)
 from app.widgets import (
+    NewProduct,
     SuggestionPopup,
-    ActivityManager,
 )
 from app.activities import (
     show_or_exit,
+    ActivityManager,
 )  
 from app.activities.TransactionEditor import TransactionEditor
 
@@ -75,24 +80,57 @@ display_map = {
 }
 display = lambda data, name: display_map[name](data) if name in display_map else data
 
-def _apply_choice_callback(activity_manager, name, widget, value):
-    txn = activity_manager.get('transaction')
-    txn.apply_choice(name)(widget, value)
-    activity_manager.show(txn.update())
+def _apply_choice_callback(activity_manager, base, name, widget, value):
+    base = activity_manager.get(base)
+    base.apply_choice(name)(widget, value)
+    activity_manager.show(base.update())
 
-def _show_suggestions_callback(activity_manager, name, options):
-    txn = activity_manager.get('transaction')
+def _show_suggestions_callback(activity_manager, base, name, options):
+    cur = activity_manager.current()
     activity_manager.show(
         activity_manager.create(SuggestionPopup, None,
-            txn.original_widget, name, options,
-            lambda w,x: _apply_choice_callback(activity_manager, name, w, x),
-            lambda: activity_manager.show(txn.update())
+            cur, name, options,
+            lambda w,x: _apply_choice_callback(activity_manager, base, name, w, x),
+            lambda: activity_manager.show(cur)
         ))
 
-def _autocomplete_callback(activity_manager, query_manager, name, data):
+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 t: _autocomplete_callback(activity_manager, query_manager, 'new_product', 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)
+        )
+    )
+
+def _autocomplete_callback(
+    activity_manager: ActivityManager,
+    query_manager: QueryManager,
+    base: str,
+    name: str, data: dict
+):
     options = query_manager.unique_suggestions(name, **data)
     if len(options) > 0:
-        _show_suggestions_callback(activity_manager, name, options)
+        _show_suggestions_callback(activity_manager, base, name, 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')
@@ -170,12 +208,12 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
 cur.execute("BEGIN")
 
 activity_manager = ActivityManager()
-query_manager = QueryManager(activity_manager, cur, display)
+query_manager = QueryManager(cur, display)
 
 activity_manager.create(TransactionEditor, 'transaction',
     query_manager, cols, grid_layout, side_pane, bottom_pane,
     lambda: _save_and_clear_callback(activity_manager),
-    lambda name, data: _autocomplete_callback(activity_manager, query_manager, name, data))
+    lambda name, data: _autocomplete_callback(activity_manager, query_manager, 'transaction', name, data))
 
 app = GroceryTransactionEditor(activity_manager, cur, log)
 

+ 2 - 2
price_check.py

@@ -11,10 +11,10 @@ from app import COPYRIGHT
 from app.db_utils import QueryManager
 from app.widgets import (
     SuggestionPopup,
-    ActivityManager,
 )
 from app.activities import (
     show_or_exit,
+    ActivityManager,
 )
 from app.activities.PriceCheck import PriceCheck
 try:
@@ -114,7 +114,7 @@ class GroceryPriceCheck(urwid.WidgetPlaceholder):
 cur.execute("BEGIN")
 
 activity_manager = ActivityManager()
-query_manager = QueryManager(activity_manager, cur, display)
+query_manager = QueryManager(cur, display)
 
 activity_manager.create(PriceCheck, 'price_check',
     query_manager, inputs, top_pane, left_pane, right_pane, bottom_pane, badge,