class ArticleCategory(ModelSQL, ModelView, CMSMenuItemMixin): "Article Categories" __name__ = 'nereid.cms.article.category' _rec_name = 'title' title = fields.Char('Title', size=100, translate=True, required=True, select=True) unique_name = fields.Char('Unique Name', required=True, select=True, help='Unique Name is used as the uri.') active = fields.Boolean('Active', select=True) description = fields.Text('Description', translate=True) template = fields.Char('Template', required=True) articles = fields.Many2Many('nereid.cms.category-article', 'category', 'article', 'Article', context={'published': True}) # Article Category can have a banner banner = fields.Many2One('nereid.cms.banner', 'Banner') sort_order = fields.Selection([ ('sequence', 'Sequence'), ('older_first', 'Older First'), ('recent_first', 'Recent First'), ], 'Sort Order') published_articles = fields.Function( fields.One2Many('nereid.cms.article', 'category', 'Published Articles'), 'get_published_articles') articles_per_page = fields.Integer('Articles per Page', required=True) @staticmethod def default_sort_order(): return 'recent_first' @staticmethod def default_active(): 'Return True' return True @staticmethod def default_template(): return 'article-category.jinja' @classmethod def __setup__(cls): super(ArticleCategory, cls).__setup__() cls._sql_constraints += [ ('unique_name', 'UNIQUE(unique_name)', 'The Unique Name of the Category must be unique.'), ] @fields.depends('title', 'unique_name') def on_change_title(self): if self.title and not self.unique_name: self.unique_name = slugify(self.title) @staticmethod def default_articles_per_page(): return 10 @classmethod @route('/article-category/<uri>/') @route('/article-category/<uri>/<int:page>') def render(cls, uri, page=1): """ Renders the category """ Article = Pool().get('nereid.cms.article') # Find in cache or load from DB try: category, = cls.search([('unique_name', '=', uri)]) except ValueError: abort(404) order = [] if category.sort_order == 'recent_first': order.append(('write_date', 'DESC')) elif category.sort_order == 'older_first': order.append(('write_date', 'ASC')) elif category.sort_order == 'sequence': order.append(('sequence', 'ASC')) articles = Pagination(Article, [('categories', '=', category.id), ('state', '=', 'published')], page, category.articles_per_page, order=order) return render_template(category.template, category=category, articles=articles) @classmethod @context_processor('get_article_category') def get_article_category(cls, uri, silent=True): """Returns the browse record of the article category given by uri """ category = cls.search([('unique_name', '=', uri)], limit=1) if not category and not silent: raise RuntimeError("Article category %s not found" % uri) return category[0] if category else None @classmethod @route('/sitemaps/article-category-index.xml') def sitemap_index(cls): index = SitemapIndex(cls, []) return index.render() @classmethod @route('/sitemaps/article-category-<int:page>.xml') def sitemap(cls, page): sitemap_section = SitemapSection(cls, [], page) sitemap_section.changefreq = 'daily' return sitemap_section.render() def get_absolute_url(self, **kwargs): return url_for('nereid.cms.article.category.render', uri=self.unique_name, **kwargs) def get_published_articles(self, name): """ Get the published articles. """ NereidArticle = Pool().get('nereid.cms.article') articles = NereidArticle.search([('state', '=', 'published'), ('categories', '=', self.id)]) return map(int, articles) def get_children(self, max_depth): """ Return serialized menu_item for current menu_item children """ NereidArticle = Pool().get('nereid.cms.article') articles = NereidArticle.search([('state', '=', 'published'), ('categories', '=', self.id)]) return [ article.get_menu_item(max_depth=max_depth - 1) for article in articles ] def serialize(self, purpose=None): """ Article category serialize method """ if purpose == 'atom': return { 'term': self.unique_name, } elif hasattr(super(ArticleCategory, self), 'serialize'): return super(ArticleCategory, self).serialize(purpose=purpose) @classmethod @route('/article-category/<uri>.atom') def atom_feed(cls, uri): """ Returns atom feed for articles published under a particular category. """ try: category, = cls.search([ ('unique_name', '=', uri), ], limit=1) except ValueError: abort(404) feed = AtomFeed("Articles by Category %s" % category.unique_name, feed_url=request.url, url=request.host_url) for article in category.published_articles: feed.add(**article.serialize(purpose='atom')) return feed.get_response()
class BalancePartyYear(ModelSQL, ModelView): "Turnover and Balances Analytic Account Parties - FiscalYear" _name = "ekd.balances.party.year" _description =__doc__ account = fields.Many2One('ekd.balances.party', 'Analytic Account', required=True, select=2, ondelete="CASCADE") fiscalyear = fields.Many2One('ekd.fiscalyear', 'FiscalYear', required=True) balance = fields.Numeric('Debit Start', digits=(16, Eval('currency_digits', 2)),) debit_01 = fields.Numeric('Debit Turnover 01', digits=(16, Eval('currency_digits', 2)),) credit_01 = fields.Numeric('Credit Turnover 01', digits=(16, Eval('currency_digits', 2)),) debit_02 = fields.Numeric('Debit Turnover 02', digits=(16, Eval('currency_digits', 2)),) credit_02 = fields.Numeric('Credit Turnover 02', digits=(16, Eval('currency_digits', 2)),) debit_03 = fields.Numeric('Debit Turnover 03', digits=(16, Eval('currency_digits', 2)),) credit_03 = fields.Numeric('Credit Turnover 03', digits=(16, Eval('currency_digits', 2)),) debit_04 = fields.Numeric('Debit Turnover 04', digits=(16, Eval('currency_digits', 2)),) credit_04 = fields.Numeric('Credit Turnover 04', digits=(16, Eval('currency_digits', 2)),) debit_05 = fields.Numeric('Debit Turnover 05', digits=(16, Eval('currency_digits', 2)),) credit_05 = fields.Numeric('Credit Turnover 05', digits=(16, Eval('currency_digits', 2)),) debit_06 = fields.Numeric('Debit Turnover 06', digits=(16, Eval('currency_digits', 2)),) credit_06 = fields.Numeric('Credit Turnover 06', digits=(16, Eval('currency_digits', 2)),) debit_07 = fields.Numeric('Debit Turnover 07', digits=(16, Eval('currency_digits', 2)),) credit_07 = fields.Numeric('Credit Turnover 07', digits=(16, Eval('currency_digits', 2)),) debit_08 = fields.Numeric('Debit Turnover 08', digits=(16, Eval('currency_digits', 2)),) credit_08 = fields.Numeric('Credit Turnover 08', digits=(16, Eval('currency_digits', 2)),) debit_09 = fields.Numeric('Debit Turnover 09', digits=(16, Eval('currency_digits', 2)),) credit_09 = fields.Numeric('Credit Turnover 09', digits=(16, Eval('currency_digits', 2)),) debit_10 = fields.Numeric('Debit Turnover 10', digits=(16, Eval('currency_digits', 2)),) credit_10 = fields.Numeric('Credit Turnover 10', digits=(16, Eval('currency_digits', 2)),) debit_11 = fields.Numeric('Debit Turnover 11', digits=(16, Eval('currency_digits', 2)),) credit_11 = fields.Numeric('Credit Turnover 11', digits=(16, Eval('currency_digits', 2)),) debit_12 = fields.Numeric('Debit Turnover 12', digits=(16, Eval('currency_digits', 2)),) credit_12 = fields.Numeric('Credit Turnover 12', digits=(16, Eval('currency_digits', 2)),) dt_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry debit lines'), 'get_entries_field') ct_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry credit lines'), 'get_entries_field') parent = fields.Many2One('ekd.balances.party.year','ID Parent balance') transfer = fields.Many2One('ekd.balances.party.year','ID Transfer balance') state = fields.Selection([ ('draft','Draft'), ('open','Open'), ('done','Closed'), ('deleted','Deleted') ], 'State', required=True) deleted = fields.Boolean('Flag Deleting') active = fields.Boolean('Active') def __init__(self): super(BalancePartyYear, self).__init__() def init(self, module_name): cursor = Transaction().cursor super(BalancePartyYear, self).init(module_name) table = TableHandler(cursor, self, module_name) # Проверяем счетчик cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name) last_value, increment_by = cursor.fetchall()[0] # Устанавливаем счетчик if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES_PERIOD[self._table]): cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES_PERIOD[self._table]) if increment_by != 10: cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10") def default_state(self): return Transaction().context.get('state') or 'draft' def default_active(self): return True def get_currency_digits(self, ids, name): res = {}.fromkeys(ids, 2) for line in self.browse(ids): res.setdefault('currency_digits', {}) res['currency_digits'][line.id] = line.account.currency_digits or 2 return res
class Glasgow(ModelSQL, ModelView): 'Glasgow Coma Scale' __name__ = 'gnuhealth.icu.glasgow' name = fields.Many2One('gnuhealth.inpatient.registration', 'Registration Code', required=True) evaluation_date = fields.DateTime('Date', help="Date / Time", required=True) glasgow = fields.Integer( 'Glasgow', on_change_with=['glasgow_verbal', 'glasgow_motor', 'glasgow_eyes'], help='Level of Consciousness - on Glasgow Coma Scale : < 9 severe -' ' 9-12 Moderate, > 13 minor') glasgow_eyes = fields.Selection([ ('1', '1 : Does not Open Eyes'), ('2', '2 : Opens eyes in response to painful stimuli'), ('3', '3 : Opens eyes in response to voice'), ('4', '4 : Opens eyes spontaneously'), ], 'Eyes', sort=False) glasgow_verbal = fields.Selection([ ('1', '1 : Makes no sounds'), ('2', '2 : Incomprehensible sounds'), ('3', '3 : Utters inappropriate words'), ('4', '4 : Confused, disoriented'), ('5', '5 : Oriented, converses normally'), ], 'Verbal', sort=False) glasgow_motor = fields.Selection([ ('1', '1 : Makes no movement'), ('2', '2 : Extension to painful stimuli - decerebrate response -'), ('3', '3 : Abnormal flexion to painful stimuli \ (decorticate response)'), ('4', '4 : Flexion / Withdrawal to painful stimuli'), ('5', '5 : localizes painful stimuli'), ('6', '6 : Obeys commands'), ], 'Motor', sort=False) @staticmethod def default_glasgow_eyes(): return '4' @staticmethod def default_glasgow_verbal(): return '5' @staticmethod def default_glasgow_motor(): return '6' @staticmethod def default_glasgow(): return 15 # Default evaluation date @staticmethod def default_evaluation_date(): return datetime.now() def on_change_with_glasgow(self): return int(self.glasgow_motor) + int(self.glasgow_eyes) + \ int(self.glasgow_verbal) # Return the Glasgow Score with each component def get_rec_name(self, name): if self.name: res = str(self.glasgow) + ': ' + 'E' + self.glasgow_eyes + ' V' + \ self.glasgow_verbal + ' M' + self.glasgow_motor return res
class Sale: 'Sale' __name__ = 'sale.sale' # Readonly because the wizard should be the one adding payment gateways as # it provides a more cusomizable UX than directly adding a record. # For example, taking CC numbers. payments = fields.One2Many( 'sale.payment', 'sale', 'Payments', readonly=True, ) sorted_payments = fields.Function( fields.One2Many('sale.payment', None, 'Payments'), 'get_sorted_payments' ) # Sale must be able to define when it should authorize and capture the # payments. payment_authorize_on = fields.Selection( 'get_authorize_options', 'Authorize payments', required=True, states=READONLY_STATES, depends=DEPENDS ) payment_capture_on = fields.Selection( 'get_capture_options', 'Capture payments', required=True, states=READONLY_STATES, depends=DEPENDS ) gateway_transactions = fields.Function( fields.One2Many( 'payment_gateway.transaction', None, 'Gateway Transactions', ), "get_gateway_transactions" ) payment_total = fields.Function( fields.Numeric( 'Total Payment', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'], help="Total value of payments" ), 'get_payment', ) payment_collected = fields.Function( fields.Numeric( 'Payment Collected', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'], help="Total value of payments collected" ), 'get_payment', ) payment_available = fields.Function( fields.Numeric( 'Payment Remaining', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'], help="Total value which is neither authorize nor captured" ), 'get_payment', ) payment_authorized = fields.Function( fields.Numeric( 'Payment Authorized', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'], help="Amount authorized to be catured" ), 'get_payment', ) payment_captured = fields.Function( fields.Numeric( 'Payment Captured', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'], help="Amount already captured" ), 'get_payment', ) payment_processing_state = fields.Selection([ (None, 'None'), ('waiting_for_auth', 'Waiting For Authorization'), ('waiting_for_capture', 'Waiting For Capture'), ], "Payment Processing State", select=True, readonly=True) @staticmethod def default_payment_processing_state(): return None @classmethod def __setup__(cls): super(Sale, cls).__setup__() cls._buttons.update({ 'add_payment': { 'invisible': Eval('state').in_(['cancel', 'draft']), }, 'auth_capture': { 'invisible': Eval('state').in_(['cancel', 'draft', 'done']), }, }) cls._error_messages.update({ 'insufficient_amount_to_authorize': "Insufficient amount remaining in payment\n" "Amount to authorize: %s\n" "Amount remaining: %s\n" "Payments: %s", 'insufficient_amount_to_capture': "Insufficient amount remaining in payment\n" "Amount to capture: %s\n" "Amount remaining: %s\n" "Payments: %s", "auth_before_capture": "Payment authorization must happen before capture", "sale_payments_waiting": "Sale Payments are %s", }) @classmethod def validate(cls, sales): super(Sale, cls).validate(sales) for sale in sales: sale.validate_payment_combination() def validate_payment_combination(self): if self.payment_authorize_on == 'sale_process' and \ self.payment_capture_on == 'sale_confirm': self.raise_user_error("auth_before_capture") @classmethod def get_authorize_options(cls): """Return all the options from sale configuration. """ SaleConfiguration = Pool().get('sale.configuration') return SaleConfiguration.get_authorize_options() @classmethod def get_capture_options(cls): """Return all the options from sale configuration. """ SaleConfiguration = Pool().get('sale.configuration') return SaleConfiguration.get_capture_options() @staticmethod def default_payment_authorize_on(): SaleConfiguration = Pool().get('sale.configuration') return SaleConfiguration(1).payment_authorize_on @staticmethod def default_payment_capture_on(): SaleConfiguration = Pool().get('sale.configuration') return SaleConfiguration(1).payment_capture_on @classmethod def get_payment_method_priority(cls): """Priority order for payment methods. Downstream modules can override this method to change the method priority. """ return ('manual', 'credit_card') def get_gateway_transactions(self, name): GatewayTransaction = Pool().get('payment_gateway.transaction') return map( int, GatewayTransaction.search( [('sale_payment', 'in', map(int, self.payments))] ) ) def get_payment(self, name): """Return amount from payments. """ Payment = Pool().get('sale.payment') payments = Payment.search([('sale', '=', self.id)]) if name == 'payment_total': return Decimal(sum([payment.amount for payment in payments])) elif name == 'payment_available': return Decimal( sum([payment.amount_available for payment in payments]) ) elif name == 'payment_captured': return Decimal(sum( [payment.amount_captured for payment in payments] )) elif name == 'payment_authorized': return Decimal(sum( [payment.amount_authorized for payment in payments] )) elif name == 'payment_collected': return self.payment_total - self.payment_available @classmethod @ModelView.button_action('sale_payment_gateway.wizard_add_payment') def add_payment(cls, sales): pass def get_sorted_payments(self, name=None): """ Return the payments in the order they should be consumed """ payment_method_priority = self.get_payment_method_priority() return map(int, sorted( self.payments, key=lambda t: payment_method_priority.index(t.method) )) def _raise_sale_payments_waiting(self): Sale = Pool().get('sale.sale') self.raise_user_error( "sale_payments_waiting", ( dict(Sale.payment_processing_state.selection).get( self.payment_processing_state ), ) ) def authorize_payments(self, amount, description="Payment from sale"): """ Authorize sale payments. It actually creates payment transactions corresponding to sale payments and set the payment processing state to `waiting to auth`. """ if self.payment_processing_state: self._raise_sale_payments_waiting() if amount > self.payment_available: self.raise_user_error( "insufficient_amount_to_authorize", error_args=( amount, self.payment_available, len(self.payments), ) ) transactions = [] for payment in self.sorted_payments: if not amount: break if not payment.amount_available or payment.method == "manual": # * if no amount available, continue to next. # * manual payment need not to be authorized. continue # The amount to authorize is the amount_available if the # amount_available is less than the amount we seek. authorize_amount = min(amount, payment.amount_available) payment_transaction = payment._create_payment_transaction( authorize_amount, description ) payment_transaction.save() amount -= authorize_amount transactions.append(payment_transaction) self.payment_processing_state = "waiting_for_auth" self.save() return transactions def capture_payments(self, amount, description="Payment from sale"): """Capture sale payments. * If existing authorizations exist, capture them * If not, capture available payments directly """ if self.payment_processing_state: self._raise_sale_payments_waiting() if amount > (self.payment_available + self.payment_authorized): self.raise_user_error( "insufficient_amount_to_capture", error_args=( amount, self.payment_available, len(self.payments), ) ) transactions = [] authorized_transactions = filter( lambda transaction: transaction.state == 'authorized', self.gateway_transactions ) for transaction in authorized_transactions: if not amount: break # pragma: no cover capture_amount = min(amount, transaction.amount) # Write the new amount of the transaction as the amount # required to be captured transaction.amount = capture_amount transaction.save() amount -= capture_amount transactions.append(transaction) for payment in self.sorted_payments: if not amount: break if not payment.amount_available: continue # The amount to capture is the amount_available if the # amount_available is less than the amount we seek. authorize_amount = min(amount, payment.amount_available) payment_transaction = payment._create_payment_transaction( authorize_amount, description ) payment_transaction.save() amount -= authorize_amount transactions.append(payment_transaction) self.payment_processing_state = "waiting_for_capture" self.save() return transactions @classmethod def auth_capture(cls, sales): """ A button triggered version of authorizing or capturing payment directly from an order. """ for sale in sales: if sale.state == 'confirmed': sale.handle_payment_on_confirm() elif sale.state == 'processing': sale.handle_payment_on_process() sale.process_pending_payments() def handle_payment_on_confirm(self): if self.payment_capture_on == 'sale_confirm': self.capture_payments( self.total_amount - self.payment_captured ) elif self.payment_authorize_on == 'sale_confirm': self.authorize_payments( self.total_amount - self.payment_authorized ) def handle_payment_on_process(self): if self.payment_capture_on == 'sale_process': self.capture_payments( self.total_amount - self.payment_captured ) elif self.payment_authorize_on == 'sale_process': self.authorize_payments( self.total_amount - self.payment_authorized ) def settle_manual_payments(self): """ Manual payments should be settled when the order is processed. This is separated into a different method so downstream modules can change this behavior to adapt to different workflows """ for payment in self.payments: if payment.amount_available and payment.method == "manual" and \ not payment.payment_transactions: payment_transaction = payment._create_payment_transaction( payment.amount_available, 'Post manual payments on Processing Order', ) payment_transaction.save() payment.capture() @classmethod @ModelView.button @Workflow.transition('confirmed') def confirm(cls, sales): super(Sale, cls).confirm(sales) for sale in sales: sale.handle_payment_on_confirm() @classmethod def process(cls, sales): for sale in sales: if sale.state != 'confirmed': continue sale.handle_payment_on_process() sale.settle_manual_payments() super(Sale, cls).process(sales) def _pay_using_credit_card(self, gateway, credit_card, amount): ''' Complete using the given credit card and finish the transaction. :param gateway: Active record of the payment gateway to process card :param credit_card: A dictionary with either of the following information sets: * owner: name of the owner (unicode) * number: number of the credit card * expiry_month: expiry month (int or string) * expiry_year: year as string * cvv: the cvv number In future this method will accept track1 and track2 as valid information. :param amount: Decimal amount to charge the card for ''' TransactionUseCardWizard = Pool().get( 'payment_gateway.transaction.use_card', type='wizard' ) PaymentTransaction = Pool().get('payment_gateway.transaction') # Manual card based operation payment_transaction = PaymentTransaction( party=self.party, address=self.invoice_address, amount=amount, currency=self.currency, gateway=gateway, sale=self, ) payment_transaction.save() use_card_wiz = TransactionUseCardWizard( TransactionUseCardWizard.create()[0] # Wizard session ) use_card_wiz.card_info.owner = credit_card['owner'] use_card_wiz.card_info.number = credit_card['number'] use_card_wiz.card_info.expiry_month = credit_card['expiry_month'] use_card_wiz.card_info.expiry_year = credit_card['expiry_year'] use_card_wiz.card_info.csc = credit_card['cvv'] with Transaction().set_context(active_id=payment_transaction.id): use_card_wiz.transition_capture() def _pay_using_profile(self, payment_profile, amount): ''' Complete the Checkout using a payment_profile. Only available to the registered users of the website. :param payment_profile: Active record of payment profile :param amount: Decimal amount to charge the card for ''' PaymentTransaction = Pool().get('payment_gateway.transaction') if payment_profile.party != self.party: self.raise_user_error( "Payment profile'd owner is %s, but the customer is %s" % ( payment_profile.party.name, self.party.name, ) ) payment_transaction = PaymentTransaction( party=self.party, address=self.invoice_address, payment_profile=payment_profile, amount=amount, currency=self.currency, gateway=payment_profile.gateway, sale=self, ) payment_transaction.save() PaymentTransaction.capture([payment_transaction]) def process_pending_payments(self): """Process waiting payments for corresponding sale. """ PaymentTransaction = Pool().get('payment_gateway.transaction') if self.payment_processing_state == "waiting_for_auth": for payment in self.sorted_payments: payment.authorize() self.payment_processing_state = None elif self.payment_processing_state == "waiting_for_capture": # Transactions waiting for capture. txns = PaymentTransaction.search([ ('sale_payment.sale', '=', self.id), ]) # Settle authorized transactions PaymentTransaction.settle(filter( lambda txn: txn.state == 'authorized', txns )) # Capture other transactions PaymentTransaction.capture(filter( lambda txn: txn.state == "draft", txns )) self.payment_processing_state = None else: # Weird! Why was I called if there is nothing to do return self.save() @classmethod def process_all_pending_payments(cls): """Cron method authorizes waiting payments. """ User = Pool().get('res.user') user = User(Transaction().user) if not (Transaction().context.get('company') or user.company): # Processing payments without user's company and company in # context is not possible at all. Skip the execution. return sales = cls.search([ ('payment_processing_state', '!=', None) ]) for sale in sales: sale.process_pending_payments() @classmethod def copy(cls, records, default=None): """ Duplicating records """ if default is None: default = {} default['payment_processing_state'] = None default['payments'] = [] return super(Sale, cls).copy(records, default)
class BalanceAnalyticParty(ModelSQL, ModelView): "Turnover and Balances Analytic Account" _name = "ekd.balances.party" _description =__doc__ _rec_name = 'name_model' company = fields.Function(fields.Many2One('company.company', 'Company'), 'get_company') account = fields.Many2One('ekd.account', 'Account', required=True, select=2, order_field="account.code", domain=[ ('company','=',Eval('company')) ], depends=['company']) type_balance = fields.Function(fields.Char('Type Balance'), 'get_account_type') level = fields.Selection(_LEVEL_ANALYTIC, 'Level analityc', required=True) model_ref = fields.Reference('Analytic', selection='model_ref_get', select=2) name_model = fields.Function(fields.Char('Type', ), 'name_model_get') name_ref = fields.Function(fields.Char('Analytic Account', ), 'name_model_get') parent = fields.Many2One('ekd.balances.party', 'Parent Analytic') amount_periods = fields.One2Many('ekd.balances.party.period', 'account', 'Balances and Turnover (Full)') amount_years = fields.One2Many('ekd.balances.party.year', 'account', 'Balances and Turnover (Full)') childs = fields.One2Many('ekd.balances.party', 'parent', 'Children') balance = fields.Function(fields.Numeric('Start Balance', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') balance_dt = fields.Function(fields.Numeric('Debit Start', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') balance_ct = fields.Function(fields.Numeric('Credit Start', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') debit = fields.Function(fields.Numeric('Debit Turnover', digits=(16, Eval('currency_digits', 2)),), 'get_balance_period') credit = fields.Function(fields.Numeric('Credit Turnover', digits=(16, Eval('currency_digits', 2)),), 'get_balance_period') balance_end = fields.Function(fields.Numeric('End Balance', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') balance_dt_end = fields.Function(fields.Numeric('Debit End', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') balance_ct_end = fields.Function(fields.Numeric('Credit End', digits=(16, Eval('currency_digits', 2))), 'get_balance_period') turnover_debit = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Entries'), 'get_entry') turnover_credit = fields.Function(fields.One2Many('ekd.account.move.line', None,'Entries'), 'get_entry') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits') state = fields.Selection([ ('draft','Draft'), ('open','Open'), ('done','Closed'), ('deleted','Deleted') ], 'State', required=True) deleted = fields.Boolean('Flag Deleting') active = fields.Boolean('Active') def __init__(self): super(BalanceAnalyticParty, self).__init__() self._order.insert(0, ('account', 'ASC')) self._order.insert(1, ('level', 'ASC')) self._order.insert(2, ('model_ref', 'ASC')) self._sql_constraints += [ ('balance_account_uniq', 'UNIQUE(account,level,model_ref, parent)',\ 'account, level, model_ref, parent - must be unique per balance!'), ] def init(self, module_name): cursor = Transaction().cursor super(BalanceAnalyticParty, self).init(module_name) table = TableHandler(cursor, self, module_name) # Проверяем счетчик cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name) last_value, increment_by = cursor.fetchall()[0] # Устанавливаем счетчик if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES[self._table]): cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES[self._table]) if increment_by != 10: cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10") def default_state(self): return Transaction().context.get('state') or 'draft' def default_currency(self): return Transaction().context.get('currency') def default_active(self): return True def model_ref_get(self): dictions_obj = self.pool.get('ir.dictions') res = [] diction_ids = dictions_obj.search([ ('model', '=', 'ekd.account.level_analytic'), ('pole', '=', 'type_analytic'), ]) for diction in dictions_obj.browse(diction_ids): res.append([diction.key, diction.value]) return res def get_company(self, ids, name): res={} context = Transaction().context res = {}.fromkeys(ids, context.get('company')) return res def get_account_type(self, ids, name): if name not in ('account_type', 'account_kind', 'account_kind_analytic'): raise Exception('Invalid name Balances Finance') res = {} for line in self.browse(ids): if line.account: if name == 'account_type': res[line.id] = line.account.type.name elif name == 'account_kind': res[line.id] = line.account.kind elif name == 'account_kind_analytic': res[line.id] = line.account.kind_analytic else: res[line.id] = line.account.type_balance return res def get_entry(self, ids, names): move_line = self.pool.get('ekd.account.move.line') move_line_analytic_dt = self.pool.get('ekd.account.move.line.analytic_dt') move_line_analytic_ct = self.pool.get('ekd.account.move.line.analytic_ct') res = {} for balance in self.browse(ids): for name in names: res.setdefault(name, {}) if name == 'turnover_debit': line_analytic_dt = move_line_analytic_dt.search([('ref_analytic','=', balance.id)]) res[name][balance.id] = [ x.get('move_line') for x in move_line_analytic_dt.read(line_analytic_dt, ['move_line']) ] elif name == 'turnover_credit': line_analytic_ct = move_line_analytic_ct.search([('ref_analytic','=', balance.id)]) res[name][balance.id] = [ x.get('move_line') for x in move_line_analytic_ct.read(line_analytic_ct, ['move_line']) ] return res def name_model_get(self, ids, names): group_model={} group_name_model={} res={} res['name_model']={}.fromkeys(ids, False) res['name_ref']={}.fromkeys(ids, False) tmp_res={} for line in self.browse(ids): res['name_model'][line.id] = line.model_ref if line.model_ref not in tmp_res.keys(): tmp_res[line.model_ref] = [line.id,] else: tmp_res[line.model_ref].append(line.id) ref_model, ref_id = line.model_ref.split(',',1) if ref_id == '0': continue if ref_model not in group_model.keys(): group_model[ref_model] = [int(ref_id),] else: group_model[ref_model].append(int(ref_id)) ir_model_obj = self.pool.get('ir.model') search_model_ids = ir_model_obj.search([('model','in',group_model.keys())]) for ir_model_id in ir_model_obj.browse(search_model_ids): group_name_model[ir_model_id.model] = ir_model_id.rec_name for model in group_model.keys(): model_obj = self.pool.get(model) for model_line in model_obj.browse(group_model[model]): rec_id = tmp_res['%s,%s'%(model,str(model_line.id))] if isinstance(rec_id, (int,long)): res['name_model'][rec_id] = group_name_model[model] res['name_ref'][rec_id] = model_line.rec_name else: for rec_id_ in rec_id: res['name_model'][rec_id_] = group_name_model[model] res['name_ref'][rec_id_ ] = model_line.rec_name return res def get_account_type(self, ids, name): if name not in ('account_type', 'account_kind', 'type_balance'): raise Exception('Invalid name') res = {} for line in self.browse(ids): if line.account: if name == 'account_type': res[line.id] = line.account.type.name elif name == 'account_kind': res[line.id] = line.account.kind_analytic else: res[line.id] = line.account.type_balance return res def get_currency_digits(self, ids, name): res = {}.fromkeys(ids, 2) for line in self.browse(ids): if line.account.currency: res[line.id] = line.account.currency.digits or 2 elif line.account.company: res[line.id] = line.account.company.currency.digits or 2 return res def get_balance_period(self, ids, names): if not ids: return {} res={} fiscalyear_obj = self.pool.get('ekd.fiscalyear') type_balance = self.browse(ids[0]).account.type_balance period_obj = self.pool.get('ekd.period') context = Transaction().context if context.get('current_period'): period_id = period_obj.browse(context.get('current_period')) current_period = context.get('current_period') elif context.get('current_date'): current_period = period_obj.search([ ('company','=',context.get('company')), ('start_date','<=',context.get('current_date')), ('end_date','>=',context.get('current_date')), ], limit=1) cr = Transaction().cursor cr.execute('SELECT id, account, balance_dt, balance_ct, '\ 'debit, credit, '\ ' balance_dt-balance_ct+debit-credit as balance_end, '\ ' balance_dt+debit as balance_dt_end, '\ ' balance_ct+credit as balance_ct_end '\ 'FROM ekd_balances_party_period '\ 'WHERE period=%s AND account in ('%(current_period)+','.join(map(str,ids))+')') for amount_id, account, balance_dt, balance_ct,\ debit, credit, balance_end,\ balance_dt_end, balance_ct_end in cr.fetchall(): # SQLite uses float for SUM if not isinstance(balance_dt, Decimal): balance_dt = Decimal(str(balance_dt)) if not isinstance(balance_ct, Decimal): balance_ct = Decimal(str(balance_ct)) if not isinstance(balance_dt_end, Decimal): balance_dt = Decimal(str(balance_dt_end)) if not isinstance(balance_ct_end, Decimal): balance_ct = Decimal(str(balance_ct_end)) if not isinstance(debit, Decimal): debit = Decimal(str(debit)) if not isinstance(credit, Decimal): credit = Decimal(str(credit)) for name in names: res.setdefault(name, {}) res[name].setdefault(account, Decimal('0.0')) amount_balance= Decimal('0.0') if name == 'balance_dt_end': if type_balance == 'active': res[name][account] = balance_end #elif type_balance == 'passive': # res[name][account] = balance_end elif type_balance == 'both': if balance_end > 0: res[name][account] = balance_end if name == 'balance_ct_end': if type_balance == 'passive': res[name][account] = -balance_end elif type_balance == 'both': if balance_end < 0: res[name][account] = -balance_end elif name == 'balance_end': res[name][account] = balance_end elif name == 'balance': res[name][account] = balance_dt-balance_ct elif name == 'debit': res[name][account] = debit elif name == 'credit': res[name][account] = credit return res # This Function Test Only!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def get_balance_year(self, ids, names): if not ids: return {} res={} fiscalyear_obj = self.pool.get('ekd.fiscalyear') period_obj = self.pool.get('ekd.period') context = Transaction().context if context.get('current_period'): period_id = period_object.browse(context.get('current_period')) start_month = period_id.start_date.strftime('%m') end_month = period_id.end_date.strftime('%m') fiscalyear = period_id.fiscalyear.id if start_month == end_month: current_period = context.get('current_period').strftime('%m') else: begin_period = False for month in _MOUNTH: if start_month == month: current_period.append(month) begin_period = True elif begin_period: current_period.append(month) elif end_month == month: current_period.append(month) break else: if context.get('current_fiscalyear'): fiscalyear = context.get('current_fiscalyear') else: fiscalyear = fiscalyear_obj.search([ ('company','=',context.get('company')), ('state','=','current') ], limit=1) current_period = datetime.datetime.now().strftime('%m') if isinstance(current_period, list): field_debit = [] field_credit = [] for month in current_period: field_debit.append("debit_%s"%(month)) field_credit.append("credit_%s"%(month)) field_debits = [] field_credits = [] for month in _MOUNTH: if month == start_month: break field_debits.append("debit_%s"%(month)) field_credits.append("credit_%s"%(month)) else: field_debit = "debit_%s"%(current_period) field_credit = "credit_%s"%(current_period) field_debits = [] field_credits = [] for month in _MOUNTH: if month == current_period: break field_debits.append("debit_%s"%(month)) field_credits.append("credit_%s"%(month)) cr = Transaction().cursor if isinstance(current_period, list): cr.execute('SELECT id, balance_dt+'+'+'.join(field_debits)+','\ 'balance_ct+'+'+'.join(field_credits)+','\ '+'.join(field_debit)+' as debit,'\ '+'.join(field_credit)+' as credit'\ 'FROM ekd_balances_party_period'\ 'WHERE fiscalyear=%s AND account in ('+','.join(map(str,ids))+')'%(field_debit,field_credit,fiscalyear)) else: cr.execute('SELECT id, balance_dt+'+'+'.join(field_debits)+','\ 'balance_ct+'+'+'.join(field_credits)+','\ '%s, %s'\ 'FROM ekd_balances_party_period'\ 'WHERE fiscalyear=%s AND account in ('+','.join(map(str,ids))+')'%(field_debit,field_credit,fiscalyear)) for id, balance_dt, balance_ct, debit, credit in cr.fetchall(): # SQLite uses float for SUM if not isinstance(balance_dt, Decimal): balance_dt = Decimal(str(balance_dt)) if not isinstance(balance_ct, Decimal): balance_ct = Decimal(str(balance_ct)) if not isinstance(debit, Decimal): debit = Decimal(str(debit)) if not isinstance(credit, Decimal): credit = Decimal(str(credit)) for name in names: res.setdefault(name, {}) res[name].setdefault(balance.id, Decimal('0.0')) amount_balance= Decimal('0.0') if not balance.amount: continue if name == 'balance_dt_end': res[name][balance.id] = balance_end elif name == 'balance_ct_end': res[name][balance.id] = balance_end elif name == 'balance_end': res[name][balance.id] = balance_end elif name == 'balance': res[name][balance.id] = balance_dt-balance_ct elif name == 'debit': res[name][balance.id] = debit elif name == 'credit': res[name][balance.id] = credit return res def button_done(self, ids): return self.close(ids) def button_draft(self, ids): return self.cancel(ids) def button_restore(self, ids): return self.restore(ids) def close(self, ids): return self.write(ids, {'state': 'done', }) def draft(self, ids): return self.write(ids, {'state': 'draft', }) def restore(self, ids): return self.write(ids, {'state': 'draft', })
class SaleMeeting(ModelView, ModelSQL): 'Sale Opportunity Meeting' __name__ = 'sale.meeting' _order_name = 'hora' opportunity = fields.Many2One( 'sale.opportunity', 'Opportunity', ) fecha = fields.Date('Fecha', required=True) hora = fields.Time('Hora', required=True) descripcion = fields.Text('Descripcion', required=True) asesor = fields.Many2One('company.employee', 'Asesor', domain=[('company', '=', Eval('company'))], required=True, readonly=True) medio_contacto = fields.Selection( [ ('Llamar', 'Llamar'), ('Correo', 'Enviar correo'), ('Whatsapp', 'Whatsapp'), ('SMS', 'SMS'), ('Visita', 'Visita'), ('Otro', 'Otro'), ], 'Medio', sort=False, required=True, ) hora_ = fields.Function(fields.Char('Hora'), 'get_hora') party = fields.Function(fields.Many2One('party.party', 'Cliente'), 'get_party') user = fields.Many2One('res.user', 'Usuario', readonly=True) def get_hora(self, name): hora = self.hora hour = self.hora.hour minute = self.hora.minute hora_ = 'Hora ' + str(hour) + ':' + str(minute) return hora_ def get_party(self, name): if self.opportunity: return self.opportunity.party.id @classmethod def default_user(cls): pool = Pool() User = pool.get('res.user') cursor = Transaction().connection.cursor() user = User(Transaction().user).id return user @classmethod def default_date(cls): pool = Pool() Date = pool.get('ir.date') return Date.today() @classmethod def default_asesor(cls): User = Pool().get('res.user') employee_id = None if Transaction().context.get('employee'): employee_id = Transaction().context['employee'] else: user = User(Transaction().user) if user.employee: employee_id = user.employee.id if employee_id: return employee_id
class WebUser: __name__ = 'web.user' _rec_name = 'email' nickname = fields.Char('Nickname', help='The name shown to other users') user = fields.One2One( 'web.user-res.user', 'web_user', 'res_user', 'Tryton User', readonly=True, states={'required': Greater(Eval('active_id', -1), 0)}, help='The Tryton user of the web user') party = fields.One2One( 'web.user-party.party', 'user', 'party', 'Party', states={'required': Greater(Eval('active_id', -1), 0)}, help='The party of the web user') pocket_account = fields.Many2One('account.account', 'Account') clients = fields.One2Many('client', 'web_user', 'Clients') roles = fields.Many2Many('web.user-web.user.role', 'user', 'role', 'Roles') default_role = fields.Selection('get_roles', 'Default Role') show_creative_info = fields.Boolean('Show Creative Info', help='Check, if the infobox is shown') picture_data = fields.Binary('Picture Data', help='Picture Data') picture_data_mime_type = fields.Char('Picture Data Mime Type', help='The mime type of picture data.') opt_in_state = fields.Selection( _OPT_IN_STATES, 'Opt-in State', help='The authentication state of the opt-in method:\n\n' 'New: The web-user is newly created.\n' 'Mail Sent: An opt-in link is sent to the email.\n' 'Opted-In: The link is clicked.\n' 'Opted-Out: The web-user opted-out.') opt_in_uuid = fields.Char( 'Opt-in UUID', help='The universally unique identifier of the opt-in of a web user') opt_in_timestamp = fields.DateTime('Date of Opt-in') opt_out_timestamp = fields.DateTime('Date of Opt-out') @classmethod def __setup__(cls): super(WebUser, cls).__setup__() cls.__rpc__.update({'authenticate': RPC(check_access=False)}) cls._sql_constraints += [ ('opt_in_uuid_uniq', 'UNIQUE(opt_in_uuid)', 'The opt-in UUID of the Webuser must be unique.'), ] @staticmethod def default_show_creative_info(): return True @staticmethod def default_opt_in_state(): return 'new' @staticmethod def default_opt_in_uuid(): return str(uuid.uuid4()) @staticmethod def get_roles(): Role = Pool().get('web.user.role') roles = Role.search([]) return [(x.code, x.name) for x in roles] + [(None, '')] @classmethod def create(cls, vlist): pool = Pool() User = pool.get('res.user') Party = pool.get('party.party') vlist = [x.copy() for x in vlist] for values in vlist: email = values.get('email') user_email = email + ':::' + ''.join( random.sample(string.lowercase, 10)) if not values.get('party'): values['party'] = Party.create([{'name': email}])[0].id if not values.get('user'): values['user'] = User.create([{ 'name': user_email, 'login': user_email, 'email': email, 'active': False, }])[0].id return super(WebUser, cls).create(vlist) @classmethod def get_balance(cls, items, names): ''' Function to compute hat balance for artist or pocket balance party items. ''' res = {} pool = Pool() MoveLine = pool.get('account.move.line') Account = pool.get('account.account') User = pool.get('res.user') cursor = Transaction().cursor line = MoveLine.__table__() account = Account.__table__() for name in names: if name not in ('hat_balance', 'pocket_balance'): raise Exception('Bad argument') res[name] = dict((i.id, Decimal('0.0')) for i in items) user_id = Transaction().user if user_id == 0 and 'user' in Transaction().context: user_id = Transaction().context['user'] user = User(user_id) if not user.company: return res company_id = user.company.id with Transaction().set_context(posted=False): line_query, _ = MoveLine.query_get(line) clause = () if name == 'hat_balance': field = line.artist clause = line.artist.in_([i.id for i in items]) if name == 'pocket_balance': field = line.party clause = line.party.in_([i.id for i in items]) for name in names: query = line.join( account, condition=account.id == line.account).select( field, Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)), where=account.active & (account.kind == name[:-8]) & clause & (line.reconciliation == None) & (account.company == company_id) & line_query, group_by=field) cursor.execute(*query) for id_, sum in cursor.fetchall(): # SQLite uses float for SUM if not isinstance(sum, Decimal): sum = Decimal(str(sum)) res[name][id_] = -sum return res @classmethod def search_balance(cls, name, clause): pool = Pool() MoveLine = pool.get('account.move.line') Account = pool.get('account.account') Company = pool.get('company.company') User = pool.get('res.user') line = MoveLine.__table__() account = Account.__table__() if name not in ('hat_balance', 'pocket_balance'): raise Exception('Bad argument') company_id = None user_id = Transaction().user if user_id == 0 and 'user' in Transaction().context: user_id = Transaction().context['user'] user = User(user_id) if Transaction().context.get('company'): child_companies = Company.search([ ('parent', 'child_of', [user.main_company.id]), ]) if Transaction().context['company'] in child_companies: company_id = Transaction().context['company'] if not company_id: if user.company: company_id = user.company.id elif user.main_company: company_id = user.main_company.id if not company_id: return [] line_query, _ = MoveLine.query_get(line) Operator = fields.SQL_OPERATORS[clause[1]] if name == 'hat_balance': field = line.artist where_clause = (line.artist != None) sign = -1 if name == 'pocket_balance': field = line.party where_clause = (line.party != None) sign = 1 query = line.join(account, condition=account.id == line.account).select( field, where=account.active & (account.kind == 'hat') & where_clause & (line.reconciliation == None) & (account.company == company_id) & line_query, group_by=field, having=Operator( Mul( sign, Sum( Coalesce(line.debit, 0) - Coalesce(line.credit, 0))), Decimal(clause[2] or 0))) return [('id', 'in', query)]
class Line(sequence_ordered(), ModelSQL, ModelView): "Subscription Line" __name__ = 'sale.subscription.line' subscription = fields.Many2One('sale.subscription', "Subscription", required=True, select=True, ondelete='CASCADE', states={ 'readonly': ((Eval('subscription_state') != 'draft') & Bool(Eval('subscription'))), }, depends=['subscription_state'], help="Add the line below the subscription.") subscription_state = fields.Function( fields.Selection(STATES, "Subscription State"), 'on_change_with_subscription_state') subscription_start_date = fields.Function( fields.Date("Subscription Start Date"), 'on_change_with_subscription_start_date') subscription_end_date = fields.Function( fields.Date("Subscription End Date"), 'on_change_with_subscription_end_date') service = fields.Many2One('sale.subscription.service', "Service", required=True, states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) description = fields.Text("Description", states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) quantity = fields.Float("Quantity", digits=(16, Eval('unit_digits', 2)), states={ 'readonly': Eval('subscription_state') != 'draft', 'required': Bool(Eval('consumption_recurrence')), }, depends=[ 'unit_digits', 'subscription_state', 'consumption_recurrence' ]) unit = fields.Many2One( 'product.uom', "Unit", required=True, states={ 'readonly': Eval('subscription_state') != 'draft', }, domain=[ If(Bool(Eval('service_unit_category')), ('category', '=', Eval('service_unit_category')), ('category', '!=', -1)), ], depends=['subscription_state', 'service_unit_category']) unit_digits = fields.Function(fields.Integer("Unit Digits"), 'on_change_with_unit_digits') service_unit_category = fields.Function( fields.Many2One('product.uom.category', "Service Unit Category"), 'on_change_with_service_unit_category') unit_price = fields.Numeric("Unit Price", digits=price_digits, states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) consumption_recurrence = fields.Many2One( 'sale.subscription.recurrence.rule.set', "Consumption Recurrence", states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) consumption_delay = fields.TimeDelta( "Consumption Delay", states={ 'readonly': Eval('subscription_state') != 'draft', 'invisible': ~Eval('consumption_recurrence'), }, depends=['subscription_state', 'consumption_recurrence']) next_consumption_date = fields.Date("Next Consumption Date", readonly=True) next_consumption_date_delayed = fields.Function( fields.Date("Next Consumption Delayed"), 'get_next_consumption_date_delayed') consumed = fields.Boolean("Consumed") start_date = fields.Date( "Start Date", required=True, domain=[ ('start_date', '>=', Eval('subscription_start_date')), ], states={ 'readonly': ((Eval('subscription_state') != 'draft') | Eval('consumed')), }, depends=['subscription_start_date', 'subscription_state', 'consumed']) end_date = fields.Date( "End Date", domain=[ 'OR', [ ('end_date', '>=', Eval('start_date')), If(Bool(Eval('subscription_end_date')), ('end_date', '<=', Eval('subscription_end_date')), ()), If(Bool(Eval('next_consumption_date')), ('end_date', '>=', Eval('next_consumption_date')), ()), ], ('end_date', '=', None), ], states={ 'readonly': ((Eval('subscription_state') != 'draft') | (~Eval('next_consumption_date') & Eval('consumed'))), }, depends=[ 'subscription_end_date', 'start_date', 'next_consumption_date', 'subscription_state', 'consumed' ]) @classmethod def __register__(cls, module): pool = Pool() Subscription = pool.get('sale.subscription') TableHandler = backend.get('TableHandler') transaction = Transaction() cursor = transaction.connection.cursor() table = cls.__table__() subscription = Subscription.__table__() # Migration from 4.8: start_date required if TableHandler.table_exist(cls._table): table_h = cls.__table_handler__(module) if table_h.column_exist('start_date'): cursor.execute( *table.update([table.start_date], subscription.select(subscription.start_date, where=subscription.id == table.subscription), where=table.start_date == Null)) super(Line, cls).__register__(module) table_h = cls.__table_handler__(module) # Migration from 4.8: drop required on description table_h.not_null_action('description', action='remove') @fields.depends('subscription', '_parent_subscription.state') def on_change_with_subscription_state(self, name=None): if self.subscription: return self.subscription.state @fields.depends('subscription', '_parent_subscription.start_date') def on_change_with_subscription_start_date(self, name=None): if self.subscription: return self.subscription.start_date @fields.depends('subscription', '_parent_subscription.end_date') def on_change_with_subscription_end_date(self, name=None): if self.subscription: return self.subscription.end_date @fields.depends('subscription', 'start_date', 'end_date', '_parent_subscription.start_date', '_parent_subscription.end_date') def on_change_subscription(self): if self.subscription: if not self.start_date: self.start_date = self.subscription.start_date if not self.end_date: self.end_date = self.subscription.end_date @classmethod def default_quantity(cls): return 1 @fields.depends('unit') def on_change_with_unit_digits(self, name=None): if self.unit: return self.unit.digits return 2 @fields.depends('service') def on_change_with_service_unit_category(self, name=None): if self.service: return self.service.product.default_uom_category.id @fields.depends('service', 'quantity', 'unit', 'subscription', '_parent_subscription.party', methods=['_get_context_sale_price']) def on_change_service(self): pool = Pool() Product = pool.get('product.product') if not self.service: self.consumption_recurrence = None self.consumption_delay = None return party = None party_context = {} if self.subscription and self.subscription.party: party = self.subscription.party if party.lang: party_context['language'] = party.lang.code product = self.service.product category = product.sale_uom.category if not self.unit or self.unit.category != category: self.unit = product.sale_uom self.unit_digits = product.sale_uom.digits with Transaction().set_context(self._get_context_sale_price()): self.unit_price = Product.get_sale_price([product], self.quantity or 0)[product.id] if self.unit_price: self.unit_price = self.unit_price.quantize( Decimal(1) / 10**self.__class__.unit_price.digits[1]) self.consumption_recurrence = self.service.consumption_recurrence self.consumption_delay = self.service.consumption_delay @fields.depends('subscription', '_parent_subscription.currency', '_parent_subscription.party', 'start_date', 'unit', 'service') def _get_context_sale_price(self): context = {} if self.subscription: if self.subscription.currency: context['currency'] = self.subscription.currency.id if self.subscription.party: context['customer'] = self.subscription.party.id if self.start_date: context['sale_date'] = self.start_date if self.unit: context['uom'] = self.unit.id elif self.service: context['uom'] = self.service.sale_uom.id # TODO tax return context def get_next_consumption_date_delayed(self, name=None): if self.next_consumption_date and self.consumption_delay: return self.next_consumption_date + self.consumption_delay return self.next_consumption_date @classmethod def default_consumed(cls): return False def get_rec_name(self, name): return '%s @ %s' % (self.service.rec_name, self.subscription.rec_name) @classmethod def search_rec_name(cls, name, clause): return [ 'OR', ('subscription.rec_name', ) + tuple(clause[1:]), ('service.rec_name', ) + tuple(clause[1:]), ] @classmethod def domain_next_consumption_date_delayed(cls, domain, tables): field = cls.next_consumption_date_delayed._field table, _ = tables[None] name, operator, value = domain Operator = fields.SQL_OPERATORS[operator] column = (table.next_consumption_date + Coalesce(table.consumption_delay, datetime.timedelta())) expression = Operator(column, field._domain_value(operator, value)) if isinstance(expression, operators.In) and not expression.right: expression = Literal(False) elif isinstance(expression, operators.NotIn) and not expression.right: expression = Literal(True) expression = field._domain_add_null(column, operator, value, expression) return expression @classmethod def generate_consumption(cls, date=None): pool = Pool() Date = pool.get('ir.date') Consumption = pool.get('sale.subscription.line.consumption') Subscription = pool.get('sale.subscription') if date is None: date = Date.today() remainings = all_lines = cls.search([ ('consumption_recurrence', '!=', None), ('next_consumption_date_delayed', '<=', date), ('subscription.state', '=', 'running'), ]) consumptions = [] subscription_ids = set() while remainings: lines, remainings = remainings, [] for line in lines: consumptions.append( line.get_consumption(line.next_consumption_date)) line.next_consumption_date = ( line.compute_next_consumption_date()) line.consumed = True if line.next_consumption_date is None: subscription_ids.add(line.subscription.id) elif line.get_next_consumption_date_delayed() <= date: remainings.append(line) Consumption.save(consumptions) cls.save(all_lines) Subscription.process(Subscription.browse(list(subscription_ids))) def get_consumption(self, date): pool = Pool() Consumption = pool.get('sale.subscription.line.consumption') return Consumption(line=self, quantity=self.quantity, date=date) def compute_next_consumption_date(self): if not self.consumption_recurrence: return None date = self.next_consumption_date or self.start_date rruleset = self.consumption_recurrence.rruleset(self.start_date) dt = datetime.datetime.combine(date, datetime.time()) inc = (self.start_date == date) and not self.next_consumption_date next_date = rruleset.after(dt, inc=inc).date() for end_date in [self.end_date, self.subscription.end_date]: if end_date: if next_date > end_date: return None return next_date @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('next_consumption_date', None) default.setdefault('consumed', None) return super(Line, cls).copy(lines, default=default)
class Inventory(Workflow, ModelSQL, ModelView): 'Stock Inventory' __name__ = 'stock.inventory' _rec_name = 'number' number = fields.Char('Number', readonly=True) location = fields.Many2One('stock.location', 'Location', required=True, domain=[('type', '=', 'storage')], states={ 'readonly': (Eval('state') != 'draft') | Eval('lines', [0]), }, depends=['state']) date = fields.Date('Date', required=True, states={ 'readonly': (Eval('state') != 'draft') | Eval('lines', [0]), }, depends=['state']) lost_found = fields.Many2One('stock.location', 'Lost and Found', required=True, domain=[('type', '=', 'lost_found')], states=STATES, depends=DEPENDS) lines = fields.One2Many('stock.inventory.line', 'inventory', 'Lines', states={ 'readonly': (STATES['readonly'] | ~Eval('location') | ~Eval('date')), }, depends=['location', 'date'] + DEPENDS) empty_quantity = fields.Selection( [ (None, ""), ('keep', "Keep"), ('empty', "Empty"), ], "Empty Quantity", states=STATES, depends=DEPENDS, help="How lines without quantity are handled.") company = fields.Many2One('company.company', 'Company', required=True, states={ 'readonly': (Eval('state') != 'draft') | Eval('lines', [0]), }, depends=['state']) state = fields.Selection(INVENTORY_STATES, 'State', readonly=True, select=True) @classmethod def __setup__(cls): super(Inventory, cls).__setup__() cls._order.insert(0, ('date', 'DESC')) cls._error_messages.update({ 'delete_cancel': ('Inventory "%s" must be canceled before ' 'deletion.'), 'unique_line': ('Line "%s" is not unique ' 'on Inventory "%s".'), }) cls._transitions |= set(( ('draft', 'done'), ('draft', 'cancel'), )) cls._buttons.update({ 'confirm': { 'invisible': Eval('state').in_(['done', 'cancel']), 'depends': ['state'], }, 'cancel': { 'invisible': Eval('state').in_(['cancel', 'done']), 'depends': ['state'], }, 'complete_lines': { 'readonly': Eval('state') != 'draft', 'depends': ['state'], }, 'count': { 'readonly': Eval('state') != 'draft', 'depends': ['state'], }, }) @classmethod def __register__(cls, module_name): super(Inventory, cls).__register__(module_name) table = cls.__table_handler__(module_name) # Add index on create_date table.index_action('create_date', action='add') @staticmethod def default_state(): return 'draft' @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() @staticmethod def default_company(): return Transaction().context.get('company') @classmethod def default_lost_found(cls): Location = Pool().get('stock.location') locations = Location.search(cls.lost_found.domain) if len(locations) == 1: return locations[0].id @classmethod def delete(cls, inventories): # Cancel before delete cls.cancel(inventories) for inventory in inventories: if inventory.state != 'cancel': cls.raise_user_error('delete_cancel', inventory.rec_name) super(Inventory, cls).delete(inventories) @classmethod @ModelView.button @Workflow.transition('done') def confirm(cls, inventories): Move = Pool().get('stock.move') moves = [] for inventory in inventories: keys = set() for line in inventory.lines: key = line.unique_key if key in keys: cls.raise_user_error('unique_line', (line.rec_name, inventory.rec_name)) keys.add(key) move = line.get_move() if move: moves.append(move) if moves: Move.save(moves) Move.do(moves) @classmethod @ModelView.button @Workflow.transition('cancel') def cancel(cls, inventories): Line = Pool().get("stock.inventory.line") Line.cancel_move([l for i in inventories for l in i.lines]) @classmethod def create(cls, vlist): pool = Pool() Sequence = pool.get('ir.sequence') Configuration = pool.get('stock.configuration') config = Configuration(1) vlist = [x.copy() for x in vlist] for values in vlist: if values.get('number') is None: values['number'] = Sequence.get_id( config.inventory_sequence.id) inventories = super(Inventory, cls).create(vlist) cls.complete_lines(inventories, fill=False) return inventories @classmethod def write(cls, inventories, values): super(Inventory, cls).write(inventories, values) cls.complete_lines(inventories, fill=False) @classmethod def copy(cls, inventories, default=None): pool = Pool() Date = pool.get('ir.date') if default is None: default = {} else: default = default.copy() default.setdefault('date', Date.today()) default.setdefault('lines.moves', None) default.setdefault('number', None) new_inventories = super().copy(inventories, default=default) cls.complete_lines(new_inventories, fill=False) return new_inventories @staticmethod def grouping(): return ('product', ) @classmethod @ModelView.button def complete_lines(cls, inventories, fill=True): ''' Complete or update the inventories ''' pool = Pool() Line = pool.get('stock.inventory.line') Product = pool.get('product.product') grouping = cls.grouping() to_create, to_write = [], [] for inventory in inventories: # Once done computation is wrong because include created moves if inventory.state == 'done': continue # Compute product quantities if fill: product_ids = None else: product_ids = [l.product.id for l in inventory.lines] with Transaction().set_context(stock_date_end=inventory.date): pbl = Product.products_by_location( [inventory.location.id], grouping=grouping, grouping_filter=(product_ids, )) # Index some data product2type = {} product2consumable = {} for product in Product.browse([line[1] for line in pbl]): product2type[product.id] = product.type product2consumable[product.id] = product.consumable # Update existing lines for line in inventory.lines: if not (line.product.type == 'goods' and not line.product.consumable): Line.delete([line]) continue key = (inventory.location.id, ) + line.unique_key if key in pbl: quantity = pbl.pop(key) else: quantity = 0.0 values = line.update_values4complete(quantity) if values: to_write.extend(([line], values)) if not fill: continue # Create lines if needed for key, quantity in pbl.items(): product_id = key[grouping.index('product') + 1] if (product2type[product_id] != 'goods' or product2consumable[product_id]): continue if not quantity: continue values = Line.create_values4complete(inventory, quantity) for i, fname in enumerate(grouping, 1): values[fname] = key[i] to_create.append(values) if to_create: Line.create(to_create) if to_write: Line.write(*to_write) @classmethod @ModelView.button_action('stock.wizard_inventory_count') def count(cls, inventories): cls.complete_lines(inventories)
class Inventory(Workflow, ModelSQL, ModelView): 'Stock Inventory' __name__ = 'stock.inventory' location = fields.Many2One('stock.location', 'Location', required=True, domain=[('type', '=', 'storage')], states={ 'readonly': Or(Not(Equal(Eval('state'), 'draft')), Bool(Eval('lines', [0]))), }, depends=['state']) date = fields.Date('Date', required=True, states={ 'readonly': Or(Not(Equal(Eval('state'), 'draft')), Bool(Eval('lines', [0]))), }, depends=['state']) lost_found = fields.Many2One('stock.location', 'Lost and Found', required=True, domain=[('type', '=', 'lost_found')], states=STATES, depends=DEPENDS) lines = fields.One2Many('stock.inventory.line', 'inventory', 'Lines', states=STATES, depends=DEPENDS) company = fields.Many2One('company.company', 'Company', required=True, states={ 'readonly': Or(Not(Equal(Eval('state'), 'draft')), Bool(Eval('lines', [0]))), }, depends=['state']) state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Canceled'), ], 'State', readonly=True, select=True) batch_number = fields.Char('Batch Number') @classmethod def __setup__(cls): super(Inventory, cls).__setup__() cls._order.insert(0, ('date', 'DESC')) cls._error_messages.update({ 'delete_cancel': ('Inventory "%s" must be cancelled before ' 'deletion.'), 'unique_line': ('Line "%s" is not unique ' 'on Inventory "%s".'), }) cls._transitions |= set(( ('draft', 'done'), ('draft', 'cancel'), )) cls._buttons.update({ 'confirm': { 'invisible': Eval('state').in_(['done', 'cancel']), }, 'cancel': { 'invisible': Eval('state').in_(['cancel', 'done']), }, 'complete_lines': { 'readonly': Eval('state') != 'draft', }, }) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(Inventory, cls).__register__(module_name) cursor = Transaction().cursor # Add index on create_date table = TableHandler(cursor, cls, module_name) table.index_action('create_date', action='add') @staticmethod def default_state(): return 'draft' @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() @staticmethod def default_company(): return Transaction().context.get('company') @classmethod def default_lost_found(cls): Location = Pool().get('stock.location') locations = Location.search(cls.lost_found.domain) if len(locations) == 1: return locations[0].id @classmethod def delete(cls, inventories): # Cancel before delete cls.cancel(inventories) for inventory in inventories: if inventory.state != 'cancel': cls.raise_user_error('delete_cancel', inventory.rec_name) super(Inventory, cls).delete(inventories) @classmethod @ModelView.button @Workflow.transition('done') def confirm(cls, inventories): Move = Pool().get('stock.move') moves = [] for inventory in inventories: keys = set() for line in inventory.lines: key = line.unique_key if key in keys: cls.raise_user_error('unique_line', (line.rec_name, inventory.rec_name)) keys.add(key) move = line.get_move() if move: moves.append(move) if moves: Move.save(moves) Move.do(moves) @classmethod @ModelView.button @Workflow.transition('cancel') def cancel(cls, inventories): Line = Pool().get("stock.inventory.line") Line.cancel_move([l for i in inventories for l in i.lines]) @classmethod def copy(cls, inventories, default=None): pool = Pool() Date = pool.get('ir.date') Line = pool.get('stock.inventory.line') if default is None: default = {} default = default.copy() default['date'] = Date.today() default['lines'] = None new_inventories = [] for inventory in inventories: new_inventory, = super(Inventory, cls).copy([inventory], default=default) Line.copy(inventory.lines, default={ 'inventory': new_inventory.id, 'moves': None, }) cls.complete_lines([new_inventory]) new_inventories.append(new_inventory) return new_inventories @staticmethod def complete_lines(inventories): ''' Complete or update the inventories ''' pool = Pool() Line = pool.get('stock.inventory.line') Product = pool.get('product.product') to_create = [] for inventory in inventories: # Compute product quantities with Transaction().set_context(stock_date_end=inventory.date): pbl = Product.products_by_location([inventory.location.id]) # Index some data product2uom = {} product2type = {} product2consumable = {} for product in Product.browse([line[1] for line in pbl]): product2uom[product.id] = product.default_uom.id product2type[product.id] = product.type product2consumable[product.id] = product.consumable product_qty = {} for (location, product), quantity in pbl.iteritems(): product_qty[product] = (quantity, product2uom[product]) # Update existing lines for line in inventory.lines: if not (line.product.active and line.product.type == 'goods' and not line.product.consumable): Line.delete([line]) continue if line.product.id in product_qty: quantity, uom_id = product_qty.pop(line.product.id) elif line.product.id in product2uom: quantity, uom_id = 0.0, product2uom[line.product.id] else: quantity, uom_id = 0.0, line.product.default_uom.id values = line.update_values4complete(quantity, uom_id) if values: Line.write([line], values) # Create lines if needed for product_id in product_qty: if (product2type[product_id] != 'goods' or product2consumable[product_id]): continue quantity, uom_id = product_qty[product_id] if not quantity: continue values = Line.create_values4complete(product_id, inventory, quantity, uom_id) to_create.append(values) if to_create: Line.create(to_create)
class Subscription(Workflow, ModelSQL, ModelView): "Subscription" __name__ = 'sale.subscription' _rec_name = 'number' company = fields.Many2One( 'company.company', "Company", required=True, select=True, states={ 'readonly': Eval('state') != 'draft', }, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], depends=['state'], help="Make the subscription belong to the company.") number = fields.Char("Number", readonly=True, select=True, help="The main identification of the subscription.") # TODO revision reference = fields.Char("Reference", select=True, help="The identification of an external origin.") description = fields.Char("Description", states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) party = fields.Many2One('party.party', "Party", required=True, states={ 'readonly': ((Eval('state') != 'draft') | (Eval('lines', [0]) & Eval('party'))), }, depends=['state'], help="The party who subscribes.") invoice_address = fields.Many2One('party.address', "Invoice Address", domain=[ ('party', '=', Eval('party')), ], states={ 'readonly': Eval('state') != 'draft', 'required': ~Eval('state').in_(['draft']), }, depends=['party', 'state']) payment_term = fields.Many2One('account.invoice.payment_term', "Payment Term", states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) currency = fields.Many2One( 'currency.currency', "Currency", required=True, states={ 'readonly': ((Eval('state') != 'draft') | (Eval('lines', [0]) & Eval('currency', 0))), }, depends=['state']) start_date = fields.Date("Start Date", required=True, states={ 'readonly': ((Eval('state') != 'draft') | Eval('next_invoice_date')), }, depends=['state', 'next_invoice_date']) end_date = fields.Date("End Date", domain=[ 'OR', ('end_date', '>=', If(Bool(Eval('start_date')), Eval('start_date', datetime.date.min), datetime.date.min)), ('end_date', '=', None), ], states={ 'readonly': Eval('state') != 'draft', }, depends=['start_date', 'state']) invoice_recurrence = fields.Many2One( 'sale.subscription.recurrence.rule.set', "Invoice Recurrence", required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) invoice_start_date = fields.Date("Invoice Start Date", states={ 'readonly': ((Eval('state') != 'draft') | Eval('next_invoice_date')), }, depends=['state', 'next_invoice_date']) next_invoice_date = fields.Date("Next Invoice Date", readonly=True) lines = fields.One2Many('sale.subscription.line', 'subscription', "Lines", states={ 'readonly': ((Eval('state') != 'draft') | ~Eval('start_date')), }, depends=['state']) state = fields.Selection(STATES, "State", readonly=True, required=True, help="The current state of the subscription.") @classmethod def __setup__(cls): super(Subscription, cls).__setup__() cls._order = [ ('start_date', 'DESC'), ('id', 'DESC'), ] cls._transitions |= set(( ('draft', 'canceled'), ('draft', 'quotation'), ('quotation', 'canceled'), ('quotation', 'draft'), ('quotation', 'running'), ('running', 'draft'), ('running', 'closed'), ('canceled', 'draft'), )) cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['draft', 'quotation']), 'icon': 'tryton-cancel', 'depends': ['state'], }, 'draft': { 'invisible': Eval('state').in_(['draft', 'closed']), 'icon': If(Eval('state') == 'canceled', 'tryton-undo', 'tryton-back'), 'depends': ['state'], }, 'quote': { 'invisible': Eval('state') != 'draft', 'readonly': ~Eval('lines', []), 'icon': 'tryton-forward', 'depends': ['state'], }, 'run': { 'invisible': Eval('state') != 'quotation', 'icon': 'tryton-forward', 'depends': ['state'], }, }) @classmethod def default_company(cls): return Transaction().context.get('company') @classmethod def default_currency(cls): pool = Pool() Company = pool.get('company.company') company = cls.default_company() if company: return Company(company).currency.id @classmethod def default_state(cls): return 'draft' @fields.depends('party') def on_change_party(self): self.invoice_address = None if self.party: self.invoice_address = self.party.address_get(type='invoice') self.payment_term = self.party.customer_payment_term @classmethod def set_number(cls, subscriptions): pool = Pool() Sequence = pool.get('ir.sequence') Config = pool.get('sale.configuration') config = Config(1) for subscription in subscriptions: if subscription.number: continue subscription.number = Sequence.get_id( config.subscription_sequence.id) cls.save(subscriptions) def compute_next_invoice_date(self): start_date = self.invoice_start_date or self.start_date date = self.next_invoice_date or self.start_date rruleset = self.invoice_recurrence.rruleset(start_date) dt = datetime.datetime.combine(date, datetime.time()) inc = (start_date == date) and not self.next_invoice_date next_date = rruleset.after(dt, inc=inc) return next_date.date() @classmethod def copy(cls, subscriptions, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('number', None) default.setdefault('next_invoice_date', None) return super(Subscription, cls).copy(subscriptions, default=default) @classmethod @ModelView.button @Workflow.transition('canceled') def cancel(cls, subscriptions): pass @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, subscriptions): pass @classmethod @ModelView.button @Workflow.transition('quotation') def quote(cls, subscriptions): cls.set_number(subscriptions) @classmethod @ModelView.button @Workflow.transition('running') def run(cls, subscriptions): pool = Pool() Line = pool.get('sale.subscription.line') lines = [] for subscription in subscriptions: if not subscription.next_invoice_date: subscription.next_invoice_date = ( subscription.compute_next_invoice_date()) for line in subscription.lines: if (line.next_consumption_date is None and not line.consumed): line.next_consumption_date = ( line.compute_next_consumption_date()) lines.extend(subscription.lines) Line.save(lines) cls.save(subscriptions) @classmethod def process(cls, subscriptions): to_close = [] for subscription in subscriptions: if all(l.next_consumption_date is None for l in subscription.lines): to_close.append(subscription) cls.close(to_close) @classmethod @Workflow.transition('closed') def close(cls, subscriptions): pass @classmethod def generate_invoice(cls, date=None): pool = Pool() Date = pool.get('ir.date') Consumption = pool.get('sale.subscription.line.consumption') Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') if date is None: date = Date.today() consumptions = Consumption.search([ ('invoice_line', '=', None), ('line.subscription.next_invoice_date', '<=', date), ('line.subscription.state', 'in', ['running', 'closed']), ], order=[ ('line.subscription.id', 'DESC'), ]) def keyfunc(consumption): return consumption.line.subscription invoices = {} lines = {} for subscription, consumptions in groupby(consumptions, key=keyfunc): invoices[subscription] = invoice = subscription._get_invoice() lines[subscription] = Consumption.get_invoice_lines( consumptions, invoice) all_invoices = list(invoices.values()) Invoice.save(all_invoices) all_invoice_lines = [] for subscription, invoice in invoices.items(): invoice_lines, _ = lines[subscription] for line in invoice_lines: line.invoice = invoice all_invoice_lines.extend(invoice_lines) InvoiceLine.save(all_invoice_lines) all_consumptions = [] for values in lines.values(): for invoice_line, consumptions in zip(*values): for consumption in consumptions: assert not consumption.invoice_line consumption.invoice_line = invoice_line all_consumptions.append(consumption) Consumption.save(all_consumptions) Invoice.update_taxes(all_invoices) subscriptions = cls.search([ ('next_invoice_date', '<=', date), ]) for subscription in subscriptions: if subscription.state == 'running': while subscription.next_invoice_date <= date: subscription.next_invoice_date = ( subscription.compute_next_invoice_date()) else: subscription.next_invoice_date = None cls.save(subscriptions) def _get_invoice(self): pool = Pool() Invoice = pool.get('account.invoice') invoice = Invoice( company=self.company, type='out', party=self.party, invoice_address=self.invoice_address, currency=self.currency, account=self.party.account_receivable, ) invoice.on_change_type() invoice.payment_term = self.payment_term return invoice
class SaleLine(metaclass=PoolMeta): __name__ = 'sale.line' purchase_request = fields.Many2One('purchase.request', 'Purchase Request', ondelete='SET NULL', readonly=True) purchase_request_state = fields.Function( fields.Selection([ ('', ''), ('requested', 'Requested'), ('purchased', 'Purchased'), ('cancel', 'Cancel'), ], 'Purchase Request State', states={ 'invisible': ~Eval('purchase_request_state'), }), 'get_purchase_request_state') def get_purchase_request_state(self, name): if self.purchase_request is not None: purchase_line = self.purchase_request.purchase_line if purchase_line is not None: purchase = purchase_line.purchase if purchase.state == 'cancel': return 'cancel' elif purchase.state in ('processing', 'done'): return 'purchased' return 'requested' return '' @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('purchase_request', None) return super(SaleLine, cls).copy(lines, default=default) @property def supply_on_sale(self): "Returns True if the sale line has to be supply by purchase request" if (self.type != 'line' or not self.product or self.quantity <= 0 or not self.product.purchasable or any(m.state not in ['staging', 'cancel'] for m in self.moves)): return False return self.product.supply_on_sale def get_move(self, shipment_type): move = super(SaleLine, self).get_move(shipment_type) if (move and shipment_type == 'out' and (self.supply_on_sale or self.purchase_request)): if self.purchase_request_state in ('', 'requested'): move.state = 'staging' return move def _get_purchase_request_product_supplier_pattern(self): return { 'company': self.sale.company.id, } def get_purchase_request(self): 'Return purchase request for the sale line' pool = Pool() Uom = pool.get('product.uom') Request = pool.get('purchase.request') if not self.supply_on_sale or self.purchase_request: return # Ensure to create the request for the maximum paid if self.sale.shipment_method == 'invoice': invoice_skips = (set(self.sale.invoices_ignored) | set(self.sale.invoices_recreated)) invoice_lines = [ l for l in self.invoice_lines if l.invoice not in invoice_skips ] if (not invoice_lines or any( (not l.invoice) or l.invoice.state != 'paid' for l in invoice_lines)): return product = self.product supplier, purchase_date = Request.find_best_supplier( product, self.shipping_date, **self._get_purchase_request_product_supplier_pattern()) uom = product.purchase_uom or product.default_uom quantity = self._get_move_quantity('out') quantity = Uom.compute_qty(self.unit, quantity, uom) return Request( product=product, party=supplier, quantity=quantity, uom=uom, computed_quantity=quantity, computed_uom=uom, purchase_date=purchase_date, supply_date=self.shipping_date, company=self.sale.company, warehouse=self.warehouse, origin=self.sale, ) def assign_supplied(self, location_quantities): ''' Assign supplied move location_quantities will be updated according to assigned quantities. ''' pool = Pool() Uom = pool.get('product.uom') Move = pool.get('stock.move') if self.purchase_request_state != 'purchased': return moves = set() for move in self.moves: for inv_move in move.shipment.inventory_moves: if inv_move.product.id == self.product.id: moves.add(inv_move) for move in moves: if move.state != 'draft': continue location_qties_converted = {} for location_id, quantity in location_quantities.items(): location_qties_converted[location_id] = (Uom.compute_qty( move.product.default_uom, quantity, move.uom, round=False)) to_pick = move.pick_product(location_qties_converted) picked_qties = sum(qty for _, qty in to_pick) if picked_qties < move.quantity: first = False Move.write([move], { 'quantity': move.quantity - picked_qties, }) else: first = True for from_location, qty in to_pick: values = { 'from_location': from_location.id, 'quantity': qty, } if first: Move.write([move], values) Move.assign([move]) else: Move.assign(Move.copy([move], default=values)) qty_default_uom = Uom.compute_qty(move.uom, qty, move.product.default_uom, round=False) location_quantities[from_location] -= qty_default_uom
class MenuItem(ModelSQL, ModelView, CMSMenuItemMixin): "Nereid CMS Menuitem" __name__ = 'nereid.cms.menuitem' _rec_name = 'title' type_ = fields.Selection([ ('view', 'View'), ('static', 'Static'), ('record', 'Record'), ], 'Type', required=True, select=True) active = fields.Boolean('Active', select=True) title = fields.Char('Title', required=True, select=True, translate=True, depends=['type_'], states={ 'required': Eval('type_') == 'static', }) link = fields.Char('Link', states={ 'required': Eval('type_') == 'static', 'invisible': Eval('type_') != 'static', }, depends=['type_']) target = fields.Selection([ ('_self', 'Self'), ('_blank', 'Blank'), ], 'Target', required=True) parent = fields.Many2One('nereid.cms.menuitem', 'Parent Menuitem', states={ 'required': Eval('type_') != 'view', }, depends=['type_'], select=True) child = fields.One2Many('nereid.cms.menuitem', 'parent', string='Child Menu Items') sequence = fields.Integer('Sequence', required=True, select=True) record = fields.Reference( 'Record', selection='allowed_models', states={ 'required': Eval('type_') == 'record', 'invisible': Eval('type_') != 'record', }, depends=['type_'], ) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().cursor sql_table = cls.__table__() super(MenuItem, cls).__register__(module_name) table = TableHandler(cursor, cls, module_name) if table.column_exist('reference'): # pragma: no cover table.not_null_action('unique_name', 'remove') # Delete the newly created record column table.drop_column('record') # Rename the reference column as record table.column_rename('reference', 'record', True) # The value of type depends on existence of record cursor.execute(*sql_table.update( columns=[sql_table.type_], values=['record'], where=(sql_table.record != None) # noqa )) @classmethod def allowed_models(cls): return [ (None, ''), ('nereid.cms.article.category', 'CMS Article Category'), ('nereid.cms.article', 'CMS Article'), ] @staticmethod def default_type_(): return 'static' @staticmethod def default_target(): return '_self' @staticmethod def default_sequence(): return 10 @staticmethod def default_active(): return True @classmethod def __setup__(cls): super(MenuItem, cls).__setup__() cls._error_messages.update({ 'recursion_error': 'Error ! You can not create recursive menuitems.', }) cls._order.insert(0, ('sequence', 'ASC')) @classmethod def validate(cls, menus): super(MenuItem, cls).validate(menus) cls.check_recursion(menus) def get_rec_name(self, name): def _name(menuitem): if menuitem.parent: return _name(menuitem.parent) + ' / ' + menuitem.title else: return menuitem.title return _name(self) def get_menu_item(self, max_depth): """ Return huge dictionary with serialized menu item { title: <display name>, target: <href target>, link: <url>, # if type_ is `static` children: [ # direct children or record children <menu_item children>, ... ], record: <instance of record> # if type_ is `record` } """ res = { 'title': self.title, 'target': self.target, 'type_': self.type_, } if self.type_ == 'static': res['link'] = self.link if self.type_ == 'record': res['record'] = self.record res['link'] = self.record.get_absolute_url() if max_depth: res['children'] = self.get_children(max_depth=max_depth - 1) if self.type_ == 'record' and not res.get('children') and max_depth: res['children'] = self.record.get_children(max_depth=max_depth - 1) return res def get_children(self, max_depth): """ Return serialized menu_item for current menu_item children """ children = self.search([('parent', '=', self.id), ('active', '=', True)]) return [ child.get_menu_item(max_depth=max_depth - 1) for child in children ] def get_absolute_url(self, *args, **kwargs): """ Return url for menu item """ if self.type_ == 'record': return self.record.get_absolute_url(*args, **kwargs) return self.link
class Article(Workflow, ModelSQL, ModelView, CMSMenuItemMixin): "CMS Articles" __name__ = 'nereid.cms.article' _rec_name = 'uri' uri = fields.Char('URI', required=True, select=True, translate=True) title = fields.Char('Title', required=True, select=True, translate=True) content = fields.Text('Content', required=True, translate=True) template = fields.Char('Template', required=True) active = fields.Boolean('Active', select=True) image = fields.Many2One('nereid.static.file', 'Image') employee = fields.Many2One('company.employee', 'Employee') author = fields.Many2One('nereid.user', 'Author') published_on = fields.Date('Published On') publish_date = fields.Function(fields.Char('Publish Date'), 'get_publish_date') sequence = fields.Integer('Sequence', required=True, select=True) reference = fields.Reference('Reference', selection='allowed_models') description = fields.Text('Short Description') attributes = fields.One2Many('nereid.cms.article.attribute', 'article', 'Attributes') categories = fields.Many2Many( 'nereid.cms.category-article', 'article', 'category', 'Categories', ) content_type = fields.Selection('content_type_selection', 'Content Type', required=True) # Article can have a banner banner = fields.Many2One('nereid.cms.banner', 'Banner') state = fields.Selection([('draft', 'Draft'), ('published', 'Published'), ('archived', 'Archived')], 'State', required=True, select=True, readonly=True) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().cursor table = TableHandler(cursor, cls, module_name) if not table.column_exist('employee'): table.column_rename('author', 'employee') super(Article, cls).__register__(module_name) @classmethod def __setup__(cls): super(Article, cls).__setup__() cls._order.insert(0, ('sequence', 'ASC')) cls._transitions |= set(( ('draft', 'published'), ('published', 'draft'), ('published', 'archived'), ('archived', 'draft'), )) cls._buttons.update({ 'archive': { 'invisible': Eval('state') != 'published', }, 'publish': { 'invisible': Eval('state').in_(['published', 'archived']), }, 'draft': { 'invisible': Eval('state') == 'draft', } }) @classmethod def content_type_selection(cls): """ Returns a selection for content_type. """ default_types = [('html', 'HTML'), ('plain', 'Plain Text')] if markdown: default_types.append(('markdown', 'Markdown')) if publish_parts: default_types.append(('rst', 'reStructured TeXT')) return default_types @classmethod def default_content_type(cls): """ Default content_type. """ return 'plain' def __html__(self): """ Uses content_type field to generate html content. Concept from Jinja2's Markup class. """ if self.content_type == 'rst': if publish_parts: res = publish_parts(self.content, writer_name='html') return res['html_body'] self.raise_user_error( "`docutils` not installed, to render rst articles.") if self.content_type == 'markdown': if markdown: return markdown(self.content) self.raise_user_error( "`markdown` not installed, to render markdown article.") return self.content @classmethod @ModelView.button @Workflow.transition('archived') def archive(cls, articles): pass @classmethod @ModelView.button @Workflow.transition('published') def publish(cls, articles): pass @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, articles): pass @classmethod def allowed_models(cls): MenuItem = Pool().get('nereid.cms.menuitem') return MenuItem.allowed_models() @staticmethod def default_active(): return True @fields.depends('title', 'uri') def on_change_title(self): res = {} if self.title and not self.uri: res['uri'] = slugify(self.title) return res @staticmethod def default_template(): return 'article.jinja' @staticmethod def default_employee(): User = Pool().get('res.user') if 'employee' in Transaction().context: return Transaction().context['employee'] user = User(Transaction().user) if user.employee: return user.employee.id if has_request_context() and request.nereid_user.employee: return request.nereid_user.employee.id @staticmethod def default_author(): if has_request_context(): return request.nereid_user.id @staticmethod def default_published_on(): Date = Pool().get('ir.date') return Date.today() @classmethod @route('/article/<uri>') def render(cls, uri): """ Renders the template """ try: article, = cls.search([ ('uri', '=', uri), ('state', '=', 'published'), ]) except ValueError: abort(404) return render_template(article.template, article=article) @classmethod @route('/sitemaps/article-index.xml') def sitemap_index(cls): index = SitemapIndex(cls, []) return index.render() @classmethod @route('/sitemaps/article-<int:page>.xml') def sitemap(cls, page): sitemap_section = SitemapSection(cls, [], page) sitemap_section.changefreq = 'daily' return sitemap_section.render() @classmethod def get_publish_date(cls, records, name): """ Return publish date to render on view """ res = {} for record in records: res[record.id] = str(record.published_on) return res def get_absolute_url(self, **kwargs): return url_for('nereid.cms.article.render', uri=self.uri, **kwargs) @staticmethod def default_state(): if 'published' in Transaction().context: return 'published' return 'draft' def get_menu_item(self, max_depth): """ Return huge dictionary with serialized article category for menu item { title: <display name>, link: <url>, record: <instance of record> # if type_ is `record` } """ return { 'record': self, 'title': self.title, 'link': self.get_absolute_url(), } def atom_id(self): """ Returns an atom ID for the article """ return ('tag:' + request.nereid_website.name + ',' + self.publish_date + ':Article/' + str(self.id)) def atom_publish_date(self): """ Returns the article's publish date with timezone set as UTC """ return pytz.utc.localize( datetime.combine(self.published_on, datetime.min.time())) def serialize(self, purpose=None): """ Serialize Article records """ if purpose == 'atom': # The keys in the dictionary returned are used by Werkzeug's # AtomFeed class. return { 'id': self.atom_id(), 'title': self.title, 'author': (self.author.serialize( purpose=purpose) if self.author else None), 'content': self.content, 'content_type': ('text' if self.content_type == 'plain' else 'html'), 'link': { 'rel': 'alternate', 'type': 'text/html', 'href': self.get_absolute_url(external=True), }, 'category': [ category.serialize(purpose=purpose) for category in self.categories ], 'published': self.atom_publish_date(), 'updated': self.write_date or self.atom_publish_date(), } elif hasattr(super(Article, self), 'serialize'): return super(Article, self).serialize(purpose=purpose) @classmethod @route('/article/all.atom') def atom_feed(cls): """ Renders the atom feed for all articles. """ feed = AtomFeed("All Articles", feed_url=request.url, url=request.host_url) for article in cls.search([('state', '=', 'published')]): feed.add(**article.serialize(purpose='atom')) return feed.get_response()
class UIMenu(DeactivableMixin, sequence_ordered(), tree(separator=' / '), ModelSQL, ModelView): "UI menu" __name__ = 'ir.ui.menu' name = fields.Char('Menu', required=True, translate=True) childs = fields.One2Many('ir.ui.menu', 'parent', 'Children') parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=True, ondelete='CASCADE') groups = fields.Many2Many('ir.ui.menu-res.group', 'menu', 'group', 'Groups') complete_name = fields.Function(fields.Char('Complete Name'), 'get_rec_name', searcher='search_rec_name') icon = fields.Selection('list_icons', 'Icon', translate=False) action = fields.Function(fields.Reference( 'Action', selection=[ ('', ''), ('ir.action.report', 'ir.action.report'), ('ir.action.act_window', 'ir.action.act_window'), ('ir.action.wizard', 'ir.action.wizard'), ('ir.action.url', 'ir.action.url'), ], translate=False), 'get_action', setter='set_action') action_keywords = fields.One2Many('ir.action.keyword', 'model', 'Action Keywords') favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite') @classmethod def order_complete_name(cls, tables): return cls.name.convert_order('name', tables, cls) @staticmethod def default_icon(): return 'tryton-folder' @staticmethod def default_sequence(): return 10 @staticmethod def list_icons(): pool = Pool() Icon = pool.get('ir.ui.icon') return sorted(CLIENT_ICONS + [(name, name) for _, name in Icon.list_icons()]) @classmethod def search_global(cls, text): # TODO improve search clause for record in cls.search([ ('rec_name', 'ilike', '%%%s%%' % text), ]): if record.action: yield record, record.rec_name, record.icon @classmethod def search(cls, domain, offset=0, limit=None, order=None, count=False, query=False): menus = super(UIMenu, cls).search(domain, offset=offset, limit=limit, order=order, count=False, query=query) if query: return menus if menus: parent_ids = {x.parent.id for x in menus if x.parent} parents = set() for sub_parent_ids in grouped_slice(parent_ids): parents.update( cls.search([ ('id', 'in', list(sub_parent_ids)), ])) # Re-browse to avoid side-cache access menus = cls.browse([ x.id for x in menus if (x.parent and x.parent in parents) or not x.parent ]) if count: return len(menus) return menus @classmethod def get_action(cls, menus, name): pool = Pool() actions = dict((m.id, None) for m in menus) with Transaction().set_context(active_test=False): menus = cls.browse(menus) action_keywords = sum((list(m.action_keywords) for m in menus), []) def action_type(keyword): return keyword.action.type action_keywords.sort(key=action_type) for type, action_keywords in groupby(action_keywords, key=action_type): action_keywords = list(action_keywords) for action_keyword in action_keywords: model = action_keyword.model actions[model.id] = '%s,-1' % type Action = pool.get(type) action2keyword = {k.action.id: k for k in action_keywords} with Transaction().set_context(active_test=False): factions = Action.search([ ('action', 'in', list(action2keyword.keys())), ]) for action in factions: model = action2keyword[action.id].model actions[model.id] = str(action) return actions @classmethod def set_action(cls, menus, name, value): pool = Pool() ActionKeyword = pool.get('ir.action.keyword') action_keywords = [] transaction = Transaction() for i in range(0, len(menus), transaction.database.IN_MAX): sub_menus = menus[i:i + transaction.database.IN_MAX] action_keywords += ActionKeyword.search([ ('keyword', '=', 'tree_open'), ('model', 'in', [str(menu) for menu in sub_menus]), ]) if action_keywords: with Transaction().set_context(_timestamp=False): ActionKeyword.delete(action_keywords) if not value: return if isinstance(value, str): action_type, action_id = value.split(',') else: action_type, action_id = value if int(action_id) <= 0: return Action = pool.get(action_type) action = Action(int(action_id)) to_create = [] for menu in menus: with Transaction().set_context(_timestamp=False): to_create.append({ 'keyword': 'tree_open', 'model': str(menu), 'action': action.action.id, }) if to_create: ActionKeyword.create(to_create) @classmethod def get_favorite(cls, menus, name): pool = Pool() Favorite = pool.get('ir.ui.menu.favorite') user = Transaction().user favorites = Favorite.search([ ('menu', 'in', [m.id for m in menus]), ('user', '=', user), ]) menu2favorite = dict( (m.id, False if m.action else None) for m in menus) menu2favorite.update(dict((f.menu.id, True) for f in favorites)) return menu2favorite
class InventoryLine(ModelSQL, ModelView): 'Stock Inventory Line' __name__ = 'stock.inventory.line' _states = { 'readonly': Eval('inventory_state') != 'draft', } _depends = ['inventory_state'] product = fields.Many2One('product.product', 'Product', required=True, domain=[ ('type', '=', 'goods'), ('consumable', '=', False), ], states=_states, depends=_depends) uom = fields.Function(fields.Many2One('product.uom', 'UOM'), 'get_uom') unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits') expected_quantity = fields.Float('Expected Quantity', required=True, digits=(16, Eval('unit_digits', 2)), readonly=True, states={ 'invisible': Eval('id', -1) < 0, }, depends=['unit_digits']) quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), states=_states, depends=['unit_digits'] + _depends) moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True) inventory = fields.Many2One('stock.inventory', 'Inventory', required=True, ondelete='CASCADE', states={ 'readonly': _states['readonly'] & Bool(Eval('inventory')), }, depends=_depends) inventory_state = fields.Function( fields.Selection(INVENTORY_STATES, 'Inventory State'), 'on_change_with_inventory_state') @classmethod def __setup__(cls): super(InventoryLine, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('check_line_qty_pos', Check(t, t.quantity >= 0), 'Line quantity must be positive.'), ] cls._order.insert(0, ('product', 'ASC')) cls._error_messages.update({ 'missing_empty_quantity': ('An option for empty quantity is ' 'missing for inventory "%(inventory)s".'), 'delete_cancel_draft': ('The line "%(line)s" must be on ' 'canceled or draft inventory to be deleted.'), }) @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() pool = Pool() Move = pool.get('stock.move') sql_table = cls.__table__() move_table = Move.__table__() super(InventoryLine, cls).__register__(module_name) table = cls.__table_handler__(module_name) # Migration from 3.0: use Move origin if table.column_exist('move'): cursor.execute(*sql_table.select( sql_table.id, sql_table.move, where=sql_table.move != Null)) for line_id, move_id in cursor.fetchall(): cursor.execute(*move_table.update( columns=[move_table.origin], values=['%s,%s' % (cls.__name__, line_id)], where=move_table.id == move_id)) table.drop_column('move') # Migration from 4.6: drop required on quantity table.not_null_action('quantity', action='remove') @staticmethod def default_unit_digits(): return 2 @staticmethod def default_expected_quantity(): return 0. @fields.depends('product') def on_change_product(self): self.unit_digits = 2 if self.product: self.uom = self.product.default_uom self.unit_digits = self.product.default_uom.digits @fields.depends('inventory', '_parent_inventory.state') def on_change_with_inventory_state(self, name=None): if self.inventory: return self.inventory.state return 'draft' def get_rec_name(self, name): return self.product.rec_name @classmethod def search_rec_name(cls, name, clause): return [('product.rec_name', ) + tuple(clause[1:])] def get_uom(self, name): return self.product.default_uom.id def get_unit_digits(self, name): return self.product.default_uom.digits @property def unique_key(self): key = [] for fname in self.inventory.grouping(): value = getattr(self, fname) if isinstance(value, Model): value = value.id key.append(value) return tuple(key) @classmethod def cancel_move(cls, lines): Move = Pool().get('stock.move') moves = [m for l in lines for m in l.moves if l.moves] Move.cancel(moves) Move.delete(moves) def get_move(self): ''' Return Move instance for the inventory line ''' pool = Pool() Move = pool.get('stock.move') Uom = pool.get('product.uom') qty = self.quantity if qty is None: if self.inventory.empty_quantity is None: self.raise_user_error('missing_empty_quantity', { 'inventory': self.inventory.rec_name, }) if self.inventory.empty_quantity == 'keep': return else: qty = 0.0 delta_qty = Uom.compute_qty(self.uom, self.expected_quantity - qty, self.uom) if delta_qty == 0.0: return from_location = self.inventory.location to_location = self.inventory.lost_found if delta_qty < 0: (from_location, to_location, delta_qty) = \ (to_location, from_location, -delta_qty) return Move( from_location=from_location, to_location=to_location, quantity=delta_qty, product=self.product, uom=self.uom, company=self.inventory.company, effective_date=self.inventory.date, origin=self, ) def update_values4complete(self, quantity): ''' Return update values to complete inventory ''' values = {} # if nothing changed, no update if self.quantity == self.expected_quantity == quantity: return values values['expected_quantity'] = quantity return values @classmethod def create_values4complete(cls, inventory, quantity): ''' Return create values to complete inventory ''' return { 'inventory': inventory.id, 'expected_quantity': quantity, } @classmethod def delete(cls, lines): for line in lines: if line.inventory_state not in {'cancel', 'draft'}: cls.raise_user_error('delete_cancel_draft', { 'line': line.rec_name, }) super(InventoryLine, cls).delete(lines)
class MoveLine: __metaclass__ = PoolMeta __name__ = 'account.move.line' payment_amount = fields.Function(fields.Numeric( 'Payment Amount', digits=(16, If(Bool(Eval('second_currency_digits')), Eval('second_currency_digits', 2), Eval('currency_digits', 2))), states={ 'invisible': ~Eval('payment_kind'), }, depends=['payment_kind', 'second_currency_digits', 'currency_digits']), 'get_payment_amount', searcher='search_payment_amount') payments = fields.One2Many('account.payment', 'line', 'Payments', readonly=True, states={ 'invisible': ~Eval('payment_kind'), }, depends=['payment_kind']) payment_kind = fields.Function(fields.Selection([ (None, ''), ] + KINDS, 'Payment Kind'), 'get_payment_kind', searcher='search_payment_kind') payment_blocked = fields.Boolean('Blocked', readonly=True) payment_direct_debit = fields.Boolean( "Direct Debit", states={ 'invisible': ~((Eval('payment_kind') == 'payable') & ((Eval('credit', 0) > 0) | (Eval('debit', 0) < 0))), }, depends=['payment_kind', 'debit', 'credit'], help="Check if the line will be paid by direct debit.") @classmethod def __setup__(cls): super(MoveLine, cls).__setup__() cls._buttons.update({ 'pay': { 'invisible': ~Eval('payment_kind').in_(dict(KINDS).keys()), }, 'payment_block': { 'invisible': Eval('payment_blocked', False), }, 'payment_unblock': { 'invisible': ~Eval('payment_blocked', False), }, }) cls._check_modify_exclude.update( ['payment_blocked', 'payment_direct_debit']) @classmethod def default_payment_direct_debit(cls): return False @classmethod def get_payment_amount(cls, lines, name): amounts = {} for line in lines: if line.account.kind not in ('payable', 'receivable'): amounts[line.id] = None continue if line.second_currency: amount = abs(line.amount_second_currency) else: amount = abs(line.credit - line.debit) for payment in line.payments: if payment.state != 'failed': amount -= payment.amount amounts[line.id] = amount return amounts @classmethod def search_payment_amount(cls, name, clause): pool = Pool() Payment = pool.get('account.payment') Account = pool.get('account.account') _, operator, value = clause Operator = fields.SQL_OPERATORS[operator] table = cls.__table__() payment = Payment.__table__() account = Account.__table__() payment_amount = Sum(Coalesce(payment.amount, 0)) main_amount = Abs(table.credit - table.debit) - payment_amount second_amount = Abs(table.amount_second_currency) - payment_amount amount = Case((table.second_currency == Null, main_amount), else_=second_amount) value = cls.payment_amount.sql_format(value) query = table.join( payment, type_='LEFT', condition=(table.id == payment.line) & (payment.state != 'failed')).join( account, condition=table.account == account.id).select( table.id, where=account.kind.in_(['payable', 'receivable']), group_by=(table.id, account.kind, table.second_currency), having=Operator(amount, value)) return [('id', 'in', query)] def get_payment_kind(self, name): return self.account.kind if self.account.kind in dict(KINDS) else None @classmethod def search_payment_kind(cls, name, clause): return [('account.kind', ) + tuple(clause[1:])] @classmethod def default_payment_blocked(cls): return False @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', None) return super(MoveLine, cls).copy(lines, default=default) @classmethod @ModelView.button_action('account_payment.act_pay_line') def pay(cls, lines): pass @classmethod @ModelView.button def payment_block(cls, lines): pool = Pool() Payment = pool.get('account.payment') cls.write(lines, { 'payment_blocked': True, }) draft_payments = [ p for l in lines for p in l.payments if p.state == 'draft' ] if draft_payments: Payment.delete(draft_payments) @classmethod @ModelView.button def payment_unblock(cls, lines): cls.write(lines, { 'payment_blocked': False, })
class ProductSaleChannelListing(ModelSQL, ModelView): '''Product - Sale Channel This model keeps a record of a product's association with Sale Channels. A product can be listed on multiple marketplaces ''' __name__ = 'product.product.channel_listing' # TODO: Only show channels where this ability is there. For example # showing a manual channel is pretty much useless channel = fields.Many2One('sale.channel', 'Sale Channel', domain=[('source', '!=', 'manual')], required=True, select=True, ondelete='RESTRICT') product = fields.Many2One('product.product', 'Product', select=True, states={'required': Eval('state') == 'active'}, ondelete='CASCADE', depends=['state']) product_identifier = fields.Char("Product Identifier", select=True, required=True) state = fields.Selection([ ('active', 'Active'), ('disabled', 'Disabled'), ], 'State', required=True, select=True) channel_source = fields.Function(fields.Char("Channel Source"), getter="get_channel_source") quantity = fields.Function( fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']), 'get_availability_fields') unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits') availability_type_used = fields.Function( fields.Selection([ ('bucket', 'Bucket'), ('quantity', 'Quantity'), ('infinite', 'Infinite'), ], 'Type'), 'get_availability_fields') availability_used = fields.Function( fields.Selection([ ('in_stock', 'In-Stock'), ('out_of_stock', 'Out Of Stock'), ], 'Availability', states={ 'invisible': ~Bool(Eval('availability_type_used') == 'bucket') }, depends=['availability_type_used']), 'get_availability_fields') listing_url = fields.Function(fields.Char('Listing URL'), 'get_listing_url') @classmethod def search_rec_name(cls, name, clause): return [ 'OR', ('product', ) + tuple(clause[1:]), ('product_identifier', ) + tuple(clause[1:]), ] @classmethod def get_unit_digits(cls, records, name): result = { r.id: r.product.default_uom.digits if r.product else 2 for r in records } return result @classmethod def get_listing_url(cls, records, name): """ Downstream modules should implement this function and return a valid url """ return dict.fromkeys([r.id for r in records]) @classmethod def get_availability_fields(cls, listings, names): listing_ids = map(int, listings) values = defaultdict(lambda: dict.fromkeys(listing_ids, None)) for name in names: # Just call the default dict once so all fields have values # even if product is absent values[name] for listing in listings: if listing.product: availability = listing.get_availability() values['availability_type_used'][listing.id] = \ availability['type'] values['availability_used'][listing.id] = availability.get( 'value') values['quantity'][listing.id] = availability.get('quantity') return values @classmethod def get_channel_source(cls, records, name): result = {r.id: r.channel and r.channel.source for r in records} return result @fields.depends('channel') def on_change_with_channel_source(self, name=None): return self.channel and self.channel.source @classmethod def __setup__(cls): ''' Setup the class and define constraints ''' super(ProductSaleChannelListing, cls).__setup__() table = cls.__table__() cls._sql_constraints += [ ('channel_product_identifier_uniq', Unique(table, table.channel, table.product_identifier), 'This external product is already mapped with same channel.') ] cls._buttons.update({ 'export_inventory_button': {}, }) @staticmethod def default_state(): return 'active' @classmethod def create_from(cls, channel, product_data): """ Create a listing for the product from channel and data """ raise NotImplementedError( "create_from is not implemented in channel listing for %s channels" % channel.source) @classmethod @ModelView.button def export_inventory_button(cls, listings): return cls.export_bulk_inventory(listings) def export_inventory(self): """ Export listing.product inventory to listing.channel Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement exporting or call super to delegate. """ raise NotImplementedError( "Export inventory is not implemented for %s channels" % self.channel.source) @classmethod def export_bulk_inventory(cls, listings): """ Export listing.product inventory to listing.channel in bulk Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement bulk exporting for respective channels. Default behaviour is to export inventory individually. """ for listing in listings: listing.export_inventory() def import_product_image(self): """ Import specific product image from external channel based on product identifier. Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement importing or call super to delegate. """ raise NotImplementedError( "Method import_product_image is not implemented for %s channel yet" % self.source) def get_availability_context(self): """ Allow overriding the context used to compute availability of products. """ return { 'locations': [self.channel.warehouse.id], } def get_availability(self): """ Return the availability of the product for this listing """ Product = Pool().get('product.product') with Transaction().set_context(**self.get_availability_context()): rv = {'type': 'bucket', 'value': None, 'quantity': None} if self.product: product = Product(self.product.id) rv['quantity'] = product.quantity if rv['quantity'] > 0: rv['value'] = 'in_stock' else: rv['value'] = 'out_of_stock' return rv
class SaleOpportunity(Workflow, ModelSQL, ModelView): 'Sale Opportunity' __name__ = "sale.opportunity" '''horario = fields.Many2One('training.horario', 'Horario', states={ 'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']), 'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']), }, depends=['state'])''' medio = fields.Selection( [ ('Facebook', 'Facebook'), ('Google', 'Google'), ('Volante', 'Volante'), ('Stand', 'Stand'), ('Manta', 'Manta'), ('Muppy', 'Muppy'), ('Otro', 'Otro'), ], 'Medio', sort=False, states={ 'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']), 'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']), }, depends=['state']) subscriptions = fields.One2Many('sale.subscription', 'origin', 'Suscripciones') meetings = fields.One2Many('sale.meeting', 'opportunity', 'Seguimiento', states=_STATES_START, depends=_DEPENDS_START) def _get_sale_opportunity(self): ''' Return subscription for an opportunity ''' pool = Pool() Subscription = pool.get('sale.subscription') Date = pool.get('ir.date') Recurrence = pool.get('sale.subscription.recurrence.rule.set') Party = pool.get('party.party') recurrences = Recurrence.search([]) recurrence = recurrences[0].id parties = Party.search(['id', '=', self.party.id]) for party in parties: Party.write([party], {'estudiante': True}) return Subscription( description=self.description, party=self.party, estudiante=self.party, payment_term=self.payment_term, company=self.company, invoice_address=self.address, currency=self.company.currency, start_date=Date.today(), invoice_recurrence=recurrence, #horario = self.horario, asesor=self.employee, medio=self.medio, origin=self, ) def create_sale(self): ''' Create a subscription for the opportunity and return the subscription ''' subscription = self._get_sale_opportunity() sale_lines = [] for line in self.lines: sale_lines.append(line.get_sale_line(subscription)) subscription.lines = sale_lines return subscription @classmethod @ModelView.button @Workflow.transition('converted') def convert(cls, opportunities): pool = Pool() Subscription = pool.get('sale.subscription') subscriptions = [ o.create_sale() for o in opportunities if not o.subscriptions ] Subscription.save(subscriptions) @staticmethod def _sale_won_states(): return ['running', 'closed'] @staticmethod def _sale_lost_states(): return ['canceled'] def is_won(self): sale_won_states = self._sale_won_states() sale_lost_states = self._sale_lost_states() end_states = sale_won_states + sale_lost_states return (self.subscriptions and all(s.state in end_states for s in self.subscriptions) and any(s.state in sale_won_states for s in self.subscriptions)) def is_lost(self): sale_lost_states = self._sale_lost_states() return (self.subscriptions and all(s.state in sale_lost_states for s in self.subscriptions)) @classmethod def process(cls, opportunities): won = [] lost = [] converted = [] for opportunity in opportunities: sale_amount = opportunity.sale_amount if opportunity.amount != sale_amount: opportunity.amount = sale_amount if opportunity.is_won(): won.append(opportunity) elif opportunity.is_lost(): lost.append(opportunity) elif (opportunity.state != 'converted' and opportunity.subscriptions): converted.append(opportunity) cls.save(opportunities) if won: cls.won(won) if lost: cls.lost(lost) if converted: cls.convert(converted) @property def is_forecast(self): pool = Pool() Date = pool.get('ir.date') today = Date.today() return self.end_date or datetime.date.max > today @classmethod @Workflow.transition('won') def won(cls, opportunities): pool = Pool() Date = pool.get('ir.date') cls.write(filter(lambda o: o.is_forecast, opportunities), { 'end_date': Date.today(), 'state': 'won', }) @property def sale_amount(self): pool = Pool() Currency = pool.get('currency.currency') if not self.subscriptions: #print "NO SUBSCRIPTIONS" return sale_lost_states = self._sale_lost_states() amount = 0 for subscription in self.subscriptions: if subscription.state not in sale_lost_states: amount += Currency.compute(subscription.currency, subscription.amount, self.currency) return amount @classmethod def default_employee(cls): User = Pool().get('res.user') employee_id = None if Transaction().context.get('employee'): employee_id = Transaction().context['employee'] else: user = User(Transaction().user) if user.employee: employee_id = user.employee.id if employee_id: return employee_id @fields.depends('party') def on_change_party(self): self.invoice_address = None if self.party: self.address = self.party.address_get(type='invoice') self.payment_term = self.party.customer_payment_term
class PartyIdentifier(ModelSQL, ModelView): 'Party Identifier' __name__ = 'party.identifier' _rec_name = 'code' party = fields.Many2One('party.party', 'Party', ondelete='CASCADE', required=True, select=True) type = fields.Selection('get_types', 'Type') code = fields.Char('Code', required=True) @classmethod def __setup__(cls): super(PartyIdentifier, cls).__setup__() cls._error_messages.update({ 'invalid_vat': ('Invalid VAT number "%(code)s" ' 'on party "%(party)s".'), }) @classmethod def __register__(cls, module_name): pool = Pool() Party = pool.get('party.party') TableHandler = backend.get('TableHandler') cursor = Transaction().cursor party = Party.__table__() super(PartyIdentifier, cls).__register__(module_name) party_h = TableHandler(cursor, Party, module_name) if (party_h.column_exist('vat_number') and party_h.column_exist('vat_country')): identifiers = [] cursor.execute(*party.select( party.id, party.vat_number, party.vat_country, where=(party.vat_number != Null) | (party.vat_country != Null))) for party_id, number, country in cursor.fetchall(): code = (country or '') + (number or '') if not code: continue type = None if vat.is_valid(code): type = 'eu_vat' identifiers.append( cls(party=party_id, code=code, type=type)) cls.save(identifiers) party_h.drop_column('vat_number') party_h.drop_column('vat_country') @classmethod def get_types(cls): return [ (None, ''), ('eu_vat', 'VAT'), ] @fields.depends('type', 'code') def on_change_with_code(self): if self.type == 'eu_vat': try: return vat.compact(self.code) except stdnum.exceptions.ValidationError: pass return self.code @classmethod def validate(cls, identifiers): super(PartyIdentifier, cls).validate(identifiers) for identifier in identifiers: identifier.check_code() def check_code(self): if self.type == 'eu_vat': if not vat.is_valid(self.code): self.raise_user_error('invalid_vat', { 'code': self.code, 'party': self.party.rec_name, })
class Carrier(ModelSQL, ModelView): 'Carrier' __name__ = 'carrier' party = fields.Many2One('party.party', 'Party', required=True, ondelete='CASCADE', help="The party which represents the carrier.") carrier_product = fields.Many2One( 'product.product', 'Carrier Product', required=True, domain=[ ('type', '=', 'service'), ('template.type', '=', 'service'), ], help="The product to invoice the carrier service.") carrier_cost_method = fields.Selection( [ ('product', 'Product Price'), ], 'Carrier Cost Method', required=True, help='Method to compute carrier cost.') @staticmethod def default_carrier_cost_method(): return 'product' def get_rec_name(self, name): return '%s - %s' % (self.party.rec_name, self.carrier_product.rec_name) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [ bool_op, ('party.rec_name', ) + tuple(clause[1:]), ('carrier_product.rec_name', ) + tuple(clause[1:]), ] def get_sale_price(self): 'Compute carrier sale price with currency' User = Pool().get('res.user') if self.carrier_cost_method == 'product': user = User(Transaction().user) return self.carrier_product.list_price, user.company.currency.id return 0, None def get_purchase_price(self): 'Compute carrier purchase price with currency' User = Pool().get('res.user') if self.carrier_cost_method == 'product': user = User(Transaction().user) return self.carrier_product.cost_price, user.company.currency.id return 0, None @classmethod def create(cls, *args, **kwargs): pool = Pool() CarrierSelection = pool.get('carrier.selection') carriers = super(Carrier, cls).create(*args, **kwargs) CarrierSelection._get_carriers_cache.clear() return carriers @classmethod def delete(cls, *args, **kwargs): pool = Pool() CarrierSelection = pool.get('carrier.selection') super(Carrier, cls).delete(*args, **kwargs) CarrierSelection._get_carriers_cache.clear()
class ProductProductAttribute(ModelSQL, ModelView): "Product's Product Attribute" __name__ = 'product.product.attribute' product = fields.Many2One("product.product", "Product", select=True, required=True, ondelete='CASCADE') attribute = fields.Many2One("product.attribute", "Attribute", required=True, select=True, domain=[('sets', '=', Eval('attribute_set'))], depends=['attribute_set'], ondelete='RESTRICT') attribute_type = fields.Function( fields.Selection(ATTRIBUTE_TYPES, "Attribute Type"), 'get_attribute_type') attribute_set = fields.Function( fields.Many2One("product.attribute.set", "Attribute Set"), 'get_attribute_set') value = fields.Function(fields.Char('Attribute Value'), getter='get_value') value_char = fields.Char("Value Char", states={ 'required': Eval('attribute_type') == 'char', 'invisible': ~(Eval('attribute_type') == 'char'), }, depends=['attribute_type']) value_numeric = fields.Numeric("Value Numeric", states={ 'required': Eval('attribute_type') == 'numeric', 'invisible': ~(Eval('attribute_type') == 'numeric'), }, depends=['attribute_type']) value_float = fields.Float("Value Float", states={ 'required': Eval('attribute_type') == 'float', 'invisible': ~(Eval('attribute_type') == 'float'), }, depends=['attribute_type']) value_selection = fields.Many2One( "product.attribute.selection_option", "Value Selection", domain=[('attribute', '=', Eval('attribute'))], states={ 'required': Eval('attribute_type') == 'selection', 'invisible': ~(Eval('attribute_type') == 'selection'), }, depends=['attribute', 'attribute_type'], ondelete='RESTRICT') value_boolean = fields.Boolean("Value Boolean", states={ 'required': Eval('attribute_type') == 'boolean', 'invisible': ~(Eval('attribute_type') == 'boolean'), }, depends=['attribute_type']) value_integer = fields.Integer("Value Integer", states={ 'required': Eval('attribute_type') == 'integer', 'invisible': ~(Eval('attribute_type') == 'integer'), }, depends=['attribute_type']) value_date = fields.Date("Value Date", states={ 'required': Eval('attribute_type') == 'date', 'invisible': ~(Eval('attribute_type') == 'date'), }, depends=['attribute_type']) value_datetime = fields.DateTime( "Value Datetime", states={ 'required': Eval('attribute_type') == 'datetime', 'invisible': ~(Eval('attribute_type') == 'datetime'), }, depends=['attribute_type']) @fields.depends('attribute') def on_change_attribute(self): self.attribute_type = self.attribute and self.attribute.type_ or None def get_attribute_type(self, name=None): """ Returns type of attribute """ return self.attribute.type_ def get_value(self, name=None): """ Consolidated method to return attribute value """ if self.attribute_type == 'selection': return self.value_selection.name if self.attribute_type == 'datetime': # XXX: Localize to the timezone in context return self.value_datetime.strftime("%Y-%m-%d %H:%M:%S") if self.attribute_type == 'date': return datetime.combine(self.value_date, time()). \ strftime("%Y-%m-%d") else: return unicode(getattr(self, 'value_' + self.attribute_type)) def get_attribute_set(self, name=None): """ Returns attribute set for corresponding product's template """ if self.product and self.product.template and \ self.product.template.attribute_set: return self.product.template.attribute_set.id @fields.depends('product') def on_change_product(self): if self.product and self.product.template.attribute_set: self.attribute_set = self.product.template.attribute_set.id
class DocumentInvoice(ModelWorkflow, ModelSQL, ModelView): "Documents (Invoice) " _name = 'ekd.document.head.invoice' _description = __doc__ #_table='documents_document_stock' _inherits = {'ekd.document': 'document'} document = fields.Many2One('ekd.document', 'Document', required=True, ondelete='CASCADE') direct_document = fields.Selection([ ('invoice_revenue', 'For Customer'), ('invoice_expense', 'From Supplier'), ], 'Direct Document') type_product = fields.Selection([ ('fixed_assets', 'Fixed Assets'), ('intangible', 'Intangible assets'), ('material', 'Material'), ('goods', 'Goods'), ], 'Type Product') template_invoice = fields.Function(fields.Many2One( 'ekd.document.template', 'Document Name', domain=[('type_account', '=', Eval('direct_document', Get(Eval('context', {}), 'direct_document')))]), 'get_fields', setter='set_fields') number_doc = fields.Function(fields.Char( 'Number Document', states={ 'invisible': And(Not(Bool(Eval('number_doc'))), Not(Equal(Eval('direct_document', ''), 'invoice_expense'))) }), 'get_fields', setter='set_fields', searcher='template_search') from_to_party = fields.Function(fields.Many2One('party.party', 'Party'), 'get_fields', setter='set_fields') lines = fields.One2Many('ekd.document.line.product', 'invoice', 'Lines', context={'type_product': Eval('type_product')}) amount_doc = fields.Function(fields.Numeric( 'Amount', digits=(16, Eval('currency_digits', 2))), 'get_fields', setter='set_fields') amount_tax = fields.Function( fields.Numeric('Amount Tax', digits=(16, Eval('currency_digits', 2))), 'get_fields') currency = fields.Many2One('currency.currency', 'Currency') currency_digits = fields.Function( fields.Integer('Currency Digits', on_change_with=['currency']), 'get_currency_digits') payment_term = fields.Many2One('ekd.document.payment_term', 'Payment Term', required=True, states=_STATES) form_payment = fields.Selection([ ('cash', 'Cash payment'), ('bank', 'Bank payment'), ('post', 'Post payment'), ('internet', 'Internet payment'), ], 'Form of payment') kind_payment = fields.Selection([ ('postpay', 'Postpay'), ('prepayment', 'Prepayment'), ('advance', 'Down payment'), ], 'Kind of payment') type_tax = fields.Selection([ ('not_vat', 'Not VAT'), ('including', 'Including'), ('over_amount', 'Over Amount'), ], 'Type Compute Tax') state_doc = fields.Function(fields.Selection(_STATE_INVOICE, required=True, readonly=True, string='State'), 'get_fields', setter='set_fields') deleting = fields.Boolean('Flag deleting', readonly=True) def __init__(self): super(DocumentInvoice, self).__init__() self._rpc.update({ 'button_add_number': True, 'button_draft': True, 'button_to_pay': True, 'button_part_paid': True, 'button_paid': True, 'button_received': True, 'button_cancel': True, 'button_restore': True, 'button_print': True, 'button_send': True, 'button_post': True, 'button_obtained': True, 'button_delivered': True, }) self._order.insert(0, ('date_document', 'ASC')) self._order.insert(1, ('template', 'ASC')) def default_state_doc(self): return Transaction().context.get('state') or 'draft' def default_type_product(self): return Transaction().context.get('type_product') or 'material' def default_from_to_party_doc(self): return Transaction().context.get('from_to_party') or False def default_direct_document(self): return Transaction().context.get( 'direct_document') or 'invoice_revenue' def default_template_invoice(self): context = Transaction().context if context.get('template_invoice', False): return context.get('template_invoice') else: template_obj = self.pool.get('ekd.document.template') template_ids = template_obj.search( [('type_account', '=', context.get('direct_document'))], order=[('sequence', 'ASC')]) if len(template_ids) > 0: return template_ids[0] def default_currency(self): company_obj = self.pool.get('company.company') company = company_obj.browse(Transaction().context['company']) return company.currency.id def default_currency_digits(self): company_obj = self.pool.get('company.company') context = Transaction().context if context.get('company'): company = company_obj.browse(context['company']) return company.currency.digits return 2 def get_currency_digits(self, ids, name): res = {} for line in self.browse(ids): res[line.id] = line.currency and line.currency.digits or 2 return res def default_payment_term(self): payment_term_obj = self.pool.get('ekd.document.payment_term') payment_term_ids = payment_term_obj.search(self.payment_term.domain) if len(payment_term_ids) == 1: return payment_term_ids[0] return False def default_amount(self): return Transaction().context.get('amount') or Decimal('0.0') def default_company(self): return Transaction().context.get('company') or False def get_fields(self, ids, names): if not ids: return {} res = {} for line in self.browse(ids): for name in names: res.setdefault(name, {}) res[name].setdefault(line.id, False) if name == 'number_doc': if line.direct_document == 'invoice_revenue': res[name][line.id] = line.number_our else: res[name][line.id] = line.number_in elif name == 'state_doc': res[name][line.id] = line.state elif name == 'amount_doc': for line_spec in line.lines: if line_spec.type == 'line': res[name][line.id] += line_spec.amount elif name == 'amount_tax': for line_spec in line.lines: if line_spec.type == 'line': res[name][line.id] += line_spec.amount_tax elif name == 'from_to_party': if line.direct_document == 'invoice_revenue': res[name][line.id] = line.from_party.id else: res[name][line.id] = line.to_party.id elif name == 'template_invoice': res[name][line.id] = line.template.id return res def set_fields(self, ids, name, value): if isinstance(ids, list): ids = ids[0] if not value: return document = self.browse(ids) if name == 'state_doc': self.write(ids, { 'state': value, }) elif name == 'template_invoice': self.write(ids, { 'template': value, }) elif name == 'number_doc': if document.direct_document == 'invoice_revenue': self.write(ids, { 'number_our': value, }) else: self.write(ids, { 'number_in': value, }) elif name == 'amount_doc': self.write(ids, { 'amount': value, }) elif name == 'from_to_party': if document.direct_document == 'invoice_revenue': self.write(ids, { 'from_party': value, }) else: self.write(ids, { 'to_party': value, }) def template_select_get(self): context = Transaction().context template_obj = self.pool.get('ekd.document.template') raise Exception(str(context)) template_ids = template_obj.search([ 'type_account', '=', context.get('direct_document', 'invoice_revenue') ]) res = [] for template in template_obj.browse(template_ids): res.append([template.id, template.name]) return res def template_search(self, name, domain=[]): if name == 'template_invoice': for table, exp, value in domain: return [('template', 'ilike', value)] elif name == 'from_party_doc': document_obj = self.pool.get('ekd.document') table, exp, value = domain[0] find_ids = document_obj.search([('from_party', 'ilike', value)]) return [('document', 'in', find_ids)] elif name == 'to_party_doc': document_obj = self.pool.get('ekd.document') table, exp, value = domain[0] find_ids = document_obj.search([('to_party', 'ilike', value)]) return [('document', 'in', find_ids)] def get_rec_name(self, ids, name): if not ids: return {} return self.pool.get('ekd.document').get_rec_name(ids, name) def documents_base_get(self): dictions_obj = self.pool.get('ir.dictions') res = [] diction_ids = dictions_obj.search([ ('model', '=', self._name), ('pole', '=', 'document_base'), ], order=[('sequence', 'ASC')]) if diction_ids: for diction in dictions_obj.browse(diction_ids): res.append([diction.key, diction.value]) return res def on_change_document_base(self, vals): if not vals.get('document_base'): return {} model, model_id = vals.get('document_base').split(',') if not model or model_id == '0': return {} model_obj = self.pool.get(model) model_line = model_obj.browse(int(model_id)) lines_new = {} field_import = [ 'product_ref', 'type_tax', 'tax', 'type', 'currency', 'unit', 'currency_digits', 'unit_price', 'amount_tax', 'product', 'description', 'type_product', 'unit_digits', 'amount', 'quantity' ] if hasattr(model_obj, 'lines'): for line_new in model_line.lines: if line_new.type == 'line': lines_new.setdefault('add', []).append( line_new.read(line_new.id, field_import)) return { 'from_to_party': model_line.from_to_party.id, 'parent': int(model_id), 'lines': lines_new } else: return { 'from_to_party': model_line.from_to_party.id, 'parent': int(model_id) } def create(self, vals): later = {} vals = vals.copy() cursor = Transaction().cursor for field in vals: if field in self._columns\ and hasattr(self._columns[field], 'set'): later[field] = vals[field] for field in later: del vals[field] if cursor.nextid(self._table): cursor.setnextid(self._table, cursor.currid(self._table)) new_id = super(DocumentInvoice, self).create(vals) invoice = self.browse(new_id) new_id = invoice.document.id cr = Transaction().cursor cr.execute('UPDATE "' + self._table + '" SET id = %s '\ 'WHERE id = %s', (invoice.document.id, invoice.id)) ModelStorage.delete(self, invoice.id) self.write(new_id, later) res = self.browse(new_id) return res.id def delete(self, ids): cr = Transaction().cursor doc_lines_obj = self.pool.get('ekd.document.line.product') for document in self.browse(ids): if document.state == 'deleted' and document.deleting: return super(DocumentInvoice, self).delete(ids) else: doc_lines_obj.write([x.id for x in document.lines], { 'state': 'deleted', 'deleting': True }) self.write(document.id, {'state': 'deleted', 'deleting': True}) return True def button_add_number(self, ids): return self.new_number(ids) def button_post(self, ids): return self.post(ids) def button_obtained(self, ids): return self.obtained(ids) def button_delivered(self, ids): return self.delivered(ids) def button_paid(self, ids): return self.paid(ids) def button_part_paid(self, ids): return self.part_paid(ids) def button_to_pay(self, ids): return self.to_pay(ids) def button_send(self, ids): return self.send(ids) def button_print(self, ids): return self.print_document(ids) def button_cancel(self, ids): return self.cancel(ids) def button_draft(self, ids): return self.draft(ids) def button_restore(self, ids): return self.draft(ids) def new_number(self, ids): sequence_obj = self.pool.get('ir.sequence') for document in self.browse(ids): if document.template and document.template.sequence: sequence = sequence_obj.get_id(document.template.sequence.id) else: raise Exception('Error', 'Sequence for document not find!') self.write(document.id, { 'number_doc': sequence, }) def post(self, ids): return self.write(ids, { 'state': 'posted', }) def obtained(self, ids): return self.write(ids, { 'state': 'obtained', }) def delivered(self, ids): return self.write(ids, { 'state': 'delivered', }) def send(self, ids): return self.write(ids, { 'state': 'sended', }) def to_pay(self, ids): return self.write(ids, { 'state': 'to_pay', }) def paid(self, ids): return self.write(ids, { 'state': 'paid', }) def part_paid(self, ids): return self.write(ids, { 'state': 'part_paid', }) def print_document(self, ids): return self.write(ids, { 'state': 'printed', }) def draft(self, ids): cr = Transaction().cursor doc_lines_obj = self.pool.get('ekd.document.line.product') for document in self.browse(ids): doc_lines_obj.write([x.id for x in document.lines], { 'state': 'draft', 'deleting': False }) self.write(document.id, {'state': 'draft', 'deleting': False}) def cancel(self, ids): return self.write(ids, { 'state': 'canceled', })
class UPSConfiguration(ModelSingleton, ModelSQL, ModelView): """ Configuration settings for UPS. """ __name__ = 'ups.configuration' license_key = fields.Char('UPS License Key', required=True) user_id = fields.Char('UPS User Id', required=True) password = fields.Char('UPS User Password', required=True) shipper_no = fields.Char('UPS Shipper Number', required=True) is_test = fields.Boolean('Is Test') negotiated_rates = fields.Boolean('Use negotiated rates') uom_system = fields.Selection([ ('00', 'Metric Units Of Measurement'), ('01', 'English Units Of Measurement'), ], 'UOM System', required=True) weight_uom = fields.Function( fields.Many2One('product.uom', 'Weight UOM'), 'get_default_uom' ) weight_uom_code = fields.Function( fields.Char('Weight UOM code'), 'get_uom_code' ) length_uom = fields.Function( fields.Many2One('product.uom', 'Length UOM'), 'get_default_uom' ) @staticmethod def default_uom_system(): return '01' @property def logger(self): """ Returns logger instance for UPS """ return Logger('trytond_ups') def get_default_uom(self, name): """ Return default UOM on basis of uom_system """ UOM = Pool().get('product.uom') uom_map = { '00': { # Metric 'weight': 'kg', 'length': 'cm', }, '01': { # English 'weight': 'lb', 'length': 'in', } } return UOM.search([ ('symbol', '=', uom_map[self.uom_system][name[:-4]]) ])[0].id def get_uom_code(self, name): """ Return UOM code names depending on the system """ uom_map = { '00': { # Metric 'weight_uom_code': 'KGS', 'length_uom_code': 'cm', }, '01': { # English 'weight_uom_code': 'LBS', 'length_uom_code': 'in', } } return uom_map[self.uom_system][name] @classmethod def __setup__(cls): super(UPSConfiguration, cls).__setup__() cls._error_messages.update({ 'ups_credentials_required': 'UPS settings on UPS configuration are incomplete.', }) def api_instance(self, call='confirm', return_xml=False): """Return Instance of UPS """ if not all([ self.license_key, self.user_id, self.password, self.uom_system, ]): self.raise_user_error('ups_credentials_required') if call == 'confirm': call_method = ShipmentConfirm elif call == 'accept': call_method = ShipmentAccept elif call == 'void': call_method = ShipmentVoid elif call == 'rate': call_method = RatingService elif call == 'address_val': call_method = AddressValidation else: call_method = None if call_method: return call_method( license_no=self.license_key, user_id=self.user_id, password=self.password, sandbox=self.is_test, return_xml=return_xml )
class BalancePartyPeriod(ModelSQL, ModelView): "Turnover and Balances parties (Period)" _name = "ekd.balances.party.period" _description =__doc__ _order_name = 'period.end_date' def get_balance_end(self, ids, names): if not ids: return {} res={} for balance in self.browse(ids): type_balance = balance.account.type_balance for name in names: res.setdefault(name, {}) res[name].setdefault(balance.id, Decimal('0.0')) amount = balance.balance_dt-balance.balance_ct+balance.debit-balance.credit if name == 'balance_end': res[name][balance.id] = amount elif name == 'balance': res[name][balance.id] = balance.balance_dt-balance.balance_ct elif name == 'balance_dt_end': if type_balance == 'active' or (type_balance == 'both' and amount > 0): res[name][balance.id] = amount elif name == 'balance_ct_end': if type_balance == 'passive' or (type_balance == 'both' and amount < 0): res[name][balance.id] = -amount return res account = fields.Many2One('ekd.balances.party', 'Analytic Account', required=True, select=2, ondelete="CASCADE") period = fields.Many2One('ekd.period', 'Period', select=2, domain=[ ('company','=',Eval('company')) ],) balance_dt = fields.Numeric('Debit Start', digits=(16, Eval('currency_digits', 2))) balance_ct = fields.Numeric('Credit Start', digits=(16, Eval('currency_digits', 2))) debit = fields.Numeric('Debit Turnover', digits=(16, Eval('currency_digits', 2))) credit = fields.Numeric('Credit Turnover', digits=(16, Eval('currency_digits', 2))) balance_end_dt = fields.Numeric('Debit End', digits=(16, Eval('currency_digits', 2))) balance_end_ct = fields.Numeric('Credit End', digits=(16, Eval('currency_digits', 2))) balance_dt_end = fields.Function(fields.Numeric('Debit End', digits=(16, Eval('currency_digits', 2))), 'get_balance_end') balance_ct_end = fields.Function(fields.Numeric('Credit End', digits=(16, Eval('currency_digits', 2))), 'get_balance_end') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'currency_digits_get') dt_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry debit lines'), 'get_entry') ct_line = fields.Function(fields.One2Many('ekd.account.move.line', None, 'Ref entry credit lines'), 'get_entry') state = fields.Selection([ ('draft','Draft'), ('open','Open'), ('done','Closed'), ('deleted','Deleted') ], 'State', required=True) parent = fields.Many2One('ekd.balances.party.period','ID Parent balance') transfer = fields.Many2One('ekd.balances.party.period','ID Transfer balance') deleted = fields.Boolean('Flag Deleting') active = fields.Boolean('Active') def __init__(self): super(BalancePartyPeriod, self).__init__() self._order.insert(0, ('period', 'DESC')) self._sql_constraints += [ ('balance_party_uniq', 'UNIQUE(account,period)',\ 'period, account - must be unique per balance!'), ] def init(self, module_name): cursor = Transaction().cursor super(BalancePartyPeriod, self).init(module_name) table = TableHandler(cursor, self, module_name) # Проверяем счетчик cursor.execute("SELECT last_value, increment_by FROM %s"%table.sequence_name) last_value, increment_by = cursor.fetchall()[0] # Устанавливаем счетчик if str(last_value)[len(str(last_value))-1] != str(_ID_TABLES_BALANCES_PERIOD[self._table]): cursor.execute("SELECT setval('"+table.sequence_name+"', %s, true)"%_ID_TABLES_BALANCES_PERIOD[self._table]) if increment_by != 10: cursor.execute("ALTER SEQUENCE "+table.sequence_name+" INCREMENT 10") def default_currency_digits(self): return 2 def default_active(self): return True def default_state(self): return 'draft' def currency_digits_get(self, ids, name): res = {}.fromkeys(ids, 2) for line in self.browse(ids): if line.account.account.currency: res[line.id] = line.account.account.currency.digits or 2 return res def get_entry(self, ids, name): move_line = self.pool.get('ekd.account.move.line') move_line_analytic_dt = self.pool.get('ekd.account.move.line.analytic_dt') move_line_analytic_ct = self.pool.get('ekd.account.move.line.analytic_ct') res = {} for balance in self.browse(ids): if name == 'dt_line': line_analytic_dt = move_line_analytic_dt.search([('ref_period','=', balance.id)]) res[balance.id] = move_line_analytic_dt.read(line_analytic_ct, ['move_line']) elif name == 'ct_line': line_analytic_ct = move_line_analytic_ct.search([('ref_period','=', balance.id)]) res[balance.id] = move_line_analytic_ct.read(line_analytic_ct, ['move_line']) return res def set_entries_field(self, id, name, value): assert name in ('dt_line', 'ct_line', 'lines'), 'Invalid name' return def search_domain(self, domain, active_test=True): domain_new=[] if domain[0] == 'AND': for (left_val, center_val, rigth_val) in domain[1]: if left_val == 'period.start_date': rigth_val = datetime.datetime.now() elif left_val == 'period.end_date': rigth_val = datetime.datetime.now() domain_new.append((left_val, center_val, rigth_val)) else: for (left_val, center_val, rigth_val) in domain: if left_val == 'period.start_date': rigth_val = datetime.datetime.now() elif left_val == 'period.end_date': rigth_val = datetime.datetime.now() domain_new.append((left_val, center_val, rigth_val)) return super(BalancePartyPeriod, self).search_domain(domain_new, active_test=active_test) # Процедура переноса остатков (если есть более поздние) def transfer_balance(self, transfer_id, vals): balance = self.browse(transfer_id) self.write(transfer_id, { 'balance_dt': vals.get('balance_dt'), 'balance_ct': vals.get('balance_ct'), }) if balance.transfer and vals.get('transfer', True): self.transfer_balance(balance.transfer.id, { 'balance_dt':balance.balance_dt_end, 'balance_ct':balance.balance_ct_end, 'transfer': vals.get('transfer', True), }) def transfer_balances(self, vals=None): ''' Transfer Balances of account - Перенос остатков. period - словарь идентификаторов периодов (периоды уже отсортированны!!!) ''' if vals is None and not vals.get('company', False) and not vals.get('account', False): return False balance_ids= {} for period in vals.get('periods'): if not balance_ids: balance_ids = self.search([ ('period','=',period), ('account','=', vals.get('account')), ]) continue for balance_id in balance_ids: balance_line = self.browse(balance_id) if balance_line.balance_dt_end or balance_line.balance_ct_end: if balance_line.transfer: self.transfer_balance(balance_line.transfer.id, { 'balance_dt': balance_line.balance_dt_end, 'balance_ct': balance_line.balance_ct_end, }) else: balance_new_id = self.search([ ('period','=',period), ('account','=',vals.get('account')), ('party','=',balance_line.party.id), ('model_ref','=',balance_line.model_ref), ]) if balance_new_id: self.write(balance_line.id, { 'transfer': balance_new_id, }) self.write(balance_new_id, { 'balance_dt': balance_line.balance_dt_end, 'balance_ct': balance_line.balance_ct_end, }) else: self.write(balance_line.id, { 'transfer': self.create({ 'company': vals.get('company'), 'period': period, 'account': balance_line.account.id , 'party': balance_line.party.id, 'model_ref': balance_line.model_ref, 'balance_dt': balance_line.balance_dt_end, 'balance_ct': balance_line.balance_ct_end, }) }) balance_ids = self.search([ ('period','=',period), ('account','=', vals.get('account')), ]) return True
class ProductSaleChannelListing: "Product Sale Channel" __name__ = 'product.product.channel_listing' price_tiers = fields.One2Many( 'product.price_tier', 'product_listing', 'Price Tiers' ) magento_product_type = fields.Selection( [ (None, ''), ('simple', 'Simple'), ('configurable', 'Configurable'), ('grouped', 'Grouped'), ('bundle', 'Bundle'), ('virtual', 'Virtual'), ('downloadable', 'Downloadable'), ], 'Magento Product Type', readonly=True, states={ "invisible": Eval('channel_source') != 'magento' }, depends=['channel_source'] ) @classmethod def __setup__(cls): super(ProductSaleChannelListing, cls).__setup__() cls._error_messages.update({ 'multi_inventory_update_fail': "FaultCode: %s, FaultMessage: %s", }) @classmethod def create_from(cls, channel, product_data): """ Create a listing for the product from channel and data """ Product = Pool().get('product.product') if channel.source != 'magento': return super(ProductSaleChannelListing, cls).create_from( channel, product_data ) try: product, = Product.search([ ('code', '=', product_data['sku']), ]) except ValueError: cls.raise_user_error("No product found for mapping") listing = cls( channel=channel, product=product, # Do not match with SKU. Magento f***s up when there are # numeric SKUs product_identifier=product_data['product_id'], magento_product_type=product_data['type'], ) listing.save() return listing def export_inventory(self): """ Export inventory of this listing """ if self.channel.source != 'magento': return super(ProductSaleChannelListing, self).export_inventory() return self.export_bulk_inventory([self]) @classmethod def export_bulk_inventory(cls, listings): """ Bulk export inventory to magento. Do not rely on the return value from this method. """ SaleChannelListing = Pool().get('product.product.channel_listing') if not listings: # Nothing to update return non_magento_listings = cls.search([ ('id', 'in', map(int, listings)), ('channel.source', '!=', 'magento'), ]) if non_magento_listings: super(ProductSaleChannelListing, cls).export_bulk_inventory( non_magento_listings ) magento_listings = filter( lambda l: l not in non_magento_listings, listings ) log.info( "Fetching inventory of %d magento listings" % len(magento_listings) ) inventory_channel_map = defaultdict(list) for listing in magento_listings: channel = listing.channel product_data = { 'qty': listing.quantity, } # TODO: Get this from availability used if listing.magento_product_type == 'simple': # Only send inventory for simple products product_data['is_in_stock'] = '1' \ if listing.quantity > 0 else '0' else: # configurable, bundle and everything else product_data['is_in_stock'] = '1' # group inventory xml by channel inventory_channel_map[channel].append([ listing.product_identifier, product_data ]) for channel, product_data_list in inventory_channel_map.iteritems(): with magento.Inventory( channel.magento_url, channel.magento_api_user, channel.magento_api_key) as inventory_api: for product_data_batch in batch(product_data_list, 50): log.info( "Pushing inventory of %d products to magento" % len(product_data_batch) ) response = inventory_api.update_multi(product_data_batch) # Magento bulk API will not raise Faults. # Instead the response contains the faults as a dict for i, result in enumerate(response): if result is not True: if result.get('isFault') is True and \ result['faultCode'] == '101': listing, = SaleChannelListing.search([ ('product_identifier', '=', product_data_batch[i][0]), # noqa ('channel', '=', channel.id), ]) listing.state = 'disabled' listing.save() else: cls.raise_user_error( 'multi_inventory_update_fail', (result['faultCode'], result['faultMessage']) # noqa )
class Account(DeactivableMixin, tree('distribution_parents'), tree(), ModelSQL, ModelView): 'Analytic Account' __name__ = 'analytic_account.account' name = fields.Char('Name', required=True, translate=True, select=True) code = fields.Char('Code', select=True) company = fields.Many2One('company.company', 'Company', required=True) currency = fields.Function( fields.Many2One('currency.currency', 'Currency'), 'on_change_with_currency') type = fields.Selection([ ('root', 'Root'), ('view', 'View'), ('normal', 'Normal'), ('distribution', 'Distribution'), ], 'Type', required=True) root = fields.Many2One('analytic_account.account', 'Root', select=True, domain=[ ('company', '=', Eval('company', -1)), ('parent', '=', None), ('type', '=', 'root'), ], states={ 'invisible': Eval('type') == 'root', 'required': Eval('type') != 'root', }) parent = fields.Many2One('analytic_account.account', 'Parent', select=True, domain=[ 'OR', ('root', '=', Eval('root', -1)), ('parent', '=', None), ], states={ 'invisible': Eval('type') == 'root', 'required': Eval('type') != 'root', }) childs = fields.One2Many('analytic_account.account', 'parent', 'Children', states={ 'invisible': Eval('id', -1) < 0, }, domain=[ ('company', '=', Eval('company', -1)), ]) balance = fields.Function( Monetary("Balance", currency='currency', digits='currency'), 'get_balance') credit = fields.Function( Monetary("Credit", currency='currency', digits='currency'), 'get_credit_debit') debit = fields.Function( Monetary("Debit", currency='currency', digits='currency'), 'get_credit_debit') state = fields.Selection([ ('draft', 'Draft'), ('opened', 'Opened'), ('closed', 'Closed'), ], "State", required=True, sort=False) note = fields.Text('Note') distributions = fields.One2Many('analytic_account.account.distribution', 'parent', "Distributions", states={ 'invisible': Eval('type') != 'distribution', 'required': Eval('type') == 'distribution', }) distribution_parents = fields.Many2Many( 'analytic_account.account.distribution', 'account', 'parent', "Distribution Parents", readonly=True) @classmethod def __setup__(cls): super(Account, cls).__setup__() cls._order.insert(0, ('code', 'ASC')) cls._order.insert(1, ('name', 'ASC')) @classmethod def __register__(cls, module_name): super(Account, cls).__register__(module_name) table = cls.__table_handler__(module_name) # Migration from 4.0: remove currency table.not_null_action('currency', action='remove') # Migration from 5.0: remove display_balance table.drop_column('display_balance') @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_type(): return 'normal' @staticmethod def default_state(): return 'draft' @classmethod def validate_fields(cls, accounts, field_names): super().validate_fields(accounts, field_names) cls.check_distribution(accounts, field_names) @classmethod def check_distribution(cls, accounts, field_names=None): if field_names and not (field_names & {'distributions', 'type'}): return for account in accounts: if account.type != 'distribution': return if sum((d.ratio for d in account.distributions)) != 1: raise AccountValidationError( gettext('analytic_account.msg_invalid_distribution', account=account.rec_name)) @fields.depends('company') def on_change_with_currency(self, name=None): if self.company: return self.company.currency.id @fields.depends('parent', 'type', '_parent_parent.id', '_parent_parent.root', '_parent_parent.type') def on_change_parent(self): if (self.parent and self.parent.id is not None and self.parent.id > 0 and self.type != 'root'): if self.parent.type == 'root': self.root = self.parent else: self.root = self.parent.root else: self.root = None @classmethod def get_balance(cls, accounts, name): pool = Pool() Line = pool.get('analytic_account.line') MoveLine = pool.get('account.move.line') cursor = Transaction().connection.cursor() table = cls.__table__() line = Line.__table__() move_line = MoveLine.__table__() ids = [a.id for a in accounts] childs = cls.search([('parent', 'child_of', ids)]) all_ids = list({}.fromkeys(ids + [c.id for c in childs]).keys()) id2account = {} all_accounts = cls.browse(all_ids) for account in all_accounts: id2account[account.id] = account line_query = Line.query_get(line) cursor.execute( *table.join(line, 'LEFT', condition=table.id == line.account).join( move_line, 'LEFT', condition=move_line.id == line.move_line). select(table.id, Sum(Coalesce(line.credit, 0) - Coalesce(line.debit, 0)), where=(table.type != 'view') & table.id.in_(all_ids) & (table.active == Literal(True)) & line_query, group_by=table.id)) account_sum = defaultdict(Decimal) for account_id, value in cursor: account_sum.setdefault(account_id, Decimal('0.0')) # SQLite uses float for SUM if not isinstance(value, Decimal): value = Decimal(str(value)) account_sum[account_id] += value balances = {} for account in accounts: balance = Decimal() childs = cls.search([ ('parent', 'child_of', [account.id]), ]) for child in childs: balance += account_sum[child.id] balances[account.id] = account.currency.round(balance) return balances @classmethod def get_credit_debit(cls, accounts, names): pool = Pool() Line = pool.get('analytic_account.line') MoveLine = pool.get('account.move.line') cursor = Transaction().connection.cursor() table = cls.__table__() line = Line.__table__() move_line = MoveLine.__table__() result = {} ids = [a.id for a in accounts] for name in names: if name not in ('credit', 'debit'): raise Exception('Bad argument') result[name] = {}.fromkeys(ids, Decimal('0.0')) id2account = {} for account in accounts: id2account[account.id] = account line_query = Line.query_get(line) columns = [table.id] for name in names: columns.append(Sum(Coalesce(Column(line, name), 0))) cursor.execute( *table.join(line, 'LEFT', condition=table.id == line.account).join( move_line, 'LEFT', condition=move_line.id == line.move_line).select(*columns, where=(table.type != 'view') & table.id.in_(ids) & (table.active == Literal(True)) & line_query, group_by=table.id)) for row in cursor: account_id = row[0] for i, name in enumerate(names, 1): value = row[i] # SQLite uses float for SUM if not isinstance(value, Decimal): value = Decimal(str(value)) result[name][account_id] += value for account in accounts: for name in names: result[name][account.id] = account.currency.round( result[name][account.id]) return result def get_rec_name(self, name): if self.code: return self.code + ' - ' + str(self.name) else: return str(self.name) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' code_value = clause[2] if clause[1].endswith('like'): code_value = lstrip_wildcard(clause[2]) return [ bool_op, ('code', clause[1], code_value) + tuple(clause[3:]), (cls._rec_name, ) + tuple(clause[1:]), ] def distribute(self, amount): "Return a list of (account, amount) distribution" assert self.type in {'normal', 'distribution'} if self.type == 'normal': return [(self, amount)] else: result = [] remainder = amount for distribution in self.distributions: account = distribution.account ratio = distribution.ratio current_amount = self.currency.round(amount * ratio) remainder -= current_amount result.extend(account.distribute(current_amount)) if remainder: i = 0 while remainder: account, current_amount = result[i] rounding = self.currency.rounding.copy_sign(remainder) result[i] = (account, current_amount + rounding) remainder -= rounding i = (i + 1) % len(result) assert sum(a for _, a in result) == amount return result
class UIMenu(sequence_ordered(), ModelSQL, ModelView): "UI menu" __name__ = 'ir.ui.menu' name = fields.Char('Menu', required=True, translate=True) childs = fields.One2Many('ir.ui.menu', 'parent', 'Children') parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=True, ondelete='CASCADE') groups = fields.Many2Many('ir.ui.menu-res.group', 'menu', 'group', 'Groups') complete_name = fields.Function(fields.Char('Complete Name'), 'get_rec_name', searcher='search_rec_name') icon = fields.Selection('list_icons', 'Icon', translate=False) action = fields.Function(fields.Reference('Action', selection=[ ('', ''), ('ir.action.report', 'ir.action.report'), ('ir.action.act_window', 'ir.action.act_window'), ('ir.action.wizard', 'ir.action.wizard'), ('ir.action.url', 'ir.action.url'), ]), 'get_action', setter='set_action') action_keywords = fields.One2Many('ir.action.keyword', 'model', 'Action Keywords') active = fields.Boolean('Active') favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite') @classmethod def __setup__(cls): super(UIMenu, cls).__setup__() cls._error_messages.update({ 'wrong_name': ('"%%s" is not a valid menu name because it is ' 'not allowed to contain "%s".' % SEPARATOR), }) @classmethod def order_complete_name(cls, tables): return cls.name.convert_order('name', tables, cls) @staticmethod def default_icon(): return 'tryton-open' @staticmethod def default_sequence(): return 10 @staticmethod def default_active(): return True @staticmethod def list_icons(): pool = Pool() Icon = pool.get('ir.ui.icon') return sorted(CLIENT_ICONS + [(name, name) for _, name in Icon.list_icons()]) @classmethod def validate(cls, menus): super(UIMenu, cls).validate(menus) cls.check_recursion(menus) for menu in menus: menu.check_name() def check_name(self): if SEPARATOR in self.name: self.raise_user_error('wrong_name', (self.name,)) def get_rec_name(self, name): parent = self.parent name = self.name while parent: name = parent.name + SEPARATOR + name parent = parent.parent return name @classmethod def search_rec_name(cls, name, clause): if isinstance(clause[2], basestring): values = clause[2].split(SEPARATOR.strip()) values.reverse() domain = [] field = 'name' for name in values: domain.append((field, clause[1], name.strip())) field = 'parent.' + field else: domain = [('name',) + tuple(clause[1:])] ids = [m.id for m in cls.search(domain, order=[])] return [('parent', 'child_of', ids)] @classmethod def search_global(cls, text): # TODO improve search clause for record in cls.search([ ('rec_name', 'ilike', '%%%s%%' % text), ]): if record.action: yield record, record.rec_name, record.icon @classmethod def search(cls, domain, offset=0, limit=None, order=None, count=False, query=False): menus = super(UIMenu, cls).search(domain, offset=offset, limit=limit, order=order, count=False, query=query) if query: return menus if menus: parent_ids = {x.parent.id for x in menus if x.parent} parents = set() for sub_parent_ids in grouped_slice(parent_ids): parents.update(cls.search([ ('id', 'in', list(sub_parent_ids)), ])) menus = [x for x in menus if (x.parent and x.parent in parents) or not x.parent] if count: return len(menus) return menus @classmethod def get_action(cls, menus, name): pool = Pool() actions = dict((m.id, None) for m in menus) with Transaction().set_context(active_test=False): menus = cls.browse(menus) action_keywords = sum((list(m.action_keywords) for m in menus), []) key = lambda k: k.action.type action_keywords.sort(key=key) for type, action_keywords in groupby(action_keywords, key=key): action_keywords = list(action_keywords) for action_keyword in action_keywords: model = action_keyword.model actions[model.id] = '%s,-1' % type Action = pool.get(type) action2keyword = {k.action.id: k for k in action_keywords} with Transaction().set_context(active_test=False): factions = Action.search([ ('action', 'in', action2keyword.keys()), ]) for action in factions: model = action2keyword[action.id].model actions[model.id] = str(action) return actions @classmethod def set_action(cls, menus, name, value): pool = Pool() ActionKeyword = pool.get('ir.action.keyword') action_keywords = [] transaction = Transaction() for i in range(0, len(menus), transaction.database.IN_MAX): sub_menus = menus[i:i + transaction.database.IN_MAX] action_keywords += ActionKeyword.search([ ('keyword', '=', 'tree_open'), ('model', 'in', [str(menu) for menu in sub_menus]), ]) if action_keywords: with Transaction().set_context(_timestamp=False): ActionKeyword.delete(action_keywords) if not value: return if isinstance(value, basestring): action_type, action_id = value.split(',') else: action_type, action_id = value if int(action_id) <= 0: return Action = pool.get(action_type) action = Action(int(action_id)) to_create = [] for menu in menus: with Transaction().set_context(_timestamp=False): to_create.append({ 'keyword': 'tree_open', 'model': str(menu), 'action': action.action.id, }) if to_create: ActionKeyword.create(to_create) @classmethod def get_favorite(cls, menus, name): pool = Pool() Favorite = pool.get('ir.ui.menu.favorite') user = Transaction().user favorites = Favorite.search([ ('menu', 'in', [m.id for m in menus]), ('user', '=', user), ]) menu2favorite = dict((m.id, False if m.action else None) for m in menus) menu2favorite.update(dict((f.menu.id, True) for f in favorites)) return menu2favorite
class ApacheII(ModelSQL, ModelView): 'Apache II scoring' __name__ = 'gnuhealth.icu.apache2' name = fields.Many2One('gnuhealth.inpatient.registration', 'Registration Code', required=True) score_date = fields.DateTime('Date', help="Date of the score", required=True) age = fields.Integer('Age', help='Patient age in years') temperature = fields.Float('Temperature', help='Rectal temperature') mean_ap = fields.Integer('MAP', help='Mean Arterial Pressure') heart_rate = fields.Integer('Heart Rate') respiratory_rate = fields.Integer('Respiratory Rate') fio2 = fields.Float('FiO2') pao2 = fields.Integer('PaO2') paco2 = fields.Integer('PaCO2') aado2 = fields.Integer('A-a DO2', on_change_with=['fio2', 'pao2', 'paco2']) ph = fields.Float('pH') serum_sodium = fields.Integer('Sodium') serum_potassium = fields.Float('Potassium') serum_creatinine = fields.Float('Creatinine') arf = fields.Boolean('ARF', help='Acute Renal Failure') wbc = fields.Float('WBC', help="White blood cells x 1000 - if you" " want to input 4500 wbc / ml, type in 4.5") hematocrit = fields.Float('Hematocrit') gcs = fields.Integer( 'GSC', help='Last Glasgow Coma Scale' ' You can use the GSC calculator from the Patient Evaluation Form.') chronic_condition = fields.Boolean( 'Chronic condition', help='Organ Failure or immunocompromised patient') hospital_admission_type = fields.Selection( [(None, ''), ('me', 'Medical or emergency postoperative'), ('el', 'elective postoperative')], 'Hospital Admission Type', states={ 'invisible': Not(Bool(Eval('chronic_condition'))), 'required': Bool(Eval('chronic_condition')) }, sort=False) apache_score = fields.Integer( 'Score', on_change_with=[ 'age', 'temperature', 'mean_ap', 'heart_rate', 'respiratory_rate', 'fio2', 'pao2', 'aado2', 'ph', 'serum_sodium', 'serum_potassium', 'serum_creatinine', 'arf', 'wbc', 'hematocrit', 'gcs', 'chronic_condition', 'hospital_admission_type' ]) #Default FiO2 PaO2 and PaCO2 so we do the A-a gradient #calculation with non-null values def on_change_with_aado2(self): # Calculates the Alveolar-arterial difference # based on FiO2, PaCO2 and PaO2 values if (self.fio2 and self.paco2 and self.pao2): return (713 * self.fio2) - (self.paco2 / 0.8) - self.pao2 def on_change_with_apache_score(self): # Calculate the APACHE SCORE from the variables in the total = 0 # Age if (self.age): if (self.age > 44 and self.age < 55): total = total + 2 elif (self.age > 54 and self.age < 65): total = total + 3 elif (self.age > 64 and self.age < 75): total = total + 5 elif (self.age > 74): total = total + 6 # Temperature if (self.temperature): if ((self.temperature >= 38.5 and self.temperature < 39) or (self.temperature >= 34 and self.temperature < 36)): total = total + 1 elif (self.temperature >= 32 and self.temperature < 34): total = total + 2 elif ((self.temperature >= 30 and self.temperature < 32) or (self.temperature >= 39 and self.temperature < 41)): total = total + 3 elif (self.temperature >= 41 or self.temperature < 30): total = total + 4 # Mean Arterial Pressure (MAP) if (self.mean_ap): if ((self.mean_ap >= 110 and self.mean_ap < 130) or (self.mean_ap >= 50 and self.mean_ap < 70)): total = total + 2 elif (self.mean_ap >= 130 and self.mean_ap < 160): total = total + 3 elif (self.mean_ap >= 160 or self.mean_ap < 50): total = total + 4 # Heart Rate if (self.heart_rate): if ((self.heart_rate >= 55 and self.heart_rate < 70) or (self.heart_rate >= 110 and self.heart_rate < 140)): total = total + 2 elif ((self.heart_rate >= 40 and self.heart_rate < 55) or (self.heart_rate >= 140 and self.heart_rate < 180)): total = total + 3 elif (self.heart_rate >= 180 or self.heart_rate < 40): total = total + 4 # Respiratory Rate if (self.respiratory_rate): if ((self.respiratory_rate >= 10 and self.respiratory_rate < 12) or (self.respiratory_rate >= 25 and self.respiratory_rate < 35)): total = total + 1 elif (self.respiratory_rate >= 6 and self.respiratory_rate < 10): total = total + 2 elif (self.respiratory_rate >= 35 and self.respiratory_rate < 50): total = total + 3 elif (self.respiratory_rate >= 50 or self.respiratory_rate < 6): total = total + 4 # FIO2 if (self.fio2): # If Fi02 is greater than 0.5, we measure the AaDO2 gradient # Otherwise, we take into account the Pa02 value if (self.fio2 >= 0.5): if (self.aado2 >= 200 and self.aado2 < 350): total = total + 2 elif (self.aado2 >= 350 and self.aado2 < 500): total = total + 3 elif (self.aado2 >= 500): total = total + 4 else: if (self.pao2 >= 61 and self.pao2 < 71): total = total + 1 elif (self.pao2 >= 55 and self.pao2 < 61): total = total + 3 elif (self.pao2 < 55): total = total + 4 # Arterial pH if (self.ph): if (self.ph >= 7.5 and self.ph < 7.6): total = total + 1 elif (self.ph >= 7.25 and self.ph < 7.33): total = total + 2 elif ((self.ph >= 7.15 and self.ph < 7.25) or (self.ph >= 7.6 and self.ph < 7.7)): total = total + 3 elif (self.ph >= 7.7 or self.ph < 7.15): total = total + 4 # Serum Sodium if (self.serum_sodium): if (self.serum_sodium >= 150 and self.serum_sodium < 155): total = total + 1 elif ((self.serum_sodium >= 155 and self.serum_sodium < 160) or (self.serum_sodium >= 120 and self.serum_sodium < 130)): total = total + 2 elif ((self.serum_sodium >= 160 and self.serum_sodium < 180) or (self.serum_sodium >= 111 and self.serum_sodium < 120)): total = total + 3 elif (self.serum_sodium >= 180 or self.serum_sodium < 111): total = total + 4 # Serum Potassium if (self.serum_potassium): if ((self.serum_potassium >= 3 and self.serum_potassium < 3.5) or (self.serum_potassium >= 5.5 and self.serum_potassium < 6)): total = total + 1 elif (self.serum_potassium >= 2.5 and self.serum_potassium < 3): total = total + 2 elif (self.serum_potassium >= 6 and self.serum_potassium < 7): total = total + 3 elif (self.serum_potassium >= 7 or self.serum_potassium < 2.5): total = total + 4 # Serum Creatinine if (self.serum_creatinine): arf_factor = 1 if (self.arf): # We multiply by 2 the score if there is concomitant ARF arf_factor = 2 if ((self.serum_creatinine < 0.6) or (self.serum_creatinine >= 1.5 and self.serum_creatinine < 2)): total = total + 2 * arf_factor elif (self.serum_creatinine >= 2 and self.serum_creatinine < 3.5): total = total + 3 * arf_factor elif (self.serum_creatinine >= 3.5): total = total + 4 * arf_factor # Hematocrit if (self.hematocrit): if (self.hematocrit >= 46 and self.hematocrit < 50): total = total + 1 elif ((self.hematocrit >= 50 and self.hematocrit < 60) or (self.hematocrit >= 20 and self.hematocrit < 30)): total = total + 2 elif (self.hematocrit >= 60 or self.hematocrit < 20): total = total + 4 # WBC ( x 1000 ) if (self.wbc): if (self.wbc >= 15 and self.wbc < 20): total = total + 1 elif ((self.wbc >= 20 and self.wbc < 40) or (self.wbc >= 1 and self.wbc < 3)): total = total + 2 elif (self.wbc >= 40 or self.wbc < 1): total = total + 4 # Immnunocompromised or severe organ failure if (self.chronic_condition): if (self.hospital_admission_type == 'me'): total = total + 5 else: total = total + 2 return total
class Banner(Workflow, ModelSQL, ModelView): """Banner for CMS.""" __name__ = 'nereid.cms.banner' name = fields.Char('Name', required=True, select=True) description = fields.Text('Description') category = fields.Many2One('nereid.cms.banner.category', 'Category', required=True, select=True) sequence = fields.Integer('Sequence', select=True) # Type related data type = fields.Selection([ ('media', 'Media'), ('custom_code', 'Custom Code'), ], 'Type', required=True) file = fields.Many2One('nereid.static.file', 'File', states={ 'required': Equal(Eval('type'), 'media'), 'invisible': Not(Equal(Eval('type'), 'media')) }) custom_code = fields.Text('Custom Code', translate=True, states={ 'required': Equal(Eval('type'), 'custom_code'), 'invisible': Not(Equal(Eval('type'), 'custom_code')) }) # Presentation related Data height = fields.Integer( 'Height', states={'invisible': Not(Equal(Eval('type'), 'media'))}) width = fields.Integer( 'Width', states={'invisible': Not(Equal(Eval('type'), 'media'))}) alternative_text = fields.Char( 'Alternative Text', translate=True, states={'invisible': Not(Equal(Eval('type'), 'media'))}) click_url = fields.Char( 'Click URL', translate=True, states={'invisible': Not(Equal(Eval('type'), 'media'))}) state = fields.Selection([('draft', 'Draft'), ('published', 'Published'), ('archived', 'Archived')], 'State', required=True, select=True, readonly=True) reference = fields.Reference('Reference', selection='allowed_models') @classmethod def __register__(cls, module_name): super(Banner, cls).__register__(module_name) table = cls.__table__() cursor = Transaction().cursor # Update type from image to media query = table.update(columns=[table.type], values=["media"], where=(table.type == "image")) cursor.execute(*query) @classmethod def view_attributes(cls): return super(Banner, cls).view_attributes() + [ ('//page[@id="image"]', 'states', { 'invisible': Eval('type') == 'custom_code' }), ('//page[@id="custom_code"]', 'states', { 'invisible': Eval('type') != 'custom_code' }) ] @classmethod def __setup__(cls): super(Banner, cls).__setup__() cls._order.insert(0, ('sequence', 'ASC')) cls._transitions |= set(( ('draft', 'published'), ('archived', 'published'), ('published', 'archived'), )) cls._buttons.update({ 'archive': { 'invisible': Eval('state') != 'published', }, 'publish': { 'invisible': Eval('state') == 'published', } }) @classmethod @ModelView.button @Workflow.transition('archived') def archive(cls, banners): pass @classmethod @ModelView.button @Workflow.transition('published') def publish(cls, banners): pass def get_html(self): """Return the HTML content""" StaticFile = Pool().get('nereid.static.file') # XXX: Use read, as all fields are not required banner = self.read([self], [ 'type', 'click_url', 'file', 'file.mimetype', 'custom_code', 'height', 'width', 'alternative_text', 'click_url' ])[0] if banner['type'] == 'media': if not (banner['file.mimetype'] and banner['file.mimetype'].startswith('image')): return "<!-- no html defined for mime type '%s' -->" % \ banner['file.mimetype'] # replace the `file` in the dictionary with the complete url # that is required to render the image based on static file file = StaticFile(banner['file']) banner['file'] = file.url image = Template(u'<a href="$click_url">' u'<img src="$file" alt="$alternative_text"' u' width="$width" height="$height"/>' u'</a>') return image.substitute(**banner) elif banner['type'] == 'custom_code': return banner['custom_code'] @classmethod def allowed_models(cls): MenuItem = Pool().get('nereid.cms.menuitem') return MenuItem.allowed_models() @staticmethod def default_type(): return 'media' @staticmethod def default_state(): if 'published' in Transaction().context: return 'published' return 'draft'