示例#1
0
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)
示例#2
0
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()
示例#3
0
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()