class BranchSynchronization(ORMObject): """Created once per branch. Contains a string which is a reference to a policy defined in stoqlib.database.policy and a timestamp which is updated each time a synchronization is done. """ __storm_table__ = 'branch_synchronization' id = IntCol(primary=True, default=AutoReload) #: last time updated sync_time = DateTimeCol(allow_none=False) branch_id = IntCol() #: a |branch| branch = Reference(branch_id, 'Branch.id') #: policy used to update the branch policy = UnicodeCol(allow_none=False)
class FiscalDayHistory(Domain): """This represents the information that needs to be used to generate a Sintegra file of type 60A. """ __storm_table__ = 'fiscal_day_history' emission_date = DateTimeCol() station_id = IdCol() station = Reference(station_id, 'BranchStation.id') serial = UnicodeCol() serial_id = IntCol() coupon_start = IntCol() coupon_end = IntCol() cro = IntCol() crz = IntCol() period_total = PriceCol() total = PriceCol() taxes = ReferenceSet('id', 'FiscalDayTax.fiscal_day_history_id') reduction_date = DateTimeCol()
class Book(Domain): """ A book class for products, holding specific data about books """ __storm_table__ = 'book' product_id = IdCol() product = Reference(product_id, 'Product.id') publisher_id = IdCol(default=None) publisher = Reference(publisher_id, 'BookPublisher.id') author = UnicodeCol(default=u'') series = UnicodeCol(default=u'') edition = UnicodeCol(default=u'') subject = UnicodeCol(default=u'') isbn = UnicodeCol(default=u'') language = UnicodeCol(default=u'') decorative_finish = UnicodeCol(default=u'') country = UnicodeCol(default=u'Brazil') pages = IntCol(default=0) year = IntCol(default=0) synopsis = UnicodeCol(default=u'')
class UIField(Domain): """This describes a field in form a. Can be used makae fields mandatory or hide them completely. """ __storm_table__ = 'ui_field' ui_form_id = IntCol() ui_form = Reference(ui_form_id, 'UIForm.id') field_name = UnicodeCol() description = UnicodeCol() visible = BoolCol() mandatory = BoolCol()
class Sale(Domain): __storm_table__ = 'sale' STATUS_INITIAL = u'initial' STATUS_QUOTE = u'quote' STATUS_ORDERED = u'ordered' status = EnumCol() invoice_id = IdCol() branch_id = IdCol() invoice_number = IntCol() operation_nature = UnicodeCol()
class SystemTable(ORMObject): """Stores information about database schema migration I{update}: the date when the database schema was updated I{patchlevel}: the version of the schema installed """ __storm_table__ = 'system_table' id = IntCol(primary=True, default=AutoReload) updated = DateTimeCol() patchlevel = IntCol() generation = IntCol() @classmethod def is_available(cls, store): """Checks if Stoqlib database is properly installed :param store: a store """ if not store.table_exists(u'system_table'): return False return bool(store.find(cls))
class Delivery(Domain): __storm_table__ = 'delivery' (STATUS_INITIAL, STATUS_SENT, STATUS_RECEIVED) = range(3) status = IntCol(default=STATUS_INITIAL) open_date = DateTimeCol(default=None) deliver_date = DateTimeCol(default=None) receive_date = DateTimeCol(default=None) tracking_code = UnicodeCol(default=u'') address_id = IntCol() address = Reference(address_id, Address.id) transporter_id = IntCol() transporter = Reference(transporter_id, Transporter.id) service_item_id = IntCol() service_item = Reference(service_item_id, SaleItem.id) def add_item(self, item): item.delivery = self
class BaseCOFINS(BaseTax): """Contains attributes to be used to calculate PIS tax in Brazil.""" CALC_PERCENTAGE = u'percentage' CALC_VALUE = u'value' cst = IntCol(default=None) #: Operation type (percentage or value) calculo = EnumCol(default=CALC_PERCENTAGE, allow_none=False) #: Aliquot in percentage p_cofins = PercentCol(default=None)
class ClientCategoryPrice(Domain): """A table that stores special prices for clients based on their category. """ __storm_table__ = 'client_category_price' sellable_id = IntCol() #: The sellable that has a special price sellable = Reference(sellable_id, 'Sellable.id') category_id = IntCol() #: The category that has the special price category = Reference(category_id, 'ClientCategory.id') #: The price for this (sellable, category) price = PriceCol(default=0) #: The max discount that may be applied. max_discount = PercentCol(default=0) def _get_markup(self): if self.sellable.cost == 0: return Decimal(0) return ((self.price / self.sellable.cost) - 1) * 100 def _set_markup(self, markup): self.price = self.sellable._get_price_by_markup(markup) markup = property(_get_markup, _set_markup) @property def category_name(self): return self.category.name def remove(self): """Removes this client category price from the database.""" self.store.remove(self)
class Quotation(Domain): __storm_table__ = 'quotation' #: A numeric identifier for this object. This value should be used instead of #: :obj:`Domain.id` when displaying a numerical representation of this object to #: the user, in dialogs, lists, reports and such. identifier = IdentifierCol() group_id = IntCol() group = Reference(group_id, 'QuoteGroup.id') purchase_id = IntCol() purchase = Reference(purchase_id, 'PurchaseOrder.id') branch_id = IntCol() branch = Reference(branch_id, 'Branch.id') implements(IDescribable) def get_description(self): supplier = self.purchase.supplier.person.name return u"Group %s - %s" % (self.group.identifier, supplier) # # Public API # def close(self): """Closes the quotation""" # we don't have a specific status for closed quotes, so we just # cancel it if not self.is_closed(): self.purchase.cancel() def is_closed(self): """Returns if the quotation is closed or not. :returns: True if the quotation is closed, False otherwise. """ return self.purchase.status == PurchaseOrder.ORDER_CANCELLED
class CostCenterEntry(Domain): """A operation that generated some cost in a |costcenter|. A cost can be generated when a lonely out |payment| is paid or when some operations on the stock are performed. """ __storm_table__ = 'cost_center_entry' cost_center_id = IntCol() #: The cost center this entry belongs to cost_center = Reference(cost_center_id, 'CostCenter.id') payment_id = IntCol() #: The payment that generated this cost. payment = Reference(payment_id, 'Payment.id') stock_transaction_id = IntCol() #: The stock movement transaction that generated this cost. stock_transaction = Reference(stock_transaction_id, 'StockTransactionHistory.id')
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 BookPublisher(Domain): """An institution created to publish books""" (STATUS_ACTIVE, STATUS_INACTIVE) = range(2) statuses = {STATUS_ACTIVE: _(u'Active'), STATUS_INACTIVE: _(u'Inactive')} __storm_table__ = 'book_publisher' person_id = IntCol() person = Reference(person_id, 'Person.id') status = IntCol(default=STATUS_ACTIVE) # # IActive implementation # def inactivate(self): assert self.is_active, ('This person facet is already inactive') self.is_active = False def activate(self): assert not self.is_active, ('This personf facet is already active') self.is_active = True def get_status_string(self): if self.is_active: return _('Active') return _('Inactive') # # IDescribable implementation # def get_description(self): return self.person.name
class WorkOrderPackageItem(Domain): """A |workorderpackage| item This is a representation of a |workorder| inside a |workorderpackage|. This is used instead of the work order directly so we can keep a history of sent and received packages. See also: `schema <http://doc.stoq.com.br/schema/tables/work_order_item.html>`__ """ __storm_table__ = 'work_order_package_item' #: notes about why the :attr:`.order` is being sent to another branch notes = UnicodeCol(default=u'') package_id = IntCol(allow_none=False) #: the |workorderpackage| this item is transported in package = Reference(package_id, 'WorkOrderPackage.id') order_id = IntCol(allow_none=False) #: the |workorder| this item represents order = Reference(order_id, 'WorkOrder.id')
class CardOperationCost(Domain): __storm_table__ = 'card_operation_cost' device_id = IntCol(default=None) device = Reference(device_id, CardPaymentDevice.id) provider_id = IntCol(default=None) card_type = IntCol(default=0) installment_start = IntCol(default=1) installment_end = IntCol(default=1) payment_days = IntCol(default=30) fee = PercentCol(default=0) fare = PriceCol(default=0)
class StorableBatch(Domain): """Batch information for storables. A batch is a colection of products (storable) that were produced at the same time and thus they have some common information, such as expiration date. This information is useful since sometimes its necessary to make decisions based on the batch like a special promotion for older batches (if it is close to the expiration date, for instance) or if a batch is somehow defective and we need to contact the clients that purchased items from this batch. """ __storm_table__ = 'storable_batch' #: The sequence number for this batch. Should be unique for a given #: storable batch_number = UnicodeCol(allow_none=False) #: The date this batch was created create_date = DateTimeCol(default_factory=localnow) #: An expiration date, specially for perishable products, like milk and food in #: general expire_date = DateTimeCol() #: Some space for the users to add notes to this batch. notes = UnicodeCol() storable_id = IntCol(allow_none=False) #: The storable that is in this batch storable = Reference(storable_id, 'Storable.id') def get_balance_for_branch(self, branch): """Return the stock balance for this |batch| in a |branch|. :param branch: the |branch| to get the stock balance for :returns: the amount of stock available in the |branch| """ store = self.store stock_items = store.find(ProductStockItem, storable=self.storable, batch=self, branch=branch) return stock_items.sum(ProductStockItem.quantity) or Decimal(0)
class InstalledPlugin(Domain): """This object represent an installed and activated plugin. :cvar plugin_name: name of the plugin :cvar plugin_version: version of the plugin """ __storm_table__ = 'installed_plugin' plugin_name = UnicodeCol() plugin_version = IntCol() @classmethod def get_plugin_names(cls, store): """Fetchs a list of installed plugin names :param store: a store :returns: list of strings """ return [p.plugin_name for p in store.find(cls)]
class SellableTaxConstant(Domain): """A tax constant tied to a sellable See also: `schema <http://doc.stoq.com.br/schema/tables/sellable_tax_constant.html>`__ """ __storm_table__ = 'sellable_tax_constant' #: description of this constant description = UnicodeCol() #: a TaxType constant, used by ECF tax_type = IntCol() #: the percentage value of the tax tax_value = PercentCol(default=None) _mapping = { int(TaxType.NONE): u'TAX_NONE', # Não tributado - ICMS int(TaxType.EXEMPTION): u'TAX_EXEMPTION', # Isento - ICMS int(TaxType.SUBSTITUTION): u'TAX_SUBSTITUTION', # Substituição tributária - ICMS int(TaxType.SERVICE): u'TAX_SERVICE', # ISS } def get_value(self): """ :returns: the value to pass to ECF """ return SellableTaxConstant._mapping.get(self.tax_type, self.tax_value) @classmethod def get_by_type(cls, tax_type, store): """Fetch the tax constant for tax_type :param tax_type: the tax constant to fetch :param store: a store :returns: a |sellabletaxconstant| or ``None`` if none is found """ return store.find(SellableTaxConstant, tax_type=int(tax_type)).one() # IDescribable def get_description(self): return self.description
class ProductTaxTemplate(Domain): (TYPE_ICMS, TYPE_IPI) = range(2) __storm_table__ = 'product_tax_template' types = {TYPE_ICMS: u"ICMS", TYPE_IPI: u"IPI"} type_map = {TYPE_ICMS: ProductIcmsTemplate, TYPE_IPI: ProductIpiTemplate} name = UnicodeCol(default=u'') tax_type = IntCol() def get_tax_model(self): klass = self.type_map[self.tax_type] store = self.store return store.find(klass, product_tax_template=self).one() def get_tax_type_str(self): return self.types[self.tax_type]
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 BillOption(Domain): """List of values for bill (boleto) generation See also: `schema <http://doc.stoq.com.br/schema/tables/bill_option.html>`__ """ __storm_table__ = 'bill_option' #: option name, such as nosso_numero option = UnicodeCol() #: value of the option value = UnicodeCol() bank_account_id = IntCol() #: the |bankaccount| this option belongs to bank_account = Reference(bank_account_id, 'BankAccount.id')
class ProductIcmsTemplate(BaseICMS): __storm_table__ = 'product_icms_template' product_tax_template_id = IntCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id') # Simples Nacional p_cred_sn_valid_until = DateTimeCol(default=None) def is_p_cred_sn_valid(self): """Returns if p_cred_sn has expired.""" if not self.p_cred_sn_valid_until: # If we don't have a valid_until, means p_cred_sn will never # expire. Therefore, p_cred_sn is valid. return True elif self.p_cred_sn_valid_until.date() < localtoday().date(): return False return True
class TransactionEntry(ORMObject): """ A TransactionEntry keeps track of state associated with a database transaction. It's main use case is to know information about the system when a domain object is created or modified. Such information will be used by stoq when syncing databases """ __storm_table__ = 'transaction_entry' id = IntCol(primary=True, default=AutoReload) #: last time this object was modified te_time = DateTimeCol(allow_none=False) #: It this object was modified since the last time it was synced #: After the object is synced, this property will be set to ``False``, so #: that when the next sync begins, only the objects that are **dirty** will be #: processed dirty = BoolCol(default=True)
class PaymentCategory(Domain): """I am a payment category. I contain a name and a color """ implements(IDescribable) __storm_table__ = 'payment_category' #: for outgoing payments (payable application) TYPE_PAYABLE = 0 #: for incoming payments (receivable application) TYPE_RECEIVABLE = 1 #: category name name = UnicodeCol() #: category color, like #ff0000 for red. color = UnicodeCol() #: category type, payable or receivable category_type = IntCol(default=TYPE_PAYABLE) # # IDescribable implementation # def get_description(self): return self.name @classmethod def get_by_type(cls, store, category_type): """Fetches a list of PaymentCategories given a category type :param store: a store :param category_type: TYPE_PAYABLE or TYPE_RECEIVABLE :rseturns: a sequence of PaymentCategory ordered by name """ return store.find(cls, category_type=category_type).order_by( PaymentCategory.name)
class BankAccount(Domain): """Information specific to a bank See also: `schema <http://doc.stoq.com.br/schema/tables/bank_account.html>`__ """ __storm_table__ = 'bank_account' account_id = IdCol() #: the |account| for this bank account account = Reference(account_id, 'Account.id') # FIXME: This is brazil specific, should probably be replaced by a # bank reference to a separate class with name in addition to # the bank number #: an identify for the bank type of this account, bank_number = IntCol(default=0) #: an identifier for the bank branch/agency which is responsible #: for this bank_branch = UnicodeCol(default=None) #: an identifier for this bank account bank_account = UnicodeCol(default=None) @property def options(self): """Get the bill options for this bank account :returns: a list of :class:`BillOption` """ return self.store.find(BillOption, bank_account=self) def add_bill_option(self, name, value): return BillOption(store=self.store, option=name, value=value, bank_account_id=self.id)
class ReturnedSale(Domain): __storm_table__ = 'returned_sale' identifier = IntCol(default=AutoReload) return_date = DateTimeCol(default_factory=datetime.datetime.now) invoice_number = IntCol(default=None) reason = UnicodeCol(default=u'') sale_id = IntCol() sale = Reference(sale_id, Sale.id) new_sale_id = IntCol() new_sale = Reference(new_sale_id, Sale.id) responsible_id = IntCol() responsible = Reference(responsible_id, LoginUser.id) branch_id = IntCol() branch = Reference(branch_id, Branch.id)
class ProfileSettings(Domain): """Profile settings for user profile instances. Each instance of this class stores information about the access availability in a certain application.""" __storm_table__ = 'profile_settings' app_dir_name = UnicodeCol() has_permission = BoolCol(default=False) user_profile_id = IntCol() user_profile = Reference(user_profile_id, 'UserProfile.id') @classmethod def set_permission(cls, store, profile, app, permission): """ Set the permission for a user profile to use a application :param store: a store :param profile: a UserProfile :param app: name of the application :param permission: a boolean of the permission """ setting = store.find(cls, user_profile=profile, app_dir_name=app).one() setting.has_permission = permission
class FiscalDayTax(Domain): """This represents the information that needs to be used to generate a Sintegra file of type 60M. """ __storm_table__ = 'fiscal_day_tax' fiscal_day_history_id = IntCol() fiscal_day_history = Reference(fiscal_day_history_id, 'FiscalDayHistory.id') #: four bytes, either the percental of the tax, 1800 for 18% or one of: #: #: * ``I``: Isento #: * ``F``: Substitucao #: * ``N``: Nao tributado #: * ``ISS``: ISS #: * ``CANC``: Cancelled #: * ``DESC``: Discount code = UnicodeCol() value = PriceCol() type = UnicodeCol()
class ReceivingOrder(Domain): """Receiving order definition. """ __storm_table__ = 'receiving_order' #: Products in the order was not received or received partially. STATUS_PENDING = u'pending' #: All products in the order has been received then the order is closed. STATUS_CLOSED = u'closed' FREIGHT_FOB_PAYMENT = u'fob-payment' FREIGHT_FOB_INSTALLMENTS = u'fob-installments' FREIGHT_CIF_UNKNOWN = u'cif-unknown' FREIGHT_CIF_INVOICE = u'cif-invoice' freight_types = collections.OrderedDict([ (FREIGHT_FOB_PAYMENT, _(u"FOB - Freight value on a new payment")), (FREIGHT_FOB_INSTALLMENTS, _(u"FOB - Freight value on installments")), (FREIGHT_CIF_UNKNOWN, _(u"CIF - Freight value is unknown")), (FREIGHT_CIF_INVOICE, _(u"CIF - Freight value highlighted on invoice")), ]) FOB_FREIGHTS = ( FREIGHT_FOB_PAYMENT, FREIGHT_FOB_INSTALLMENTS, ) CIF_FREIGHTS = (FREIGHT_CIF_UNKNOWN, FREIGHT_CIF_INVOICE) #: A numeric identifier for this object. This value should be used instead of #: :obj:`Domain.id` when displaying a numerical representation of this object to #: the user, in dialogs, lists, reports and such. identifier = IdentifierCol() #: status of the order status = EnumCol(allow_none=False, default=STATUS_PENDING) #: Date that order has been closed. receival_date = DateTimeCol(default_factory=localnow) #: Date that order was send to Stock application. confirm_date = DateTimeCol(default=None) #: Some optional additional information related to this order. notes = UnicodeCol(default=u'') #: Type of freight freight_type = EnumCol(allow_none=False, default=FREIGHT_FOB_PAYMENT) #: Total of freight paid in receiving order. freight_total = PriceCol(default=0) surcharge_value = PriceCol(default=0) #: Discount value in receiving order's payment. discount_value = PriceCol(default=0) #: Secure value paid in receiving order's payment. secure_value = PriceCol(default=0) #: Other expenditures paid in receiving order's payment. expense_value = PriceCol(default=0) # This is Brazil-specific information icms_total = PriceCol(default=0) ipi_total = PriceCol(default=0) #: The invoice number of the order that has been received. invoice_number = IntCol() #: The invoice total value of the order received invoice_total = PriceCol(default=None) #: The invoice key of the order received invoice_key = UnicodeCol() cfop_id = IdCol() cfop = Reference(cfop_id, 'CfopData.id') responsible_id = IdCol() responsible = Reference(responsible_id, 'LoginUser.id') supplier_id = IdCol() supplier = Reference(supplier_id, 'Supplier.id') branch_id = IdCol() branch = Reference(branch_id, 'Branch.id') transporter_id = IdCol(default=None) transporter = Reference(transporter_id, 'Transporter.id') purchase_orders = ReferenceSet('ReceivingOrder.id', 'PurchaseReceivingMap.receiving_id', 'PurchaseReceivingMap.purchase_id', 'PurchaseOrder.id') def __init__(self, store=None, **kw): Domain.__init__(self, store=store, **kw) # These miss default parameters and needs to be set before # cfop, which triggers an implicit flush. self.branch = kw.pop('branch', None) self.supplier = kw.pop('supplier', None) if not 'cfop' in kw: self.cfop = sysparam.get_object(store, 'DEFAULT_RECEIVING_CFOP') # # Public API # def confirm(self): for item in self.get_items(): item.add_stock_items() purchases = list(self.purchase_orders) # XXX: Maybe FiscalBookEntry should not reference the payment group, but # lets keep this way for now until we refactor the fiscal book related # code, since it will pretty soon need a lot of changes. group = purchases[0].group FiscalBookEntry.create_product_entry(self.store, group, self.cfop, self.invoice_number, self.icms_total, self.ipi_total) self.invoice_total = self.total for purchase in purchases: if purchase.can_close(): purchase.close() def add_purchase(self, order): return PurchaseReceivingMap(store=self.store, purchase=order, receiving=self) def add_purchase_item(self, item, quantity=None, batch_number=None, parent_item=None): """Add a |purchaseitem| on this receiving order :param item: the |purchaseitem| :param decimal.Decimal quantity: the quantity of that item. If ``None``, it will be get from the item's pending quantity :param batch_number: a batch number that will be used to get or create a |batch| it will be get from the item's pending quantity or ``None`` if the item's |storable| is not controlling batches. :raises: :exc:`ValueError` when validating the quantity and testing the item's order for equality with :obj:`.order` """ pending_quantity = item.get_pending_quantity() if quantity is None: quantity = pending_quantity if not (0 < quantity <= item.quantity): raise ValueError("The quantity must be higher than 0 and lower " "than the purchase item's quantity") if quantity > pending_quantity: raise ValueError("The quantity must be lower than the item's " "pending quantity") sellable = item.sellable storable = sellable.product_storable if batch_number is not None: batch = StorableBatch.get_or_create(self.store, storable=storable, batch_number=batch_number) else: batch = None self.validate_batch(batch, sellable) return ReceivingOrderItem(store=self.store, sellable=item.sellable, batch=batch, quantity=quantity, cost=item.cost, purchase_item=item, receiving_order=self, parent_item=parent_item) def update_payments(self, create_freight_payment=False): """Updates the payment value of all payments realated to this receiving. If create_freight_payment is set, a new payment will be created with the freight value. The other value as the surcharges and discounts will be included in the installments. :param create_freight_payment: True if we should create a new payment with the freight value, False otherwise. """ difference = self.total - self.products_total if create_freight_payment: difference -= self.freight_total if difference != 0: # Get app pending payments for the purchases associated with this # receiving, and update them. payments = self.payments.find(status=Payment.STATUS_PENDING) payments_number = payments.count() if payments_number > 0: # XXX: There is a potential rounding error here. per_installments_value = difference / payments_number for payment in payments: new_value = payment.value + per_installments_value payment.update_value(new_value) if self.freight_total and create_freight_payment: self._create_freight_payment() def _create_freight_payment(self): store = self.store money_method = PaymentMethod.get_by_name(store, u'money') # If we have a transporter, the freight payment will be for him # (and in another payment group). purchases = list(self.purchase_orders) if len(purchases) == 1 and self.transporter is None: group = purchases[0].group else: if self.transporter: recipient = self.transporter.person else: recipient = self.supplier.person group = PaymentGroup(store=store, recipient=recipient) description = _(u'Freight for receiving %s') % (self.identifier, ) payment = money_method.create_payment(Payment.TYPE_OUT, group, self.branch, self.freight_total, due_date=localnow(), description=description) payment.set_pending() return payment def get_items(self, with_children=True): store = self.store query = ReceivingOrderItem.receiving_order == self if not with_children: query = And(query, Eq(ReceivingOrderItem.parent_item_id, None)) return store.find(ReceivingOrderItem, query) def remove_items(self): for item in self.get_items(): item.receiving_order = None def remove_item(self, item): assert item.receiving_order == self type(item).delete(item.id, store=self.store) def is_totally_returned(self): return all(item.is_totally_returned() for item in self.get_items()) # # Properties # @property def payments(self): tables = [PurchaseReceivingMap, PurchaseOrder, Payment] query = And(PurchaseReceivingMap.receiving_id == self.id, PurchaseReceivingMap.purchase_id == PurchaseOrder.id, Payment.group_id == PurchaseOrder.group_id) return self.store.using(tables).find(Payment, query) @property def supplier_name(self): if not self.supplier: return u"" return self.supplier.get_description() # # Accessors # @property def cfop_code(self): return self.cfop.code @property def transporter_name(self): if not self.transporter: return u"" return self.transporter.get_description() @property def branch_name(self): return self.branch.get_description() @property def responsible_name(self): return self.responsible.get_description() @property def products_total(self): total = sum([item.get_total() for item in self.get_items()], currency(0)) return currency(total) @property def receival_date_str(self): return self.receival_date.strftime("%x") @property def total_surcharges(self): """Returns the sum of all surcharges (purchase & receiving)""" total_surcharge = 0 if self.surcharge_value: total_surcharge += self.surcharge_value if self.secure_value: total_surcharge += self.secure_value if self.expense_value: total_surcharge += self.expense_value for purchase in self.purchase_orders: total_surcharge += purchase.surcharge_value if self.ipi_total: total_surcharge += self.ipi_total # CIF freights don't generate payments. if (self.freight_total and self.freight_type not in (self.FREIGHT_CIF_UNKNOWN, self.FREIGHT_CIF_INVOICE)): total_surcharge += self.freight_total return currency(total_surcharge) @property def total_quantity(self): """Returns the sum of all received quantities""" return sum(item.quantity for item in self.get_items(with_children=False)) @property def total_discounts(self): """Returns the sum of all discounts (purchase & receiving)""" total_discount = 0 if self.discount_value: total_discount += self.discount_value for purchase in self.purchase_orders: total_discount += purchase.discount_value return currency(total_discount) @property def total(self): """Fetch the total, including discount and surcharge for both the purchase order and the receiving order. """ total = self.products_total total -= self.total_discounts total += self.total_surcharges return currency(total) def guess_freight_type(self): """Returns a freight_type based on the purchase's freight_type""" purchases = list(self.purchase_orders) assert len(purchases) == 1 purchase = purchases[0] if purchase.freight_type == PurchaseOrder.FREIGHT_FOB: if purchase.is_paid(): freight_type = ReceivingOrder.FREIGHT_FOB_PAYMENT else: freight_type = ReceivingOrder.FREIGHT_FOB_INSTALLMENTS elif purchase.freight_type == PurchaseOrder.FREIGHT_CIF: if purchase.expected_freight: freight_type = ReceivingOrder.FREIGHT_CIF_INVOICE else: freight_type = ReceivingOrder.FREIGHT_CIF_UNKNOWN return freight_type def _get_percentage_value(self, percentage): if not percentage: return currency(0) subtotal = self.products_total percentage = Decimal(percentage) return subtotal * (percentage / 100) @property def discount_percentage(self): discount_value = self.discount_value if not discount_value: return currency(0) subtotal = self.products_total assert subtotal > 0, (u'the subtotal should not be zero ' u'at this point') total = subtotal - discount_value percentage = (1 - total / subtotal) * 100 return quantize(percentage) @discount_percentage.setter def discount_percentage(self, value): """Discount by percentage. Note that percentage must be added as an absolute value not as a factor like 1.05 = 5 % of surcharge The correct form is 'percentage = 3' for a discount of 3 % """ self.discount_value = self._get_percentage_value(value) @property def surcharge_percentage(self): """Surcharge by percentage. Note that surcharge must be added as an absolute value not as a factor like 0.97 = 3 % of discount. The correct form is 'percentage = 3' for a surcharge of 3 % """ surcharge_value = self.surcharge_value if not surcharge_value: return currency(0) subtotal = self.products_total assert subtotal > 0, (u'the subtotal should not be zero ' u'at this point') total = subtotal + surcharge_value percentage = ((total / subtotal) - 1) * 100 return quantize(percentage) @surcharge_percentage.setter def surcharge_percentage(self, value): self.surcharge_value = self._get_percentage_value(value)
class ProductIpiTemplate(BaseIPI): __storm_table__ = 'product_ipi_template' product_tax_template_id = IntCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id')