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)
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()
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()
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()
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
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']
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
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)
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()
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
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
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))
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)
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()
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')])
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()
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
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
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
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)
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)
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)
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()
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()
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()
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()
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()
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()
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)