# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2009 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>
##
""" Slaves for production """
from decimal import Decimal
import pango
import gtk
from kiwi import ValueUnset
from kiwi.datatypes import ValidationError
from kiwi.python import Settable
from kiwi.ui.objectlist import Column, ColoredColumn
from kiwi.utils import gsignal
from stoqlib.database.runtime import get_current_user, get_current_branch
from stoqlib.domain.inventory import Inventory
from stoqlib.domain.product import ProductQualityTest
from stoqlib.domain.production import (ProductionOrder, ProductionMaterial,
ProductionProducedItem)
from stoqlib.lib.translation import stoqlib_gettext
from stoqlib.lib.formatters import format_quantity
from stoqlib.gui.base.dialogs import run_dialog
from stoqlib.gui.editors.baseeditor import BaseEditorSlave
_ = stoqlib_gettext
# XXX: This is just a workaround to avoid the zillions of queries
# when handling production items and materials.
class _TemporaryMaterial(object):
def __init__(self, production, component, store):
storable = component.storable
if storable is not None:
self.stock_quantity = storable.get_balance_for_branch(production.branch)
else:
self.stock_quantity = Decimal(0)
sellable = component.sellable
self.sellable = sellable
self.code = sellable.code
self.description = sellable.get_description()
self.category_description = sellable.get_category_description()
self.unit_description = sellable.unit_description
self.product = component
self.needed = Decimal(0)
self.to_purchase = Decimal(0)
self.to_make = Decimal(0)
self.order = production
self._material = None
self._store = store
@property
def material(self):
if self._material is None:
# At this point, the needed quantity have already been updated.
assert self.needed > 0
material = self._store.find(ProductionMaterial, order=self.order,
product=self.product).one()
if material is not None:
self._material = material
self._material.needed = self.needed
else:
self._material = ProductionMaterial(
needed=self.needed,
to_purchase=self.to_purchase,
to_make=self.to_make,
order=self.order,
product=self.product,
store=self._store)
return self._material
def create(self):
return self.material
def sync(self):
# assert self._material is not None
self.to_purchase = self.material.to_purchase
self.to_make = self.material.to_make
def add_quantity(self, quantity):
assert quantity > 0
self.needed += quantity
self.update_quantities()
def update_quantities(self):
missing_quantity = self.needed - self.stock_quantity
if missing_quantity < 0:
missing_quantity = Decimal(0)
# Unmanaged products dont have missing quantity
if not self.sellable.product.manage_stock:
missing_quantity = 0
if self.product.has_components():
self.to_make = missing_quantity
else:
self.to_purchase = missing_quantity
if self._material is not None:
self._material.needed = self.needed
self._material.to_make = self.to_make
self._material.to_purchase = self.to_purchase
[docs]class ProductionMaterialListSlave(BaseEditorSlave):
gladefile = 'ProductionMaterialListSlave'
model_type = ProductionOrder
def __init__(self, store, model, visual_mode=False):
BaseEditorSlave.__init__(self, store, model, visual_mode)
self._setup_widgets()
def _add_materials(self, production_item):
self._materials_objects = []
for product_component in production_item.get_components():
material = self._get_or_create_material(product_component)
quantity = product_component.quantity * production_item.quantity
material.add_quantity(quantity)
material.sync()
if material not in self.materials:
self.materials.append(material)
else:
self.materials.update(material)
def _get_or_create_material(self, product_component):
component = product_component.component
for material in self.materials:
if material.product is component:
return material
return _TemporaryMaterial(self.model, component, self.store)
def _edit_production_material(self):
from stoqlib.gui.editors.productioneditor import ProductionMaterialEditor
material = self.materials.get_selected()
assert material is not None
self.store.savepoint('before_run_editor_production_material')
toplevel = self.get_toplevel().get_toplevel()
retval = run_dialog(ProductionMaterialEditor, toplevel, self.store,
material.material)
if retval:
material.sync()
self.materials.update(material)
else:
self.store.rollback_to_savepoint('before_run_editor_production_material')
def _setup_widgets(self):
self.edit_button.set_sensitive(False)
if not self.visual_mode:
self.start_production_check.hide()
has_open_inventory = Inventory.has_open(self.store,
get_current_branch(self.store))
self.start_production_check.set_sensitive(not bool(has_open_inventory))
self.materials.set_columns(self._get_columns())
for production_item in self.model.get_items():
self._add_materials(production_item)
def _get_columns(self):
return [
Column('code', title=_('Code'), data_type=str),
Column('category_description', title=_('Category'),
data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END),
Column('description', title=_('Description'), data_type=str,
expand=True, ellipsize=pango.ELLIPSIZE_END, sorted=True),
Column('unit_description', title=_('Unit'),
data_type=str),
Column('needed', title=_('Needed'), data_type=Decimal,
format_func=format_quantity),
Column('stock_quantity', title=_('In Stock'), data_type=Decimal,
format_func=format_quantity),
ColoredColumn('to_purchase', title=_('To Purchase'),
data_type=Decimal, format_func=format_quantity,
use_data_model=True, color='red',
data_func=self._colorize_to_purchase_col),
ColoredColumn('to_make', title=_('To Make'), data_type=Decimal,
format_func=format_quantity, use_data_model=True,
color='red', data_func=self._colorize_to_make_col)]
# XXX: Some duplication here, since the columns will never be both red.
def _colorize_to_purchase_col(self, material):
if material.product.has_components():
return
if not material.product.manage_stock:
return
stock_qty = material.stock_quantity
if material.to_purchase + stock_qty - material.needed < 0:
return True
return False
def _colorize_to_make_col(self, material):
if not material.product.has_components():
return
stock_qty = material.stock_quantity
if material.to_make + stock_qty - material.needed < 0:
return True
return False
#
# BaseEditorSlave
#
[docs] def validate_confirm(self):
for material in self.materials:
material.create()
# In visual mode the user can choose if we start the production now or
# later. If not in visual mode, we start the production now.
if self.start_production_check.get_active() or not self.visual_mode:
self.model.start_production()
elif self.model.status != ProductionOrder.ORDER_WAITING:
for material in self.materials:
if material.to_purchase > 0 or material.to_make > 0:
self.model.set_production_waiting()
break
return True
[docs] def reload_materials(self):
"""Reloads the material list if needed."""
if len(self.materials) == 0:
return
self.materials.clear()
# will trigger the material re-population.
self._setup_widgets()
for material in self.materials:
material.update_quantities()
#
# Kiwi Callbacks
#
[docs] def on_materials__selection_changed(self, widget, material):
self.edit_button.set_sensitive(bool(material))
[docs] def on_materials__double_click(self, widget, material):
self._edit_production_material()
[docs]class QualityTestResultSlave(BaseEditorSlave):
model_name = _('Quality Test Result')
model_type = Settable
gladefile = 'QualityTestResultSlave'
proxy_widgets = ['quality_test', 'decimal_value', 'boolean_value']
gsignal('test-updated', object, object, object)
def __init__(self, store):
self._items = []
self._product = None
BaseEditorSlave.__init__(self, store=store, model=None)
@property
def test_type(self):
if self.model.quality_test:
return self.model.quality_test.test_type
return None
[docs] def setup_proxies(self):
self._setup_widgets()
self.proxy = self.add_proxy(self.model, self.proxy_widgets)
self.quality_vbox.set_sensitive(False)
def _setup_widgets(self):
self.sizegroup1.add_widget(self.decimal_value)
self.sizegroup1.add_widget(self.boolean_value)
self.decimal_value.set_visible(False)
self.boolean_value.prefill([(_('True'), True), (_('False'), False)])
[docs] def create_model(self, store):
return Settable(quality_test=None,
decimal_value=Decimal(0),
boolean_value=False)
def _check_value_passes(self):
if not self._product:
return
if self.test_type == ProductQualityTest.TYPE_BOOLEAN:
value = self.model.boolean_value
else:
value = self.model.decimal_value
test = self.model.quality_test
if test.result_value_passes(value):
self.result_icon.set_from_stock(gtk.STOCK_OK,
gtk.ICON_SIZE_BUTTON)
else:
self.result_icon.set_from_stock(gtk.STOCK_DIALOG_WARNING,
gtk.ICON_SIZE_BUTTON)
#
# Public API
#
[docs] def set_item_tests(self, items, product):
self._items = items
self.quality_vbox.set_sensitive(bool(product and items))
# Tests didnt change
if self._product == product:
return
self._product = product
if product:
self.quality_test.prefill([(i.description, i)
for i in product.quality_tests])
[docs] def apply(self):
if self.test_type == ProductQualityTest.TYPE_BOOLEAN:
value = self.model.boolean_value
else:
value = self.model.decimal_value
for item in self._items:
result = item.set_test_result_value(self.model.quality_test, value,
get_current_user(self.store))
self.emit('test-updated', item, self.model.quality_test, result)
[docs] def after_quality_test__changed(self, widget):
if not widget.get_selected():
return
if self.test_type == ProductQualityTest.TYPE_BOOLEAN:
self.boolean_value.show()
self.decimal_value.hide()
else:
self.boolean_value.hide()
self.decimal_value.show()
self._check_value_passes()
[docs] def after_boolean_value__changed(self, widget):
self._check_value_passes()
[docs] def after_decimal_value__changed(self, widget):
self._check_value_passes()
[docs]class ProducedItemSlave(BaseEditorSlave):
"""A slave that shows produced production items.
"""
gladefile = 'ProducedItemSlave'
model_type = Settable
proxy_widgets = ['serial_number']
def __init__(self, store, parent):
self._parent = parent
self._product = self._parent.model.product
BaseEditorSlave.__init__(self, store)
#
# BaseEditorSlave hooks
#
[docs] def create_model(self, store):
serial = ProductionProducedItem.get_last_serial_number(
self._product, store)
return Settable(serial_number=serial + 1)
[docs] def setup_proxies(self):
self.proxy = self.add_proxy(self.model, self.proxy_widgets)
[docs] def on_serial_number__validate(self, widget, value):
qty = self._parent.quantity.read()
if qty is ValueUnset:
qty = 0
first = value
last = value + qty - 1
if not ProductionProducedItem.is_valid_serial_range(self._product,
first, last, self.store):
return ValidationError(_('There already is a serial number in '
'the range %d - %d') % (first, last))