Source code for stoqlib.gui.slaves.productionslave

# -*- 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] def on_edit_button__clicked(self, widget): 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] def on_apply_button__clicked(self, widget): self.apply()
[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))