class GUI: def __init__(self, main): Figure = None self.main = main self.builder = Gtk.Builder() self.builder.add_from_file(UI_FILE) self.builder.connect_signals(self) self.db = main.db self.cursor = self.db.cursor() self.store = self.builder.get_object('unpaid_invoice_store') self.window = self.builder.get_object('window') self.window.show_all() main.unpaid_invoices_window = self.window self.date_calendar = DateTimeCalendar() self.date_calendar.connect("day-selected", self.date_selected) amount_column = self.builder.get_object ('treeviewcolumn3') amount_renderer = self.builder.get_object ('cellrenderertext3') amount_column.set_cell_data_func(amount_renderer, self.amount_cell_func) def present (self): self.window.present() def amount_cell_func(self, column, cellrenderer, model, iter1, data): amount = model.get_value(iter1, 6) cellrenderer.set_property("text" , str(amount)) def destroy(self, window): self.main.unpaid_invoices_window = None self.cursor.close() def invoice_chart_clicked (self, button): global Figure if Figure == None: from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas from matplotlib.pyplot import pie self.figure = Figure(figsize=(4, 4), dpi=100) canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea canvas.set_size_request(800, 500) overlay = self.builder.get_object('overlay1') overlay.add (canvas) a = self.figure.add_subplot(111) labels = list() fractions = list() unpaid = 0 self.cursor.execute("SELECT SUM(amount_due), c.name FROM invoices " "JOIN contacts AS c ON c.id = invoices.customer_id " "WHERE (canceled, paid, posted) = " "(False, False, True) GROUP BY customer_id, c.name " "ORDER BY SUM(amount_due)") for row in self.cursor.fetchall(): customer_total = row[0] customer_name = row[1] fractions.append(customer_total) labels.append(customer_name) unpaid += 1 if unpaid == 0: labels.append("None") fractions.append(1.00) a.pie(fractions, labels=labels, autopct='%1.f%%', radius=0.7) window = self.builder.get_object('window1') window.show_all() def unpaid_chart_window_delete_event (self, window, event): window.hide() return True def date_entry_icon_released (self, entry, icon, position): self.date_calendar.set_relative_to(entry) self.date_calendar.show_all() def date_selected (self, calendar): self.date = calendar.get_date() button = self.builder.get_object('button6') button.set_sensitive(True) button.set_label("Yes, cancel invoice") entry = self.builder.get_object('entry1') entry.set_text(calendar.get_text()) def cancel_dialog (self, widget): button = self.builder.get_object('button6') button.set_sensitive(False) button.set_label("No date selected") cancel_dialog = self.builder.get_object('dialog1') message = "Do you want to cancel %s ?\nThis is not reversible!" % self.invoice_name self.builder.get_object('label1').set_label(message) response = cancel_dialog.run() cancel_dialog.hide() if response == Gtk.ResponseType.ACCEPT: transactor.cancel_invoice(self.db, self.date, self.invoice_id) self.cursor.execute("UPDATE invoices SET canceled = True " "WHERE id = %s" "; " "UPDATE serial_numbers " "SET invoice_item_id = NULL " "WHERE invoice_item_id IN " "(SELECT id FROM invoice_items " "WHERE invoice_id = %s)", (self.invoice_id, self.invoice_id)) self.db.commit() self.treeview_populate () def view_invoice(self, widget): treeselection = self.builder.get_object('treeview-selection') model, path = treeselection.get_selected_rows () if path != []: tree_iter = model.get_iter(path) invoice_id = model.get_value(tree_iter, 0) self.cursor.execute("SELECT name, pdf_data FROM invoices " "WHERE id = %s", (invoice_id ,)) for cell in self.cursor.fetchall(): file_name = cell[0] + ".pdf" file_data = cell[1] f = open("/tmp/" + file_name,'wb') f.write(file_data) subprocess.call("xdg-open /tmp/" + str(file_name), shell = True) f.close() def focus(self, window, event): self.treeview_populate() def treeview_populate(self): treeview_selection = self.builder.get_object('treeview-selection') model, path = treeview_selection.get_selected_rows() model.clear() c = self.db.cursor() c.execute("SELECT " "i.id, " "i.name, " "c.id, " "c.name, " "dated_for::text, " "format_date(dated_for), " "amount_due " "FROM invoices AS i " "JOIN contacts AS c ON i.customer_id = c.id " "WHERE (canceled, paid, posted) = " "(False, False, True) " "ORDER BY i.id") tupl = c.fetchall() for row in tupl: model.append(row) if path != [] and tupl != []: treeview_selection.select_path(path) self.builder.get_object('treeview1').scroll_to_cell(path) c.execute("SELECT COALESCE(i.amount_due - pi.amount, 0.00)::money " "FROM (SELECT SUM(amount_due) AS amount_due FROM invoices " "WHERE (posted, canceled, active) = (True, False, True)) i, " "(SELECT SUM(amount) AS amount FROM payments_incoming " "WHERE (misc_income) = (False)) pi ") unpaid = c.fetchone()[0] self.builder.get_object('label3').set_label(unpaid) c.close() def row_activated(self, treeview, path, treeviewcolumn): treeiter = self.store.get_iter(path) self.invoice_id = self.store.get_value(treeiter, 0) self.invoice_name = self.store.get_value(treeiter, 1) self.contact_id = self.store.get_value(treeiter, 2) self.builder.get_object('button1').set_sensitive(True) self.builder.get_object('button2').set_sensitive(True) self.builder.get_object('button4').set_sensitive(True) def payment_window (self, widget): selection = self.builder.get_object('treeview-selection') model, path = selection.get_selected_rows() if path == []: return customer_id = model[path][2] import customer_payment customer_payment.GUI(self.main, customer_id) def new_statement (self, widget): new_statement.GUI(self.db)
class CreditMemoGUI: credit_memo_template = None def __init__(self): self.builder = Gtk.Builder() self.builder.add_from_file(UI_FILE) self.builder.connect_signals(self) self.cursor = DB.cursor() self.customer_store = self.builder.get_object('customer_store') self.product_store = self.builder.get_object('credit_products_store') self.credit_items_store = self.builder.get_object('credit_items_store') self.handler_ids = list() for connection in (("contacts_changed", self.populate_customer_store), ): handler = broadcaster.connect(connection[0], connection[1]) self.handler_ids.append(handler) self.populate_customer_store() self.date_returned_calendar = DateTimeCalendar() self.date_returned_calendar.connect('day-selected', self.return_day_selected) date_column = self.builder.get_object('label3') self.date_returned_calendar.set_relative_to(date_column) self.date_calendar = DateTimeCalendar() self.date_calendar.connect('day-selected', self.day_selected) product_completion = self.builder.get_object('product_completion') product_completion.set_match_func(self.product_match_func) customer_completion = self.builder.get_object('customer_completion') customer_completion.set_match_func(self.customer_match_func) self.window = self.builder.get_object('window1') self.window.show_all() def window_destroy(self, window): for handler in self.handler_ids: broadcaster.disconnect(handler) self.cursor.close() def customer_match_func(self, completion, key, iter): split_search_text = key.split() for text in split_search_text: if text not in self.customer_store[iter][1].lower(): return False return True def product_match_selected(self, completion, model, _iter_): invoice_item_id = model[_iter_][0] selection = self.builder.get_object('treeview-selection1') model, path = selection.get_selected_rows() if path == []: return self.update_product_row(path, invoice_item_id) def product_renderer_changed(self, combo, path, tree_iter): invoice_item_id = self.product_store[tree_iter][0] self.update_product_row(path, invoice_item_id) def update_product_row(self, path, invoice_item_id): c = DB.cursor() iter_ = self.credit_items_store.get_iter(path) self.check_row_id(iter_) row_id = self.credit_items_store[iter_][0] c.execute( "WITH tax_cte AS " "(SELECT tr.rate / 100 AS rate, price " "FROM invoice_items AS ii " "JOIN tax_rates AS tr ON tr.id = ii.tax_rate_id " "WHERE ii.id = %s" ") " "UPDATE credit_memo_items AS cmi " "SET (invoice_item_id, " "price, " "ext_price, " "tax" ") " "= " "(%s, " "(SELECT price FROM tax_cte), " "qty * (SELECT price FROM tax_cte), " "qty * (SELECT price FROM tax_cte) * (SELECT rate FROM tax_cte)" ") " "WHERE id = %s; " #new sql; this updates values "SELECT " "p.id, " "p.name, " "p.ext_name, " "ii.price::text, " "cmi.ext_price::text, " "cmi.tax::text, " "cmi.invoice_item_id, " "ii.invoice_id " "FROM credit_memo_items AS cmi " "JOIN invoice_items AS ii ON ii.id = cmi.invoice_item_id " "JOIN products AS p ON p.id = ii.product_id " "WHERE cmi.id = %s", (invoice_item_id, invoice_item_id, row_id, row_id)) for row in c.fetchall(): self.credit_items_store[iter_][2] = row[0] self.credit_items_store[iter_][3] = row[1] self.credit_items_store[iter_][4] = row[2] self.credit_items_store[iter_][5] = row[3] self.credit_items_store[iter_][6] = row[4] self.credit_items_store[iter_][7] = row[5] self.credit_items_store[iter_][8] = row[6] self.credit_items_store[iter_][9] = row[7] self.calculate_totals() def product_editing_started(self, renderer, combo, path): renderer_invoice = Gtk.CellRendererText() combo.pack_start(renderer_invoice, True) combo.add_attribute(renderer_invoice, "text", 2) renderer_date = Gtk.CellRendererText() combo.pack_start(renderer_date, True) combo.add_attribute(renderer_date, "text", 3) entry = combo.get_child() entry.set_completion(self.builder.get_object('product_completion')) def price_edited(self, cellrenderer, path, text): c = DB.cursor() iter_ = self.credit_items_store.get_iter(path) self.check_row_id(iter_) row_id = self.credit_items_store[iter_][0] invoice_item_id = self.credit_items_store[iter_][8] try: c.execute( "WITH tax_cte AS " "(SELECT tr.rate / 100 AS rate " "FROM invoice_items AS ii " "JOIN tax_rates AS tr ON tr.id = ii.tax_rate_id " "WHERE ii.id = %s" ") " "UPDATE credit_memo_items " "SET " "(price, " "ext_price, " "tax) " "= " "(%s, " "qty*%s, " "qty*%s*(SELECT rate FROM tax_cte)) " "WHERE id = %s " "RETURNING price::text, ext_price::text, tax::text", (invoice_item_id, text, text, text, row_id)) except psycopg2.DataError as e: self.show_message(str(e)) DB.rollback() return for row in c.fetchall(): price = row[0] ext_price = row[1] tax = row[2] self.credit_items_store[iter_][5] = price self.credit_items_store[iter_][6] = ext_price self.credit_items_store[iter_][7] = tax c.close() self.calculate_totals() def qty_edited(self, cellrenderertext, path, text): c = DB.cursor() iter_ = self.credit_items_store.get_iter(path) self.check_row_id(iter_) row_id = self.credit_items_store[iter_][0] invoice_item_id = self.credit_items_store[iter_][8] try: c.execute( "WITH tax_cte AS " "(SELECT tr.rate / 100 AS rate " "FROM invoice_items AS ii " "JOIN tax_rates AS tr ON tr.id = ii.tax_rate_id " "WHERE ii.id = %s" ") " "UPDATE credit_memo_items " "SET " "(qty, " "ext_price, " "tax) " "= " "(%s, " "%s*price, " "%s*price*(SELECT rate FROM tax_cte)) " "WHERE id = %s " "RETURNING qty::text, ext_price::text", (invoice_item_id, text, text, text, row_id)) except psycopg2.DataError as e: self.show_message(str(e)) DB.rollback() return for row in c.fetchall(): qty = row[0] ext_price = row[1] self.credit_items_store[iter_][1] = qty self.credit_items_store[iter_][6] = ext_price c.close() self.calculate_totals() def tax_edited(self, cellrendererspin, path, text): iter_ = self.credit_items_store.get_iter(path) self.check_row_id(iter_) row_id = self.credit_items_store[iter_][0] try: self.cursor.execute( "UPDATE credit_memo_items " "SET tax = %s " "WHERE id = %s " "RETURNING tax::text", (text, row_id)) except psycopg2.DataError as e: self.show_message(str(e)) DB.rollback() return for row in self.cursor.fetchall(): tax = row[0] self.credit_items_store[iter_][7] = tax self.calculate_totals() def product_match_func(self, completion, key, tree_iter): split_search_text = key.split() for text in split_search_text: if text not in self.product_store[tree_iter][1].lower(): return False return True def calculate_totals(self): c = DB.cursor() c.execute( "WITH cte AS " "(SELECT " "SUM(ext_price) AS subtotal, " "SUM(tax) AS tax, " "SUM(tax + ext_price) AS total " "FROM credit_memo_items WHERE " "(credit_memo_id, deleted) = (%s, False) " ")" "UPDATE credit_memos " "SET " "(total, " "tax, " "amount_owed) " "= " "((SELECT subtotal FROM cte)," "(SELECT tax FROM cte)," "(SELECT total FROM cte)" ")" "WHERE id = %s " "RETURNING " "total::money, " "tax::money, " "amount_owed::money", (self.credit_memo_id, self.credit_memo_id)) for row in c.fetchall(): subtotal = row[0] tax = row[1] total = row[2] self.builder.get_object('subtotal_entry').set_text(subtotal) self.builder.get_object('tax_entry').set_text(tax) self.builder.get_object('total_entry').set_text(total) c.close() DB.commit() self.credit_memo_template = None # credit memo changed, force regenerate def populate_customer_store(self, m=None, i=None): self.customer_store.clear() self.cursor.execute("SELECT c.id::text, c.name, c.ext_name " "FROM contacts AS c " "JOIN invoices AS i ON c.id = i.customer_id " "WHERE (c.deleted, c.customer, i.paid) = " "(False, True, True) " "GROUP BY c.id, c.name, c.ext_name " "ORDER BY name") for row in self.cursor.fetchall(): self.customer_store.append(row) def customer_combo_changed(self, combo): customer_id = combo.get_active_id() if customer_id != None: self.select_customer(customer_id) def customer_match_selected(self, completion, model, _iter): customer_id = model[_iter][0] self.select_customer(customer_id) def select_customer(self, customer_id): self.customer_id = customer_id self.cursor.execute( "SELECT " "address, " "COALESCE(cm.id, NULL), " "COALESCE(-total, -0.00)::money, " "COALESCE(-tax, -0.00)::money, " "COALESCE(-amount_owed, -0.00)::money, " "COALESCE(dated_for, now()), " "COALESCE(comments, '') " "FROM contacts AS c " "LEFT JOIN credit_memos AS cm " "ON cm.customer_id = c.id AND cm.posted = False " "WHERE c.id = %s", (customer_id, )) for row in self.cursor.fetchall(): address = row[0] self.credit_memo_id = row[1] subtotal = row[2] tax = row[3] total = row[4] self.date = row[5] comments = row[6] self.builder.get_object('address_entry').set_text(address) self.builder.get_object('subtotal_entry').set_text(subtotal) self.builder.get_object('tax_entry').set_text(tax) self.builder.get_object('total_entry').set_text(total) self.date_calendar.set_date(self.date) self.builder.get_object('comments_buffer').set_text(comments) self.populate_credit_memo() self.populate_product_store() self.builder.get_object('menuitem2').set_sensitive(True) self.builder.get_object('button1').set_sensitive(True) self.builder.get_object('button2').set_sensitive(True) self.builder.get_object('comments_textview').set_sensitive(True) def populate_credit_memo(self): c = DB.cursor() self.credit_items_store.clear() c.execute( "SELECT " "cmi.id, " "cmi.qty::text, " "p.id, " "p.name, " "p.ext_name, " "cmi.price::text, " "cmi.ext_price::text, " "cmi.tax::text, " "cmi.invoice_item_id, " "ili.invoice_id, " "date_returned::text, " "format_date(date_returned) " "FROM credit_memo_items AS cmi " "JOIN invoice_items AS ili ON ili.id = cmi.invoice_item_id " "JOIN products AS p ON p.id = ili.product_id " "WHERE (credit_memo_id, cmi.deleted) = (%s, False) " "ORDER BY cmi.id", (self.credit_memo_id, )) for row in c.fetchall(): self.credit_items_store.append(row) c.close() def populate_product_store(self, m=None, i=None): self.product_store.clear() c = DB.cursor() c.execute( "SELECT ili.id::text, p.name || ' {' || ext_name || '}', " "i.id::text, format_date(i.dated_for) " "FROM products AS p " "JOIN invoice_items AS ili ON ili.product_id = p.id " "JOIN invoices AS i ON ili.invoice_id = i.id " "WHERE (customer_id, posted) = (%s, True) " "ORDER BY p.name", (self.customer_id, )) for row in c.fetchall(): self.product_store.append(row) c.close() def treeview_cursor_changed(self, treeview): selection = treeview.get_selection() model, path = selection.get_selected_rows() if path == []: return product_id = model[path][2] store = self.builder.get_object('serial_number_store') store.clear() self.cursor.execute( "SELECT ii.id, sn.serial_number " "FROM serial_numbers AS sn " "JOIN invoice_items AS ii ON ii.id = sn.invoice_item_id " "JOIN invoices AS i ON i.id = ii.invoice_id " "WHERE (i.customer_id, ii.product_id) = (%s, %s)", (self.customer_id, product_id)) for row in self.cursor.fetchall(): store.append(row) def serial_number_changed(self, combo, path, tree_iter): model = self.builder.get_object('serial_number_store') invoice_item_id = model[tree_iter][0] serial_number = model[tree_iter][1] self.credit_items_store[path][6] = invoice_item_id self.credit_items_store[path][11] = serial_number def return_day_selected(self, calendar): date = calendar.get_date() _iter = self.credit_items_store.get_iter(self.path) row_id = self.credit_items_store[_iter][0] self.cursor.execute( "UPDATE credit_memo_items " "SET date_returned = %s " "WHERE id = %s " "RETURNING date_returned::text, " "format_date (date_returned)", (date, row_id)) for row in self.cursor.fetchall(): date = row[0] date_formatted = row[1] self.credit_items_store[_iter][10] = str(date) self.credit_items_store[_iter][11] = date_formatted def date_entry_icon_released(self, entry, icon, position): self.date_calendar.set_relative_to(entry) self.date_calendar.show_all() def day_selected(self, calendar): self.date = calendar.get_date() text = calendar.get_text() self.builder.get_object('entry1').set_text(text) if self.credit_memo_id: self.cursor.execute( "UPDATE credit_memos " "SET dated_for = %s " "WHERE id = %s", (self.date, self.credit_memo_id)) DB.commit() def date_returned_editing_started(self, renderer, entry, path): self.path = path current_date = self.credit_items_store[path][10] self.date_returned_calendar.set_date(current_date) GLib.idle_add(self.date_returned_calendar.show_all) entry.destroy() def check_row_id(self, _iter): c = DB.cursor() row_id = self.credit_items_store[_iter][0] qty = self.credit_items_store[_iter][1] price = self.credit_items_store[_iter][5] tax = self.credit_items_store[_iter][7] invoice_item_id = self.credit_items_store[_iter][8] if row_id == 0: c.execute( "INSERT INTO credit_memo_items " "(qty, " "invoice_item_id, " "price, " "tax, " "date_returned, " "credit_memo_id) " "VALUES " "(%s, %s, %s, %s, %s, %s) RETURNING id", (qty, invoice_item_id, price, tax, self.date, self.credit_memo_id)) row_id = c.fetchone()[0] self.credit_items_store[_iter][0] = row_id DB.commit() c.close() def new_item_clicked(self, button): c = DB.cursor() self.check_credit_memo_id() invoice_item_id = self.product_store[0][0] c.execute( "SELECT " "0, " "1.0::text, " "p.id, " "p.name, " "p.ext_name, " "price::text, " "price::text, " "ROUND(1.0 * price * tr.rate/100, 2)::text, " "ii.id, " "ii.invoice_id, " "CURRENT_DATE::text, " "format_date(CURRENT_DATE) " "FROM invoice_items AS ii " "JOIN products AS p ON p.id = ii.product_id " "JOIN tax_rates AS tr ON tr.id = ii.tax_rate_id " "WHERE ii.id = %s LIMIT 1", (invoice_item_id, )) for row in c.fetchall(): iter_ = self.credit_items_store.append(row) treeview = self.builder.get_object('treeview1') column = treeview.get_column(0) path = self.credit_items_store.get_path(iter_) treeview.set_cursor(path, column, True) def delete_item_clicked(self, menuitem): selection = self.builder.get_object('treeview-selection1') model, path = selection.get_selected_rows() if path == []: return row_id = model[path][0] self.cursor.execute( "UPDATE credit_memo_items " "SET deleted = True " "WHERE id = %s", (row_id, )) DB.commit() self.populate_credit_memo() def treeview_key_release_event(self, treeview, event): keyname = Gdk.keyval_name(event.keyval) path, col = treeview.get_cursor() # only visible columns!! columns = [c for c in treeview.get_columns() if c.get_visible()] colnum = columns.index(col) if keyname == "Tab" or keyname == "Esc": if colnum + 1 < len(columns): next_column = columns[colnum + 1] else: tmodel = treeview.get_model() titer = tmodel.iter_next(tmodel.get_iter(path)) if titer is None: titer = tmodel.get_iter_first() path = tmodel.get_path(titer) next_column = columns[0] if keyname == 'Tab': GLib.idle_add(treeview.set_cursor, path, next_column, True) elif keyname == 'Escape': pass def check_credit_memo_id(self): if self.credit_memo_id == None: self.cursor.execute( "INSERT INTO credit_memos " "(name, customer_id, date_created, total) " "VALUES ('Credit Memo', %s, now(), 0.00) " "RETURNING id", (self.customer_id, )) self.credit_memo_id = self.cursor.fetchone()[0] DB.commit() def post_credit_memo_clicked(self, button): import credit_memo_template as cmt self.credit_memo_template = cmt.Setup(self.credit_items_store, self.credit_memo_id, self.customer_id) self.credit_memo_template.print_pdf(self.window) self.credit_memo_template.post() DB.commit() self.window.destroy() def view_document_activated(self, button): if not self.credit_memo_template: import credit_memo_template as cmt self.credit_memo_template = cmt.Setup(self.credit_items_store, self.credit_memo_id, self.customer_id) self.credit_memo_template.view_odt() def comments_buffer_changed(self, textbuffer): start = textbuffer.get_start_iter() end = textbuffer.get_end_iter() notes = textbuffer.get_text(start, end, True) self.cursor.execute( "UPDATE credit_memos SET comments = %s " "WHERE id = %s", (notes, self.credit_memo_id)) DB.commit() def show_message(self, message): dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.CLOSE) dialog.set_transient_for(self.window) dialog.set_markup(message) dialog.run() dialog.destroy()
class GUI (Gtk.Builder): def __init__(self): Gtk.Builder.__init__(self) self.add_from_file(UI_FILE) self.connect_signals(self) self.cursor = DB.cursor() self.store = self.get_object('unpaid_invoice_store') self.window = self.get_object('window') self.set_window_layout_from_settings() self.window.show_all() self.date_calendar = DateTimeCalendar() self.date_calendar.connect("day-selected", self.date_selected) def set_window_layout_from_settings(self): sqlite = get_apsw_connection() c = sqlite.cursor() c.execute("SELECT value FROM unpaid_invoices " "WHERE widget_id = 'window_width'") width = c.fetchone()[0] c.execute("SELECT value FROM unpaid_invoices " "WHERE widget_id = 'window_height'") height = c.fetchone()[0] self.window.resize(width, height) c.execute("SELECT value FROM unpaid_invoices " "WHERE widget_id = 'sort_column'") sort_column = c.fetchone()[0] c.execute("SELECT value FROM unpaid_invoices " "WHERE widget_id = 'sort_type'") sort_type = Gtk.SortType(c.fetchone()[0]) store = self.get_object('unpaid_invoice_store') store.set_sort_column_id(sort_column, sort_type) c.execute("SELECT widget_id, value FROM unpaid_invoices WHERE " "widget_id IN ('number_column', " "'invoice_column', " "'customer_column', " "'date_column', " "'amount_column')") for row in c.fetchall(): column = self.get_object(row[0]) width = row[1] if width == 0: column.set_visible(False) else: column.set_fixed_width(width) sqlite.close() def save_window_layout_activated (self, menuitem): sqlite = get_apsw_connection() c = sqlite.cursor() width, height = self.window.get_size() c.execute("REPLACE INTO unpaid_invoices (widget_id, value) " "VALUES ('window_width', ?)", (width,)) c.execute("REPLACE INTO unpaid_invoices (widget_id, value) " "VALUES ('window_height', ?)", (height,)) tuple_ = self.get_object('unpaid_invoice_store').get_sort_column_id() sort_column = tuple_[0] if sort_column == None: sort_column = 0 sort_type = 0 else: sort_type = tuple_[1].numerator c.execute("REPLACE INTO unpaid_invoices (widget_id, value) " "VALUES ('sort_column', ?)", (sort_column,)) c.execute("REPLACE INTO unpaid_invoices (widget_id, value) " "VALUES ('sort_type', ?)", (sort_type,)) for column in ['number_column', 'invoice_column', 'customer_column', 'date_column', 'amount_column']: try: width = self.get_object(column).get_width() except Exception as e: self.show_message("On column %s\n %s" % (column, str(e))) continue c.execute("REPLACE INTO unpaid_invoices (widget_id, value) " "VALUES (?, ?)", (column, width)) sqlite.close() def present (self): self.window.present() def window_delete_event (self, window, event): window.hide() return True def treeview_button_release_event (self, widget, event): if event.button == 3: menu = self.get_object('right_click_menu') menu.popup_at_pointer() def contact_hub_activated (self, menuitem): selection = self.get_object('treeview-selection') model, path = selection.get_selected_rows() if path == []: return customer_id = model[path][2] import contact_hub contact_hub.ContactHubGUI(customer_id) def invoice_chart_clicked (self, button): window = Gtk.Window() box = Gtk.VBox() window.add (box) from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar figure = Figure(figsize=(4, 4), dpi=100) canvas = FigureCanvas(figure) # a Gtk.DrawingArea canvas.set_size_request(900, 600) box.pack_start(canvas, True, True, 0) toolbar = NavigationToolbar(canvas, window) box.pack_start(toolbar, False, False, 0) plot = figure.add_subplot(111) labels = list() fractions = list() unpaid = 0 cursor = DB.cursor() cursor.execute("SELECT SUM(amount_due), c.name FROM invoices " "JOIN contacts AS c ON c.id = invoices.customer_id " "WHERE (canceled, paid, posted) = " "(False, False, True) GROUP BY customer_id, c.name " "ORDER BY c.name") for row in cursor.fetchall(): customer_total = row[0] customer_name = row[1] fractions.append(customer_total) labels.append(customer_name) unpaid += 1 if unpaid == 0: labels.append("None") fractions.append(1.00) cursor.close() plot.pie (fractions, labels=labels, autopct='%1.f%%', radius=0.9) window.set_title ('Unpaid invoices pie chart') window.set_icon_name ('pygtk-posting') window.show_all() DB.rollback() def unpaid_chart_window_delete_event (self, window, event): window.hide() return True def date_entry_icon_released (self, entry, icon, position): self.date_calendar.set_relative_to(entry) self.date_calendar.show_all() def date_selected (self, calendar): self.date = calendar.get_date() button = self.get_object('button6') button.set_sensitive(True) button.set_label("Yes, cancel invoice") entry = self.get_object('entry1') entry.set_text(calendar.get_text()) def cancel_dialog (self, widget): button = self.get_object('button6') button.set_sensitive(False) button.set_label("No date selected") cancel_dialog = self.get_object('dialog1') message = "Do you want to cancel %s ?\nThis is not reversible!" % self.invoice_name self.get_object('label1').set_label(message) response = cancel_dialog.run() cancel_dialog.hide() if response == Gtk.ResponseType.ACCEPT: transactor.cancel_invoice(self.date, self.invoice_id) self.cursor.execute("UPDATE invoices SET canceled = True " "WHERE id = %s" "; " "UPDATE serial_numbers " "SET invoice_item_id = NULL " "WHERE invoice_item_id IN " "(SELECT id FROM invoice_items " "WHERE invoice_id = %s)", (self.invoice_id, self.invoice_id)) DB.commit() self.populate_unpaid_invoices () def view_invoice(self, widget): treeselection = self.get_object('treeview-selection') model, path = treeselection.get_selected_rows () if path != []: tree_iter = model.get_iter(path) invoice_id = model.get_value(tree_iter, 0) self.cursor.execute("SELECT name, pdf_data FROM invoices " "WHERE id = %s", (invoice_id ,)) for cell in self.cursor.fetchall(): file_name = cell[0] + ".pdf" file_data = cell[1] f = open("/tmp/" + file_name,'wb') f.write(file_data) subprocess.call("xdg-open /tmp/" + str(file_name), shell = True) f.close() DB.rollback() def focus(self, window, event): self.populate_unpaid_invoices() def populate_unpaid_invoices(self): unpaid_invoice_amount = decimal.Decimal() treeview_selection = self.get_object('treeview-selection') model, path = treeview_selection.get_selected_rows() model.clear() c = DB.cursor() c.execute("SELECT " "i.id, " "i.name, " "c.id, " "c.name, " "format_date(dated_for), " "dated_for::text, " "amount_due::text, " "amount_due::float " "FROM invoices AS i " "JOIN contacts AS c ON i.customer_id = c.id " "WHERE (canceled, paid, posted) = " "(False, False, True) " "ORDER BY i.id") tupl = c.fetchall() for row in tupl: model.append(row) unpaid_invoice_amount += decimal.Decimal(row[6]) if path != [] and tupl != []: treeview_selection.select_path(path) self.get_object('treeview1').scroll_to_cell(path) c.execute("SELECT " "COALESCE(i.amount_due - (pi.amount + cm.amount), 0.00)::money " "FROM (SELECT SUM(amount_due) AS amount_due FROM invoices " "WHERE (posted, canceled, active) = (True, False, True)) i, " "(SELECT SUM(amount) AS amount FROM payments_incoming " "WHERE misc_income = False) pi, " "(SELECT SUM(amount_owed) AS amount FROM credit_memos " "WHERE posted = True) cm ") unpaid = c.fetchone()[0] self.get_object('AR_balance_label').set_label(unpaid) l = '${:,.2f}'.format(unpaid_invoice_amount) self.get_object('unpaid_invoices_label').set_label(l) c.close() DB.rollback() def row_activated(self, treeview, path, treeviewcolumn): treeiter = self.store.get_iter(path) self.invoice_id = self.store.get_value(treeiter, 0) self.invoice_name = self.store.get_value(treeiter, 1) self.contact_id = self.store.get_value(treeiter, 2) self.get_object('button1').set_sensitive(True) self.get_object('button2').set_sensitive(True) self.get_object('button4').set_sensitive(True) def payment_window (self, widget): selection = self.get_object('treeview-selection') model, path = selection.get_selected_rows() if path == []: return customer_id = model[path][2] import customer_payment customer_payment.GUI(customer_id) def new_statement (self, widget): new_statement.GUI() def show_message (self, message): dialog = Gtk.MessageDialog( message_type = Gtk.MessageType.ERROR, buttons = Gtk.ButtonsType.CLOSE) dialog.set_transient_for(self.window) dialog.set_markup (message) dialog.run() dialog.destroy()