Exemple #1
0
class BaseICMS(BaseTax):
    """NfeProductIcms stores the default values that will be used when
    creating NfeItemIcms objects
    """

    # FIXME: this is only used by pylint
    __storm_table__ = 'invalid'

    orig = IntCol(default=None)
    cst = IntCol(default=None)

    mod_bc = IntCol(default=None)
    p_icms = PercentCol(default=None)

    mod_bc_st = IntCol(default=None)
    p_mva_st = PercentCol(default=None)
    p_red_bc_st = PercentCol(default=None)
    p_icms_st = PercentCol(default=None)
    p_red_bc = PercentCol(default=None)

    bc_include_ipi = BoolCol(default=True)
    bc_st_include_ipi = BoolCol(default=True)

    # Simples Nacional
    csosn = IntCol(default=None)
    p_cred_sn = PercentCol(default=None)
Exemple #2
0
class UIField(Domain):
    __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 #3
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 = IdCol()
    ui_form = Reference(ui_form_id, 'UIForm.id')
    field_name = UnicodeCol()
    description = UnicodeCol()
    visible = BoolCol()
    mandatory = BoolCol()
Exemple #4
0
class SellableBranchOverride(Domain):
    __storm_table__ = 'sellable_branch_override'

    status = EnumCol()

    base_price = PriceCol()

    price_last_updated = DateTimeCol()

    max_discount = PercentCol()

    tax_constant_id = IdCol()
    tax_constant = Reference(tax_constant_id, 'SellableTaxConstant.id')

    default_sale_cfop_id = IdCol()
    default_sale_cfop = Reference(default_sale_cfop_id, 'CfopData.id')

    on_sale_price = PriceCol()
    on_sale_start_date = DateTimeCol()
    on_sale_end_date = DateTimeCol()

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

    sellable_id = IdCol()
    sellable = Reference(sellable_id, 'Sellable.id')

    #: specifies whether the product requires kitchen production
    requires_kitchen_production = BoolCol()

    @classmethod
    def find_by_sellable(cls, sellable, branch):
        return sellable.store.find(cls, sellable=sellable, branch=branch).one()
Exemple #5
0
class ParameterData(Domain):
    """ Class to store system parameters.

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

    #: name of the parameter we want to query on
    field_name = UnicodeCol()

    #: current result(or value) of this parameter
    field_value = UnicodeCol()

    #: the item can't be edited through an editor.
    is_editable = BoolCol()

    def get_group(self):
        from stoqlib.lib.parameters import sysparam
        return sysparam.get_detail_by_name(self.field_name).group

    def get_short_description(self):
        from stoqlib.lib.parameters import sysparam
        return sysparam.get_detail_by_name(self.field_name).short_desc

    def get_field_value(self):
        # FIXME: This is a workaround to handle some parameters which are
        #       locale specific.
        if self.field_value:
            return _(self.field_value)
        return self.field_value
Exemple #6
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)

    #: id of the last |loginuser| that modified this object
    user_id = IdCol(default=None)

    #: id of the last |branchstation| this object was modified on
    station_id = IdCol(default=None)

    #: 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)
class Dong(Domain):
    __storm_table__ = 'dong'
    bool_field = BoolCol(default=False)
    ding_id = IdCol()
    ding = Reference(ding_id, Ding.id)

    repr_fields = ['ding_id']
Exemple #8
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'

    #: The user profile that has these settings.
    user_profile_id = IdCol()

    user_profile = Reference(user_profile_id, 'UserProfile.id')

    #: The app name this user has or does not have access to.
    app_dir_name = UnicodeCol()

    #: Has this user permission to use this app?
    has_permission = BoolCol(default=False)

    @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 #9
0
class TransactionEntry(ORMObject):
    __storm_table__ = 'transaction_entry'

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

    te_time = DateTimeCol(allow_none=False)
    user_id = IntCol(default=None)
    station_id = IntCol(default=None)
    dirty = BoolCol(default=True)
Exemple #10
0
class PaymentMethod(Domain):
    __storm_table__ = 'payment_method'
    method_name = UnicodeCol()
    is_active = BoolCol(default=True)
    daily_penalty = PercentCol(default=0)
    interest = PercentCol(default=0)
    payment_day = IntCol(default=None)
    closing_day = IntCol(default=None)
    max_installments = IntCol(default=1)
    destination_account_id = IntCol()
Exemple #11
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'

    #: The user profile that has these settings.
    user_profile_id = IdCol()

    user_profile = Reference(user_profile_id, 'UserProfile.id')

    #: The app name this user has or does not have access to.
    app_dir_name = UnicodeCol()

    #: Has this user permission to use this app?
    has_permission = BoolCol(default=False)

    #: Virtual apps. They will have permission if one of the apps mapped
    #: on the list have permission
    virtual_apps = {
        'link': ['admin'],
    }

    @classmethod
    def get_permission(cls, store, profile, app):
        """Check if a profile has access to an app

        :param store: A store
        :param profile: The :class:`.UserProfile` to check for permission
        :param app: The name of the application
        :return: Whether the profile has access to the profile or not
        """
        apps = [app] + cls.virtual_apps.get(app, [])
        res = store.find(
            cls,
            And(cls.user_profile_id == profile.id, Eq(cls.has_permission,
                                                      True),
                cls.app_dir_name.is_in(apps)))
        res.config(limit=1)
        return res.one() is not None

    @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 #12
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 = IdCol()
    ui_form = Reference(ui_form_id, 'UIForm.id')
    field_name = UnicodeCol()
    description = UnicodeCol()
    visible = BoolCol()
    mandatory = BoolCol()

    def update_field(self, mandatory=False, visible=False):
        """This method changes some properties of the field

        :param mandatory: A boolean indicating if the field is mandatory
        :param visible: A boolean indicating if the field is visible
        """
        self.mandatory = mandatory
        self.visible = visible
Exemple #13
0
class Certificate(Domain):
    __storm_table__ = 'certificate'

    TYPE_PKCS11 = u'pkcs11'
    TYPE_PKCS12 = u'pkcs12'

    types_str = collections.OrderedDict([
        (TYPE_PKCS11, _("A3: Smartcard")),
        (TYPE_PKCS12, _("A1: Digital certificate")),
    ])

    #: The type of the certificate
    type = EnumCol(allow_none=False, default=TYPE_PKCS12)

    #: If the certificate is active or not
    active = BoolCol(default=True)

    #: The name of the certificate/lib when it was uploaded to the dataabse
    name = UnicodeCol(default=u'')

    #: The content of the certificate. The library file for PKCS11
    #: or the certificate itself for PKCS12
    content = BLOBCol()

    #: The certificate password. If it is ``None`` it means that the user
    #: should be asked each time it is going to be used (for PKCS11 only)
    _password = BLOBCol(name='password', allow_none=True, default=None)

    #: The certificate expiration date.
    expiration_date = DateTimeCol(default=None)

    @property
    def password(self):
        po = PasswordObfuscator()
        po.hashed_password = self._password and self._password
        return po

    @password.setter
    def password(self, password):
        assert isinstance(password, PasswordObfuscator)
        hashed = password.hashed_password
        self._password = hashed

    @property
    def type_str(self):
        return self.types_str[self.type]

    @classmethod
    def get_active_certs(cls, store, exclude=None):
        """Get active certificates except the one given in exclude parameter"""
        except_id = exclude and exclude.id
        return store.find(cls, And(Eq(cls.active, True), cls.id != except_id))
Exemple #14
0
class Address(Domain):
    __storm_table__ = 'address'

    street = UnicodeCol(default=u'')
    streetnumber = IntCol(default=None)
    district = UnicodeCol(default=u'')
    postal_code = UnicodeCol(default=u'')
    complement = UnicodeCol(default=u'')
    is_main_address = BoolCol(default=False)
    person_id = IntCol()
    person = Reference(person_id, Person.id)
    city_location_id = IntCol()
    city_location = Reference(city_location_id, CityLocation.id)
Exemple #15
0
class ProductionItemQualityResult(Domain):
    """This table stores the test results for every produced item.
    """

    implements(IDescribable)

    __storm_table__ = 'production_item_quality_result'

    produced_item_id = IntCol()
    produced_item = Reference(produced_item_id, 'ProductionProducedItem.id')
    quality_test_id = IntCol()
    quality_test = Reference(quality_test_id, 'ProductQualityTest.id')
    tested_by_id = IntCol()
    tested_by = Reference(tested_by_id, 'LoginUser.id')
    tested_date = DateTimeCol(default=None)
    result_value = UnicodeCol()
    test_passed = BoolCol(default=False)

    def get_description(self):
        return self.quality_test.description

    @property
    def result_value_str(self):
        return _(self.result_value)

    def get_boolean_value(self):
        if self.result_value == u'True':
            return True
        elif self.result_value == u'False':
            return False
        else:
            raise ValueError

    def get_decimal_value(self):
        return Decimal(self.result_value)

    def set_value(self, value):
        if isinstance(value, bool):
            self.set_boolean_value(value)
        else:
            self.set_decimal_value(value)

    def set_boolean_value(self, value):
        self.test_passed = self.quality_test.result_value_passes(value)
        self.result_value = unicode(value)
        self.produced_item.check_tests()

    def set_decimal_value(self, value):
        self.test_passed = self.quality_test.result_value_passes(value)
        self.result_value = u'%s' % (value, )
        self.produced_item.check_tests()
Exemple #16
0
class OpticalMedic(Domain):
    """Information about the Medic (Ophtamologist)"""

    __storm_table__ = 'optical_medic'

    person_id = IdCol(allow_none=False)
    person = Reference(person_id, 'Person.id')

    # TODO: Find out a better name for crm
    crm_number = UnicodeCol()

    #: If this medic is a partner of the store, ie, if they recomend clients to
    #: this store
    partner = BoolCol()

    #
    # IDescribable implementation
    #

    @classmethod
    def get_person_by_crm(cls, store, document):
        query = cls.crm_number == document

        tables = [
            Person,
            Join(OpticalMedic, Person.id == OpticalMedic.person_id)
        ]
        return store.using(*tables).find(Person, query).one()

    def get_description(self):
        return _('%s (upid: %s)') % (self.person.name, self.crm_number)

    @DomainMergeEvent.connect
    @classmethod
    def on_domain_merge(cls, obj, other):
        if type(obj) != Person:
            return
        this_facet = obj.store.find(cls, person=obj).one()
        other_facet = obj.store.find(cls, person=other).one()
        if not this_facet and not other_facet:
            return

        # If this facet does not have a crm, but the other one does, the crm would be copied to
        # this, but we need to clear the other value, since crm is unique in the database.
        if other_facet and other_facet.crm_number and not this_facet.crm_number:
            crm = other_facet.crm_number
            other_facet.crm_number = None
            this_facet.crm_number = crm

        obj.merge_facet(this_facet, other_facet)
        return set([('optical_medic', 'person_id')])
Exemple #17
0
class ParameterData(Domain):
    """ Class to store system parameters.

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

    #: name of the parameter we want to query on
    field_name = UnicodeCol()

    #: current result(or value) of this parameter
    field_value = UnicodeCol()

    #: the item can't be edited through an editor.
    is_editable = BoolCol()
Exemple #18
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 #19
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 #20
0
class Address(Domain):
    """An Address is a class that stores a physical street location
    for a |person|.

    A Person can have many addresses.
    The city, state and country is found in |citylocation|.

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

    __storm_table__ = 'address'

    #: street of the address, something like ``"Wall street"``
    street = UnicodeCol(default=u'')

    #: streetnumber, eg ``100``
    streetnumber = IntCol(default=None)

    #: district, eg ``"Manhattan"``
    district = UnicodeCol(default=u'')

    #: postal code, eg ``"12345-678"``
    postal_code = UnicodeCol(default=u'')

    #: complement, eg ``"apartment 35"``
    complement = UnicodeCol(default=u'')

    #: If this is the primary address for the |person|, this is set
    #: when you register a person for the first time.
    is_main_address = BoolCol(default=False)

    person_id = IdCol()

    #: the |person| who resides at this address
    person = Reference(person_id, 'Person.id')

    city_location_id = IntCol()

    #: the |citylocation| this address is in
    city_location = Reference(city_location_id, 'CityLocation.id')

    #
    # IDescribable
    #

    def get_description(self):
        """See `IDescribable.get_description()`"""
        return self.get_address_string()

    # Public API

    def is_valid_model(self):
        """Verifies if this model is properly filled in,
        that there's a street, district and valid |citylocation| set.

        :returns: ``True`` if this address is filled in.
        """

        # FIXME: This should probably take uiforms into account.
        return (self.street and self.district
                and self.city_location.is_valid_model())

    def get_city(self):
        """Get the city for this address. It's fetched from
        the |citylocation|.

        :returns: the city
        """
        return self.city_location.city

    def get_country(self):
        """Get the country for this address. It's fetched from
        the |citylocation|.

        :returns: the country
        """
        return self.city_location.country

    def get_state(self):
        """Get the state for this address. It's fetched from
        the |citylocation|.

        :returns: the state
        """

        return self.city_location.state

    def get_postal_code_number(self):
        """Get the postal code without any non-numeric characters.

        :returns: the postal code as a number
        """
        if not self.postal_code:
            return 0
        return int(''.join([c for c in self.postal_code
                            if c in u'1234567890']))

    def get_address_string(self):
        """Formats the address as a string

        :returns: the formatted address
        """
        return format_address(self)

    def get_details_string(self):
        """ Returns a string like ``postal_code - city - state``.
        If city or state are missing, return only postal_code; and
        if postal_code is missing, return ``city - state``, otherwise,
        return an empty string

        :returns: the detailed string
        """
        details = []
        if self.postal_code:
            details.append(self.postal_code)
        if self.city_location.city and self.city_location.state:
            details.extend([self.city_location.city, self.city_location.state])
        details = u" - ".join(details)
        return details
Exemple #21
0
class CostCenter(Domain):
    """A |costcenter| holds a set of |costcenterentry| objects.

    |costcenterentry| are created when a resource from the company is spent. Right
    now, these resources are:

    * Money used to pay an lonely out |payment|
    * Products removed from the stock (not all situations).

    Entries are not created for out |payment| related to a |purchase|,
    |stockdecrease| or any other operation that changes the stock, since those
    costs will be accounted when the stock is actually decreased.

    Also, entries are only created for stock removal when the products are
    actually destined for a final usage. For instance, |transfer| and |loan| should
    not generate cost entries.

    As of this writing, the only stock operations that should trigger a cost
    entry creation are:

    * |sale|
    * |stockdecrease|
    """

    __storm_table__ = 'cost_center'

    #: the name of the cost center
    name = UnicodeCol(default=u'')

    #: a description for the cost center
    description = UnicodeCol(default=u'')

    #: The budget available for this cost center
    budget = PriceCol(default=0)

    #: indicates whether it's still possible to add entries to this
    #: |costcenter|.
    is_active = BoolCol(default=True)

    #
    # Public API
    #

    def get_payment_entries(self):
        return self.store.find(
            CostCenterEntry,
            And(CostCenterEntry.cost_center == self,
                Ne(CostCenterEntry.payment_id, None)))

    def get_stock_trasaction_entries(self):
        return self.store.find(
            CostCenterEntry,
            And(CostCenterEntry.cost_center == self,
                Ne(CostCenterEntry.stock_transaction_id, None)))

    def get_stock_decreases(self):
        """This method fetches all the |stockdecreases| related to this
        |costcenter|.
        """
        return self.store.find(StockDecrease, cost_center=self)

    def get_sales(self):
        """This method fetches all the |sales| related to this |costcenter|"""
        from stoqlib.domain.sale import Sale
        return self.store.find(Sale, cost_center=self)

    def get_payments(self):
        """Returns all payments registred in this |costcenter|
        """
        from stoqlib.domain.payment.payment import Payment
        query = And(CostCenterEntry.cost_center == self,
                    CostCenterEntry.payment_id == Payment.id)
        return self.store.find(Payment, query)

    def get_entries(self):
        """This method gets all the |costcenterentry| related to this
        |costcenter|.
        """
        return self.store.find(CostCenterEntry, cost_center=self)

    @classmethod
    def get_active(cls, store):
        return store.find(cls, is_active=True)

    def add_stock_transaction(self, stock_transaction):
        """This method is called to create a |costcenterentry| when a product
        is removed from stock and this is being related to this
        |costcenter|."""

        assert stock_transaction.quantity < 0
        assert self.is_active

        CostCenterEntry(cost_center=self,
                        stock_transaction=stock_transaction,
                        store=self.store)

    def add_lonely_payment(self, lonely_payment):
        """This method is called to create a |costcenterentry| when a lonely
        payment is confirmed and being related to this |costcenter|."""

        assert self.is_active

        CostCenterEntry(cost_center=self,
                        payment=lonely_payment,
                        store=self.store)
Exemple #22
0
class FiscalBookEntry(Domain):

    __storm_table__ = 'fiscal_book_entry'

    (TYPE_PRODUCT, TYPE_SERVICE, TYPE_INVENTORY) = range(3)

    date = DateTimeCol(default_factory=localnow)
    is_reversal = BoolCol(default=False)
    invoice_number = IntCol()
    cfop_id = IdCol()
    cfop = Reference(cfop_id, 'CfopData.id')
    branch_id = IdCol()
    branch = Reference(branch_id, 'Branch.id')
    drawee_id = IdCol(default=None)
    drawee = Reference(drawee_id, 'Person.id')
    payment_group_id = IdCol(default=None)
    payment_group = Reference(payment_group_id, 'PaymentGroup.id')
    iss_value = PriceCol(default=None)
    icms_value = PriceCol(default=None)
    ipi_value = PriceCol(default=None)
    entry_type = IntCol(default=None)

    @classmethod
    def has_entry_by_payment_group(cls, store, payment_group, entry_type):
        return bool(
            cls.get_entry_by_payment_group(store, payment_group, entry_type))

    @classmethod
    def get_entry_by_payment_group(cls, store, payment_group, entry_type):
        return store.find(cls,
                          payment_group=payment_group,
                          is_reversal=False,
                          entry_type=entry_type).one()

    @classmethod
    def _create_fiscal_entry(cls,
                             store,
                             entry_type,
                             group,
                             cfop,
                             invoice_number,
                             iss_value=0,
                             icms_value=0,
                             ipi_value=0):
        return FiscalBookEntry(entry_type=entry_type,
                               iss_value=iss_value,
                               ipi_value=ipi_value,
                               icms_value=icms_value,
                               invoice_number=invoice_number,
                               cfop=cfop,
                               drawee=group.recipient,
                               branch=get_current_branch(store),
                               date=TransactionTimestamp(),
                               payment_group=group,
                               store=store)

    @classmethod
    def create_product_entry(cls,
                             store,
                             group,
                             cfop,
                             invoice_number,
                             value,
                             ipi_value=0):
        """Creates a new product entry in the fiscal book

        :param store: a store
        :param group: payment group
        :type  group: :class:`PaymentGroup`
        :param cfop: cfop for the entry
        :type  cfop: :class:`CfopData`
        :param invoice_number: payment invoice number
        :param value: value of the payment
        :param ipi_value: ipi value of the payment
        :returns: a fiscal book entry
        :rtype: :class:`FiscalBookEntry`
        """
        return cls._create_fiscal_entry(
            store,
            FiscalBookEntry.TYPE_PRODUCT,
            group,
            cfop,
            invoice_number,
            icms_value=value,
            ipi_value=ipi_value,
        )

    @classmethod
    def create_service_entry(cls, store, group, cfop, invoice_number, value):
        """Creates a new service entry in the fiscal book

        :param store: a store
        :param group: payment group
        :type  group: :class:`PaymentGroup`
        :param cfop: cfop for the entry
        :type  cfop: :class:`CfopData`
        :param invoice_number: payment invoice number
        :param value: value of the payment
        :returns: a fiscal book entry
        :rtype: :class:`FiscalBookEntry`
        """
        return cls._create_fiscal_entry(
            store,
            FiscalBookEntry.TYPE_SERVICE,
            group,
            cfop,
            invoice_number,
            iss_value=value,
        )

    def reverse_entry(self,
                      invoice_number,
                      iss_value=None,
                      icms_value=None,
                      ipi_value=None):
        store = self.store
        icms_value = icms_value if icms_value is not None else self.icms_value
        iss_value = iss_value if iss_value is not None else self.iss_value
        ipi_value = ipi_value if ipi_value is not None else self.ipi_value

        return FiscalBookEntry(
            entry_type=self.entry_type,
            iss_value=iss_value,
            icms_value=icms_value,
            ipi_value=ipi_value,
            cfop_id=sysparam.get_object_id('DEFAULT_SALES_CFOP'),
            branch=self.branch,
            invoice_number=invoice_number,
            drawee=self.drawee,
            is_reversal=True,
            payment_group=self.payment_group,
            store=store)
Exemple #23
0
class PurchaseOrder(Domain):
    """Purchase and order definition."""

    __storm_table__ = 'purchase_order'

    ORDER_QUOTING = u'quoting'
    ORDER_PENDING = u'pending'
    ORDER_CONFIRMED = u'confirmed'
    ORDER_CONSIGNED = u'consigned'
    ORDER_CANCELLED = u'cancelled'
    ORDER_CLOSED = u'closed'

    statuses = collections.OrderedDict([
        (ORDER_QUOTING, _(u'Quoting')),
        (ORDER_PENDING, _(u'Pending')),
        (ORDER_CONFIRMED, _(u'Confirmed')),
        (ORDER_CONSIGNED, _(u'Consigned')),
        (ORDER_CANCELLED, _(u'Cancelled')),
        (ORDER_CLOSED, _(u'Closed')),
    ])

    FREIGHT_FOB = u'fob'
    FREIGHT_CIF = u'cif'

    freight_types = {FREIGHT_FOB: _(u'FOB'),
                     FREIGHT_CIF: _(u'CIF')}

    #: 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 = EnumCol(allow_none=False, default=ORDER_QUOTING)
    open_date = DateTimeCol(default_factory=localnow, allow_none=False)
    quote_deadline = DateTimeCol(default=None)
    expected_receival_date = DateTimeCol(default_factory=localnow)
    expected_pay_date = DateTimeCol(default_factory=localnow)
    # XXX This column is not being used anywhere
    receival_date = DateTimeCol(default=None)
    confirm_date = DateTimeCol(default=None)
    notes = UnicodeCol(default=u'')
    salesperson_name = UnicodeCol(default=u'')
    freight_type = EnumCol(allow_none=False, default=FREIGHT_FOB)
    expected_freight = PriceCol(default=0)

    surcharge_value = PriceCol(default=0)
    discount_value = PriceCol(default=0)

    consigned = BoolCol(default=False)
    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')
    responsible_id = IdCol()
    responsible = Reference(responsible_id, 'LoginUser.id')
    group_id = IdCol()
    group = Reference(group_id, 'PaymentGroup.id')

    #: Indicates if the order is from a work order
    work_order_id = IdCol()
    work_order = Reference(work_order_id, 'WorkOrder.id')

    #
    # IContainer Implementation
    #

    def get_items(self, with_children=True):
        """Get the items of the purchase order

        :param with_children: indicate if we should fetch children_items or not
        """
        query = PurchaseItem.order == self
        if not with_children:
            query = And(query, Eq(PurchaseItem.parent_item_id, None))
        return self.store.find(PurchaseItem, query)

    def remove_item(self, item):
        if item.order is not self:
            raise ValueError(_(u'Argument item must have an order attribute '
                               'associated with the current purchase instance'))
        item.order = None
        self.store.maybe_remove(item)

    def add_item(self, sellable, quantity=Decimal(1), parent=None, cost=None,
                 icms_st_value=0, ipi_value=0):
        """Add a sellable to this purchase.

        If the sellable is part of a package (parent is not None), then the actual cost
        and quantity will be calculated based on how many items of this component is on
        the package.

        :param sellable: the sellable being added
        :param quantity: How many units of this sellable we are adding
        :param cost: The price being paid for this sellable
        :param parent: The parent of this sellable, incase of a package
        """
        if cost is None:
            cost = sellable.cost

        if parent:
            component = parent.sellable.product.get_component(sellable)
            cost = cost / component.quantity
            quantity = quantity * component.quantity
        else:
            if sellable.product.is_package:
                # If this is a package, the cost will be calculated and updated by the
                # compoents of the package
                cost = Decimal('0')

        store = self.store
        return PurchaseItem(store=store, order=self,
                            sellable=sellable, quantity=quantity, cost=cost,
                            parent_item=parent, icms_st_value=icms_st_value,
                            ipi_value=ipi_value)

    #
    # Properties
    #

    @property
    def discount_percentage(self):
        """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 %"""
        discount_value = self.discount_value
        if not discount_value:
            return currency(0)
        subtotal = self.purchase_subtotal
        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):
        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.purchase_subtotal
        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)

    @property
    def payments(self):
        """Returns all valid payments for this purchase

        This will return a list of valid payments for this purchase, that
        is, all payments on the payment group that were not cancelled.
        If you need to get the cancelled too, use self.group.payments. If this
        purchase does not have a payment group, return a empty list.

        :returns: a list of |payment|
        """
        return self.group.get_valid_payments() if self.group else []

    #
    # Private
    #

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

    def _payback_paid_payments(self):
        paid_value = self.group.get_total_paid()

        # If we didn't pay anything yet, there is no need to create a payback.
        if not paid_value:
            return

        money = PaymentMethod.get_by_name(self.store, u'money')
        payment = money.create_payment(
            Payment.TYPE_IN, self.group, self.branch,
            paid_value, description=_(u'%s Money Returned for Purchase %s') % (
                u'1/1', self.identifier))
        payment.set_pending()
        payment.pay()

    #
    # Public API
    #

    def is_paid(self):
        if not self.group:
            return False

        for payment in self.payments:
            if not payment.is_paid():
                return False
        return True

    def can_cancel(self):
        """Find out if it's possible to cancel the order

        :returns: True if it's possible to cancel the order, otherwise False
        """
        # FIXME: Canceling partial orders disabled until we fix bug 3282
        for item in self.get_items():
            if item.has_partial_received():
                return False
        return self.status in [self.ORDER_QUOTING,
                               self.ORDER_PENDING,
                               self.ORDER_CONFIRMED]

    def can_close(self):
        """Find out if it's possible to close the order

        :returns: True if it's possible to close the order, otherwise False
        """

        # Consigned orders can be closed only after being confirmed
        if self.status == self.ORDER_CONSIGNED:
            return False

        for item in self.get_items():
            if not item.has_been_received():
                return False
        return True

    def confirm(self, confirm_date=None):
        """Confirms the purchase order

        :param confirm_data: optional, datetime
        """
        if confirm_date is None:
            confirm_date = TransactionTimestamp()

        if self.status not in [PurchaseOrder.ORDER_PENDING,
                               PurchaseOrder.ORDER_CONSIGNED]:
            fmt = _(u'Invalid order status, it should be '
                    u'ORDER_PENDING or ORDER_CONSIGNED, got %s')
            raise ValueError(fmt % (self.status_str, ))

        # In consigned purchases there is no payments at this point.
        if self.status != PurchaseOrder.ORDER_CONSIGNED and self.group:
            for payment in self.payments:
                payment.set_pending()

        if self.supplier and self.group:
            self.group.recipient = self.supplier.person

        self.responsible = get_current_user(self.store)
        self.status = PurchaseOrder.ORDER_CONFIRMED
        self.confirm_date = confirm_date

        Event.log(self.store, Event.TYPE_ORDER,
                  _(u"Order %s, total value %2.2f, supplier '%s' "
                    u"is now confirmed") % (self.identifier,
                                            self.purchase_total,
                                            self.supplier.person.name))

    def set_consigned(self):
        if self.status != PurchaseOrder.ORDER_PENDING:
            raise ValueError(
                _(u'Invalid order status, it should be '
                  u'ORDER_PENDING, got %s') % (self.status_str, ))

        self.responsible = get_current_user(self.store)
        self.status = PurchaseOrder.ORDER_CONSIGNED

    def close(self):
        """Closes the purchase order
        """
        if self.status != PurchaseOrder.ORDER_CONFIRMED:
            raise ValueError(_(u'Invalid status, it should be confirmed '
                               u'got %s instead') % self.status_str)
        self.status = self.ORDER_CLOSED

        Event.log(self.store, Event.TYPE_ORDER,
                  _(u"Order %s, total value %2.2f, supplier '%s' "
                    u"is now closed") % (self.identifier,
                                         self.purchase_total,
                                         self.supplier.person.name))

    def cancel(self):
        """Cancels the purchase order
        """
        assert self.can_cancel()

        # we have to cancel the payments too
        self._payback_paid_payments()
        self.group.cancel()

        self.status = self.ORDER_CANCELLED

    def receive_item(self, item, quantity_to_receive):
        if not item in self.get_pending_items():
            raise StoqlibError(_(u'This item is not pending, hence '
                                 u'cannot be received'))
        quantity = item.quantity - item.quantity_received
        if quantity < quantity_to_receive:
            raise StoqlibError(_(u'The quantity that you want to receive '
                                 u'is greater than the total quantity of '
                                 u'this item %r') % item)
        self.increase_quantity_received(item, quantity_to_receive)

    def increase_quantity_received(self, purchase_item, quantity_received):
        sellable = purchase_item.sellable
        items = [item for item in self.get_items()
                 if item.sellable.id == sellable.id]
        qty = len(items)
        if not qty:
            raise ValueError(_(u'There is no purchase item for '
                               u'sellable %r') % sellable)

        purchase_item.quantity_received += quantity_received

    def update_products_cost(self):
        """Update purchase's items cost

        Update the costs of all products on this purchase
        to the costs specified in the order.
        """
        for item in self.get_items():
            item.sellable.cost = item.cost
            product = item.sellable.product
            product_supplier = product.get_product_supplier_info(self.supplier)
            product_supplier.base_cost = item.cost

    @property
    def status_str(self):
        return PurchaseOrder.translate_status(self.status)

    @property
    def freight_type_name(self):
        if not self.freight_type in self.freight_types.keys():
            raise DatabaseInconsistency(_(u'Invalid freight_type, got %d')
                                        % self.freight_type)
        return self.freight_types[self.freight_type]

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

    @property
    def supplier_name(self):
        return self.supplier.get_description()

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

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

    @property
    def purchase_subtotal(self):
        """Get the subtotal of the purchase.
        The sum of all the items cost * items quantity
        """
        return currency(self.get_items().sum(
            PurchaseItem.cost * PurchaseItem.quantity) or 0)

    @property
    def purchase_total(self):
        subtotal = self.purchase_subtotal
        total = subtotal - self.discount_value + self.surcharge_value
        if total < 0:
            raise ValueError(_(u'Purchase total can not be lesser than zero'))
        # XXX: Since the purchase_total value must have two digits
        # (at the moment) we need to format the value to a 2-digit number and
        # then convert it to currency data type, because the subtotal value
        # may return a 3-or-more-digit value, depending on COST_PRECISION_DIGITS
        # parameters.
        return currency(get_formatted_price(total))

    @property
    def received_total(self):
        """Like {purchase_subtotal} but only takes into account the
        received items
        """
        return currency(self.get_items().sum(
            PurchaseItem.cost *
            PurchaseItem.quantity_received) or 0)

    def get_remaining_total(self):
        """The total value to be paid for the items not received yet
        """
        return self.purchase_total - self.received_total

    def get_pending_items(self, with_children=True):
        """
        Returns a sequence of all items which we haven't received yet.
        """
        return self.get_items(with_children=with_children).find(
            PurchaseItem.quantity_received < PurchaseItem.quantity)

    def get_partially_received_items(self):
        """
        Returns a sequence of all items which are partially received.
        """
        return self.get_items().find(
            PurchaseItem.quantity_received > 0)

    def get_open_date_as_string(self):
        return self.open_date and self.open_date.strftime("%x") or u""

    def get_quote_deadline_as_string(self):
        return self.quote_deadline and self.quote_deadline.strftime("%x") or u""

    def get_receiving_orders(self):
        """Returns all ReceivingOrder related to this purchase order
        """
        from stoqlib.domain.receiving import PurchaseReceivingMap, ReceivingOrder
        tables = [PurchaseReceivingMap, ReceivingOrder]
        query = And(PurchaseReceivingMap.purchase_id == self.id,
                    PurchaseReceivingMap.receiving_id == ReceivingOrder.id)
        return self.store.using(*tables).find(ReceivingOrder, query)

    def get_data_for_labels(self):
        """ This function returns some necessary data to print the purchase's
        items labels
        """
        for purchase_item in self.get_items():
            sellable = purchase_item.sellable
            label_data = Settable(barcode=sellable.barcode, code=sellable.code,
                                  description=sellable.description,
                                  price=sellable.price, sellable=sellable,
                                  quantity=purchase_item.quantity)
            yield label_data

    def has_batch_item(self):
        """Fetch the storables from this purchase order and returns ``True`` if
        any of them is a batch storable.

        :returns: ``True`` if this purchase order has batch items, ``False`` if
        it doesn't.
        """
        return not self.store.find(Storable,
                                   And(self.id == PurchaseOrder.id,
                                       PurchaseOrder.id == PurchaseItem.order_id,
                                       PurchaseItem.sellable_id == Sellable.id,
                                       Sellable.id == Storable.id,
                                       Eq(Storable.is_batch, True))).is_empty()

    def create_receiving_order(self):
        from stoqlib.domain.receiving import ReceivingOrder
        receiving = ReceivingOrder(self.store, branch=self.branch)
        receiving.add_purchase(self)
        for item in self.get_items():
            receiving.add_purchase_item(item, quantity=item.quantity)

        return receiving

    #
    # Classmethods
    #

    @classmethod
    def translate_status(cls, status):
        if not status in cls.statuses:
            raise DatabaseInconsistency(_(u'Got an unexpected status value: '
                                          u'%s') % status)
        return cls.statuses[status]

    @classmethod
    def find_by_work_order(cls, store, work_order):
        return store.find(PurchaseOrder, work_order=work_order)
Exemple #24
0
class BranchStation(Domain):
    """Defines a computer which access Stoqlib database and lives in a
    certain branch company
    """
    __storm_table__ = 'branch_station'

    name = UnicodeCol()
    is_active = BoolCol(default=False)
    branch_id = IdCol()
    branch = Reference(branch_id, 'Branch.id')

    #: An code for this station. Will be used when formatting identifier codes (for a sale, for
    #: instance)
    code = UnicodeCol(default='')

    type_id = IdCol()
    #: The type of this station (like a self-checkout or regular point of sale)
    type = Reference(type_id, 'StationType.id')

    #: Identifies if the station supports Kitchen Production System
    has_kps_enabled = BoolCol()

    # Public

    @classmethod
    def get_active_stations(cls, store):
        """Returns the currently active branch stations.
        :param store: a store
        :returns: a sequence of currently active stations
        """
        return store.find(cls, is_active=True).order_by(cls.name)

    @classmethod
    def create(cls, store, branch, name):
        """Create a new station id for the current machine.
        Optionally a branch can be specified which will be set as the branch
        for created station.

        :param store: a store
        :param branch: the branch
        :param name: name of the station
        :returns: a BranchStation instance
        """

        if cls.get_station(store, branch, name):
            raise StoqlibError(
                _(u"There is already a station registered as `%s'.") % name)
        return cls(name=name, is_active=True, branch=branch, store=store)

    def check_station_exists(self, name):
        """Returns True if we already have a station with the given name
        """
        # FIXME: We should allow computers with the same on different
        # branches.
        return self.check_unique_value_exists(BranchStation.name, name)

    @classmethod
    def get_station(cls, store, branch, name):
        """Fetches a station from a branch

        :param store: a store
        :param branch: the branch
        :param name: name of the station
        """

        if branch is None:
            raise TypeError(u"BranchStation.get_station() requires a Branch")
        return store.find(cls, name=name, branch=branch).one()

    #
    # IActive implementation
    #

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

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

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

    #
    # Accessors
    #

    def get_branch_name(self):
        """Returns the branch name
        """
        return self.branch.get_description()
Exemple #25
0
class OpticalProduct(Domain):
    """Stores information about products sold by optical stores.

    There are 3 main types of products sold by optical stores:

    - Glass frames (without lenses)
    - Glass lenses
    - Contact lenses
    """
    __storm_table__ = 'optical_product'

    #: The frame of the glases (without lenses)
    TYPE_GLASS_FRAME = u'glass-frame'

    #: The glasses to be used with a frame
    TYPE_GLASS_LENSES = u'glass-lenses'

    #: Contact lenses
    TYPE_CONTACT_LENSES = u'contact-lenses'

    product_id = IdCol(allow_none=False)
    product = Reference(product_id, 'Product.id')

    # The type indicates what of the following fields should be edited.
    optical_type = EnumCol()

    #: If this product should be reserved automatically when added to the sale
    #: with work order
    auto_reserve = BoolCol(default=True)

    #
    # Glass frame details
    #

    #: The type of the frame (prescription or sunglasses)
    gf_glass_type = UnicodeCol()

    #: Size of the frame, accordingly to the manufacturer (may also be a string,
    #: for instance Large, one size fits all, etc..)
    gf_size = UnicodeCol()

    # The type of the lenses used in this frame. (for isntance: demo lens,
    # solar, polarized, mirrored)
    gf_lens_type = UnicodeCol()

    # Color of the frame, accordingly to the manufacturer specification
    gf_color = UnicodeCol()

    #
    # Glass lenses details
    #

    # Fotossensivel
    #: Type of the lenses photosensitivity (for instance: tints, sunsensors,
    #: transitions, etc...)
    gl_photosensitive = UnicodeCol()

    # Anti reflexo
    #: A description of the anti glare treatment the lenses have.
    gl_anti_glare = UnicodeCol()

    # Índice refração
    #: Decimal value describing the refraction index
    gl_refraction_index = DecimalCol()

    # Classificação
    #: lenses may be monofocal, bifocal or multifocal
    gl_classification = UnicodeCol()

    # Adição
    #: Free text describing the range of the possible additions.
    gl_addition = UnicodeCol()

    # Diametro
    # Free text describing the range of the possible diameters for the lens
    gl_diameter = UnicodeCol()

    # Altura
    #: Free text describing the height of the lens
    gl_height = UnicodeCol()

    # Disponibilidade
    #: Free text describint the avaiability of the lens (in what possible
    #: parameters they are avaiable. For instance: "-10,00 a -2,25 Cil -2,00"
    gl_availability = UnicodeCol()

    #
    # Contact lenses details
    #

    # Grau
    #: Degree of the lenses, a decimal from -30 to +30, in steps of +- 0.25
    cl_degree = DecimalCol()

    # Classificação
    #: Free text describing the classification of the lenses (solid, gel, etc..)
    cl_classification = UnicodeCol()

    # tipo lente
    #: The type of the lenses (monofocal, toric, etc..)
    cl_lens_type = UnicodeCol()

    # Descarte
    #: How often the lens should be discarded (anually, daily, etc..)
    cl_discard = UnicodeCol()

    # Adição
    #: Free text describing the addition of the lenses.
    cl_addition = UnicodeCol()

    # Cilindrico
    # XXX: I still need to verify if a decimal column is ok, or if there are
    # possible text values.
    #: Cylindrical value of the lenses.
    cl_cylindrical = DecimalCol()

    # Eixo
    # XXX: I still need to verify if a decimal column is ok, or if there are
    # possible text values.
    #: Axix  of the lenses.
    cl_axis = DecimalCol()

    #: Free text color description of the lens (for cosmetic use)
    cl_color = UnicodeCol()

    # Curvatura
    #: Free text description of the curvature. normaly a decimal, but may have
    #: textual descriptions
    cl_curvature = UnicodeCol()

    @classmethod
    def get_from_product(cls, product):
        return product.store.find(cls, product=product).one()
Exemple #26
0
class OpticalPatientTest(Domain):
    __storm_table__ = 'optical_patient_test'

    create_date = DateTimeCol(default_factory=StatementTimestamp)

    client_id = IdCol(allow_none=False)
    #: The related client
    client = Reference(client_id, 'Client.id')

    responsible_id = IdCol(allow_none=False)
    #: The user that registred this information
    responsible = Reference(responsible_id, 'LoginUser.id')

    #: The contact lens that is being tested. This could be a reference to a
    #: |product in the future
    le_item = UnicodeCol()

    #: The brand of the tested contact lenses
    le_brand = UnicodeCol()

    #: Curva Base - CB
    le_base_curve = UnicodeCol()

    le_spherical_degree = UnicodeCol()
    le_cylindrical = UnicodeCol()
    le_axis = UnicodeCol()
    le_diameter = UnicodeCol()
    le_movement = UnicodeCol()
    le_centralization = UnicodeCol()
    le_spin = UnicodeCol()
    le_fluorescein = UnicodeCol()

    #: Sobre refração - SRF
    le_over_refraction = UnicodeCol()
    le_bichrome = UnicodeCol()

    #: If the client is satisfied with this product
    le_client_approved = BoolCol()

    #: If the client has purchased this product after the test.
    le_client_purchased = BoolCol()

    #: If the product being tested was delivered to the client
    le_delivered = BoolCol()

    re_item = UnicodeCol()
    re_brand = UnicodeCol()
    re_base_curve = UnicodeCol()
    re_spherical_degree = UnicodeCol()
    re_cylindrical = UnicodeCol()
    re_axis = UnicodeCol()
    re_diameter = UnicodeCol()
    re_movement = UnicodeCol()
    re_centralization = UnicodeCol()
    re_spin = UnicodeCol()
    re_fluorescein = UnicodeCol()
    re_over_refraction = UnicodeCol()
    re_bichrome = UnicodeCol()
    re_client_approved = BoolCol()
    re_client_purchased = BoolCol()
    re_delivered = BoolCol()

    #: Free notes
    notes = UnicodeCol()

    @property
    def responsible_name(self):
        return self.responsible.get_description()
Exemple #27
0
class DeviceSettings(Domain):

    __storm_table__ = 'device_settings'

    type = IntCol()
    brand = UnicodeCol()
    model = UnicodeCol()
    device_name = UnicodeCol()
    station_id = IdCol()
    station = Reference(station_id, 'BranchStation.id')
    is_active = BoolCol(default=True)

    (SCALE_DEVICE, _UNUSED, CHEQUE_PRINTER_DEVICE) = range(1, 4)

    device_types = {
        SCALE_DEVICE: _(u'Scale'),
        CHEQUE_PRINTER_DEVICE: _(u'Cheque Printer')
    }

    #
    # Domain
    #

    def get_printer_description(self):
        return u"%s %s" % (self.brand.capitalize(), self.model)

    def get_device_type_name(self, type=None):
        return DeviceSettings.device_types[type or self.type]

    # XXX: Maybe stoqdrivers can implement a generic way to do this?
    def get_interface(self):
        """ Based on the column values instantiate the stoqdrivers interface
        for the device itself.
        """
        port = SerialPort(device=self.device_name)

        if self.type == DeviceSettings.CHEQUE_PRINTER_DEVICE:
            return ChequePrinter(brand=self.brand, model=self.model, port=port)
        elif self.type == DeviceSettings.SCALE_DEVICE:
            return Scale(brand=self.brand,
                         model=self.model,
                         device=self.device_name)
        raise DatabaseInconsistency("The device type referred by this "
                                    "record (%r) is invalid, given %r." %
                                    (self, self.type))

    def is_a_printer(self):
        return self.type == DeviceSettings.CHEQUE_PRINTER_DEVICE

    def is_valid(self):
        return (all((self.model, self.device_name, self.brand, self.station))
                and self.type in DeviceSettings.device_types)

    @classmethod
    def get_by_station_and_type(cls, store, station, type):
        """Fetch all settings for a specific station and type.

        :param store: a store
        :param station: a BranchStation instance
        :param type: device type
        """
        return store.find(cls, station=station, type=type)

    @classmethod
    def get_scale_settings(cls, store):
        """
        Get the scale device settings for the current station
        :param store: a store
        :returns: a :class:`DeviceSettings` object or None if there is none
        """
        station = get_current_station(store)
        return store.find(cls, station=station, type=cls.SCALE_DEVICE).one()

    #
    # IActive implementation
    #

    def inactivate(self):
        self.is_active = False

    def activate(self):
        self.is_active = True

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

    #
    # IDescribable implementation
    #

    def get_description(self):
        return self.get_printer_description()
Exemple #28
0
class ECFPrinter(Domain):
    """
    @param model:
    @param brand:
    @param device_name:
    @param device_serial:
    @param station:
    @param is_active:
    @param constants:
    @param baudrate:
    @cvar last_sale: reference for the last Sale
    @cvar last_till_entry: reference for the last TillEntry
    @cvar user_number: the current registrer user in the printer
    @cvar register_date: when the current user was registred
    @cvar register_cro: cro when the user was registred
    """

    __storm_table__ = 'ecf_printer'

    model = UnicodeCol()
    brand = UnicodeCol()
    device_name = UnicodeCol()
    device_serial = UnicodeCol()
    station_id = IdCol()
    station = Reference(station_id, 'BranchStation.id')
    is_active = BoolCol(default=True)
    baudrate = IntCol()
    last_sale_id = IdCol(default=None)
    last_sale = Reference(last_sale_id, 'Sale.id')
    last_till_entry_id = IdCol(default=None)
    last_till_entry = Reference(last_till_entry_id, 'TillEntry.id')
    user_number = IntCol(default=None)
    register_date = DateTimeCol(default=None)
    register_cro = IntCol(default=None)

    constants = ReferenceSet('id', 'DeviceConstant.printer_id')

    #
    # Public API
    #

    def create_fiscal_printer_constants(self):
        """
        Creates constants for a fiscal printer
        This can be called multiple times
        """
        # We only want to populate 'empty' objects.
        if not self.constants.find().is_empty():
            return

        store = self.store
        driver = self.get_fiscal_driver()
        constants = driver.get_constants()
        for constant in constants.get_items():
            constant_value = None
            if isinstance(constant, PaymentMethodType):
                constant_type = DeviceConstant.TYPE_PAYMENT
            elif isinstance(constant, UnitType):
                constant_type = DeviceConstant.TYPE_UNIT
            else:
                continue

            DeviceConstant(constant_type=constant_type,
                           constant_name=unicode(describe_constant(constant)),
                           constant_value=constant_value,
                           constant_enum=int(constant),
                           device_value=constants.get_value(constant, None),
                           printer=self,
                           store=store)

        for constant, device_value, value in driver.get_tax_constants():
            # FIXME: Looks like this is not used and/or is duplicating code from
            # ecfpriterdialog.py (_populate_constants)
            if constant == TaxType.CUSTOM:
                constant_name = '%0.2f %%' % value
            else:
                constant_name = describe_constant(constant)
            DeviceConstant(constant_type=DeviceConstant.TYPE_TAX,
                           constant_name=unicode(constant_name),
                           constant_value=value,
                           constant_enum=int(constant),
                           device_value=device_value,
                           printer=self,
                           store=store)

    def get_constants_by_type(self, constant_type):
        """
        Fetchs a list of constants for the current ECFPrinter object.
        @param constant_type: type of constant
        @type constant_type: :class:`DeviceConstant`
        @returns: list of constants
        """
        return self.store.find(DeviceConstant, printer=self,
                               constant_type=constant_type)

    def get_payment_constant(self, payment):
        """
        @param payment: the payment whose method we will lookup the constant
        @returns: the payment constant
        @rtype: :class:`DeviceConstant`
        """
        constant_enum = payment.method.operation.get_constant(payment)

        if constant_enum is None:
            raise AssertionError

        return self.store.find(DeviceConstant,
                               printer=self,
                               constant_type=DeviceConstant.TYPE_PAYMENT,
                               constant_enum=int(constant_enum)).one()

    def get_tax_constant_for_device(self, sellable):
        """
        Returns a tax_constant for a device
        Raises DeviceError if a constant is not found

        @param sellable: sellable which has the tax codes
        @type sellable: :class:`stoqlib.domain.sellable.Sellable`
        @returns: the tax constant
        @rtype: :class:`DeviceConstant`
        """

        sellable_constant = sellable.get_tax_constant()
        if sellable_constant is None:
            raise DeviceError("No tax constant set for sellable %r" % sellable)

        store = self.store
        if sellable_constant.tax_type == TaxType.CUSTOM:
            constant = DeviceConstant.get_custom_tax_constant(
                self, sellable_constant.tax_value, store)
            if constant is None:
                raise DeviceError(_(
                    "fiscal printer is missing a constant for the custom "
                    "tax constant '%s'") % (sellable_constant.description, ))
        else:
            constant = DeviceConstant.get_tax_constant(
                self, sellable_constant.tax_type, store)
            if constant is None:
                raise DeviceError(_(
                    "fiscal printer is missing a constant for tax "
                    "constant '%s'") % (sellable_constant.description, ))

        return constant

    def get_fiscal_driver(self):
        if self.brand == 'virtual':
            port = VirtualPort()
        else:
            port = SerialPort(device=self.device_name, baudrate=self.baudrate)
        return FiscalPrinter(brand=self.brand, model=self.model, port=port)

    def set_user_info(self, user_info):
        self.user_number = user_info.user_number
        self.register_cro = user_info.cro
        self.register_date = user_info.register_date

    #
    # IActive implementation
    #

    def inactivate(self):
        self.is_active = False

    def activate(self):
        self.is_active = True

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

    #
    # IDescribable implementation
    #

    def get_description(self):
        # Quick workaround to avoid calling FiscalPrinter.setup(), since that
        # may send commands to the ECF, and we just need the description.
        # TODO: Improve stoqdrivers so we can get this easyer
        port = VirtualPort()
        driver = BasePrinter(brand=self.brand, model=self.model, port=port)
        return driver.get_model_name()

    @classmethod
    def get_last_document(cls, station, store):
        return store.find(cls, station=station, is_active=True).one()
Exemple #29
0
class ProductionProducedItem(Domain):
    """This class represents a composed product that was produced, but
    didn't enter the stock yet. Its used mainly for the quality assurance
    process
    """

    __storm_table__ = 'production_produced_item'

    order_id = IdCol()
    order = Reference(order_id, 'ProductionOrder.id')
    # ProductionItem already has a reference to Product, but we need it for
    # constraint checks UNIQUE(product_id, serial_number)
    product_id = IdCol()
    product = Reference(product_id, 'Product.id')
    produced_by_id = IdCol()
    produced_by = Reference(produced_by_id, 'LoginUser.id')
    produced_date = DateTimeCol()
    serial_number = IntCol()
    entered_stock = BoolCol(default=False)
    test_passed = BoolCol(default=False)
    test_results = ReferenceSet(
        'id', 'ProductionItemQualityResult.produced_item_id')

    def get_pending_tests(self):
        tests_done = set([t.quality_test for t in self.test_results])
        all_tests = set(self.product.quality_tests)
        return list(all_tests.difference(tests_done))

    @classmethod
    def get_last_serial_number(cls, product, store):
        return store.find(cls, product=product).max(cls.serial_number) or 0

    @classmethod
    def is_valid_serial_range(cls, product, first, last, store):
        query = And(cls.product_id == product.id, cls.serial_number >= first,
                    cls.serial_number <= last)
        # There should be no results for the range to be valid
        return store.find(cls, query).is_empty()

    def send_to_stock(self):
        # Already is in stock
        if self.entered_stock:
            return

        storable = self.product.storable
        storable.increase_stock(1, self.order.branch,
                                StockTransactionHistory.TYPE_PRODUCTION_SENT,
                                self.id)
        self.entered_stock = True

    def set_test_result_value(self, quality_test, value, tester):
        store = self.store
        result = store.find(ProductionItemQualityResult,
                            quality_test=quality_test,
                            produced_item=self).one()
        if not result:
            result = ProductionItemQualityResult(store=self.store,
                                                 quality_test=quality_test,
                                                 produced_item=self,
                                                 tested_by=tester,
                                                 result_value=u'')
        else:
            result.tested_by = tester

        result.tested_date = localnow()
        result.set_value(value)
        return result

    def get_test_result(self, quality_test):
        store = self.store
        return store.find(ProductionItemQualityResult,
                          quality_test=quality_test,
                          produced_item=self).one()

    def check_tests(self):
        """Checks if all tests for this produced items passes.

        If all tests passes, sets self.test_passed = True
        """
        results = [i.test_passed for i in self.test_results]

        passed = all(results)
        self.test_passed = (passed and len(results)
                            == self.product.quality_tests.count())
        if self.test_passed:
            self.order.try_finalize_production()
Exemple #30
0
class Image(Domain):
    """Class responsible for storing images and it's description

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

    """

    __storm_table__ = 'image'

    (THUMBNAIL_SIZE_HEIGHT, THUMBNAIL_SIZE_WIDTH) = (128, 128)

    #: the image itself in a bin format
    image = BLOBCol(default=None)

    #: the image thumbnail in a bin format
    thumbnail = BLOBCol(default=None)

    #: the image description
    description = UnicodeCol(default=u'')

    #: The image filename
    filename = UnicodeCol(default=u'')

    #: The date that this image was uploaded to the database
    create_date = DateTimeCol(default_factory=StatementTimestamp)

    #: Some keywords for this image
    keywords = UnicodeCol(default=u'')

    #: Some notes about the image
    notes = UnicodeCol(default=u'')

    #: If this is the main image. Only makes sense if :obj:`.sellable`
    #: is not `None`
    is_main = BoolCol(default=False)

    #: If this image is only for internal use (i.e. it won't be synchronized
    #: to any e-commerce website to be displayed publicly)
    internal_use = BoolCol(default=False)

    sellable_id = IdCol(default=None)
    #: The |sellable| that this image belongs to
    sellable = Reference(sellable_id, 'Sellable.id')

    category_id = IdCol(default=None)
    #: The |category| that this image belongs to
    category = Reference(category_id, 'SellableCategory.id')

    station_type_id = IdCol(default=None)
    #: The station type this image should be used instead of the main image.
    station_type = Reference(station_type_id, 'StationType.id')

    #
    #  Public API
    #

    def get_base64_encoded(self):
        return base64.b64encode(self.image).decode()

    #
    #  IDescribable implementation
    #

    def get_description(self):
        if self.description:
            return self.description
        return _(u"Stoq image")

    #
    #  ORMObject
    #

    @classmethod
    def delete(cls, id, store):
        image = store.get(cls, id)
        ImageRemoveEvent.emit(image)
        store.remove(image)

    #
    # Domain
    #

    def on_create(self):
        ImageCreateEvent.emit(self)

    def on_update(self):
        ImageEditEvent.emit(self)