Exemple #1
0
class BranchSynchronization(ORMObject):
    """Created once per branch. Contains a string which is a reference to a policy
    defined in stoqlib.database.policy and a timestamp which is updated each
    time a synchronization is done.
    """

    __storm_table__ = 'branch_synchronization'

    id = IntCol(primary=True, default=AutoReload)

    #: last time updated
    sync_time = DateTimeCol(allow_none=False)

    branch_id = IntCol()

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

    #: policy used to update the branch
    policy = UnicodeCol(allow_none=False)
Exemple #2
0
class FiscalDayHistory(Domain):
    """This represents the information that needs to be used to
    generate a Sintegra file of type 60A.
    """

    __storm_table__ = 'fiscal_day_history'

    emission_date = DateTimeCol()
    station_id = IdCol()
    station = Reference(station_id, 'BranchStation.id')
    serial = UnicodeCol()
    serial_id = IntCol()
    coupon_start = IntCol()
    coupon_end = IntCol()
    cro = IntCol()
    crz = IntCol()
    period_total = PriceCol()
    total = PriceCol()
    taxes = ReferenceSet('id', 'FiscalDayTax.fiscal_day_history_id')
    reduction_date = DateTimeCol()
Exemple #3
0
class Book(Domain):
    """ A book class for products, holding specific data about books  """

    __storm_table__ = 'book'

    product_id = IdCol()
    product = Reference(product_id, 'Product.id')
    publisher_id = IdCol(default=None)
    publisher = Reference(publisher_id, 'BookPublisher.id')
    author = UnicodeCol(default=u'')
    series = UnicodeCol(default=u'')
    edition = UnicodeCol(default=u'')
    subject = UnicodeCol(default=u'')
    isbn = UnicodeCol(default=u'')
    language = UnicodeCol(default=u'')
    decorative_finish = UnicodeCol(default=u'')
    country = UnicodeCol(default=u'Brazil')
    pages = IntCol(default=0)
    year = IntCol(default=0)
    synopsis = UnicodeCol(default=u'')
Exemple #4
0
class UIField(Domain):
    """This describes a field in form a.
    Can be used makae fields mandatory or hide them completely.
    """
    __storm_table__ = 'ui_field'

    ui_form_id = IntCol()
    ui_form = Reference(ui_form_id, 'UIForm.id')
    field_name = UnicodeCol()
    description = UnicodeCol()
    visible = BoolCol()
    mandatory = BoolCol()
Exemple #5
0
class Sale(Domain):
    __storm_table__ = 'sale'

    STATUS_INITIAL = u'initial'
    STATUS_QUOTE = u'quote'
    STATUS_ORDERED = u'ordered'

    status = EnumCol()
    invoice_id = IdCol()
    branch_id = IdCol()
    invoice_number = IntCol()
    operation_nature = UnicodeCol()
Exemple #6
0
class SystemTable(ORMObject):
    """Stores information about database schema migration

    I{update}: the date when the database schema was updated
    I{patchlevel}: the version of the schema installed
    """
    __storm_table__ = 'system_table'

    id = IntCol(primary=True, default=AutoReload)
    updated = DateTimeCol()
    patchlevel = IntCol()
    generation = IntCol()

    @classmethod
    def is_available(cls, store):
        """Checks if Stoqlib database is properly installed
        :param store: a store
        """
        if not store.table_exists(u'system_table'):
            return False

        return bool(store.find(cls))
Exemple #7
0
class Delivery(Domain):
    __storm_table__ = 'delivery'

    (STATUS_INITIAL,
     STATUS_SENT,
     STATUS_RECEIVED) = range(3)

    status = IntCol(default=STATUS_INITIAL)
    open_date = DateTimeCol(default=None)
    deliver_date = DateTimeCol(default=None)
    receive_date = DateTimeCol(default=None)
    tracking_code = UnicodeCol(default=u'')

    address_id = IntCol()
    address = Reference(address_id, Address.id)
    transporter_id = IntCol()
    transporter = Reference(transporter_id, Transporter.id)
    service_item_id = IntCol()
    service_item = Reference(service_item_id, SaleItem.id)

    def add_item(self, item):
        item.delivery = self
Exemple #8
0
class BaseCOFINS(BaseTax):
    """Contains attributes to be used to calculate PIS tax in Brazil."""

    CALC_PERCENTAGE = u'percentage'
    CALC_VALUE = u'value'

    cst = IntCol(default=None)

    #: Operation type (percentage or value)
    calculo = EnumCol(default=CALC_PERCENTAGE, allow_none=False)

    #: Aliquot in percentage
    p_cofins = PercentCol(default=None)
Exemple #9
0
class ClientCategoryPrice(Domain):
    """A table that stores special prices for clients based on their
    category.
    """
    __storm_table__ = 'client_category_price'

    sellable_id = IntCol()

    #: The sellable that has a special price
    sellable = Reference(sellable_id, 'Sellable.id')

    category_id = IntCol()

    #: The category that has the special price
    category = Reference(category_id, 'ClientCategory.id')

    #: The price for this (sellable, category)
    price = PriceCol(default=0)

    #: The max discount that may be applied.
    max_discount = PercentCol(default=0)

    def _get_markup(self):
        if self.sellable.cost == 0:
            return Decimal(0)
        return ((self.price / self.sellable.cost) - 1) * 100

    def _set_markup(self, markup):
        self.price = self.sellable._get_price_by_markup(markup)

    markup = property(_get_markup, _set_markup)

    @property
    def category_name(self):
        return self.category.name

    def remove(self):
        """Removes this client category price from the database."""
        self.store.remove(self)
Exemple #10
0
class Quotation(Domain):
    __storm_table__ = 'quotation'

    #: A numeric identifier for this object. This value should be used instead of
    #: :obj:`Domain.id` when displaying a numerical representation of this object to
    #: the user, in dialogs, lists, reports and such.
    identifier = IdentifierCol()

    group_id = IntCol()
    group = Reference(group_id, 'QuoteGroup.id')
    purchase_id = IntCol()
    purchase = Reference(purchase_id, 'PurchaseOrder.id')
    branch_id = IntCol()
    branch = Reference(branch_id, 'Branch.id')

    implements(IDescribable)

    def get_description(self):
        supplier = self.purchase.supplier.person.name
        return u"Group %s - %s" % (self.group.identifier, supplier)

    #
    # Public API
    #

    def close(self):
        """Closes the quotation"""
        # we don't have a specific status for closed quotes, so we just
        # cancel it
        if not self.is_closed():
            self.purchase.cancel()

    def is_closed(self):
        """Returns if the quotation is closed or not.

        :returns: True if the quotation is closed, False otherwise.
        """
        return self.purchase.status == PurchaseOrder.ORDER_CANCELLED
Exemple #11
0
class CostCenterEntry(Domain):
    """A operation that generated some cost in a |costcenter|.

    A cost can be generated when a lonely out |payment| is paid or when some
    operations on the stock are performed.
    """
    __storm_table__ = 'cost_center_entry'

    cost_center_id = IntCol()

    #: The cost center this entry belongs to
    cost_center = Reference(cost_center_id, 'CostCenter.id')

    payment_id = IntCol()

    #: The payment that generated this cost.
    payment = Reference(payment_id, 'Payment.id')

    stock_transaction_id = IntCol()

    #: The stock movement transaction that generated this cost.
    stock_transaction = Reference(stock_transaction_id,
                                  'StockTransactionHistory.id')
Exemple #12
0
class InvoiceLayout(Domain):
    """A layout of an invoice.
    """
    __storm_table__ = 'invoice_layout'

    #: description of the layout, this is human friendly
    #: string which is displayed in interfaces.
    description = UnicodeCol()

    #: the width in units of the layout
    width = IntCol()

    #: the height in units of the layout
    height = IntCol()

    #: Indicates the type of paper used to print the layout
    continuous_page = BoolCol()

    @property
    def size(self):
        return self.width, self.height

    @property
    def fields(self):
        """Fetches all the fields tied to this layout

        :returns: a sequence of InvoiceField
        """
        return self.store.find(InvoiceField,
                               layout=self)

    def get_description(self):
        """Gets the description of the field

        :returns: description.
        """
        return self.description
Exemple #13
0
class BookPublisher(Domain):
    """An institution created to publish books"""

    (STATUS_ACTIVE, STATUS_INACTIVE) = range(2)

    statuses = {STATUS_ACTIVE: _(u'Active'), STATUS_INACTIVE: _(u'Inactive')}

    __storm_table__ = 'book_publisher'

    person_id = IntCol()
    person = Reference(person_id, 'Person.id')
    status = IntCol(default=STATUS_ACTIVE)

    #
    # IActive implementation
    #

    def inactivate(self):
        assert self.is_active, ('This person facet is already inactive')
        self.is_active = False

    def activate(self):
        assert not self.is_active, ('This personf facet is already active')
        self.is_active = True

    def get_status_string(self):
        if self.is_active:
            return _('Active')
        return _('Inactive')

    #
    # IDescribable implementation
    #

    def get_description(self):
        return self.person.name
Exemple #14
0
class WorkOrderPackageItem(Domain):
    """A |workorderpackage| item

    This is a representation of a |workorder| inside a
    |workorderpackage|. This is used instead of the work
    order directly so we can keep a history of sent and
    received packages.

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

    __storm_table__ = 'work_order_package_item'

    #: notes about why the :attr:`.order` is being sent to another branch
    notes = UnicodeCol(default=u'')

    package_id = IntCol(allow_none=False)
    #: the |workorderpackage| this item is transported in
    package = Reference(package_id, 'WorkOrderPackage.id')

    order_id = IntCol(allow_none=False)
    #: the |workorder| this item represents
    order = Reference(order_id, 'WorkOrder.id')
Exemple #15
0
class CardOperationCost(Domain):
    __storm_table__ = 'card_operation_cost'
    device_id = IntCol(default=None)
    device = Reference(device_id, CardPaymentDevice.id)
    provider_id = IntCol(default=None)
    card_type = IntCol(default=0)
    installment_start = IntCol(default=1)
    installment_end = IntCol(default=1)
    payment_days = IntCol(default=30)
    fee = PercentCol(default=0)
    fare = PriceCol(default=0)
Exemple #16
0
class StorableBatch(Domain):
    """Batch information for storables.

    A batch is a colection of products (storable) that were produced at the same
    time and thus they have some common information, such as expiration date.

    This information is useful since sometimes its necessary to make decisions
    based on the batch like a special promotion for older batches (if it is
    close to the expiration date, for instance) or if a batch is somehow
    defective and we need to contact the clients that purchased items from this
    batch.
    """
    __storm_table__ = 'storable_batch'

    #: The sequence number for this batch. Should be unique for a given
    #: storable
    batch_number = UnicodeCol(allow_none=False)

    #: The date this batch was created
    create_date = DateTimeCol(default_factory=localnow)

    #: An expiration date, specially for perishable products, like milk and food in
    #: general
    expire_date = DateTimeCol()

    #: Some space for the users to add notes to this batch.
    notes = UnicodeCol()

    storable_id = IntCol(allow_none=False)

    #: The storable that is in this batch
    storable = Reference(storable_id, 'Storable.id')

    def get_balance_for_branch(self, branch):
        """Return the stock balance for this |batch| in a |branch|.

        :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.storable,
                                 batch=self,
                                 branch=branch)
        return stock_items.sum(ProductStockItem.quantity) or Decimal(0)
Exemple #17
0
class InstalledPlugin(Domain):
    """This object represent an installed and activated plugin.

    :cvar plugin_name: name of the plugin
    :cvar plugin_version: version of the plugin
    """
    __storm_table__ = 'installed_plugin'

    plugin_name = UnicodeCol()
    plugin_version = IntCol()

    @classmethod
    def get_plugin_names(cls, store):
        """Fetchs a list of installed plugin names
        :param store: a store
        :returns: list of strings
        """
        return [p.plugin_name for p in store.find(cls)]
Exemple #18
0
class SellableTaxConstant(Domain):
    """A tax constant tied to a sellable

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

    #: description of this constant
    description = UnicodeCol()

    #: a TaxType constant, used by ECF
    tax_type = IntCol()

    #: the percentage value of the tax
    tax_value = PercentCol(default=None)

    _mapping = {
        int(TaxType.NONE): u'TAX_NONE',  # Não tributado - ICMS
        int(TaxType.EXEMPTION): u'TAX_EXEMPTION',  # Isento - ICMS
        int(TaxType.SUBSTITUTION):
        u'TAX_SUBSTITUTION',  # Substituição tributária - ICMS
        int(TaxType.SERVICE): u'TAX_SERVICE',  # ISS
    }

    def get_value(self):
        """
        :returns: the value to pass to ECF
        """
        return SellableTaxConstant._mapping.get(self.tax_type, self.tax_value)

    @classmethod
    def get_by_type(cls, tax_type, store):
        """Fetch the tax constant for tax_type
        :param tax_type: the tax constant to fetch
        :param store: a store
        :returns: a |sellabletaxconstant| or ``None`` if none is found
        """
        return store.find(SellableTaxConstant, tax_type=int(tax_type)).one()

    # IDescribable

    def get_description(self):
        return self.description
Exemple #19
0
class ProductTaxTemplate(Domain):
    (TYPE_ICMS, TYPE_IPI) = range(2)

    __storm_table__ = 'product_tax_template'

    types = {TYPE_ICMS: u"ICMS", TYPE_IPI: u"IPI"}

    type_map = {TYPE_ICMS: ProductIcmsTemplate, TYPE_IPI: ProductIpiTemplate}

    name = UnicodeCol(default=u'')
    tax_type = IntCol()

    def get_tax_model(self):
        klass = self.type_map[self.tax_type]
        store = self.store
        return store.find(klass, product_tax_template=self).one()

    def get_tax_type_str(self):
        return self.types[self.tax_type]
Exemple #20
0
class SellableUnit(Domain):
    """
    The unit of a |sellable|. For instance: ``Kg`` (kilo), ``l`` (liter) and
    ``h`` (hour)
    When selling a sellable in a |sale|  the quantity of a |saleitem| will
    be entered in this unit.

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

    #: The values on the list are enums used to fill
    # ``'unit_index'`` column above. That list is useful for many things,
    # e.g. See if the user can delete the unit. It should not be possible
    # to delete a primitive one.
    SYSTEM_PRIMITIVES = [UnitType.WEIGHT,
                         UnitType.METERS,
                         UnitType.LITERS]

    #: The unit description
    description = UnicodeCol()

    # FIXME: Using an int cast on UnitType because
    #        SQLObject doesn't recognize it's type.
    #: This column defines if this object represents a custom product unit
    #: (created by the user through the product editor) or a *native unit*,
    #: like ``Km``, ``Lt`` and ``pc``.
    #:
    #: This data is used mainly to interact with stoqdrivers, since when adding
    #: an item in a coupon we need to know if its unit must be specified as
    #: a description (using ``CUSTOM_PM`` constant) or as an index (using UNIT_*).
    #: Also, this is directly related to the DeviceSettings editor.
    unit_index = IntCol(default=int(UnitType.CUSTOM))

    #: If the unit allows to be represented in fractions.
    #:  e.g. We can have 1 car, 2 cars, but not 1/2 car.
    allow_fraction = BoolCol(default=True)

    # IDescribable

    def get_description(self):
        return self.description
Exemple #21
0
class BillOption(Domain):
    """List of values for bill (boleto) generation

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

    __storm_table__ = 'bill_option'

    #: option name, such as nosso_numero
    option = UnicodeCol()

    #: value of the option
    value = UnicodeCol()

    bank_account_id = IntCol()

    #: the |bankaccount| this option belongs to
    bank_account = Reference(bank_account_id, 'BankAccount.id')
Exemple #22
0
class ProductIcmsTemplate(BaseICMS):
    __storm_table__ = 'product_icms_template'

    product_tax_template_id = IntCol()
    product_tax_template = Reference(product_tax_template_id,
                                     'ProductTaxTemplate.id')

    # Simples Nacional
    p_cred_sn_valid_until = DateTimeCol(default=None)

    def is_p_cred_sn_valid(self):
        """Returns if p_cred_sn has expired."""
        if not self.p_cred_sn_valid_until:
            # If we don't have a valid_until, means p_cred_sn will never
            # expire. Therefore, p_cred_sn is valid.
            return True
        elif self.p_cred_sn_valid_until.date() < localtoday().date():
            return False

        return True
Exemple #23
0
class TransactionEntry(ORMObject):
    """
    A TransactionEntry keeps track of state associated with a database
    transaction. It's main use case is to know information about the system when
    a domain object is created or modified.

    Such information will be used by stoq when syncing databases
    """
    __storm_table__ = 'transaction_entry'

    id = IntCol(primary=True, default=AutoReload)

    #: last time this object was modified
    te_time = DateTimeCol(allow_none=False)

    #: It this object was modified since the last time it was synced
    #: After the object is synced, this property will be set to ``False``, so
    #: that when the next sync begins, only the objects that are **dirty** will be
    #: processed
    dirty = BoolCol(default=True)
Exemple #24
0
class PaymentCategory(Domain):
    """I am a payment category.
    I contain a name and a color
    """

    implements(IDescribable)

    __storm_table__ = 'payment_category'

    #: for outgoing payments (payable application)
    TYPE_PAYABLE = 0

    #: for incoming payments (receivable application)
    TYPE_RECEIVABLE = 1

    #: category name
    name = UnicodeCol()

    #: category color, like #ff0000 for red.
    color = UnicodeCol()

    #: category type, payable or receivable
    category_type = IntCol(default=TYPE_PAYABLE)

    #
    # IDescribable implementation
    #

    def get_description(self):
        return self.name

    @classmethod
    def get_by_type(cls, store, category_type):
        """Fetches a list of PaymentCategories given a category type

        :param store: a store
        :param category_type: TYPE_PAYABLE or TYPE_RECEIVABLE
        :rseturns: a sequence of PaymentCategory ordered by name
        """
        return store.find(cls, category_type=category_type).order_by(
            PaymentCategory.name)
Exemple #25
0
class BankAccount(Domain):
    """Information specific to a bank

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

    __storm_table__ = 'bank_account'

    account_id = IdCol()

    #: the |account| for this bank account
    account = Reference(account_id, 'Account.id')

    # FIXME: This is brazil specific, should probably be replaced by a
    #        bank reference to a separate class with name in addition to
    #        the bank number
    #: an identify for the bank type of this account,
    bank_number = IntCol(default=0)

    #: an identifier for the bank branch/agency which is responsible
    #: for this
    bank_branch = UnicodeCol(default=None)

    #: an identifier for this bank account
    bank_account = UnicodeCol(default=None)

    @property
    def options(self):
        """Get the bill options for this bank account
        :returns: a list of :class:`BillOption`
        """
        return self.store.find(BillOption, bank_account=self)

    def add_bill_option(self, name, value):
        return BillOption(store=self.store,
                          option=name,
                          value=value,
                          bank_account_id=self.id)
Exemple #26
0
class ReturnedSale(Domain):
    __storm_table__ = 'returned_sale'
    identifier = IntCol(default=AutoReload)
    return_date = DateTimeCol(default_factory=datetime.datetime.now)
    invoice_number = IntCol(default=None)
    reason = UnicodeCol(default=u'')

    sale_id = IntCol()
    sale = Reference(sale_id, Sale.id)

    new_sale_id = IntCol()
    new_sale = Reference(new_sale_id, Sale.id)

    responsible_id = IntCol()
    responsible = Reference(responsible_id, LoginUser.id)
    branch_id = IntCol()
    branch = Reference(branch_id, Branch.id)
Exemple #27
0
class ProfileSettings(Domain):
    """Profile settings for user profile instances. Each instance of this
    class stores information about the access availability in a certain
    application."""

    __storm_table__ = 'profile_settings'
    app_dir_name = UnicodeCol()
    has_permission = BoolCol(default=False)
    user_profile_id = IntCol()
    user_profile = Reference(user_profile_id, 'UserProfile.id')

    @classmethod
    def set_permission(cls, store, profile, app, permission):
        """
        Set the permission for a user profile to use a application
        :param store: a store
        :param profile: a UserProfile
        :param app: name of the application
        :param permission: a boolean of the permission
        """
        setting = store.find(cls, user_profile=profile,
                             app_dir_name=app).one()
        setting.has_permission = permission
Exemple #28
0
class FiscalDayTax(Domain):
    """This represents the information that needs to be used to
    generate a Sintegra file of type 60M.
    """

    __storm_table__ = 'fiscal_day_tax'

    fiscal_day_history_id = IntCol()
    fiscal_day_history = Reference(fiscal_day_history_id,
                                   'FiscalDayHistory.id')

    #: four bytes, either the percental of the tax, 1800 for 18% or one of:
    #:
    #: * ``I``: Isento
    #: * ``F``: Substitucao
    #: * ``N``: Nao tributado
    #: * ``ISS``: ISS
    #: * ``CANC``: Cancelled
    #: * ``DESC``: Discount
    code = UnicodeCol()

    value = PriceCol()
    type = UnicodeCol()
Exemple #29
0
class ReceivingOrder(Domain):
    """Receiving order definition.
    """

    __storm_table__ = 'receiving_order'

    #: Products in the order was not received or received partially.
    STATUS_PENDING = u'pending'

    #: All products in the order has been received then the order is closed.
    STATUS_CLOSED = u'closed'

    FREIGHT_FOB_PAYMENT = u'fob-payment'
    FREIGHT_FOB_INSTALLMENTS = u'fob-installments'
    FREIGHT_CIF_UNKNOWN = u'cif-unknown'
    FREIGHT_CIF_INVOICE = u'cif-invoice'

    freight_types = collections.OrderedDict([
        (FREIGHT_FOB_PAYMENT, _(u"FOB - Freight value on a new payment")),
        (FREIGHT_FOB_INSTALLMENTS, _(u"FOB - Freight value on installments")),
        (FREIGHT_CIF_UNKNOWN, _(u"CIF - Freight value is unknown")),
        (FREIGHT_CIF_INVOICE,
         _(u"CIF - Freight value highlighted on invoice")),
    ])

    FOB_FREIGHTS = (
        FREIGHT_FOB_PAYMENT,
        FREIGHT_FOB_INSTALLMENTS,
    )
    CIF_FREIGHTS = (FREIGHT_CIF_UNKNOWN, FREIGHT_CIF_INVOICE)

    #: A numeric identifier for this object. This value should be used instead of
    #: :obj:`Domain.id` when displaying a numerical representation of this object to
    #: the user, in dialogs, lists, reports and such.
    identifier = IdentifierCol()

    #: status of the order
    status = EnumCol(allow_none=False, default=STATUS_PENDING)

    #: Date that order has been closed.
    receival_date = DateTimeCol(default_factory=localnow)

    #: Date that order was send to Stock application.
    confirm_date = DateTimeCol(default=None)

    #: Some optional additional information related to this order.
    notes = UnicodeCol(default=u'')

    #: Type of freight
    freight_type = EnumCol(allow_none=False, default=FREIGHT_FOB_PAYMENT)

    #: Total of freight paid in receiving order.
    freight_total = PriceCol(default=0)

    surcharge_value = PriceCol(default=0)

    #: Discount value in receiving order's payment.
    discount_value = PriceCol(default=0)

    #: Secure value paid in receiving order's payment.
    secure_value = PriceCol(default=0)

    #: Other expenditures paid in receiving order's payment.
    expense_value = PriceCol(default=0)

    # This is Brazil-specific information
    icms_total = PriceCol(default=0)
    ipi_total = PriceCol(default=0)

    #: The invoice number of the order that has been received.
    invoice_number = IntCol()

    #: The invoice total value of the order received
    invoice_total = PriceCol(default=None)

    #: The invoice key of the order received
    invoice_key = UnicodeCol()

    cfop_id = IdCol()
    cfop = Reference(cfop_id, 'CfopData.id')

    responsible_id = IdCol()
    responsible = Reference(responsible_id, 'LoginUser.id')

    supplier_id = IdCol()
    supplier = Reference(supplier_id, 'Supplier.id')

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

    transporter_id = IdCol(default=None)
    transporter = Reference(transporter_id, 'Transporter.id')

    purchase_orders = ReferenceSet('ReceivingOrder.id',
                                   'PurchaseReceivingMap.receiving_id',
                                   'PurchaseReceivingMap.purchase_id',
                                   'PurchaseOrder.id')

    def __init__(self, store=None, **kw):
        Domain.__init__(self, store=store, **kw)
        # These miss default parameters and needs to be set before
        # cfop, which triggers an implicit flush.
        self.branch = kw.pop('branch', None)
        self.supplier = kw.pop('supplier', None)
        if not 'cfop' in kw:
            self.cfop = sysparam.get_object(store, 'DEFAULT_RECEIVING_CFOP')

    #
    #  Public API
    #

    def confirm(self):
        for item in self.get_items():
            item.add_stock_items()

        purchases = list(self.purchase_orders)
        # XXX: Maybe FiscalBookEntry should not reference the payment group, but
        # lets keep this way for now until we refactor the fiscal book related
        # code, since it will pretty soon need a lot of changes.
        group = purchases[0].group
        FiscalBookEntry.create_product_entry(self.store, group, self.cfop,
                                             self.invoice_number,
                                             self.icms_total, self.ipi_total)

        self.invoice_total = self.total

        for purchase in purchases:
            if purchase.can_close():
                purchase.close()

    def add_purchase(self, order):
        return PurchaseReceivingMap(store=self.store,
                                    purchase=order,
                                    receiving=self)

    def add_purchase_item(self,
                          item,
                          quantity=None,
                          batch_number=None,
                          parent_item=None):
        """Add a |purchaseitem| on this receiving order

        :param item: the |purchaseitem|
        :param decimal.Decimal quantity: the quantity of that item.
            If ``None``, it will be get from the item's pending quantity
        :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 or ``None`` if the item's |storable|
            is not controlling batches.
        :raises: :exc:`ValueError` when validating the quantity
            and testing the item's order for equality with :obj:`.order`
        """
        pending_quantity = item.get_pending_quantity()
        if quantity is None:
            quantity = pending_quantity

        if not (0 < quantity <= item.quantity):
            raise ValueError("The quantity must be higher than 0 and lower "
                             "than the purchase item's quantity")
        if quantity > pending_quantity:
            raise ValueError("The quantity must be lower than the item's "
                             "pending quantity")

        sellable = item.sellable
        storable = sellable.product_storable
        if batch_number is not None:
            batch = StorableBatch.get_or_create(self.store,
                                                storable=storable,
                                                batch_number=batch_number)
        else:
            batch = None

        self.validate_batch(batch, sellable)

        return ReceivingOrderItem(store=self.store,
                                  sellable=item.sellable,
                                  batch=batch,
                                  quantity=quantity,
                                  cost=item.cost,
                                  purchase_item=item,
                                  receiving_order=self,
                                  parent_item=parent_item)

    def update_payments(self, create_freight_payment=False):
        """Updates the payment value of all payments realated to this
        receiving. If create_freight_payment is set, a new payment will be
        created with the freight value. The other value as the surcharges and
        discounts will be included in the installments.

        :param create_freight_payment: True if we should create a new payment
                                       with the freight value, False otherwise.
        """
        difference = self.total - self.products_total
        if create_freight_payment:
            difference -= self.freight_total

        if difference != 0:
            # Get app pending payments for the purchases associated with this
            # receiving, and update them.
            payments = self.payments.find(status=Payment.STATUS_PENDING)
            payments_number = payments.count()
            if payments_number > 0:
                # XXX: There is a potential rounding error here.
                per_installments_value = difference / payments_number
                for payment in payments:
                    new_value = payment.value + per_installments_value
                    payment.update_value(new_value)

        if self.freight_total and create_freight_payment:
            self._create_freight_payment()

    def _create_freight_payment(self):
        store = self.store
        money_method = PaymentMethod.get_by_name(store, u'money')
        # If we have a transporter, the freight payment will be for him
        # (and in another payment group).
        purchases = list(self.purchase_orders)
        if len(purchases) == 1 and self.transporter is None:
            group = purchases[0].group
        else:
            if self.transporter:
                recipient = self.transporter.person
            else:
                recipient = self.supplier.person
            group = PaymentGroup(store=store, recipient=recipient)

        description = _(u'Freight for receiving %s') % (self.identifier, )
        payment = money_method.create_payment(Payment.TYPE_OUT,
                                              group,
                                              self.branch,
                                              self.freight_total,
                                              due_date=localnow(),
                                              description=description)
        payment.set_pending()
        return payment

    def get_items(self, with_children=True):
        store = self.store
        query = ReceivingOrderItem.receiving_order == self
        if not with_children:
            query = And(query, Eq(ReceivingOrderItem.parent_item_id, None))
        return store.find(ReceivingOrderItem, query)

    def remove_items(self):
        for item in self.get_items():
            item.receiving_order = None

    def remove_item(self, item):
        assert item.receiving_order == self
        type(item).delete(item.id, store=self.store)

    def is_totally_returned(self):
        return all(item.is_totally_returned() for item in self.get_items())

    #
    # Properties
    #

    @property
    def payments(self):
        tables = [PurchaseReceivingMap, PurchaseOrder, Payment]
        query = And(PurchaseReceivingMap.receiving_id == self.id,
                    PurchaseReceivingMap.purchase_id == PurchaseOrder.id,
                    Payment.group_id == PurchaseOrder.group_id)
        return self.store.using(tables).find(Payment, query)

    @property
    def supplier_name(self):
        if not self.supplier:
            return u""
        return self.supplier.get_description()

    #
    # Accessors
    #

    @property
    def cfop_code(self):
        return self.cfop.code

    @property
    def transporter_name(self):
        if not self.transporter:
            return u""
        return self.transporter.get_description()

    @property
    def branch_name(self):
        return self.branch.get_description()

    @property
    def responsible_name(self):
        return self.responsible.get_description()

    @property
    def products_total(self):
        total = sum([item.get_total() for item in self.get_items()],
                    currency(0))
        return currency(total)

    @property
    def receival_date_str(self):
        return self.receival_date.strftime("%x")

    @property
    def total_surcharges(self):
        """Returns the sum of all surcharges (purchase & receiving)"""
        total_surcharge = 0
        if self.surcharge_value:
            total_surcharge += self.surcharge_value
        if self.secure_value:
            total_surcharge += self.secure_value
        if self.expense_value:
            total_surcharge += self.expense_value

        for purchase in self.purchase_orders:
            total_surcharge += purchase.surcharge_value

        if self.ipi_total:
            total_surcharge += self.ipi_total

        # CIF freights don't generate payments.
        if (self.freight_total and self.freight_type
                not in (self.FREIGHT_CIF_UNKNOWN, self.FREIGHT_CIF_INVOICE)):
            total_surcharge += self.freight_total

        return currency(total_surcharge)

    @property
    def total_quantity(self):
        """Returns the sum of all received quantities"""
        return sum(item.quantity
                   for item in self.get_items(with_children=False))

    @property
    def total_discounts(self):
        """Returns the sum of all discounts (purchase & receiving)"""
        total_discount = 0
        if self.discount_value:
            total_discount += self.discount_value

        for purchase in self.purchase_orders:
            total_discount += purchase.discount_value

        return currency(total_discount)

    @property
    def total(self):
        """Fetch the total, including discount and surcharge for both the
        purchase order and the receiving order.
        """
        total = self.products_total
        total -= self.total_discounts
        total += self.total_surcharges

        return currency(total)

    def guess_freight_type(self):
        """Returns a freight_type based on the purchase's freight_type"""
        purchases = list(self.purchase_orders)
        assert len(purchases) == 1

        purchase = purchases[0]
        if purchase.freight_type == PurchaseOrder.FREIGHT_FOB:
            if purchase.is_paid():
                freight_type = ReceivingOrder.FREIGHT_FOB_PAYMENT
            else:
                freight_type = ReceivingOrder.FREIGHT_FOB_INSTALLMENTS
        elif purchase.freight_type == PurchaseOrder.FREIGHT_CIF:
            if purchase.expected_freight:
                freight_type = ReceivingOrder.FREIGHT_CIF_INVOICE
            else:
                freight_type = ReceivingOrder.FREIGHT_CIF_UNKNOWN

        return freight_type

    def _get_percentage_value(self, percentage):
        if not percentage:
            return currency(0)
        subtotal = self.products_total
        percentage = Decimal(percentage)
        return subtotal * (percentage / 100)

    @property
    def discount_percentage(self):
        discount_value = self.discount_value
        if not discount_value:
            return currency(0)
        subtotal = self.products_total
        assert subtotal > 0, (u'the subtotal should not be zero '
                              u'at this point')
        total = subtotal - discount_value
        percentage = (1 - total / subtotal) * 100
        return quantize(percentage)

    @discount_percentage.setter
    def discount_percentage(self, value):
        """Discount by percentage.
        Note that percentage must be added as an absolute value not as a
        factor like 1.05 = 5 % of surcharge
        The correct form is 'percentage = 3' for a discount of 3 %
        """
        self.discount_value = self._get_percentage_value(value)

    @property
    def surcharge_percentage(self):
        """Surcharge by percentage.
        Note that surcharge must be added as an absolute value not as a
        factor like 0.97 = 3 % of discount.
        The correct form is 'percentage = 3' for a surcharge of 3 %
        """
        surcharge_value = self.surcharge_value
        if not surcharge_value:
            return currency(0)
        subtotal = self.products_total
        assert subtotal > 0, (u'the subtotal should not be zero '
                              u'at this point')
        total = subtotal + surcharge_value
        percentage = ((total / subtotal) - 1) * 100
        return quantize(percentage)

    @surcharge_percentage.setter
    def surcharge_percentage(self, value):
        self.surcharge_value = self._get_percentage_value(value)
Exemple #30
0
class ProductIpiTemplate(BaseIPI):
    __storm_table__ = 'product_ipi_template'
    product_tax_template_id = IntCol()
    product_tax_template = Reference(product_tax_template_id,
                                     'ProductTaxTemplate.id')