Ejemplo n.º 1
0
class StorableBranchOverride(Domain):
    __storm_table__ = 'storable_branch_override'

    minimum_quantity = QuantityCol()
    maximum_quantity = QuantityCol()

    branch_id = IdCol()
    branch = Reference(branch_id, 'Branch.id')

    storable_id = IdCol()
    storable = Reference(storable_id, 'Storable.id')
Ejemplo n.º 2
0
class TransferOrderItem(Domain):
    """Transfer order item

    """

    __storm_table__ = 'transfer_order_item'

    sellable_id = IntCol()

    # FIXME: This should be a product, since it does not make sense to transfer
    # serviçes
    #: The |sellable| to transfer
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IntCol()

    #: If the sellable is a storable, the |batch| that was transfered
    batch = Reference(batch_id, 'StorableBatch.id')

    transfer_order_id = IntCol()

    #: The |transfer| this item belongs to
    transfer_order = Reference(transfer_order_id, 'TransferOrder.id')

    #: The quantity to transfer
    quantity = QuantityCol()

    #
    # Public API
    #

    def get_total(self):
        """Returns the total cost of a transfer item eg quantity * cost"""
        return self.quantity * self.sellable.cost

    def send(self):
        """Sends this item to it's destination |branch|"""
        assert self.transfer_order.can_close()

        storable = self.sellable.product_storable
        storable.decrease_stock(self.quantity,
                                self.transfer_order.source_branch,
                                StockTransactionHistory.TYPE_TRANSFER_TO,
                                self.id)
        ProductHistory.add_transfered_item(self.store,
                                           self.transfer_order.source_branch,
                                           self)

    def receive(self):
        """Receives this item, increasing the quantity in the stock
        """
        storable = self.sellable.product_storable
        from_stock = storable.get_stock_item(self.transfer_order.source_branch,
                                             self.batch)
        storable.increase_stock(self.quantity,
                                self.transfer_order.destination_branch,
                                StockTransactionHistory.TYPE_TRANSFER_FROM,
                                self.id,
                                unit_cost=from_stock.stock_cost,
                                batch=self.batch)
Ejemplo n.º 3
0
class ProductStockItem(Domain):
    __storm_table__ = 'product_stock_item'

    stock_cost = PriceCol(default=0)
    quantity = QuantityCol(default=0)
    storable_id = IntCol()
    storable = Reference(storable_id, Storable.id)
Ejemplo n.º 4
0
class ProductionService(Domain):
    """Production Service object implementation.

    :attribute order: The :class:`ProductionOrder` of this service.
    :attribute service: The service that will be used by the production.
    :attribute quantity: The service's quantity.
    """
    implements(IDescribable)

    __storm_table__ = 'production_service'

    service_id = IntCol()
    service = Reference(service_id, 'Service.id')
    order_id = IntCol()
    order = Reference(order_id, 'ProductionOrder.id')
    quantity = QuantityCol(default=1)

    #
    # IDescribable Implementation
    #

    def get_description(self):
        return self.service.sellable.get_description()

    # Accessors

    def get_unit_description(self):
        return self.service.sellable.get_unit_description()
Ejemplo n.º 5
0
class ProductionService(Domain):
    """Production Service object implementation.
    """

    __storm_table__ = 'production_service'

    service_id = IdCol()

    #: The service that will be used by the production.
    service = Reference(service_id, 'Service.id')

    order_id = IdCol()

    #: The :class:`ProductionOrder` of this service.
    order = Reference(order_id, 'ProductionOrder.id')

    #: The service's quantity.
    quantity = QuantityCol(default=1)

    #
    # IDescribable Implementation
    #

    def get_description(self):
        return self.service.sellable.get_description()

    # Properties

    @property
    def unit_description(self):
        return self.service.sellable.unit_description

    @property
    def sellable(self):
        return self.service.sellable
Ejemplo n.º 6
0
class ReturnedSaleItem(Domain):
    __storm_table__ = 'returned_sale_item'
    quantity = QuantityCol(default=0)
    price = PriceCol()
    sale_item_id = IntCol()
    sale_item = Reference(sale_item_id, SaleItem.id)
    sellable_id = IntCol()
    returned_sale_id = IntCol()
Ejemplo n.º 7
0
class StockDecreaseItem(Domain):
    """An item in a stock decrease object.

    Note that objects of this type should not be created manually, only by
    calling :meth:`StockDecrease.add_sellable`
    """

    __storm_table__ = 'stock_decrease_item'

    stock_decrease_id = IntCol(default=None)

    #: The stock decrease this item belongs to
    stock_decrease = Reference(stock_decrease_id, 'StockDecrease.id')

    sellable_id = IntCol()

    #: the |sellable| for this decrease
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IntCol()

    #: If the sellable is a storable, the |batch| that it was removed from
    batch = Reference(batch_id, 'StorableBatch.id')

    #: the cost of the |sellable| on the moment this decrease was created
    cost = PriceCol(default=0)

    #: the quantity decreased for this item
    quantity = QuantityCol()

    def __init__(self, store=None, **kw):
        if not 'kw' in kw:
            if not 'sellable' in kw:
                raise TypeError('You must provide a sellable argument')
        Domain.__init__(self, store=store, **kw)

    def decrease(self, branch):
        assert branch

        storable = self.sellable.product_storable
        if storable:
            storable.decrease_stock(self.quantity, branch,
                                    StockTransactionHistory.TYPE_STOCK_DECREASE,
                                    self.id,
                                    cost_center=self.stock_decrease.cost_center)

    #
    # Accessors
    #

    def get_total(self):
        return currency(self.cost * self.quantity)

    def get_quantity_unit_string(self):
        return u"%s %s" % (self.quantity, self.sellable.get_unit_description())

    def get_description(self):
        return self.sellable.get_description()
Ejemplo n.º 8
0
class InvoiceItemCofins(BaseCOFINS):
    """Invoice of COFINS tax."""

    __storm_table__ = 'invoice_item_cofins'

    #: Value of COFINS tax
    v_cofins = PriceCol(default=0)

    #: Value of the COFINS tax calculation basis.
    v_bc = PriceCol(default=None)

    #: Quantity sold
    q_bc_prod = QuantityCol(default=None)

    #
    # Public API
    #

    def set_initial_values(self, invoice_item):
        self.update_values(invoice_item)

    def update_values(self, invoice_item):
        self.q_bc_prod = invoice_item.quantity

        # When the CST is contained in the list the calculation is not performed
        # because the taxpayer is exempt.
        if self.cst in [4, 5, 6, 7, 8, 9]:
            return
        # When the branch is Simples Nacional (CRT 1 or 2) and the cofins is 99,
        # the values should be zero
        if self.cst == 99 and invoice_item.parent.branch.crt in [1, 2]:
            self.v_bc = 0
            self.p_cofins = 0
            self.v_cofins = 0
            return
        cost = self._get_item_cost(invoice_item)
        self.v_bc = invoice_item.quantity * (invoice_item.price - cost)
        if self.p_cofins is not None:
            self.v_cofins = self.v_bc * self.p_cofins / 100

    @classmethod
    def get_tax_template(cls, invoice_item):
        return invoice_item.sellable.product.cofins_template

    #
    # Private API
    #

    def _get_item_cost(self, invoice_item):
        from stoqlib.domain.sale import SaleItem

        if isinstance(invoice_item, SaleItem):
            return invoice_item.average_cost

        return 0
Ejemplo n.º 9
0
class StockTransactionHistory(Domain):
    __storm_table__ = 'stock_transaction_history'

    TYPE_IMPORTED = 15
    product_stock_item_id = IntCol()
    stock_cost = PriceCol()
    quantity = QuantityCol()
    responsible_id = IntCol()
    date = DateTimeCol()
    object_id = IntCol()
    type = IntCol()
Ejemplo n.º 10
0
class ProductComponent(Domain):
    """A |product| and it's related |component| eg other product

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/product_component.html>`__
    """

    __storm_table__ = 'product_component'

    quantity = QuantityCol(default=Decimal(1))
    product_id = IdCol()
    product = Reference(product_id, 'Product.id')
    component_id = IdCol()
    component = Reference(component_id, 'Product.id')
    design_reference = UnicodeCol(default=u'')
Ejemplo n.º 11
0
class InvoiceItemPis(BasePIS):
    """Invoice of PIS tax."""

    __storm_table__ = 'invoice_item_pis'

    #: Value of PIS tax.
    v_pis = PriceCol(default=0)

    #: Value of the PIS tax calculation basis.
    v_bc = PriceCol(default=None)

    #: Quantity sold
    q_bc_prod = QuantityCol(default=None)

    #
    # Public API
    #

    def set_initial_values(self, invoice_item):
        self.update_values(invoice_item)

    def update_values(self, invoice_item):
        self.q_bc_prod = invoice_item.quantity

        # When the CST is contained in the list the calculation is not performed
        # because the taxpayer is exempt.
        if self.cst in [4, 5, 6, 7, 8, 9]:
            return
        cost = self._get_item_cost(invoice_item)
        self.v_bc = invoice_item.quantity * (invoice_item.price - cost)
        if self.p_pis is not None:
            self.v_pis = self.v_bc * self.p_pis / 100

    @classmethod
    def get_tax_template(cls, invoice_item):
        return invoice_item.sellable.product.pis_template

    #
    # Private API
    #

    def _get_item_cost(self, invoice_item):
        from stoqlib.domain.sale import SaleItem

        if isinstance(invoice_item, SaleItem):
            return invoice_item.average_cost

        return 0
Ejemplo n.º 12
0
class BaseIPI(BaseTax):
    CALC_ALIQUOTA = u'aliquot'
    CALC_UNIDADE = u'unit'

    cl_enq = UnicodeCol(default=u'')
    cnpj_prod = UnicodeCol(default=u'')
    c_selo = UnicodeCol(default=u'')
    q_selo = IntCol(default=None)
    c_enq = UnicodeCol(default=u'')

    cst = IntCol(default=None)
    p_ipi = PercentCol(default=None)

    q_unid = QuantityCol(default=None)

    calculo = EnumCol(default=CALC_ALIQUOTA, allow_none=False)
Ejemplo n.º 13
0
class ProductStockItem(Domain):
    """Class that makes a reference to the |product| stock of a
    certain |branch|.

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/product_stock_item.html>`__
    """

    __storm_table__ = 'product_stock_item'

    #: the average stock price, will be updated as new stock items are
    #: received.
    stock_cost = PriceCol(default=0)

    #: number of storables in the stock item
    quantity = QuantityCol(default=0)

    branch_id = IdCol()

    #: the |branch| this stock item belongs to
    branch = Reference(branch_id, 'Branch.id')

    storable_id = IdCol()

    #: the |storable| the stock item refers to
    storable = Reference(storable_id, 'Storable.id')

    batch_id = IdCol()

    #: The |batch| that the storable is in.
    batch = Reference(batch_id, 'StorableBatch.id')

    def update_cost(self, new_quantity, new_cost):
        """Update the stock_item according to new quantity and cost.

        :param new_quantity: The new quantity added to stock.
        :param new_cost: The cost of one unit of the added stock.
        """
        total_cost = self.quantity * self.stock_cost
        total_cost += new_quantity * new_cost
        total_items = self.quantity + new_quantity
        self.stock_cost = total_cost / total_items
Ejemplo n.º 14
0
class PurchaseItem(Domain):
    """This class stores information of the purchased items.
    """

    __storm_table__ = 'purchase_item'

    quantity = QuantityCol(default=1)
    quantity_received = QuantityCol(default=0)
    quantity_sold = QuantityCol(default=0)
    quantity_returned = QuantityCol(default=0)

    #: the cost which helps the purchaser to define the
    #: main cost of a certain product.
    base_cost = PriceCol()

    cost = PriceCol()

    #: The ICMS ST value for the product purchased
    icms_st_value = PriceCol(default=0)

    #: The IPI value for the product purchased
    ipi_value = PriceCol(default=0)

    expected_receival_date = DateTimeCol(default=None)

    sellable_id = IdCol()

    #: the |sellable|
    sellable = Reference(sellable_id, 'Sellable.id')

    order_id = IdCol()

    #: the |purchase|
    order = Reference(order_id, 'PurchaseOrder.id')

    parent_item_id = IdCol()
    parent_item = Reference(parent_item_id, 'PurchaseItem.id')

    children_items = ReferenceSet('id', 'PurchaseItem.parent_item_id')

    def __init__(self, store=None, **kw):
        if not 'sellable' in kw:
            raise TypeError('You must provide a sellable argument')
        if not 'order' in kw:
            raise TypeError('You must provide a order argument')

        # FIXME: Avoding shadowing sellable.cost
        kw['base_cost'] = kw['sellable'].cost

        if not 'cost' in kw:
            kw['cost'] = kw['sellable'].cost

        Domain.__init__(self, store=store, **kw)

    #
    # Accessors
    #

    def get_total(self):
        return currency(self.quantity * self.cost)

    def get_total_sold(self):
        return currency(self.quantity_sold * self.cost)

    def get_received_total(self):
        return currency(self.quantity_received * self.cost)

    def has_been_received(self):
        return self.quantity_received >= self.quantity

    def has_partial_received(self):
        return self.quantity_received > 0

    def get_pending_quantity(self):
        return self.quantity - self.quantity_received

    def get_quantity_as_string(self):
        unit = self.sellable.unit
        return u"%s %s" % (format_quantity(self.quantity),
                           unit and unit.description or u"")

    def get_quantity_received_as_string(self):
        unit = self.sellable.unit
        return u"%s %s" % (format_quantity(self.quantity_received),
                           unit and unit.description or u"")

    @classmethod
    def get_ordered_quantity(cls, store, sellable):
        """Returns the quantity already ordered of a given sellable.

        :param store: a store
        :param sellable: the sellable we want to know the quantity ordered.
        :returns: the quantity already ordered of a given sellable or zero if
          no quantity have been ordered.
        """
        query = And(PurchaseItem.sellable_id == sellable.id,
                    PurchaseOrder.id == PurchaseItem.order_id,
                    PurchaseOrder.status == PurchaseOrder.ORDER_CONFIRMED)
        ordered_items = store.find(PurchaseItem, query)
        return ordered_items.sum(PurchaseItem.quantity) or Decimal(0)

    def return_consignment(self, quantity):
        """
        Return this as a consignment item

        :param quantity: the quantity to return
        """
        storable = self.sellable.product_storable
        assert storable
        storable.decrease_stock(quantity=quantity,
                                branch=self.order.branch,
                                type=StockTransactionHistory.TYPE_CONSIGNMENT_RETURNED,
                                object_id=self.id)

    def get_component_quantity(self, parent):
        """Get the quantity of a component.

        :param parent: the |purchase_item| parent_item of self
        :returns: the quantity of the component
        """
        for component in parent.sellable.product.get_components():
            if self.sellable.product == component.component:
                return component.quantity
Ejemplo n.º 15
0
class WorkOrderItem(Domain):
    """A |workorder| item

    This is an item in a |workorder|. That is, a |product| or a |service|
    (here referenced by their respective |sellable|) used on the work
    and that will be after used to compose the |saleitem| of the |sale|.

    Note that objects of this type should not be created manually, only by
    calling :meth:`WorkOrder.add_sellable`

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/work_order_item.html>`__
    """

    __storm_table__ = 'work_order_item'

    #: |sellable|'s quantity used on the |workorder|
    quantity = QuantityCol(default=0)

    #: price of the |sellable|, this is how much the |client| is going
    #: to be charged for the sellable. This includes discounts and markup.
    price = PriceCol()

    sellable_id = IntCol()
    #: the |sellable| of this item, either a |service| or a |product|
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IntCol()

    #: If the sellable is a storable, the |batch| that it was removed from
    batch = Reference(batch_id, 'StorableBatch.id')

    order_id = IntCol()
    #: |workorder| this item belongs
    order = Reference(order_id, 'WorkOrder.id')

    @property
    def total(self):
        """The total value for this item

        Note that this is the same as :obj:`.quantity` * :obj:`.price`
        """
        return currency(self.price * self.quantity)

    def __init__(self, *args, **kwargs):
        self._original_quantity = 0
        super(WorkOrderItem, self).__init__(*args, **kwargs)

    def __storm_loaded__(self):
        super(WorkOrderItem, self).__storm_loaded__()
        self._original_quantity = self.quantity

    #
    #  Public API
    #

    def sync_stock(self):
        """Synchronizes the stock, increasing/decreasing it accordingly.

        When setting :obj:`~.quantity` be sure to call this to properly
        synchronize the stock (increase or decrease it). That counts
        for object creation too.
        """
        storable = self.sellable.product_storable
        if not storable:
            # Not a product
            return

        diff_quantity = self._original_quantity - self.quantity
        if diff_quantity > 0:
            storable.increase_stock(
                diff_quantity, self.order.branch,
                StockTransactionHistory.TYPE_WORK_ORDER_USED, self.id)
        elif diff_quantity < 0:
            diff_quantity = -diff_quantity
            storable.decrease_stock(
                diff_quantity, self.order.branch,
                StockTransactionHistory.TYPE_WORK_ORDER_USED, self.id)

        # Reset the values used to calculate the stock quantity, just like
        # when the object as loaded from the database again.
        self._original_quantity = self.quantity
Ejemplo n.º 16
0
class InvoiceItemCofins(BaseCOFINS):
    """Invoice of COFINS tax."""

    __storm_table__ = 'invoice_item_cofins'

    COFINS_NAO_CUMULATIVO_PADRAO = Decimal('7.6')

    #: Value of COFINS tax
    v_cofins = PriceCol(default=0)

    #: Value of the COFINS tax calculation basis.
    v_bc = PriceCol(default=None)

    #: Quantity sold
    q_bc_prod = QuantityCol(default=None)

    #
    # Public API
    #

    def set_initial_values(self, invoice_item):
        self.update_values(invoice_item)

    def update_values(self, invoice_item):
        self.q_bc_prod = invoice_item.quantity

        # When the CST is contained in the list the calculation is not performed
        # because the taxpayer is exempt.
        if self.cst in [4, 5, 6, 7, 8, 9]:
            return

        # When the branch is Simples Nacional (CRT 1 or 2) and the cofins is 99,
        # the values should be zero
        if self.cst == 99 and invoice_item.parent.branch.crt in [1, 2]:
            self.v_bc = 0
            self.p_cofins = 0
            self.v_cofins = 0
            return

        cost = self._get_item_cost(invoice_item)
        if self.p_cofins == self.COFINS_NAO_CUMULATIVO_PADRAO:
            # Regime de incidencia não cumulativa
            self.v_bc = quantize(invoice_item.quantity * (invoice_item.price - cost))
        else:
            # Regime de incidencia cumulativa
            self.v_bc = quantize(invoice_item.quantity * invoice_item.price)

        if self.p_cofins is not None:
            self.v_cofins = quantize(self.v_bc * self.p_cofins / 100)

    @classmethod
    def get_tax_template(cls, invoice_item):
        default_cofins = sysparam.get_object(invoice_item.store, 'DEFAULT_PRODUCT_COFINS_TEMPLATE')
        # FIXME: Allow use COFINS templates in services
        if invoice_item.sellable.service:
            return default_cofins

        return invoice_item.sellable.product.cofins_template or default_cofins

    #
    # Private API
    #

    def _get_item_cost(self, invoice_item):
        from stoqlib.domain.sale import SaleItem

        if isinstance(invoice_item, SaleItem):
            return invoice_item.average_cost

        return 0
Ejemplo n.º 17
0
class ProductionMaterial(Domain):
    """Production Material object implementation.

    This represents the material needed by a production. It can either be
    consumed or lost (due to manufacturing process).

    """
    __storm_table__ = 'production_material'

    product_id = IdCol()

    #: The |product| that will be consumed.
    product = Reference(product_id, 'Product.id')

    order_id = IdCol()
    #: The |production| that will consume this material.
    order = Reference(order_id, 'ProductionOrder.id')

    # The quantity needed of this material.
    needed = QuantityCol(default=1)

    #: The quantity that is actually allocated to this production. It may be
    #: more than the quantity required (and in this case, the remaining quantity
    #: will be returned to the stock later.
    allocated = QuantityCol(default=0)

    #: The quantity already used of this material.
    consumed = QuantityCol(default=0)

    #: The quantity lost of this material.
    lost = QuantityCol(default=0)

    #: The quantity to purchase of this material.
    to_purchase = QuantityCol(default=0)

    #: The quantity to manufacture of this material.
    to_make = QuantityCol(default=0)

    #
    # Public API
    #

    def can_add_lost(self, quantity):
        """Returns if we can loose a certain quantity of this material.

        :param quantity: the quantity that will be lost.
        """
        return self.can_consume(quantity)

    def can_consume(self, quantity):
        assert quantity > 0
        if self.order.status != ProductionOrder.ORDER_PRODUCING:
            return False

        return self.lost + quantity <= self.needed - self.consumed

    def allocate(self, quantity=None):
        """Allocates the needed quantity of this material by decreasing the
        stock quantity. If no quantity was specified, it will decrease all the
        stock needed or the maximum quantity available. Otherwise, allocate the
        quantity specified or raise a ValueError exception, if the quantity is
        not available.

        :param quantity: the quantity to be allocated or None to allocate the
                         maximum quantity possible.
        """
        storable = self.product.storable
        # If there is no storable for the product, than we just need to allocate
        # what is necessary
        if not storable:
            self.allocated = self.needed
            return

        stock = self.get_stock_quantity()
        if quantity is None:
            required = self.needed - self.allocated
            if stock > required:
                quantity = required
            else:
                quantity = stock
        elif quantity > stock:
            raise ValueError(_(u'Can not allocate this quantity.'))

        if quantity > 0:
            self.allocated += quantity
            storable.decrease_stock(
                quantity, self.order.branch,
                StockTransactionHistory.TYPE_PRODUCTION_ALLOCATED, self.id)

    def return_remaining(self):
        """Returns remaining allocated material to the stock

        This should be called only after the production order is closed.
        """
        assert self.order.status == ProductionOrder.ORDER_CLOSED
        remaining = self.allocated - self.lost - self.consumed
        assert remaining >= 0
        if not remaining:
            return
        storable = self.product.storable
        if not storable:
            return
        storable.increase_stock(
            remaining, self.order.branch,
            StockTransactionHistory.TYPE_PRODUCTION_RETURNED, self.id)
        self.allocated -= remaining

    def add_lost(self, quantity):
        """Adds the quantity lost of this material. The maximum quantity that
        can be lost is given by the formula::

            - max_lost(quantity) = needed - consumed - lost - quantity

        :param quantity: the quantity that was lost.
        """
        assert quantity > 0

        if self.lost + quantity > self.needed - self.consumed:
            raise ValueError(_(u'Cannot loose this quantity.'))

        required = self.consumed + self.lost + quantity
        if required > self.allocated:
            self.allocate(required - self.allocated)

        self.lost += quantity
        store = self.store
        ProductHistory.add_lost_item(store, self.order.branch, self)

    def consume(self, quantity):
        """Consumes a certain quantity of material. The maximum quantity
        allowed to be consumed is given by the following formula:

            - max_consumed(quantity) = needed - consumed - lost - quantity

        :param quantity: the quantity to be consumed.
        """
        assert quantity > 0

        available = self.allocated - self.consumed - self.lost
        if quantity > available:
            raise ValueError(_(u'Can not consume this quantity.'))

        required = self.consumed + self.lost + quantity
        if required > self.allocated:  # pragma nocover
            self.allocate(required - self.allocated)

        self.consumed += quantity
        store = self.store
        ProductHistory.add_consumed_item(store, self.order.branch, self)

    #
    # IDescribable Implementation
    #

    def get_description(self):
        return self.product.sellable.get_description()

    # Accessors

    @property
    def unit_description(self):
        return self.product.sellable.unit_description

    def get_stock_quantity(self):
        storable = self.product.storable
        assert storable is not None
        return storable.get_balance_for_branch(self.order.branch)
Ejemplo n.º 18
0
class LoanItem(Domain):
    """An item in a :class:`loan <Loan>`

    Note that when changing :obj:`~.quantity`, :obj:`~.return_quantity`
    or :obj:`~.sale_quantity` you will need to call :meth:`.sync_stock`
    to synchronize the stock (increase or decrease it).

    Also note that objects of this type should never be created manually, only
    by calling :meth:`Loan.add_sellable`

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/loan_item.html>`__
    """
    __storm_table__ = 'loan_item'

    #: The total quantity that was loaned. The product stock for this
    #: will be decreased when the loan stock is synchonized
    quantity = QuantityCol()

    #: The loadned quantity that was sold. Will increase stock so
    #: it's decreased correctly when the
    #: :class:`sale <stoqlib.domain.sale.Sale>` is confirmed
    sale_quantity = QuantityCol(default=Decimal(0))

    #: The loaned quantity that was returned. Will increase stock
    return_quantity = QuantityCol(default=Decimal(0))

    #: price to use for this :obj:`~.sellable` when creating
    #: a :class:`sale <stoqlib.domain.sale.Sale>`
    price = PriceCol()

    #: original price of a sellable
    base_price = PriceCol()

    sellable_id = IdCol(allow_none=False)

    #: :class:`sellable <stoqlib.domain.sellable.Sellable>` that is loaned
    #: cannot be *None*
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IdCol()

    #: If the sellable is a storable, the |batch| that it was returned in
    batch = Reference(batch_id, 'StorableBatch.id')

    loan_id = IdCol()

    #: :class:`loan <Loan>` this item belongs to
    loan = Reference(loan_id, 'Loan.id')

    icms_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIcms` tax for *self*
    icms_info = Reference(icms_info_id, 'InvoiceItemIcms.id')

    ipi_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIpi` tax for *self*
    ipi_info = Reference(ipi_info_id, 'InvoiceItemIpi.id')

    pis_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemPis` tax for *self*
    pis_info = Reference(pis_info_id, 'InvoiceItemPis.id')

    cofins_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemCofins` tax for *self*
    cofins_info = Reference(cofins_info_id, 'InvoiceItemCofins.id')

    def __init__(self, *args, **kwargs):
        # stores the total quantity that was loaned before synching stock
        self._original_quantity = 0
        # stores the loaned quantity that was returned before synching stock
        self._original_return_quantity = self.return_quantity
        check_tax_info_presence(kwargs, kwargs.get('store'))

        super(LoanItem, self).__init__(*args, **kwargs)

        product = self.sellable.product
        if product:
            self.ipi_info.set_item_tax(self)
            self.icms_info.set_item_tax(self)
            self.pis_info.set_item_tax(self)
            self.cofins_info.set_item_tax(self)

    def __storm_loaded__(self):
        super(LoanItem, self).__storm_loaded__()
        self._original_quantity = self.quantity
        self._original_return_quantity = self.return_quantity

    @property
    def branch(self):
        return self.loan.branch

    @property
    def storable(self):
        return self.sellable.product_storable

    #
    # IInvoiceItem implementation
    #

    @property
    def parent(self):
        return self.loan

    @property
    def item_discount(self):
        if self.price < self.base_price:
            return self.base_price - self.price
        return Decimal('0')

    @property
    def cfop_code(self):
        return u'5917'

    def sync_stock(self):
        """Synchronizes the stock, increasing/decreasing it accordingly.
        Using the stored values when this object is created/loaded, compute how
        much we should increase or decrease the stock quantity.

        When setting :obj:`~.quantity`, :obj:`~.return_quantity`
        or :obj:`~.sale_quantity` be sure to call this to properly
        synchronize the stock (increase or decrease it). That counts
        for object creation too.
        """
        loaned = self._original_quantity - self.quantity
        returned = self.return_quantity - self._original_return_quantity
        diff_quantity = loaned + returned

        if diff_quantity > 0:
            self.storable.increase_stock(
                diff_quantity,
                self.branch,
                StockTransactionHistory.TYPE_RETURNED_LOAN,
                self.id,
                batch=self.batch)
        elif diff_quantity < 0:
            diff_quantity = -diff_quantity
            self.storable.decrease_stock(diff_quantity,
                                         self.branch,
                                         StockTransactionHistory.TYPE_LOANED,
                                         self.id,
                                         batch=self.batch)

        # Reset the values used to calculate the stock quantity, just like
        # when the object as loaded from the database again.
        self._original_quantity = self.quantity
        self._original_return_quantity = self.return_quantity

    def get_remaining_quantity(self):
        """The remaining quantity that wasn't returned/sold yet

        This is the same as
        :obj:`.quantity` - :obj:`.sale_quantity` - :obj:`.return_quantity`
        """
        return self.quantity - self.sale_quantity - self.return_quantity

    def get_quantity_unit_string(self):
        return u"%s %s" % (self.quantity, self.sellable.unit_description)

    def get_total(self):
        return currency(self.price * self.quantity)

    def set_discount(self, discount):
        """Apply *discount* on this item

        Note that the discount will be applied based on :obj:`.base_price`
        and then substitute :obj:`.price`, making any previous
        discount/surcharge being lost

        :param decimal.Decimal discount: the discount to be applied
            as a percentage, e.g. 10.0, 22.5
        """
        self.price = quantize(self.base_price * (1 - discount / 100))
Ejemplo n.º 19
0
class TransferOrderItem(Domain):
    """Transfer order item

    """

    __storm_table__ = 'transfer_order_item'

    sellable_id = IdCol()

    # FIXME: This should be a product, since it does not make sense to transfer
    # serviçes
    #: The |sellable| to transfer
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IdCol()

    #: If the sellable is a storable, the |batch| that was transfered
    batch = Reference(batch_id, 'StorableBatch.id')

    transfer_order_id = IdCol()

    #: The |transfer| this item belongs to
    transfer_order = Reference(transfer_order_id, 'TransferOrder.id')

    #: The quantity to transfer
    quantity = QuantityCol()

    #: Average cost of the item in the source branch at the time of transfer.
    stock_cost = PriceCol(default=0)

    icms_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIcms` tax for *self*
    icms_info = Reference(icms_info_id, 'InvoiceItemIcms.id')

    ipi_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIpi` tax for *self*
    ipi_info = Reference(ipi_info_id, 'InvoiceItemIpi.id')

    item_discount = Decimal('0')

    def __init__(self, store=None, **kwargs):
        if not 'sellable' in kwargs:
            raise TypeError('You must provide a sellable argument')
        kwargs['ipi_info'] = InvoiceItemIpi(store=store)
        kwargs['icms_info'] = InvoiceItemIcms(store=store)
        super(TransferOrderItem, self).__init__(store=store, **kwargs)

        product = self.sellable.product
        if product:
            self.ipi_info.set_item_tax(self)
            self.icms_info.set_item_tax(self)

    #
    # IInvoiceItem implementation
    #

    @property
    def parent(self):
        return self.transfer_order

    @property
    def base_price(self):
        return self.stock_cost

    @property
    def price(self):
        return self.stock_cost

    @property
    def nfe_cfop_code(self):
        source_branch = self.transfer_order.source_branch
        source_address = source_branch.person.get_main_address()

        destination_branch = self.transfer_order.destination_branch
        destination_address = destination_branch.person.get_main_address()

        same_state = True
        if (source_address.city_location.state !=
                destination_address.city_location.state):
            same_state = False

        if same_state:
            return u'5152'
        else:
            return u'6152'

    #
    # Public API
    #

    def get_total(self):
        """Returns the total cost of a transfer item eg quantity * cost"""
        return self.quantity * self.sellable.cost

    def send(self):
        """Sends this item to it's destination |branch|.
        This method should never be used directly, and to send a transfer you
        should use TransferOrder.send().
        """
        product = self.sellable.product
        if product.manage_stock:
            storable = product.storable
            storable.decrease_stock(self.quantity,
                                    self.transfer_order.source_branch,
                                    StockTransactionHistory.TYPE_TRANSFER_TO,
                                    self.id,
                                    batch=self.batch)
        ProductHistory.add_transfered_item(self.store,
                                           self.transfer_order.source_branch,
                                           self)

    def receive(self):
        """Receives this item, increasing the quantity in the stock.
        This method should never be used directly, and to receive a transfer
        you should use TransferOrder.receive().
        """
        product = self.sellable.product
        if product.manage_stock:
            storable = product.storable
            storable.increase_stock(self.quantity,
                                    self.transfer_order.destination_branch,
                                    StockTransactionHistory.TYPE_TRANSFER_FROM,
                                    self.id,
                                    unit_cost=self.stock_cost,
                                    batch=self.batch)
Ejemplo n.º 20
0
class ProductionItem(Domain):
    """Production Item object implementation.
    """

    __storm_table__ = 'production_item'

    #: The product's quantity that will be manufactured.
    quantity = QuantityCol(default=1)

    #: The product's quantity that was manufactured.
    produced = QuantityCol(default=0)

    #: The product's quantity that was lost.
    lost = QuantityCol(default=0)

    order_id = IdCol()

    #: The :class:`ProductionOrder` of this item.
    order = Reference(order_id, 'ProductionOrder.id')

    product_id = IdCol()

    #: The product that will be manufactured.
    product = Reference(product_id, 'Product.id')

    def get_description(self):
        return self.product.sellable.get_description()

    #
    # Properties
    #

    @property
    def unit_description(self):
        return self.product.sellable.unit_description

    @property
    def sellable(self):
        return self.product.sellable

    #
    # Private API
    #

    def _get_material_from_component(self, component):
        store = self.store
        return store.find(ProductionMaterial,
                          product=component.component,
                          order=self.order).one()

    #
    # Public API
    #

    def get_components(self):
        return self.product.get_components()

    def can_produce(self, quantity):
        """Returns if we can produce a certain quantity.  We can produce a
        quantity items until we reach the total quantity that will be
        manufactured minus the quantity that was lost.

        :param quantity: the quantity that will be produced.
        """
        assert quantity > 0
        if self.order.status != ProductionOrder.ORDER_PRODUCING:
            return False

        return self.produced + quantity + self.lost <= self.quantity

    def is_completely_produced(self):
        return self.quantity == self.produced + self.lost

    def produce(self, quantity, produced_by=None, serials=None):
        """Sets a certain quantity as produced. The quantity will be marked as
        produced only if there are enough materials allocated, otherwise a
        ValueError exception will be raised.

        :param quantity: the quantity that will be produced.
        """
        assert self.can_produce(quantity)

        # check if its ok to produce before consuming material
        if self.product.has_quality_tests():
            # We have some quality tests to assure. Register it for later
            assert produced_by
            assert len(serials) == quantity
            # We only support yield quantity > 1 when there are no tests
            assert self.product.yield_quantity == 1

        self.store.savepoint(u'before_produce')

        for component in self.get_components():
            material = self._get_material_from_component(component)
            needed_material = quantity * component.quantity

            try:
                material.consume(needed_material)
            except ValueError:
                self.store.rollback_to_savepoint(u'before_produce')
                raise

        if self.product.has_quality_tests():
            for serial in serials:
                ProductionProducedItem(store=self.store,
                                       order=self.order,
                                       product=self.product,
                                       produced_by=produced_by,
                                       produced_date=localnow(),
                                       serial_number=serial,
                                       entered_stock=False)
        else:
            # There are no quality tests for this product. Increase stock
            # right now.
            storable = self.product.storable
            # A production process may yield more than one unit of this product
            yield_quantity = quantity * self.product.yield_quantity
            storable.increase_stock(
                yield_quantity, self.order.branch,
                StockTransactionHistory.TYPE_PRODUCTION_PRODUCED, self.id)
        self.produced += quantity
        self.order.try_finalize_production()
        ProductHistory.add_produced_item(self.store, self.order.branch, self)

    def add_lost(self, quantity):
        """Adds a quantity that was lost. The maximum quantity that can be
        lost is the total quantity minus the quantity already produced.

        :param quantity: the quantity that was lost.
        """
        if self.lost + quantity > self.quantity - self.produced:
            raise ValueError(
                _(u'Can not lost more items than the total production quantity.'
                  ))

        store = self.store
        store.savepoint(u'before_lose')

        for component in self.get_components():
            material = self._get_material_from_component(component)
            try:
                material.add_lost(quantity * component.quantity)
            except ValueError:
                store.rollback_to_savepoint(u'before_lose')
                raise

        self.lost += quantity
        self.order.try_finalize_production()
        ProductHistory.add_lost_item(store, self.order.branch, self)
Ejemplo n.º 21
0
class ReturnedSaleItem(Domain):
    """An item of a :class:`returned sale <ReturnedSale>`

    Note that objects of this type should never be created manually, only by
    calling :meth:`Sale.create_sale_return_adapter`
    """

    __storm_table__ = 'returned_sale_item'

    #: the returned quantity
    quantity = QuantityCol(default=0)

    #: The price which this :obj:`.sale_item` was sold.
    #: When creating this object, if *price* is not passed to the
    #: contructor, it defaults to :obj:`.sale_item.price` or
    #: :obj:`.sellable.price`
    price = PriceCol()

    sale_item_id = IdCol(default=None)

    #: the returned |saleitem|
    sale_item = Reference(sale_item_id, 'SaleItem.id')

    sellable_id = IdCol()

    #: The returned |sellable|
    #: Note that if :obj:`.sale_item` != ``None``, this is the same as
    #: :obj:`.sale_item.sellable`
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IdCol()

    #: If the sellable is a storable, the |batch| that it was removed from
    batch = Reference(batch_id, 'StorableBatch.id')

    returned_sale_id = IdCol()

    #: the |returnedsale| which this item belongs
    returned_sale = Reference(returned_sale_id, 'ReturnedSale.id')

    #: Id of ICMS tax in product tax template
    icms_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIcms` tax for *self*
    icms_info = Reference(icms_info_id, 'InvoiceItemIcms.id')

    #: Id of IPI tax in product tax template
    ipi_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIpi` tax fo *self*
    ipi_info = Reference(ipi_info_id, 'InvoiceItemIpi.id')

    #: Id of PIS tax in product tax template
    pis_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemPis` tax fo *self*
    pis_info = Reference(pis_info_id, 'InvoiceItemPis.id')

    #: Id of COFINS tax in product tax template
    cofins_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemCofins` tax fo *self*
    cofins_info = Reference(cofins_info_id, 'InvoiceItemCofins.id')

    item_discount = Decimal('0')

    parent_item_id = IdCol()
    parent_item = Reference(parent_item_id, 'ReturnedSaleItem.id')

    children_items = ReferenceSet('id', 'ReturnedSaleItem.parent_item_id')

    def __init__(self, store=None, **kwargs):
        # TODO: Add batch logic here. (get if from sale_item or check if was
        # passed togheter with sellable)
        sale_item = kwargs.get('sale_item')
        sellable = kwargs.get('sellable')

        if not sale_item and not sellable:
            raise ValueError(
                "A sale_item or a sellable is mandatory to create this object")
        elif sale_item and sellable and sale_item.sellable != sellable:
            raise ValueError("sellable must be the same as sale_item.sellable")
        elif sale_item and not sellable:
            sellable = sale_item.sellable
            kwargs['sellable'] = sellable

        if not 'price' in kwargs:
            # sale_item.price takes priority over sellable.price
            kwargs['price'] = sale_item.price if sale_item else sellable.price

        check_tax_info_presence(kwargs, store)

        super(ReturnedSaleItem, self).__init__(store=store, **kwargs)

        product = self.sellable.product
        if product:
            self.ipi_info.set_item_tax(self)
            self.icms_info.set_item_tax(self)
            self.pis_info.set_item_tax(self)
            self.cofins_info.set_item_tax(self)

    @property
    def total(self):
        """The total being returned

        This is the same as :obj:`.price` * :obj:`.quantity`
        """
        return self.price * self.quantity

    #
    # IInvoiceItem implementation
    #

    @property
    def base_price(self):
        return self.price

    @property
    def parent(self):
        return self.returned_sale

    @property
    def nfe_cfop_code(self):
        sale = self.returned_sale.sale
        client_address = sale.client.person.get_main_address()
        branch_address = sale.branch.person.get_main_address()

        same_state = True
        if branch_address.city_location.state != client_address.city_location.state:
            same_state = False

        if same_state:
            return u'1202'
        else:
            return u'2202'

    #
    #  Public API
    #

    def get_total(self):
        return self.total

    def return_(self, branch):
        """Do the real return of this item

        When calling this, the real return will happen, that is,
        if :obj:`.sellable` is a |product|, it's stock will be
        increased on *branch*.
        """
        storable = self.sellable.product_storable
        if storable:
            storable.increase_stock(self.quantity,
                                    branch,
                                    StockTransactionHistory.TYPE_RETURNED_SALE,
                                    self.id,
                                    batch=self.batch)
        if self.sale_item:
            self.sale_item.quantity_decreased -= self.quantity

    def undo(self):
        """Undo this item return.

        This is the oposite of the return, ie, the item will be removed back
        from stock and the sale item decreased quantity will be restored.
        """
        storable = self.sellable.product_storable
        if storable:
            storable.decrease_stock(
                self.quantity,
                self.returned_sale.branch,
                # FIXME: Create a new type
                StockTransactionHistory.TYPE_RETURNED_SALE,
                self.id,
                batch=self.batch)
        if self.sale_item:
            self.sale_item.quantity_decreased += self.quantity

    def get_component_quantity(self, parent):
        for component in parent.sellable.product.get_components():
            if self.sellable.product == component.component:
                return component.quantity
Ejemplo n.º 22
0
class StockTransactionHistory(Domain):
    """ This class stores information about all transactions made in the
    stock

    Everytime a |storable| has its stock increased or decreased, an object of
    this class will be created, registering the quantity, cost, responsible and
    reason for the transaction
    """

    __storm_table__ = 'stock_transaction_history'

    #: the transaction is an initial stock adustment. Note that with this
    #: transaction, there is no related object.
    TYPE_INITIAL = 0

    #: the transaction is a sale
    TYPE_SELL = 1

    #: the transaction is a return of a sale
    TYPE_RETURNED_SALE = 2

    #: the transaction is the cancellation of a sale
    TYPE_CANCELED_SALE = 3

    #: the transaction is the receival of a purchase
    TYPE_RECEIVED_PURCHASE = 4

    #: the transaction is the return of a loan
    TYPE_RETURNED_LOAN = 5

    #: the transaction is a loan
    TYPE_LOANED = 6

    #: the transaction is the allocation of a product to a production
    TYPE_PRODUCTION_ALLOCATED = 7

    #: the transaction is the production of a product
    TYPE_PRODUCTION_PRODUCED = 8

    #: the transaction is the return of an item from a production
    TYPE_PRODUCTION_RETURNED = 9

    #: the transaction is a stock decrease
    TYPE_STOCK_DECREASE = 10

    #: the transaction is a transfer from a branch
    TYPE_TRANSFER_FROM = 11

    #: the transaction is a transfer to a branch
    TYPE_TRANSFER_TO = 12

    #: the transaction is the adjustment of an inventory
    TYPE_INVENTORY_ADJUST = 13

    #: the transaction is the production of a product that didn't enter
    #: stock right after its creation
    TYPE_PRODUCTION_SENT = 14

    #: this transaction was created automatically by an upgrade from a previous
    #: version of Stoq, when this history didn't exist.
    TYPE_IMPORTED = 15

    #: the transaction is the return of a product to another company (ie, this
    #: shop is giving the product back to the other company).
    TYPE_CONSIGNMENT_RETURNED = 16

    #: the transaction is the utilization of a
    #: |product|/|service| on a |workorderitem|
    TYPE_WORK_ORDER_USED = 17

    types = {TYPE_INVENTORY_ADJUST: _(u'Adjustment for inventory %s'),
             TYPE_RETURNED_LOAN: _(u'Returned from loan %s'),
             TYPE_LOANED: _(u'Loaned for loan %s'),
             TYPE_PRODUCTION_ALLOCATED: _(u'Allocated for production %s'),
             TYPE_PRODUCTION_PRODUCED: _(u'Produced in production %s'),
             TYPE_PRODUCTION_SENT: _(u'Produced in production %s'),
             TYPE_PRODUCTION_RETURNED: _(u'Returned remaining from '
                                         u'production %s'),
             TYPE_RECEIVED_PURCHASE: _(u'Received for receiving order %s'),
             TYPE_RETURNED_SALE: _(u'Returned sale %s'),
             TYPE_CANCELED_SALE: _(u'Returned from canceled sale %s'),
             TYPE_SELL: _(u'Sold in sale %s'),
             TYPE_STOCK_DECREASE: _(u'Product removal for stock decrease %s'),
             TYPE_TRANSFER_FROM: _(u'Transfered from branch in transfer '
                                   u'order %s'),
             TYPE_TRANSFER_TO: _(u'Transfered to this branch in transfer '
                                 u'order %s'),
             TYPE_INITIAL: _(u'Registred initial stock'),
             TYPE_IMPORTED: _(u'Imported from previous version'),
             TYPE_CONSIGNMENT_RETURNED: _(u'Consigned product returned.'),
             TYPE_WORK_ORDER_USED: _(u'Used on work order %s.'),
             }

    #: the date and time the transaction was made
    date = DateTimeCol(default_factory=localnow)

    product_stock_item_id = IdCol()

    #: the |productstockitem| used in the transaction
    product_stock_item = Reference(product_stock_item_id, 'ProductStockItem.id')

    #: the stock cost of the transaction on the time it was made
    stock_cost = PriceCol()

    #: The quantity that was removed or added to the stock.
    #: Positive value if the value was increased, negative if decreased.
    quantity = QuantityCol()

    responsible_id = IdCol()

    #: the |loginuser| responsible for the transaction
    responsible = Reference(responsible_id, 'LoginUser.id')

    #: the id of the object who altered the stock
    object_id = IdCol()

    #: the type of the transaction
    type = IntCol()

    @property
    def total(self):
        return currency(abs(self.stock_cost * self.quantity))

    def get_object(self):
        if self.type in [self.TYPE_INITIAL, self.TYPE_IMPORTED]:
            return None
        elif self.type in [self.TYPE_SELL, self.TYPE_CANCELED_SALE]:
            from stoqlib.domain.sale import SaleItem
            return self.store.get(SaleItem, self.object_id)
        elif self.type == self.TYPE_RETURNED_SALE:
            from stoqlib.domain.returnedsale import ReturnedSaleItem
            return self.store.get(ReturnedSaleItem, self.object_id)
        elif self.type == self.TYPE_PRODUCTION_PRODUCED:
            from stoqlib.domain.production import ProductionItem
            return self.store.get(ProductionItem, self.object_id)
        elif self.type in [self.TYPE_PRODUCTION_ALLOCATED,
                           self.TYPE_PRODUCTION_RETURNED]:
            from stoqlib.domain.production import ProductionMaterial
            return self.store.get(ProductionMaterial, self.object_id)
        elif self.type == self.TYPE_RECEIVED_PURCHASE:
            from stoqlib.domain.receiving import ReceivingOrderItem
            return self.store.get(ReceivingOrderItem, self.object_id)
        elif self.type in [self.TYPE_RETURNED_LOAN, self.TYPE_LOANED]:
            from stoqlib.domain.loan import LoanItem
            return self.store.get(LoanItem, self.object_id)
        elif self.type == self.TYPE_STOCK_DECREASE:
            from stoqlib.domain.stockdecrease import StockDecreaseItem
            return self.store.get(StockDecreaseItem, self.object_id)
        elif self.type in [self.TYPE_TRANSFER_FROM, self.TYPE_TRANSFER_TO]:
            from stoqlib.domain.transfer import TransferOrderItem
            return self.store.get(TransferOrderItem, self.object_id)
        elif self.type == self.TYPE_INVENTORY_ADJUST:
            from stoqlib.domain.inventory import InventoryItem
            return self.store.get(InventoryItem, self.object_id)
        elif self.type == self.TYPE_PRODUCTION_SENT:
            from stoqlib.domain.production import ProductionProducedItem
            return self.store.get(ProductionProducedItem, self.object_id)
        elif self.type == self.TYPE_CONSIGNMENT_RETURNED:
            from stoqlib.domain.purchase import PurchaseItem
            return self.store.get(PurchaseItem, self.object_id)
        elif self.type == self.TYPE_CONSIGNMENT_RETURNED:
            from stoqlib.domain.purchase import PurchaseItem
            return self.store.get(PurchaseItem, self.object_id)
        elif self.type == self.TYPE_WORK_ORDER_USED:
            from stoqlib.domain.workorder import WorkOrderItem
            return self.store.get(WorkOrderItem, self.object_id)
        else:
            raise ValueError(_('%s has invalid type: %s.') % (self, self.type))

    def get_object_parent(self):
        obj = self.get_object()
        if not obj:
            return None

        from stoqlib.domain.inventory import InventoryItem
        from stoqlib.domain.loan import LoanItem
        from stoqlib.domain.production import ProductionItem
        from stoqlib.domain.production import ProductionMaterial
        from stoqlib.domain.purchase import PurchaseItem
        from stoqlib.domain.receiving import ReceivingOrderItem
        from stoqlib.domain.returnedsale import ReturnedSaleItem
        from stoqlib.domain.sale import SaleItem
        from stoqlib.domain.stockdecrease import StockDecreaseItem
        from stoqlib.domain.workorder import WorkOrderItem
        from stoqlib.domain.transfer import TransferOrderItem

        if isinstance(obj, SaleItem):
            return obj.sale
        elif isinstance(obj, ReturnedSaleItem):
            return obj.returned_sale
        elif isinstance(obj, ProductionItem):
            return obj.order
        elif isinstance(obj, ProductionMaterial):
            return obj.order
        elif isinstance(obj, ReceivingOrderItem):
            return obj.receiving_order
        elif isinstance(obj, LoanItem):
            return obj.loan
        elif isinstance(obj, StockDecreaseItem):
            return obj.stock_decrease
        elif isinstance(obj, TransferOrderItem):
            return obj.transfer_order
        elif isinstance(obj, InventoryItem):
            return obj.inventory
        elif isinstance(obj, PurchaseItem):
            return obj.inventory
        elif isinstance(obj, WorkOrderItem):
            return obj.order
        else:
            raise TypeError(_('Object %s has invalid type (%s)') %
                            (obj, type(obj)))

    def get_description(self):
        """ Based on the type of the transaction, returns the string
        description
        """
        if self.type in [self.TYPE_INITIAL, self.TYPE_IMPORTED]:
            return self.types[self.type]

        object_parent = self.get_object_parent()
        number = unicode(object_parent.identifier)

        return self.types[self.type] % number
Ejemplo n.º 23
0
class InventoryItem(Domain):
    """The InventoryItem belongs to an Inventory.
    It contains the recorded quantity and the actual quantity related
    to a specific product.
    If those quantities are not identitical, it will also contain a reason
    and a cfop describing that.

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/inventory_item.html>`__
    """

    __storm_table__ = 'inventory_item'

    product_id = IntCol()

    #: the item
    product = Reference(product_id, 'Product.id')

    batch_id = IntCol()

    #: If the product is a storable, the |batch| of the product that is being
    #: inventored
    batch = Reference(batch_id, 'StorableBatch.id')

    #: the recorded quantity of a product
    recorded_quantity = QuantityCol()

    #: the actual quantity of a product
    actual_quantity = QuantityCol(default=None)

    #: the product's cost when the product was adjusted.
    product_cost = PriceCol()

    #: the reason of why this item has been adjusted
    reason = UnicodeCol(default=u"")

    cfop_data_id = IntCol(default=None)

    #: the cfop used to adjust this item, this is only set when
    #: an adjustment is done

    cfop_data = Reference(cfop_data_id, 'CfopData.id')

    inventory_id = IntCol()

    #: the inventory process that contains this item
    inventory = Reference(inventory_id, 'Inventory.id')

    def _add_inventory_fiscal_entry(self, invoice_number):
        inventory = self.inventory
        return FiscalBookEntry(
            entry_type=FiscalBookEntry.TYPE_INVENTORY,
            invoice_number=inventory.invoice_number,
            branch=inventory.branch,
            cfop=self.cfop_data,
            store=self.store)

    def adjust(self, invoice_number):
        """Create an entry in fiscal book registering the adjustment
        with the related cfop data and change the product quantity
        available in stock.

        :param invoice_number: invoice number to register
        """
        storable = self.product.storable
        if storable is None:
            raise TypeError(
                "The adjustment item must be a storable product.")

        adjustment_qty = self.get_adjustment_quantity()
        if not adjustment_qty:
            return
        elif adjustment_qty > 0:
            storable.increase_stock(adjustment_qty,
                                    self.inventory.branch,
                                    StockTransactionHistory.TYPE_INVENTORY_ADJUST,
                                    self.id)
        else:
            storable.decrease_stock(abs(adjustment_qty),
                                    self.inventory.branch,
                                    StockTransactionHistory.TYPE_INVENTORY_ADJUST,
                                    self.id)

        self._add_inventory_fiscal_entry(invoice_number)

    def adjusted(self):
        """Find out if this item has been adjusted.

        :returns: ``True`` if the item have already been adjusted,
          ``False`` otherwise.
        """
        # We check reason and cfop_data attributes because they only
        # exist after the item be adjusted
        return self.reason and self.cfop_data

    def get_code(self):
        """Get the product code of this item

        :returns: the product code
        """
        return self.product.sellable.code

    def get_description(self):
        """Returns the product description"""
        return self.product.sellable.get_description()

    def get_fiscal_description(self):
        """Returns a description of the product tax constant"""
        return self.product.sellable.tax_constant.get_description()

    def get_unit_description(self):
        """Returns the product unit description or None if it's not set
        """
        sellable = self.product.sellable
        if sellable.unit:
            return sellable.unit.description

    def get_adjustment_quantity(self):
        """Returns the adjustment quantity, the actual quantity minus
        the recorded quantity or None if there is no actual quantity yet.
        """
        if self.actual_quantity is not None:
            return self.actual_quantity - self.recorded_quantity

    def get_total_cost(self):
        """Returns the total cost of this item, the actual quantity multiplied
        by the product cost in the moment it was adjusted. If the item was not
        adjusted yet, the total cost will be zero.
        """
        if not self.adjusted():
            return Decimal(0)

        return self.product_cost * self.actual_quantity
Ejemplo n.º 24
0
class StockDecreaseItem(Domain):
    """An item in a stock decrease object.

    Note that objects of this type should not be created manually, only by
    calling :meth:`StockDecrease.add_sellable`
    """

    __storm_table__ = 'stock_decrease_item'

    stock_decrease_id = IdCol(default=None)

    #: The stock decrease this item belongs to
    stock_decrease = Reference(stock_decrease_id, 'StockDecrease.id')

    sellable_id = IdCol()

    #: the |sellable| for this decrease
    sellable = Reference(sellable_id, 'Sellable.id')

    batch_id = IdCol()

    #: If the sellable is a storable, the |batch| that it was removed from
    batch = Reference(batch_id, 'StorableBatch.id')

    #: the cost of the |sellable| on the moment this decrease was created
    cost = PriceCol(default=0)

    #: the quantity decreased for this item
    quantity = QuantityCol()

    #: Id of ICMS tax in product tax template
    icms_info_id = IdCol()

    #:the :class:`stoqlib.domain.taxes.InvoiceItemIcms` tax for *self*
    icms_info = Reference(icms_info_id, 'InvoiceItemIcms.id')

    #: Id of IPI tax in product tax template
    ipi_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemIpi` tax for *self*
    ipi_info = Reference(ipi_info_id, 'InvoiceItemIpi.id')

    #: Id of PIS tax in product tax template
    pis_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemPis` tax for *self*
    pis_info = Reference(pis_info_id, 'InvoiceItemPis.id')

    #: Id of COFINS tax in product tax template
    cofins_info_id = IdCol()

    #: the :class:`stoqlib.domain.taxes.InvoiceItemCofins` tax for *self*
    cofins_info = Reference(cofins_info_id, 'InvoiceItemCofins.id')

    item_discount = Decimal('0')

    def __init__(self, store=None, sellable=None, **kwargs):
        if sellable is None:
            raise TypeError('You must provide a sellable argument')
        check_tax_info_presence(kwargs, store)

        super(StockDecreaseItem, self).__init__(store=store,
                                                sellable=sellable,
                                                **kwargs)

        product = self.sellable.product
        if product:
            self.ipi_info.set_item_tax(self)
            self.icms_info.set_item_tax(self)
            self.pis_info.set_item_tax(self)
            self.cofins_info.set_item_tax(self)

    #
    # Properties
    #

    @property
    def total_cost(self):
        return currency(self.cost * self.quantity)

    #
    # IInvoiceItem implementation
    #

    @property
    def parent(self):
        return self.stock_decrease

    @property
    def base_price(self):
        return self.cost

    @property
    def price(self):
        return self.cost

    @property
    def cfop_code(self):
        cfop = self.stock_decrease.cfop.code
        return cfop.replace('.', '')

    #
    # Public API
    #

    def decrease(self, branch):
        # FIXME: We should not be receiving a branch here. We should be using
        # self.stock_decrease.branch for that.
        assert branch

        storable = self.sellable.product_storable
        if storable:
            storable.decrease_stock(
                self.quantity,
                branch,
                StockTransactionHistory.TYPE_STOCK_DECREASE,
                self.id,
                cost_center=self.stock_decrease.cost_center,
                batch=self.batch)

    #
    # Accessors
    #

    def get_total(self):
        return currency(self.cost * self.quantity)

    def get_quantity_unit_string(self):
        unit = self.sellable.unit_description
        if unit:
            return u"%s %s" % (self.quantity, unit)
        return unicode(self.quantity)

    def get_description(self):
        return self.sellable.get_description()
Ejemplo n.º 25
0
class ProductHistory(Domain):
    """Stores |product| history, such as sold (via |sale|),
    received (via |receive|), transfered (via |transfer|) and
    decreased quantities.

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/product_history.html>`__

    .. note:: We keep a reference to |sellable| instead of |product| because
              we want to display the sellable id in the interface instead
              of the product id for consistency with interfaces that display
              both.
    """
    __storm_table__ = 'product_history'

    quantity_sold = QuantityCol(default=None)
    quantity_received = QuantityCol(default=None)
    quantity_transfered = QuantityCol(default=None)
    quantity_produced = QuantityCol(default=None)
    quantity_consumed = QuantityCol(default=None)
    quantity_lost = QuantityCol(default=None)
    quantity_decreased = QuantityCol(default=None)
    production_date = DateTimeCol(default=None)
    sold_date = DateTimeCol(default=None)
    received_date = DateTimeCol(default=None)
    decreased_date = DateTimeCol(default=None)

    branch_id = IdCol()

    #: the |branch|
    branch = Reference(branch_id, 'Branch.id')

    sellable_id = IdCol()

    #: the |sellable|
    sellable = Reference(sellable_id, 'Sellable.id')

    @classmethod
    def add_sold_item(cls, store, branch, product_sellable_item):
        """Adds a |saleitem| to the history. *product_sale_item* is an item
        that was created during a |sale|.

        :param store: a store
        :param branch: the |branch|
        :param product_sellable_item: the |saleitem| for the sold |product|
        """
        cls(branch=branch,
            sellable=product_sellable_item.sellable,
            quantity_sold=product_sellable_item.quantity,
            sold_date=TransactionTimestamp(),
            store=store)

    @classmethod
    def add_received_item(cls, store, branch, receiving_order_item):
        """
        Adds a received item, populates the ProductHistory table using a
        *receiving_order_item* created during a |purchase|

        :param store: a store
        :param branch: the |branch|
        :param receiving_order_item: the item received for |purchase|
        """
        cls(branch=branch, sellable=receiving_order_item.sellable,
            quantity_received=receiving_order_item.quantity,
            received_date=receiving_order_item.receiving_order.receival_date,
            store=store)

    @classmethod
    def add_transfered_item(cls, store, branch, transfer_order_item):
        """
        Adds a transfered_item, populates the ProductHistory table using a
        *transfered_order_item* created during a |transfer|.

        :param store: a store
        :param branch: the source branch
        :param transfer_order_item: the item transfered from source branch
        """
        cls(branch=branch, sellable=transfer_order_item.sellable,
            quantity_transfered=transfer_order_item.quantity,
            received_date=transfer_order_item.transfer_order.receival_date,
            store=store)

    @classmethod
    def add_consumed_item(cls, store, branch, consumed_item):
        """
        Adds a consumed_item, populates the ProductHistory table using a
        production_material item that was used in a |production|.

        :param store: a store
        :param branch: the source branch
        :param retained_item: a ProductionMaterial instance
        """
        cls(branch=branch, sellable=consumed_item.product.sellable,
            quantity_consumed=consumed_item.consumed,
            production_date=localtoday().date(),
            store=store)

    @classmethod
    def add_produced_item(cls, store, branch, produced_item):
        """
        Adds a produced_item, populates the ProductHistory table using a
        production_item that was produced in a production order.

        :param store: a store
        :param branch: the source branch
        :param retained_item: a ProductionItem instance
        """
        cls(branch=branch, sellable=produced_item.product.sellable,
            quantity_produced=produced_item.produced,
            production_date=localtoday().date(), store=store)

    @classmethod
    def add_lost_item(cls, store, branch, lost_item):
        """
        Adds a lost_item, populates the ProductHistory table using a
        production_item/product_material that was lost in a production order.

        :param store: a store
        :param branch: the source branch
        :param lost_item: a ProductionItem or ProductionMaterial instance
        """
        cls(branch=branch, sellable=lost_item.product.sellable,
            quantity_lost=lost_item.lost,
            production_date=localtoday().date(), store=store)

    @classmethod
    def add_decreased_item(cls, store, branch, item):
        """
        Adds a decreased item, populates the ProductHistory table informing
        how many items wore manually decreased from stock.

        :param store: a store
        :param branch: the source |branch|
        :param item: a StockDecreaseItem instance
        """
        cls(branch=branch, sellable=item.sellable,
            quantity_decreased=item.quantity,
            decreased_date=localtoday().date(),
            store=store)
Ejemplo n.º 26
0
class ProductSupplierInfo(Domain):
    """Supplier information for a |product|.

    Each product can has more than one |supplier|.

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/product_supplier_info.html>`__
    """

    __storm_table__ = 'product_supplier_info'

    #: the cost which helps the purchaser to define the main cost of a
    #: certain product. Each product can have multiple |suppliers| and for
    #: each |supplier| a base_cost is available. The purchaser in this case
    #: must decide how to define the main cost based in the base cost avarage
    #: of all suppliers.
    base_cost = PriceCol(default=0)

    notes = UnicodeCol(default=u'')

    #: if this object stores information for the main |supplier|.
    is_main_supplier = BoolCol(default=False)

    #: the number of days needed to deliver the product to purchaser.
    lead_time = IntCol(default=1)

    #: the minimum amount that we can buy from this supplier.
    minimum_purchase = QuantityCol(default=Decimal(1))

    #: a Brazil-specific attribute that means 'Imposto sobre circulacao
    #: de mercadorias e prestacao de servicos'
    icms = PercentCol(default=0)

    supplier_id = IdCol()

    #: the |supplier|
    supplier = Reference(supplier_id, 'Supplier.id')

    product_id = IdCol()

    #: the |product|
    product = Reference(product_id, 'Product.id')

    #: the product code in the supplier
    supplier_code = UnicodeCol(default=u'')

    #
    # Auxiliary methods
    #

    def get_name(self):
        if self.supplier:
            return self.supplier.get_description()

    def get_lead_time_str(self):
        if self.lead_time > 1:
            day_str = _(u"Days")
            lead_time = self.lead_time
        else:
            day_str = _(u"Day")
            lead_time = self.lead_time or 0

        return u"%d %s" % (lead_time, day_str)
Ejemplo n.º 27
0
class Storable(Domain):
    '''Storable represents the stock of a |product|.

    The actual stock of an item is in ProductStockItem.

    See also:
    `schema <http://doc.stoq.com.br/schema/tables/storable.html>`__
    '''

    __storm_table__ = 'storable'

    product_id = IdCol()

    #: the |product| the stock represents
    product = Reference(product_id, 'Product.id')

    #: If this storable should have a finer grain control by batches. When this
    #: is true, stock for this storable will also require a batch information.
    #: This will allow us to control from what batch a sale item came from, and
    #: it will also let us know from what purchase this batch came from.
    is_batch = BoolCol(default=False)

    #: minimum quantity of stock items allowed
    minimum_quantity = QuantityCol(default=0)

    #: maximum quantity of stock items allowed
    maximum_quantity = QuantityCol(default=0)

    #
    #  Classmethods
    #

    @classmethod
    def get_storables_without_stock_item(cls, store, branch):
        """Get |storables| without a |productstockitem|

        This will get all storables that doesn't have a
        |productstockitem| on the given branch.

        :param store: the store used to query the storables
        :param branch: the |branch| used to check for the stock item existence
        :returns: a result set of |storables|
        """
        join = LeftJoin(ProductStockItem,
                        And(ProductStockItem.storable_id == cls.id,
                            ProductStockItem.branch_id == branch.id))
        return store.using(cls, join).find(cls, Eq(ProductStockItem.id, None))

    #
    #  Public API
    #

    def increase_stock(self, quantity, branch, type, object_id, unit_cost=None,
                       batch=None):
        """When receiving a product, update the stock reference for this new
        item on a specific |branch|.

        :param quantity: amount to increase
        :param branch: |branch| that stores the stock
        :param type: the type of the stock increase. One of the
            StockTransactionHistory.types
        :param object_id: the id of the object responsible for the transaction
        :param unit_cost: unit cost of the new stock or `None`
        :param batch: The batch of the storable. Should be not ``None`` if
            self.is_batch is ``True``
        """
        assert isinstance(type, int)

        if quantity <= 0:
            raise ValueError(_(u"quantity must be a positive number"))
        if branch is None:
            raise ValueError(u"branch cannot be None")

        stock_item = self.get_stock_item(branch, batch)
        # If the stock_item is missing create a new one
        if stock_item is None:
            store = self.store
            stock_item = ProductStockItem(store=store, storable=self,
                                          batch=batch, branch=store.fetch(branch))

        # Unit cost must be updated here as
        # 1) we need the stock item which might not exist
        # 2) it needs to be updated before we change the quantity of the
        #    stock item
        if unit_cost is not None:
            stock_item.update_cost(quantity, unit_cost)

        old_quantity = stock_item.quantity
        stock_item.quantity += quantity

        StockTransactionHistory(product_stock_item=stock_item,
                                quantity=quantity,
                                stock_cost=stock_item.stock_cost,
                                responsible=get_current_user(self.store),
                                type=type,
                                object_id=object_id,
                                store=self.store)

        ProductStockUpdateEvent.emit(self.product, branch, old_quantity,
                                     stock_item.quantity)

    def decrease_stock(self, quantity, branch, type, object_id,
                       cost_center=None, batch=None):
        """When receiving a product, update the stock reference for the sold item
        this on a specific |branch|. Returns the stock item that was
        decreased.

        :param quantity: amount to decrease
        :param branch: a |branch|
        :param type: the type of the stock increase. One of the
            StockTransactionHistory.types
        :param object_id: the id of the object responsible for the transaction
        :param cost_center: the |costcenter| to which this stock decrease is
            related, if any
        :param batch: The batch of the storable. Should be not ``None`` if
            self.is_batch is ``True``
        """
        # FIXME: Put this back once 1.6 is released
        # assert isinstance(type, int)

        if quantity <= 0:
            raise ValueError(_(u"quantity must be a positive number"))
        if branch is None:
            raise ValueError(u"branch cannot be None")

        stock_item = self.get_stock_item(branch, batch)
        if stock_item is None or quantity > stock_item.quantity:
            raise StockError(
                _('Quantity to sell is greater than the available stock.'))

        old_quantity = stock_item.quantity
        stock_item.quantity -= quantity

        stock_transaction = StockTransactionHistory(
            product_stock_item=stock_item,
            quantity=-quantity,
            stock_cost=stock_item.stock_cost,
            responsible=get_current_user(self.store),
            type=type,
            object_id=object_id,
            store=self.store)

        if cost_center is not None:
            cost_center.add_stock_transaction(stock_transaction)

        ProductStockUpdateEvent.emit(self.product, branch, old_quantity,
                                     stock_item.quantity)

        return stock_item

    def register_initial_stock(self, quantity, branch, unit_cost,
                               batch_number=None):
        """Register initial stock, by increasing the amount of this storable,
        for the given quantity and |branch|

        :param quantity: The inital stock quantity for this storable
        :param branch: The branch where the given quantity is avaiable for this
          storable
        :param unit_cost: The unitary cost for the initial stock
        :param batch_number: a batch number that will be used to
            get or create a |batch| it will be get from the item's
            pending quantity. Must be ``None`` if the :obj:`.is_batch`
            is ``False``.
        """
        if batch_number is not None:
            batch = StorableBatch.get_or_create(
                self.store,
                storable=self,
                batch_number=batch_number)
        else:
            batch = None

        self.increase_stock(quantity, branch,
                            StockTransactionHistory.TYPE_INITIAL,
                            object_id=None, unit_cost=unit_cost, batch=batch)

    def get_total_balance(self):
        """Return the stock balance for the |product| in all |branches|

        :returns: the amount of stock available in all |branches|
        """
        stock_items = self.get_stock_items()
        return stock_items.sum(ProductStockItem.quantity) or Decimal(0)

    def get_balance_for_branch(self, branch):
        """Return the stock balance for the |product| in a |branch|. If this
        storable have batches, the balance for all batches will be returned.

        If you want the balance for a specific |batch|, the

        :param branch: the |branch| to get the stock balance for
        :returns: the amount of stock available in the |branch|
        """
        store = self.store
        stock_items = store.find(ProductStockItem, storable=self,
                                 branch=branch)
        return stock_items.sum(ProductStockItem.quantity) or Decimal(0)

    def get_stock_items(self):
        """Fetches the stock items available for all |branches|.

        :returns: a sequence of stock items
        """
        return self.store.find(ProductStockItem, storable=self)

    def get_stock_item(self, branch, batch):
        """Fetches the stock item for the given |branch|, |batch| and this
        |storable|.

        :param branch: the |branch| to get the stock item for
        :param batch: the |batch| to get the stock item for
        :returns: a stock item
        """
        self.validate_batch(batch, sellable=self.product.sellable)
        return self.store.find(ProductStockItem, branch=branch, storable=self,
                               batch=batch).one()

    def get_available_batches(self, branch):
        """Return all batches that have some stock left in the given branch

        :param branch: the |branch| that we are getting the avaiable batches
        :returns: all batches available for this storable in the given branch
        """
        tables = [StorableBatch,
                  LeftJoin(ProductStockItem,
                           And(ProductStockItem.storable_id == StorableBatch.storable_id,
                               ProductStockItem.batch_id == StorableBatch.id))]
        query = And(ProductStockItem.storable_id == self.id,
                    ProductStockItem.branch_id == branch.id,
                    ProductStockItem.quantity > 0)
        return self.store.using(*tables).find(StorableBatch, query)