# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2006 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
##
""" Classes for client details """
import datetime
import gtk
from kiwi.currency import currency
from kiwi.ui.objectlist import Column, ColoredColumn, SummaryLabel, ObjectTree
from kiwi.ui.gadgets import render_pixbuf
from stoqlib.api import api
from stoqlib.domain.inventory import Inventory
from stoqlib.domain.person import Client
from stoqlib.gui.base.dialogs import run_dialog
from stoqlib.gui.editors.baseeditor import BaseEditor
from stoqlib.gui.editors.personeditor import ClientEditor
from stoqlib.gui.search.searchcolumns import IdentifierColumn
from stoqlib.gui.utils.iconutils import get_workorder_state_icon
from stoqlib.gui.wizards.personwizard import run_person_role_dialog
from stoqlib.lib.defaults import payment_value_colorize
from stoqlib.lib.translation import stoqlib_gettext
_ = stoqlib_gettext
[docs]class DetailsTab(gtk.VBox):
details_dialog_class = None
def __init__(self, model, parent):
super(DetailsTab, self).__init__()
self.model = model
self._parent = parent
self.set_spacing(6)
self.set_border_width(6)
self.klist = ObjectTree(self.get_columns())
self.populate()
self.pack_start(self.klist)
self.klist.show()
if len(self.klist) and self.get_details_dialog_class():
self.button_box = gtk.HButtonBox()
self.button_box.set_layout(gtk.BUTTONBOX_START)
details_button = gtk.Button(self.details_lbl)
self.button_box.pack_start(details_button)
details_button.set_sensitive(bool(self.klist.get_selected()))
details_button.show()
self.pack_end(self.button_box, False, False)
self.button_box.show()
self.button_box.details_button = details_button
details_button.connect('clicked', self._on_details_button__clicked)
self.klist.connect('row-activated', self._on_klist__row_activated)
self.klist.connect('selection-changed',
self._on_klist__selection_changed)
self.setup_widgets()
[docs] def refresh(self):
"""Refreshes the list of respective tab."""
self.klist.clear()
self.klist.add_list(self.populate())
[docs] def get_columns(self):
"""Returns a list of columns this tab should show."""
raise NotImplementedError
[docs] def show_details(self):
"""Called when the details button is clicked. Displays the details of
the selected object in the list."""
model = self.get_details_model(self.klist.get_selected())
run_dialog(self.get_details_dialog_class(),
parent=self._parent,
store=self._parent.store,
model=model,
visual_mode=True)
[docs] def get_label(self):
"""Returns the name of the tab."""
label = gtk.Label(self.labels[1])
return label
[docs] def get_details_model(self, model):
"""Subclassses can overwrite this method if the details dialog class
needs a model different than the one on the list."""
return model
[docs] def get_details_dialog_class(self):
"""Subclasses must return the dialog that should be displayed for more
information about the item on the list"""
return self.details_dialog_class
#
# Callbacks
#
def _on_details_button__clicked(self, button):
self.show_details()
def _on_klist__row_activated(self, klist, item):
self.show_details()
def _on_klist__selection_changed(self, klist, data):
self.button_box.details_button.set_sensitive(bool(data))
[docs]class SalesTab(DetailsTab):
labels = _('Sale'), _('Sales')
details_lbl = _('Sale details')
def _has_open_inventory(self):
store = self._parent.store
has_open = Inventory.has_open(store, api.get_current_branch(store))
return bool(has_open)
def _return_sale(self):
from stoqlib.gui.slaves.saleslave import return_sale
sale_view = self.klist.get_selected()
with api.new_store() as store:
retval = return_sale(self.get_toplevel(),
store.fetch(sale_view.sale), store)
if retval:
self.refresh()
def _can_return_sale(self, sale_view):
if sale_view:
return sale_view.can_return() and not self.has_open_inventory
return False
[docs] def get_columns(self):
return [IdentifierColumn('identifier', title=_('Sale #'), sorted=True),
Column("invoice_number", title=_("Invoice #"),
data_type=int, width=90),
Column("open_date", title=_("Date"), data_type=datetime.date,
justify=gtk.JUSTIFY_RIGHT, width=80),
Column("salesperson_name", title=_("Salesperson"),
searchable=True, expand=True, data_type=str),
Column("status_name", title=_("Status"), width=80,
data_type=str),
Column("total", title=_("Total"), justify=gtk.JUSTIFY_RIGHT,
data_type=currency, width=100)]
[docs] def populate(self):
for obj in self.model.get_client_sales():
self.klist.append(None, obj)
[docs] def get_details_dialog_class(self):
from stoqlib.gui.dialogs.saledetails import SaleDetailsDialog
return SaleDetailsDialog
def _on_return_button__clicked(self, button):
self._return_sale()
def _on_klist__selection_changed(self, klist, data):
can_return = self._can_return_sale(data)
self.button_box.details_button.set_sensitive(bool(data))
self.button_box.return_button.set_sensitive(bool(can_return))
[docs]class ReturnedSalesTab(DetailsTab):
labels = _('Returned Sale'), _('Returned Sales')
details_lbl = _('Returned sale details')
[docs] def get_columns(self):
return [IdentifierColumn('identifier', title=_('Returned #'), sorted=True),
Column("invoice_number", title=_("Invoice #"),
data_type=int, width=90),
Column("return_date", title=_("Return Date"),
data_type=datetime.date, justify=gtk.JUSTIFY_RIGHT,
width=80),
Column("product_name", title=_("Product"),
searchable=True, data_type=str),
Column("salesperson_name", title=_("Salesperson"),
searchable=True, visible=False, data_type=str),
Column("responsible_name", title=_("Responsible"),
searchable=True, data_type=str),
Column("reason", title=_("Reason"), searchable=False,
expand=True, data_type=str),
Column("price", title=_("Price"), data_type=currency),
Column("quantity", title=_("Qty"), data_type=str),
Column("total", title=_("Total"), justify=gtk.JUSTIFY_RIGHT,
data_type=currency, width=100)]
[docs] def populate(self):
for item in self.model.get_client_returned_sales():
self.klist.append(None, item)
sellable = item.returned_item.sellable
if (sellable.service or
(sellable.product and not sellable.product.is_package)):
continue
for child in item.get_children_items():
self.klist.append(item, child)
[docs] def get_details_model(self, model):
from stoqlib.domain.sale import Sale, SaleView
return self._parent.store.find(SaleView, Sale.id == model.sale_id).one()
[docs] def get_details_dialog_class(self):
from stoqlib.gui.dialogs.saledetails import SaleDetailsDialog
return SaleDetailsDialog
[docs]class ProductsTab(DetailsTab):
labels = _('Product'), _('Products')
details_lbl = _('Product details')
[docs] def get_columns(self):
return [Column("code", title=_("Code"), data_type=str,
width=120, sorted=True),
Column("description", title=_("Description"), data_type=str,
expand=True, searchable=True),
Column("quantity", title=_("Total quantity"),
data_type=str, width=120, justify=gtk.JUSTIFY_RIGHT),
Column("sale_date", title=_("Sale date"),
data_type=datetime.date, width=150),
Column("value", title=_("Value"), width=100,
data_type=currency, justify=gtk.JUSTIFY_RIGHT),
Column("total_value", title=_("Total value"), width=100,
data_type=currency, justify=gtk.JUSTIFY_RIGHT, )]
[docs] def populate(self):
for item in self.model.get_client_products(with_children=False):
self.klist.append(None, item)
if item.sale_item.sellable.product.is_composed:
continue
for child in item.get_children_items():
self.klist.append(item, child)
[docs]class ServicesTab(DetailsTab):
labels = _('Service'), _('Services')
details_lbl = _('Service details')
[docs] def get_columns(self):
return [Column("code", title=_("Code"), data_type=str,
justify=gtk.JUSTIFY_RIGHT, width=120, sorted=True),
Column("description",
title=_("Description"), data_type=str, expand=True,
searchable=True),
Column("estimated_fix_date", title=_("Estimated fix date"),
width=150, data_type=datetime.date)]
[docs] def populate(self):
for obj in self.model.get_client_services():
self.klist.append(None, obj)
[docs]class WorkOrdersTab(DetailsTab):
labels = _('Work Order'), _('Work Orders')
details_lbl = _('Work order details')
[docs] def get_columns(self):
return [IdentifierColumn("identifier", title=_('WO #'), sorted=True),
Column("equipment", title=_("Equipment"),
data_type=str, expand=True, pack_end=True),
Column('category_color', title=_(u'Equipment'),
column='equipment', data_type=gtk.gdk.Pixbuf,
format_func=render_pixbuf),
Column('flag_icon', title=_(u'Equipment'), column='equipment',
data_type=gtk.gdk.Pixbuf, format_func_data=True,
format_func=self._format_state_icon),
Column("open_date", title=_("Open date"),
data_type=datetime.date, width=120),
Column("approve_date", title=_("Approve date"),
data_type=datetime.date, width=120),
Column("finish_date", title=_("Finish date"),
data_type=datetime.date, width=120),
Column("total", title=_("Total"),
data_type=currency, width=100)]
[docs] def populate(self):
for obj in self.model.get_client_work_orders():
self.klist.append(None, obj)
[docs] def get_details_model(self, model):
return model.work_order
[docs] def get_details_dialog_class(self):
from stoqlib.gui.editors.workordereditor import WorkOrderEditor
return WorkOrderEditor
def _format_state_icon(self, item, data):
stock_id, tooltip = get_workorder_state_icon(item.work_order)
if stock_id is not None:
# We are using self because render_icon is a gtk.Widget's # method.
# It has nothing to do with results tough.
return self.render_icon(stock_id, gtk.ICON_SIZE_MENU)
[docs]class PaymentsTab(DetailsTab):
labels = _('Payment'), _('Payments')
details_lbl = _('Payment details')
[docs] def get_columns(self):
return [IdentifierColumn('identifier', title=_('Payment #')),
Column("method_description", title=_("Type"),
data_type=str, width=90),
Column("description", title=_("Description"),
data_type=str, searchable=True, width=190,
expand=True),
Column("due_date", title=_("Due date"), width=110,
data_type=datetime.date, sorted=True),
Column("paid_date", title=_("Paid date"), width=110,
data_type=datetime.date),
Column("status_str", title=_("Status"), width=80,
data_type=str),
ColoredColumn("value", title=_("Value"),
justify=gtk.JUSTIFY_RIGHT, data_type=currency,
color='red', width=100,
data_func=payment_value_colorize),
Column("days_late", title=_("Days Late"), width=110,
format_func=(lambda days_late: days_late and
str(days_late) or u""),
justify=gtk.JUSTIFY_RIGHT, data_type=str)]
[docs] def populate(self):
for obj in self.model.get_client_payments():
self.klist.append(None, obj)
[docs] def get_details_model(self, model):
return model.payment
[docs] def get_details_dialog_class(self):
from stoqlib.gui.editors.paymenteditor import InPaymentEditor
return InPaymentEditor
[docs] def show_details(self):
model = self.get_details_model(self.klist.get_selected())
run_dialog(self.get_details_dialog_class(),
store=self._parent.store,
model=model)
[docs]class CreditAccountsTab(DetailsTab):
labels = _('Credit Account'), _('Credit Accounts')
details_lbl = _('Credit details')
[docs] def get_columns(self):
return [IdentifierColumn('identifier', title=_('Payment #'), sorted=True),
Column('paid_date', title=_(u'Date'), data_type=datetime.date,
width=150),
Column('description', title=_(u'Description'),
data_type=str, width=150, expand=True),
ColoredColumn('paid_value', title=_(u'Value'), color='red',
data_type=currency, width=100,
use_data_model=True,
data_func=lambda p: not p.is_outpayment())]
[docs] def populate(self):
for obj in self.model.get_credit_transactions():
self.klist.append(None, obj)
[docs]class CallsTab(DetailsTab):
labels = _('Call'), _('Calls')
details_lbl = _('Call details')
[docs] def get_columns(self):
return [Column("date", title=_("Date"),
data_type=datetime.date, width=150, sorted=True),
Column("description", title=_("Description"),
data_type=str, width=150, expand=True),
Column("attendant.person.name", title=_("Attendant"),
data_type=str, width=100, expand=True)]
[docs] def populate(self):
for obj in self.model.person.calls:
self.klist.append(None, obj)
[docs]class ClientDetailsDialog(BaseEditor):
"""This dialog shows some important details about clients like:
- history of sales
- all products tied with sales
- all services tied with sales
- all payments already created
"""
title = _(u"Client Details")
hide_footer = True
size = (-1, 400)
model_type = Client
gladefile = "ClientDetailsDialog"
proxy_widgets = ('client',
'last_purchase_date',
'status')
def __init__(self, store, model):
BaseEditor.__init__(self, store, model)
self._setup_widgets()
def _setup_widgets(self):
for tab_class in [SalesTab,
ReturnedSalesTab,
ProductsTab,
ServicesTab,
WorkOrdersTab,
PaymentsTab,
CreditAccountsTab,
CallsTab]:
tab = tab_class(self.model, self)
label = tab.get_label()
label.set_sensitive(len(tab.klist))
self.details_notebook.append_page(tab, label)
label.show()
tab.show()
#
# BaseEditor Hooks
#
[docs] def setup_proxies(self):
self.add_proxy(self.model, self.proxy_widgets)
#
# Callbacks
#