class Sale: __name__ = 'sale.sale' channel = fields.Many2One( 'sale.channel', 'Channel', required=True, domain=[ ('id', 'in', Eval('context', {}).get('allowed_read_channels', [])), ], states={ 'readonly': Or( (Eval('id', default=0) > 0), Bool(Eval('lines', default=[])), ) }, depends=['id'] ) channel_type = fields.Function( fields.Char('Channel Type'), 'on_change_with_channel_type' ) has_channel_exception = fields.Function( fields.Boolean('Has Channel Exception ?'), 'get_has_channel_exception', searcher='search_has_channel_exception' ) exceptions = fields.Function( fields.One2Many("channel.exception", None, "Exceptions"), 'get_channel_exceptions', setter='set_channel_exceptions' ) @classmethod def search_has_channel_exception(cls, name, clause): """ Returns domain for sale with exceptions """ ChannelException = Pool().get('channel.exception') Sale = Pool().get('sale.sale') cursor = Transaction().cursor sale_with_exceptions = [] # TODO: Convert this expression to python sql cursor.execute( "SELECT DISTINCT(sale.id) " "FROM %s as exception " "JOIN %s as sale on " "(exception.origin = 'sale.sale,'||sale.id) " "WHERE is_resolved is %s", (AsIs(ChannelException._table), AsIs(Sale._table), False) ) sale_with_exceptions = map(lambda t: t[0], cursor.fetchall()) return clause[2] and [('id', 'in', sale_with_exceptions)] or \ [('id', 'not in', sale_with_exceptions)] def get_channel_exceptions(self, name=None): ChannelException = Pool().get('channel.exception') return map( int, ChannelException.search([ ('origin', '=', '%s,%s' % (self.__name__, self.id)), ('channel', '=', self.channel.id), ], order=[('is_resolved', 'desc')]) ) @classmethod def set_channel_exceptions(cls, exceptions, name, value): pass def get_has_channel_exception(self, name): """ Returs True if sale has exception """ ChannelException = Pool().get('channel.exception') return bool( ChannelException.search([ ('origin', '=', '%s,%s' % (self.__name__, self.id)), ('channel', '=', self.channel.id), ('is_resolved', '=', False) ]) ) @classmethod def __setup__(cls): super(Sale, cls).__setup__() cls._error_messages.update({ 'channel_missing': ( 'Go to user preferences and select a current_channel ("%s")' ), 'channel_change_not_allowed': ( 'Cannot change channel' ), 'not_create_channel': ( 'You cannot create order under this channel because you do not ' 'have required permissions' ), }) @classmethod def default_channel(cls): User = Pool().get('res.user') user = User(Transaction().user) channel_id = Transaction().context.get('current_channel') if channel_id: return channel_id return user.current_channel and \ user.current_channel.id # pragma: nocover @staticmethod def default_company(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') channel_id = Sale.default_channel() if channel_id: return Channel(channel_id).company.id return Transaction().context.get('company') # pragma: nocover @staticmethod def default_invoice_method(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') Config = Pool().get('sale.configuration') channel_id = Sale.default_channel() if not channel_id: # pragma: nocover config = Config(1) return config.sale_invoice_method return Channel(channel_id).invoice_method @staticmethod def default_shipment_method(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') Config = Pool().get('sale.configuration') channel_id = Sale.default_channel() if not channel_id: # pragma: nocover config = Config(1) return config.sale_invoice_method return Channel(channel_id).shipment_method @staticmethod def default_warehouse(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') Location = Pool().get('stock.location') channel_id = Sale.default_channel() if not channel_id: # pragma: nocover return Location.search([('type', '=', 'warehouse')], limit=1)[0].id else: return Channel(channel_id).warehouse.id @staticmethod def default_price_list(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') channel_id = Sale.default_channel() if channel_id: return Channel(channel_id).price_list.id return None # pragma: nocover @staticmethod def default_payment_term(): Sale = Pool().get('sale.sale') Channel = Pool().get('sale.channel') channel_id = Sale.default_channel() if channel_id: return Channel(channel_id).payment_term.id return None # pragma: nocover @fields.depends('channel', 'party') def on_change_channel(self): if not self.channel: return {} # pragma: nocover res = {} for fname in ('company', 'warehouse', 'currency', 'payment_term'): fvalue = getattr(self.channel, fname) if fvalue: res[fname] = fvalue.id if (not self.party or not self.party.sale_price_list): res['price_list'] = self.channel.price_list.id # pragma: nocover if self.channel.invoice_method: res['invoice_method'] = self.channel.invoice_method if self.channel.shipment_method: res['shipment_method'] = self.channel.shipment_method # Update AR record for key, value in res.iteritems(): if '.' not in key: setattr(self, key, value) return res @fields.depends('channel') def on_change_party(self): # pragma: nocover res = super(Sale, self).on_change_party() channel = self.channel if channel: if not res.get('price_list') and res.get('invoice_address'): res['price_list'] = channel.price_list.id res['price_list.rec_name'] = channel.price_list.rec_name if not res.get('payment_term') and res.get('invoice_address'): res['payment_term'] = channel.payment_term.id res['payment_term.rec_name'] = \ self.channel.payment_term.rec_name # Update AR record for key, value in res.iteritems(): setattr(self, key, value) return res @fields.depends('channel') def on_change_with_channel_type(self, name=None): """ Returns the source of the channel """ if self.channel: return self.channel.source def check_create_access(self, silent=False): """ Check sale creation in channel """ User = Pool().get('res.user') user = User(Transaction().user) if user.id == 0: return # pragma: nocover if self.channel not in user.allowed_create_channels: if silent: return False self.raise_user_error('not_create_channel') return True @classmethod def write(cls, sales, values, *args): """ Check if channel in sale is is user's create_channel """ if 'channel' in values: # Channel cannot be changed at any cost. cls.raise_user_error('channel_change_not_allowed') super(Sale, cls).write(sales, values, *args) @classmethod def create(cls, vlist): """ Check if user is allowed to create sale in channel """ User = Pool().get('res.user') user = User(Transaction().user) for values in vlist: if 'channel' not in values and not cls.default_channel(): cls.raise_user_error( 'channel_missing', (user.rec_name,) ) # pragma: nocover sales = super(Sale, cls).create(vlist) for sale in sales: sale.check_create_access() return sales @classmethod def copy(cls, sales, default=None): """ Duplicating records """ if default is None: default = {} for sale in sales: if not sale.check_create_access(True): default['channel'] = cls.default_channel() return super(Sale, cls).copy(sales, default=default)
# The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. from sql import Null from trytond.model import fields, Check from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval from .move import StockMixin __all__ = ['Lot'] STATES_ON_CREATE = { 'readonly': Eval('id', -1) > 0, 'invisible': ~Bool(Eval('package')) } DEPENDS_ON_CREATE = ['id', 'package'] STATES_REQUIRED = STATES_ON_CREATE.copy() STATES_REQUIRED['required'] = Eval('package_required', False) DEPENDS_REQUIRED = DEPENDS_ON_CREATE + ['package_required'] class Lot(StockMixin, metaclass=PoolMeta): __name__ = 'stock.lot' package = fields.Many2One('product.pack', 'Packaging', domain=[ ('product.products', 'in', [Eval('product')
class StatementLine(metaclass=PoolMeta): __name__ = 'account.statement.line' payment = fields.Many2One( 'account.payment', 'Payment', domain=[ If(Bool(Eval('party')), [('party', '=', Eval('party'))], []), ('state', 'in', ['processing', 'succeeded', 'failed']), ], states={ 'invisible': Bool(Eval('payment_group')) | Bool(Eval('invoice')), 'readonly': Eval('statement_state') != 'draft', }, depends=['party', 'statement_state']) payment_group = fields.Many2One('account.payment.group', "Payment Group", domain=[ ('company', '=', Eval('company', -1)), ], states={ 'invisible': Bool(Eval('payment')) | Bool(Eval('invoice')), 'readonly': Eval('statement_state') != 'draft', }, depends=['company', 'statement_state']) @classmethod def __setup__(cls): super(StatementLine, cls).__setup__() invoice_invisible = Bool(Eval('payment')) | Bool(Eval('payment_group')) if 'invisible' in cls.invoice.states: cls.invoice.states['invisible'] |= invoice_invisible else: cls.invoice.states['invisible'] = invoice_invisible @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payment', None) default.setdefault('payment_group', None) return super(StatementLine, cls).copy(lines, default=default) @fields.depends('payment', 'party', 'account', 'statement', '_parent_statement.journal') def on_change_payment(self): pool = Pool() Currency = pool.get('currency.currency') if self.payment: if not self.party: self.party = self.payment.party clearing_account = self.payment.journal.clearing_account if not self.account and clearing_account: self.account = clearing_account if self.statement and self.statement.journal: with Transaction().set_context(date=self.payment.date): amount = Currency.compute(self.payment.currency, self.payment.amount, self.statement.journal.currency) self.amount = amount if self.payment.kind == 'payable': self.amount *= -1 @fields.depends('payment_group', 'account') def on_change_payment_group(self): if self.payment_group: self.party = None clearing_account = self.payment_group.journal.clearing_account if not self.account and clearing_account: self.account = clearing_account @fields.depends('party', 'payment') def on_change_party(self): super(StatementLine, self).on_change_party() if self.payment: if self.payment.party != self.party: self.payment = None self.payment_group = None @fields.depends('account', 'payment', 'payment_group') def on_change_account(self): super(StatementLine, self).on_change_account() if self.payment: clearing_account = self.payment.journal.clearing_account elif self.payment_group: clearing_account = self.payment_group.journal.clearing_account else: return if self.account != clearing_account: self.payment = None @classmethod def post_move(cls, lines): pool = Pool() Move = pool.get('account.move') super(StatementLine, cls).post_move(lines) Move.post([ l.payment.clearing_move for l in lines if l.payment and l.payment.clearing_move and l.payment.clearing_move.state == 'draft' ])
class InpatientIcu(ModelSQL, ModelView): 'Patient ICU Information' __name__ = 'gnuhealth.inpatient.icu' def icu_duration(self, name): now = datetime.now() admission = datetime.strptime(str(self.icu_admission_date), '%Y-%m-%d %H:%M:%S') if self.discharged_from_icu: discharge = datetime.strptime(str(self.icu_discharge_date), '%Y-%m-%d %H:%M:%S') delta = relativedelta(discharge, admission) else: delta = relativedelta(now, admission) years_months_days = str(delta.years) + 'y ' \ + str(delta.months) + 'm ' \ + str(delta.days) + 'd' return years_months_days name = fields.Many2One('gnuhealth.inpatient.registration', 'Registration Code', required=True) admitted = fields.Boolean('Admitted', help="Will be set when the patient \ is currently admitted at ICU", on_change_with=['discharged_from_icu']) icu_admission_date = fields.DateTime('ICU Admission', help="ICU Admission Date", required=True) discharged_from_icu = fields.Boolean('Discharged') icu_discharge_date = fields.DateTime( 'Discharge', states={ 'invisible': Not(Bool(Eval('discharged_from_icu'))), 'required': Bool(Eval('discharged_from_icu')), }, depends=['discharged_from_icu']) icu_stay = fields.Function(fields.Char('Duration'), 'icu_duration') mv_history = fields.One2Many('gnuhealth.icu.ventilation', 'name', "Mechanical Ventilation History") @classmethod def __setup__(cls): super(InpatientIcu, cls).__setup__() cls._constraints += [ ('check_patient_admitted_at_icu', 'patient_already_at_icu'), ] cls._error_messages.update({ 'patient_already_at_icu': 'Our records indicate that the patient' ' is already admitted at ICU' }) def check_patient_admitted_at_icu(self): # Verify that the patient is not at ICU already cursor = Transaction().cursor cursor.execute( "SELECT count(name) " "FROM " + self._table + " \ WHERE (name = %s AND admitted)", (str(self.name.id), )) if cursor.fetchone()[0] > 1: return False return True @staticmethod def default_admitted(): return True def on_change_with_admitted(self): # Reset the admission flag when the patient is discharged from ICU if self.discharged_from_icu: res = False else: res = True return res
""" from decimal import Decimal from trytond.model import ModelView, fields, Workflow from trytond.pool import PoolMeta, Pool from trytond.transaction import Transaction from trytond.pyson import Eval, Bool, And, Not, Or from trytond.wizard import Wizard, StateView, StateTransition, Button from trytond.modules.payment_gateway.transaction import BaseCreditCardViewMixin __all__ = ['Sale', 'PaymentTransaction', 'AddSalePaymentView', 'AddSalePayment'] __metaclass__ = PoolMeta READONLY_IF_PAYMENTS = { 'readonly': Not(Bool(Eval('payments'))) } 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'),
class InvoiceLine: __metaclass__ = PoolMeta __name__ = 'account.invoice.line' principal = fields.Many2One('commission.agent', 'Commission Principal', domain=[ ('type_', '=', 'principal'), ('company', '=', Eval('_parent_invoice', {}).get('company', Eval('company', -1))), ], states={ 'invisible': If(Bool(Eval('_parent_invoice')), Eval('_parent_invoice', {}).get('type') == 'in', Eval('invoice_type') == 'in'), }, depends=['invoice_type', 'company']) commissions = fields.One2Many('commission', 'origin', 'Commissions', readonly=True, states={ 'invisible': ~Eval('commissions'), }) from_commissions = fields.One2Many('commission', 'invoice_line', 'From Commissions', readonly=True, states={ 'invisible': ~Eval('from_commissions'), }) @property def agent_plans_used(self): "List of agent, plan tuple" used = [] if self.invoice.agent: used.append((self.invoice.agent, self.invoice.agent.plan)) if self.principal: used.append((self.principal, self.principal.plan)) return used def get_commissions(self): pool = Pool() Commission = pool.get('commission') Currency = pool.get('currency.currency') Date = pool.get('ir.date') if self.type != 'line': return [] today = Date.today() commissions = [] for agent, plan in self.agent_plans_used: if not plan: continue with Transaction().set_context(date=self.invoice.currency_date): amount = Currency.compute(self.invoice.currency, self.amount, agent.currency, round=False) amount = self._get_commission_amount(amount, plan) if amount: digits = Commission.amount.digits amount = amount.quantize(Decimal(str(10.0 ** -digits[1]))) if not amount: continue commission = Commission() commission.origin = self if plan.commission_method == 'posting': commission.date = today commission.agent = agent commission.product = plan.commission_product commission.amount = amount commissions.append(commission) return commissions def _get_commission_amount(self, amount, plan, pattern=None): return plan.compute(amount, self.product, pattern=pattern) @fields.depends('product', 'principal') def on_change_product(self): super(InvoiceLine, self).on_change_product() if self.product: if self.product.principals: if self.principal not in self.product.principals: self.principal = self.product.principal elif self.principal: self.principal = None @classmethod def view_attributes(cls): return super(InvoiceLine, cls).view_attributes() + [ ('//page[@id="commissions"]', 'states', { 'invisible': Eval('type') != 'line', })] @classmethod def copy(cls, lines, default=None): if default is None: default = {} default.setdefault('commissions', None) default.setdefault('from_commissions', None) return super(InvoiceLine, cls).copy(lines, default=default)
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 AccountRetencionEfectuada(ModelSQL, ModelView): 'Account Retencion Efectuada' __name__ = 'account.retencion.efectuada' name = fields.Char('Number', states={ 'required': Bool(Eval('name_required')), 'readonly': Not(Bool(Eval('name_required'))), }, depends=['name_required']) name_required = fields.Function(fields.Boolean('Name Required'), 'on_change_with_name_required') amount = fields.Numeric('Amount', digits=(16, 2), required=True) aliquot = fields.Float('Aliquot') date = fields.Date('Date', required=True) tax = fields.Many2One('account.retencion', 'Tax', domain=[('type', '=', 'efectuada')]) voucher = fields.Many2One('account.voucher', 'Voucher') party = fields.Many2One('party.party', 'Party') state = fields.Selection([ ('draft', 'Draft'), ('issued', 'Issued'), ('cancelled', 'Cancelled'), ], 'State', readonly=True) @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() sql_table = cls.__table__() super().__register__(module_name) cursor.execute(*sql_table.update([sql_table.state], ['cancelled'], where=sql_table.state == 'canceled')) @staticmethod def default_amount(): return Decimal('0.00') @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() @fields.depends('tax') def on_change_with_name_required(self, name=None): if self.tax and self.tax.sequence: return False return True @staticmethod def default_state(): return 'draft' @classmethod def delete(cls, retenciones): cls.check_delete(retenciones) super().delete(retenciones) @classmethod def check_delete(cls, retenciones): for retencion in retenciones: if retencion.voucher: raise UserError( gettext('account_retencion_ar.msg_not_delete', retention=retencion.name)) @classmethod def copy(cls, retenciones, default=None): if default is None: default = {} current_default = default.copy() current_default['state'] = 'draft' current_default['name'] = None current_default['voucher'] = None return super().copy(retenciones, default=current_default)
class ChagasDUSurvey(ModelSQL, ModelView): 'Chagas DU Entomological Survey' __name__ = 'gnuhealth.chagas_du_survey' name = fields.Char('Survey Code', readonly=True) du = fields.Many2One('gnuhealth.du', 'DU', help="Domiciliary Unit") survey_date = fields.Date('Date', required=True) du_status = fields.Selection([ (None, ''), ('initial', 'Initial'), ('unchanged', 'Unchanged'), ('better', 'Improved'), ('worse', 'Worsen'), ], 'Status', help="DU status compared to last visit", required=True, sort=False) # Findings of Triatomines in the DU triatomines = fields.Boolean( 'Triatomines', help="Check this box if triatomines were found") vector = fields.Selection([ (None, ''), ('t_infestans', 'T. infestans'), ('t_brasilensis', 'T. brasilensis'), ('r_prolixus', 'R. prolixus'), ('t_dimidiata', 'T. dimidiata'), ('p_megistus', 'P. megistus'), ], 'Vector', help="Vector", sort=False) nymphs = fields.Boolean('Nymphs', "Check this box if triatomine nymphs were found") t_in_house = fields.Boolean( 'Domiciliary', help="Check this box if triatomines were found inside the house") t_peri = fields.Boolean( 'Peri-Domiciliary', help= "Check this box if triatomines were found in the peridomiciliary area") # Infrastructure conditions dfloor = fields.Boolean('Floor', help="Current floor can host triatomines") dwall = fields.Boolean('Walls', help="Wall materials or state can host triatomines") droof = fields.Boolean('Roof', help="Roof materials or state can host triatomines") dperi = fields.Boolean('Peri-domicilary', help="Peri domiciliary area can host triatomines") # Preventive measures bugtraps = fields.Boolean('Bug traps', help="The DU has traps to detect triatomines") # Chemical controls du_fumigation = fields.Boolean('Fumigation', help="The DU has been fumigated") fumigation_date = fields.Date( 'Fumigation Date', help="Last Fumigation Date", states={'invisible': Not(Bool(Eval('du_fumigation')))}) du_paint = fields.Boolean( 'Insecticide Paint', help="The DU has been treated with insecticide-containing paint") paint_date = fields.Date('Paint Date', help="Last Paint Date", states={'invisible': Not(Bool(Eval('du_paint')))}) observations = fields.Text('Observations') next_survey_date = fields.Date('Next survey') @staticmethod def default_survey_date(): return datetime.now() @classmethod def create(cls, vlist): Sequence = Pool().get('ir.sequence') Config = Pool().get('gnuhealth.sequences') vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('name'): config = Config(1) values['name'] = Sequence.get_id( config.chagas_du_survey_sequence.id) return super(ChagasDUSurvey, cls).create(vlist)
class PurchaseRequest(ModelSQL, ModelView): 'Purchase Request' __name__ = 'purchase.request' product = fields.Many2One('product.product', 'Product', select=True, readonly=True, domain=[('purchasable', '=', True)]) description = fields.Text('Description', readonly=True, states=STATES, depends=DEPENDS) party = fields.Many2One('party.party', 'Party', select=True, states=STATES, depends=DEPENDS) quantity = fields.Float('Quantity', required=True, states=STATES, digits=(16, Eval('uom_digits', 2)), depends=DEPENDS + ['uom_digits']) uom = fields.Many2One('product.uom', 'UOM', select=True, states={ 'required': Bool(Eval('product')), 'readonly': STATES['readonly'], }, depends=['product'] + DEPENDS) uom_digits = fields.Function(fields.Integer('UOM Digits'), 'on_change_with_uom_digits') computed_quantity = fields.Float('Computed Quantity', readonly=True) computed_uom = fields.Many2One('product.uom', 'Computed UOM', readonly=True) purchase_date = fields.Date('Best Purchase Date', readonly=True) supply_date = fields.Date('Expected Supply Date', readonly=True) default_uom_digits = fields.Function(fields.Integer('Default UOM Digits'), 'on_change_with_default_uom_digits') stock_level = fields.Float('Stock at Supply Date', readonly=True, digits=(16, Eval('default_uom_digits', 2)), depends=['default_uom_digits']) warehouse = fields.Many2One( 'stock.location', "Warehouse", states={ 'required': Eval('warehouse_required', False), }, domain=[('type', '=', 'warehouse')], depends=['warehouse_required'], readonly=True) warehouse_required = fields.Function(fields.Boolean('Warehouse Required'), 'get_warehouse_required') purchase_line = fields.Many2One('purchase.line', 'Purchase Line', readonly=True) purchase = fields.Function(fields.Many2One('purchase.purchase', 'Purchase'), 'get_purchase', searcher='search_purchase') company = fields.Many2One('company.company', 'Company', required=True, readonly=True, domain=[ ('id', If(In('company', Eval('context', {})), '=', '!='), Eval('context', {}).get('company', -1)), ]) origin = fields.Reference('Origin', selection='get_origin', readonly=True) exception_ignored = fields.Boolean('Ignored Exception') state = fields.Selection([ ('purchased', "Purchased"), ('done', "Done"), ('draft', "Draft"), ('cancel', "Cancel"), ('exception', "Exception"), ], "State", required=True, readonly=True, select=True) @classmethod def __setup__(cls): super(PurchaseRequest, cls).__setup__() cls._order[0] = ('id', 'DESC') cls._error_messages.update({ 'create_request': ('Purchase requests are only created ' 'by the system.'), 'delete_purchase_line': ('You can not delete purchased ' 'request.'), }) cls._buttons.update({ 'handle_purchase_cancellation_exception': { 'invisible': Eval('state') != 'exception', 'depends': ['state'], }, }) @classmethod def __register__(cls, module_name): pool = Pool() ModelData = pool.get('ir.model.data') Purchase = pool.get('purchase.purchase') PurchaseLine = pool.get('purchase.line') model_data = ModelData.__table__() purchase = Purchase.__table__() purchase_line = PurchaseLine.__table__() request = cls.__table__() tablehandler = cls.__table_handler__(module_name) state_exist = tablehandler.column_exist('state') super(PurchaseRequest, cls).__register__(module_name) # Migration from 3.6: removing the constraint on the quantity tablehandler = cls.__table_handler__(module_name) tablehandler.drop_constraint('check_purchase_request_quantity') # Migration from 3.8: renaming module of Purchase Request group entry cursor = Transaction().connection.cursor() cursor.execute(*model_data.update( columns=[model_data.module], values=['purchase_request'], where=((model_data.fs_id == 'group_purchase_request') & (model_data.module == 'stock_supply')))) # Migration from 4.0: remove required on product and uom tablehandler.not_null_action('product', action='remove') tablehandler.not_null_action('uom', action='remove') # Migration from 4.2: add state if not state_exist: cursor = Transaction().connection.cursor() update = Transaction().connection.cursor() query = request.join(purchase_line, type_='INNER', condition=request.purchase_line == purchase_line.id ).join(purchase, type_='INNER', condition=purchase_line.purchase == purchase.id ).select( request.id, purchase.state, request.exception_ignored) cursor.execute(*query) for request_id, purchase_state, exception_ignored in cursor: if purchase_state == 'cancel' and not exception_ignored: state = 'exception' elif purchase_state == 'cancel': state = 'cancel' elif purchase_state == 'done': state = 'done' else: state = 'purchased' update.execute(*request.update( [request.state], [state], where=request.id == request_id)) # Migration from 4.4: remove required on origin tablehandler.not_null_action('origin', action='remove') def get_rec_name(self, name): product_name = (self.product.name if self.product else self.description.splitlines()[0]) if self.warehouse: return "%s@%s" % (product_name, self.warehouse.name) else: return product_name @classmethod def search_rec_name(cls, name, clause): res = [] names = clause[2].split('@', 1) res.append(('product.template.name', clause[1], names[0])) if len(names) != 1 and names[1]: res.append(('warehouse', clause[1], names[1])) return ['OR', res, ('description',) + tuple(clause[1:]), ] @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_exception_ignored(): return False def get_purchase(self, name): if self.purchase_line: return self.purchase_line.purchase.id @classmethod def search_purchase(cls, name, clause): return [('purchase_line.' + clause[0],) + tuple(clause[1:])] @property def currency(self): return self.company.currency @classmethod def default_state(cls): return 'draft' def get_state(self): if self.purchase_line: if (self.purchase_line.purchase.state == 'cancel' and not self.exception_ignored): return 'exception' elif self.purchase_line.purchase.state == 'cancel': return 'cancel' elif self.purchase_line.purchase.state == 'done': return 'done' else: return 'purchased' return 'draft' @classmethod def update_state(cls, requests): for request in requests: state = request.get_state() if state != request.state: request.state = state cls.save(requests) def get_warehouse_required(self, name): return self.product and self.product.type in ('goods', 'assets') @fields.depends('uom') def on_change_with_uom_digits(self, name=None): if self.uom: return self.uom.digits return 2 @fields.depends('product') def on_change_with_default_uom_digits(self, name=None): if self.product: return self.product.default_uom.digits return 2 @classmethod def _get_origin(cls): 'Return the set of Model names for origin Reference' return set() @classmethod def get_origin(cls): pool = Pool() IrModel = pool.get('ir.model') models = IrModel.search([ ('model', 'in', list(cls._get_origin())), ]) return [(None, '')] + [(m.model, m.name) for m in models] @classmethod def create(cls, vlist): for vals in vlist: for field_name in ('quantity', 'company'): if vals.get(field_name) is None: cls.raise_user_error('create_request') return super(PurchaseRequest, cls).create(vlist) @classmethod def delete(cls, requests): if any(r.purchase_line for r in requests): cls.raise_user_error('delete_purchase_line') super(PurchaseRequest, cls).delete(requests) @classmethod def find_best_supplier(cls, product, date): ''' Return the best supplier and purchase_date for the product. ''' Date = Pool().get('ir.date') supplier = None today = Date.today() for product_supplier in product.product_suppliers: supply_date = product_supplier.compute_supply_date(date=today) timedelta = date - supply_date if not supplier and timedelta >= datetime.timedelta(0): supplier = product_supplier.party break if supplier: purchase_date = product_supplier.compute_purchase_date(date) else: purchase_date = today return supplier, purchase_date @classmethod @ModelView.button_action( 'purchase_request.wizard_purchase_cancellation_handle_exception') def handle_purchase_cancellation_exception(cls, purchases): pass
class Journal(ModelSQL, ModelView): 'Journal' __name__ = 'account.journal' name = fields.Char('Name', size=None, required=True, translate=True) code = fields.Char('Code', size=None) active = fields.Boolean('Active', select=True) type = fields.Selection('get_types', 'Type', required=True) view = fields.Many2One('account.journal.view', 'View') update_posted = fields.Boolean('Allow updating posted moves') sequence = fields.Property( fields.Many2One('ir.sequence', 'Sequence', domain=[('code', '=', 'account.journal')], context={'code': 'account.journal'}, states={ 'required': Bool(Eval('context', {}).get('company', -1)), })) credit_account = fields.Property( fields.Many2One('account.account', 'Default Credit Account', domain=[ ('kind', '!=', 'view'), ('company', '=', Eval('context', {}).get('company', -1)), ], states={ 'required': ((Eval('type').in_(['cash', 'write-off'])) & (Eval('context', {}).get('company', -1) != -1)), 'invisible': ~Eval('context', {}).get('company', -1), }, depends=['type'])) debit_account = fields.Property( fields.Many2One('account.account', 'Default Debit Account', domain=[ ('kind', '!=', 'view'), ('company', '=', Eval('context', {}).get('company', -1)), ], states={ 'required': ((Eval('type').in_(['cash', 'write-off'])) & (Eval('context', {}).get('company', -1) != -1)), 'invisible': ~Eval('context', {}).get('company', -1), }, depends=['type'])) debit = fields.Function( fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_debit_credit_balance') credit = fields.Function( fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_debit_credit_balance') balance = fields.Function( fields.Numeric('Balance', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_debit_credit_balance') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits') @classmethod def __setup__(cls): super(Journal, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(Journal, cls).__register__(module_name) cursor = Transaction().cursor table = TableHandler(cursor, cls, module_name) # Migration from 1.0 sequence Many2One change into Property if table.column_exist('sequence'): Property = Pool().get('ir.property') sql_table = cls.__table__() cursor.execute(*sql_table.select(sql_table.id, sql_table.sequence)) for journal_id, sequence_id in cursor.fetchall(): Property.set('sequence', cls._name, journal_id, (sequence_id and 'ir.sequence,' + str(sequence_id) or False)) table.drop_column('sequence', exception=True) @staticmethod def default_active(): return True @staticmethod def default_update_posted(): return False @staticmethod def default_sequence(): return None @staticmethod def get_types(): Type = Pool().get('account.journal.type') types = Type.search([]) return [(x.code, x.name) for x in types] @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, ('code', ) + tuple(clause[1:]), (cls._rec_name, ) + tuple(clause[1:]), ] @classmethod def get_currency_digits(cls, journals, name): pool = Pool() Company = pool.get('company.company') company_id = Transaction().context.get('company') if company_id: company = Company(company_id) digits = company.currency.digits else: digits = 2 return dict.fromkeys([j.id for j in journals], digits) @classmethod def get_debit_credit_balance(cls, journals, names): pool = Pool() MoveLine = pool.get('account.move.line') Move = pool.get('account.move') context = Transaction().context cursor = Transaction().cursor result = {} ids = [j.id for j in journals] for name in ['debit', 'credit', 'balance']: result[name] = dict.fromkeys(ids, 0) line = MoveLine.__table__() move = Move.__table__() where = ((move.date >= context['start_date']) & (move.date <= context['end_date'])) for sub_journals in grouped_slice(journals): sub_journals = list(sub_journals) red_sql = reduce_ids(move.journal, [j.id for j in sub_journals]) accounts = None for journal in sub_journals: credit_account = (journal.credit_account.id if journal.credit_account else None) debit_account = (journal.debit_account.id if journal.debit_account else None) clause = ((move.journal == journal.id) & (((line.credit != Null) & (line.account == credit_account)) | ((line.debit != Null) & (line.account == debit_account)))) if accounts is None: accounts = clause else: accounts |= clause query = line.join(move, 'LEFT', condition=line.move == move.id).select( move.journal, Sum(line.debit), Sum(line.credit), where=where & red_sql & accounts, group_by=move.journal) cursor.execute(*query) for journal_id, debit, credit in cursor.fetchall(): # SQLite uses float for SUM if not isinstance(debit, Decimal): debit = Decimal(str(debit)) if not isinstance(credit, Decimal): credit = Decimal(str(credit)) result['debit'][journal_id] = debit result['credit'][journal_id] = credit result['balance'][journal_id] = debit - credit return result
class SaleLine: __name__ = 'sale.line' is_round_off = fields.Boolean('Round Off', readonly=True) delivery_mode = fields.Selection( [ (None, ''), ('pick_up', 'Pick Up'), ('ship', 'Ship'), ], 'Delivery Mode', states={ 'invisible': Eval('type') != 'line', 'required': And(Eval('type') == 'line', Bool(Eval('product_type_is_goods'))) }, depends=['type', 'product_type_is_goods']) product_type_is_goods = fields.Function( fields.Boolean('Product Type is Goods?'), 'get_product_type_is_goods') @classmethod def __register__(cls, module_name): super(SaleLine, cls).__register__(module_name) TableHandler = backend.get('TableHandler') cursor = Transaction().cursor table = TableHandler(cursor, cls, module_name) table.not_null_action('delivery_mode', action='remove') @classmethod def __setup__(cls): super(SaleLine, cls).__setup__() # Hide product and unit fields. cls.product.states['invisible'] |= Bool(Eval('is_round_off')) cls.unit.states['invisible'] |= Bool(Eval('is_round_off')) cls.delivery_mode.states['invisible'] |= Bool(Eval('is_round_off')) cls.product.depends.insert(0, 'is_round_off') cls.unit.depends.insert(0, 'is_round_off') @fields.depends('product', 'unit', 'quantity', '_parent_sale.party', '_parent_sale.currency', '_parent_sale.sale_date', 'delivery_mode', '_parent_sale.channel', '_parent_sale.shipment_address', 'warehouse', '_parent_sale.warehouse') def on_change_delivery_mode(self): """ This method can be overridden by downstream modules to make changes according to delivery mode. Like change taxes according to delivery mode. """ return {} @staticmethod def default_is_round_off(): return False def get_invoice_line(self, invoice_type): SaleConfiguration = Pool().get('sale.configuration') InvoiceLine = Pool().get('account.invoice.line') if not self.is_round_off: return super(SaleLine, self).get_invoice_line(invoice_type) # The line is a round off line and apply the logic here. # Check if the invoice line already exists for the sale line # If yes, then no line needs to be created # XXX: What if the invoice was cancelled ? if self.invoice_lines: return [] round_down_account = SaleConfiguration(1).round_down_account if not round_down_account: self.raise_user_error( '''Set round down account from Sale Configuration to add round off line''') invoice_line = InvoiceLine() invoice_line.origin = self invoice_line.account = round_down_account invoice_line.unit_price = self.unit_price invoice_line.description = self.description # For positive sales transactions (where the order is effectively # a positive total), the round_down is applied on out_invoice # and if overall order total is negative, then the round_down is # tied to credit note. if self.sale.total_amount >= Decimal('0'): if invoice_type == 'out_credit_note': # positive order looking for credit note return [] invoice_line.quantity = self.quantity else: if invoice_type == 'out_invoice': # negative order looking for invoice return [] invoice_line.quantity = -self.quantity return [invoice_line] @staticmethod def default_delivery_mode(): Channel = Pool().get('sale.channel') User = Pool().get('res.user') user = User(Transaction().user) sale_channel = user.current_channel if Transaction().context.get('current_channel'): sale_channel = Channel( Transaction().context.get('current_channel')) return sale_channel and sale_channel.delivery_mode def get_warehouse(self, name): """ Return the warehouse from the channel for orders being picked up and the backorder warehouse for orders with ship. """ if self.sale.channel.source == 'pos' and \ self.delivery_mode == 'ship' and \ self.sale.channel.backorder_warehouse: return self.sale.channel.backorder_warehouse.id return super(SaleLine, self).get_warehouse(name) def serialize(self, purpose=None): """ Serialize for the purpose of POS """ if purpose == 'pos': return { 'id': self.id, 'description': self.description, 'product': self.product and { 'id': self.product.id, 'code': self.product.code, 'rec_name': self.product.rec_name, 'default_image': self.product.default_image and self.product.default_image.id, }, 'unit': self.unit and { 'id': self.unit.id, 'rec_name': self.unit.rec_name, }, 'unit_price': self.unit_price, 'quantity': self.quantity, 'amount': self.amount, 'delivery_mode': self.delivery_mode } elif hasattr(super(SaleLine, self), 'serialize'): return super(SaleLine, self).serialize(purpose) # pragma: no cover def get_product_type_is_goods(self, name): """ Return True if product is of type goods """ if self.product and self.product.type == 'goods': return True return False
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'), ], states=_states, depends=_depends) uom = fields.Function(fields.Many2One('product.uom', 'UOM', help="The unit in which the quantity is specified."), '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'], help="The quantity the system calculated should be in the location.") quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), states=_states, depends=['unit_digits'] + _depends, help="The actual quantity found in the location.") 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, help="The inventory the line belongs to.") inventory_state = fields.Function( fields.Selection('get_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), 'stock.msg_inventory_line_quantity_positive'), ] cls._order.insert(0, ('product', 'ASC')) @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 @classmethod def get_inventory_states(cls): pool = Pool() Inventory = pool.get('stock.inventory') return Inventory.fields_get(['state'])['state']['selection'] @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: raise InventoryValidationError( gettext('stock.msg_inventory_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.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'}: raise AccessError( gettext('stock.msg_inventory_line_delete_cancel', line=line.rec_name, inventory=line.inventory.rec_name)) super(InventoryLine, cls).delete(lines)
class Artist(ModelSQL, ModelView): 'Artist' __name__ = 'artist' name = fields.Char('Name', required=True, select=True, states=STATES, depends=DEPENDS) code = fields.Char('Code', required=True, select=True, states={ 'readonly': True, }, help='The unique code of the artist') party = fields.Many2One( 'party.party', 'Party', states=STATES, depends=DEPENDS, help='The legal person or organization acting the artist') group = fields.Boolean( 'Group', states={ 'readonly': Or( ~Eval('active'), Bool(Eval('group_artists')), Bool(Eval('solo_artists')), ), }, depends=DEPENDS + ['group_artists', 'solo_artists'], help='Check, if artist is a group of other artists, ' 'otherwise the artist is a solo artist') solo_artists = fields.Many2Many( 'artist-artist', 'group_artist', 'solo_artist', 'Solo Artists', domain=[ ('group', '=', False), ], states={ 'readonly': ~Eval('active'), 'invisible': ~Eval('group'), }, depends=['active', 'group'], help='The membering solo artists of this group') group_artists = fields.Many2Many( 'artist-artist', 'solo_artist', 'group_artist', 'Group Artists', domain=[ ('group', '=', True), ], states={ 'readonly': ~Eval('active'), 'invisible': Bool(Eval('group')), }, depends=['active', 'group'], help='The groups this solo artist is member of') access_parties = fields.Function( fields.Many2Many( 'party.party', None, None, 'Access Parties', help='Shows the collection of all parties with access ' 'permissions to this artist'), 'get_access_parties') invitation_token = fields.Char('Invitation Token', help='The invitation token of a web user ' 'to claim this artist') description = fields.Text('Description', states=STATES, depends=DEPENDS, help='A description of the artist') picture_data = fields.Binary('Picture Data', states=STATES, depends=DEPENDS, help='Picture data of a photograph or logo') picture_data_mime_type = fields.Char('Picture Data Mime Type', states=STATES, depends=DEPENDS, help='The mime type of picture data.') payee = fields.Many2One( 'party.party', 'Payee', domain=[('id', 'in', Eval('access_parties', []))], states={ 'readonly': Or( ~Eval('active'), Bool(Eval('valid_payee')), ), }, depends=DEPENDS + ['access_parties', 'group_artists', 'solo_artists'], help='The actual payee party of this artist which is ' 'responsible for earnings of this artist') payee_proposal = fields.Many2One( 'party.party', 'Payee Proposal', domain=[('id', 'in', Eval('access_parties', []))], states=STATES, depends=DEPENDS + ['access_parties'], help='The proposed payee party of this artist') payee_acceptances = fields.Many2Many( 'artist.payee.acceptance', 'artist', 'party', 'Payee Acceptances', domain=[('id', 'in', Eval('access_parties', []))], states=STATES, depends=DEPENDS + ['access_parties'], help='The parties which accepts the payee proposal') valid_payee = fields.Boolean( 'Valid Payee', states={ 'readonly': Or( ~Eval('active'), ~Bool(Eval('payee')), ), }, depends=DEPENDS + ['payee'], help='Check, if the payee is manually validated by ' 'administration.') bank_account_number = fields.Many2One( 'bank.account.number', 'Bank Account Number', states={'readonly': ~Bool(Eval('payee'))}, domain=[('id', 'in', Eval('bank_account_numbers'))], depends=['payee', 'bank_account_numbers'], help='The bank account number for this artist') bank_account_numbers = fields.Function( fields.Many2Many( 'bank.account.number', None, None, 'Bank Account Numbers', help='Shows the collection of all available bank account ' 'numbers of this artist'), 'on_change_with_bank_account_numbers') bank_account_owner = fields.Function( fields.Many2One('party.party', 'Bank Account Owner', states={ 'readonly': (Or(~Bool(Eval('payee')), ~Bool(Eval('bank_account_number')))) }, help='Shows the bank account owner for this artist', depends=['payee', 'bank_account_number']), 'on_change_with_bank_account_owner') hat_account = fields.Property( fields.Many2One('account.account', 'Hat Account', domain=[ ('kind', '=', 'hat'), ('company', '=', Eval('context', {}).get('company', -1)), ], states={ 'required': Bool(Eval('context', {}).get('company')), 'invisible': ~Eval('context', {}).get('company'), })) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits') hat_balance = fields.Function(fields.Numeric( 'Hat Account Balance', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_hat_balance', searcher='search_hat_balance') active = fields.Boolean('Active') @classmethod def __setup__(cls): super(Artist, cls).__setup__() cls._sql_constraints = [ ('code_uniq', 'UNIQUE(code)', 'The code of the Artist must be unique.'), ('invitation_token_uniq', 'UNIQUE(invitation_token)', 'The invitation token of the artist must be unique.'), ] cls._error_messages.update({ 'wrong_name': ('Invalid Artist name "%%s": You can not use ' '"%s" in name field.' % SEPARATOR), }) cls._order.insert(1, ('name', 'ASC')) @staticmethod def default_invitation_token(): return str(uuid.uuid4()) @classmethod def validate(cls, artists): super(Artist, cls).validate(artists) for artist in artists: artist.check_name() def check_name(self): if SEPARATOR in self.name: self.raise_user_error('wrong_name', (self.name, )) @staticmethod def order_code(tables): table, _ = tables[None] return [CharLength(table.code), table.code] @staticmethod def default_active(): return True @staticmethod def default_payee_validation_state(): return 'accepted' @classmethod def get_access_parties(cls, artists, name): parties = {} for artist in artists: parties[artist.id] = [] if artist.party: parties[artist.id] += [artist.party.id] if artist.solo_artists: for solo_artist in artist.solo_artists: if solo_artist.party: parties[artist.id] += [solo_artist.party.id] return parties @fields.depends('party', 'access_parties') def on_change_with_bank_account_numbers(self, name=None): BankAccountNumber = Pool().get('bank.account.number') bank_account_numbers = [] numbers = BankAccountNumber.search([ 'OR', [('account.owner.id', 'in', [p.id for p in self.access_parties])], [ ('account.owner.id', '=', self.party.id if self.party else None), ] ]) if numbers: bank_account_numbers = [n.id for n in numbers] else: bank_account_numbers = None return bank_account_numbers @fields.depends('bank_account_number') def on_change_with_bank_account_owner(self, name=None): if self.bank_account_number: bank_account_owner = (self.bank_account_number.account.owner.id) else: bank_account_owner = None return bank_account_owner @fields.depends('payee') def on_change_payee(self): changes = {} if self.payee: changes = { 'payee_acceptances': { 'remove': [a.id for a in self.payee_acceptances] }, 'valid_payee': False, 'payee_proposal': None } return changes @fields.depends('payee_proposal') def on_change_payee_proposal(self): changes = {} if self.payee_proposal: changes = { 'payee_acceptances': { 'remove': [a.id for a in self.payee_acceptances] }, 'valid_payee': False } return changes @classmethod def get_hat_balance(cls, artists, names): WebUser = Pool().get('web.user') result = WebUser.get_balance(items=artists, names=names) return result def get_currency_digits(self, name): Company = Pool().get('company.company') if Transaction().context.get('company'): company = Company(Transaction().context['company']) return company.currency.digits return 2 @classmethod def search_hat_balance(cls, name, clause): WebUser = Pool().get('web.user') result = WebUser.search_balance(name, clause) return result @classmethod def create(cls, vlist): Sequence = Pool().get('ir.sequence') Configuration = Pool().get('collecting_society.configuration') vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('code'): config = Configuration(1) values['code'] = Sequence.get_id(config.artist_sequence.id) return super(Artist, cls).create(vlist) @classmethod def copy(cls, artists, default=None): if default is None: default = {} default = default.copy() default['code'] = None return super(Artist, cls).copy(artists, default=default) @classmethod def search_rec_name(cls, name, clause): return [ 'OR', ('code', ) + tuple(clause[1:]), ('name', ) + tuple(clause[1:]), ]
from flask.ext.babel import format_currency from trytond.model import ModelSQL, ModelView, fields from trytond.pyson import Eval, Not, Bool from trytond.transaction import Transaction from trytond.pool import Pool, PoolMeta from trytond import backend from sql import Null __all__ = [ 'Product', 'ProductsRelated', 'ProductTemplate', 'ProductMedia', 'ProductCategory' ] __metaclass__ = PoolMeta DEFAULT_STATE = {'invisible': Not(Bool(Eval('displayed_on_eshop')))} DEFAULT_STATE2 = { 'invisible': Not(Bool(Eval('displayed_on_eshop'))), 'required': Bool(Eval('displayed_on_eshop')), } class ProductMedia(ModelSQL, ModelView): "Product Media" __name__ = "product.media" sequence = fields.Integer("Sequence", required=True, select=True) static_file = fields.Many2One("nereid.static.file", "Static File", required=True, select=True)
class SaleChannel(ModelSQL, ModelView): """ Sale Channel model """ __name__ = 'sale.channel' name = fields.Char('Name', required=True, select=True, states=STATES, depends=DEPENDS) code = fields.Char('Code', select=True, states={'readonly': Eval('code', True)}, depends=['code']) active = fields.Boolean('Active', select=True) company = fields.Many2One('company.company', 'Company', required=True, select=True, states=STATES, depends=DEPENDS) address = fields.Many2One('party.address', 'Address', domain=[ ('party', '=', Eval('company_party')), ], depends=['company_party']) source = fields.Selection('get_source', 'Source', required=True, states=STATES, depends=DEPENDS) read_users = fields.Many2Many('sale.channel-read-res.user', 'channel', 'user', 'Read Users') create_users = fields.Many2Many('sale.channel-write-res.user', 'channel', 'user', 'Create Users') warehouse = fields.Many2One('stock.location', "Warehouse", required=True, domain=[('type', '=', 'warehouse')]) invoice_method = fields.Selection([('manual', 'Manual'), ('order', 'On Order Processed'), ('shipment', 'On Shipment Sent')], 'Invoice Method', required=True) shipment_method = fields.Selection([ ('manual', 'Manual'), ('order', 'On Order Processed'), ('invoice', 'On Invoice Paid'), ], 'Shipment Method', required=True) currency = fields.Many2One('currency.currency', 'Currency', required=True) price_list = fields.Many2One('product.price_list', 'Price List', required=True) payment_term = fields.Many2One('account.invoice.payment_term', 'Payment Term', required=True) company_party = fields.Function( fields.Many2One('party.party', 'Company Party'), 'on_change_with_company_party') taxes = fields.One2Many("sale.channel.tax", "channel", "Taxes") # These fields would be needed at the time of product imports from # external channel default_uom = fields.Many2One('product.uom', 'Default Product UOM', states=PRODUCT_STATES, depends=['source']) exceptions = fields.One2Many('channel.exception', 'channel', 'Exceptions') order_states = fields.One2Many("sale.channel.order_state", "channel", "Order States") shipping_carriers = fields.One2Many('sale.channel.carrier', 'channel', 'Shipping Carriers') last_order_import_time = fields.DateTime( 'Last Order Import Time', depends=['source', 'last_order_import_time_required'], states={ 'invisible': Eval('source') == 'manual', 'required': Bool(Eval('last_order_import_time_required')) }) last_order_export_time = fields.DateTime("Last Order Export Time", states=INVISIBLE_IF_MANUAL, depends=['source']) last_shipment_export_time = fields.DateTime('Last shipment export time', states=INVISIBLE_IF_MANUAL, depends=['source']) last_product_price_export_time = fields.DateTime( 'Last Product Price Export Time', states=INVISIBLE_IF_MANUAL, depends=['source']) last_product_export_time = fields.DateTime('Last Product Export Time', states=INVISIBLE_IF_MANUAL, depends=['source']) last_order_import_time_required = fields.Function( fields.Boolean('Last Order Import Time Required'), 'get_last_order_import_time_required') last_inventory_export_time = fields.DateTime('Last Inventory Export Time', states=INVISIBLE_IF_MANUAL, depends=['source']) payment_gateways = fields.One2Many('sale.channel.payment_gateway', 'channel', 'Payment Gateways') timezone = fields.Selection(TIMEZONES, 'Timezone', translate=False, required=True) # This field is to set according to sequence sequence = fields.Integer('Sequence', select=True) invoice_tax_account = fields.Many2One( 'account.account', 'Invoice Tax Account', domain=[ ('company', '=', Eval('company')), ('kind', 'not in', ['view', 'receivable', 'payable']), ], depends=['company'], help="GL to book for taxes when new taxes are created from channel", ) credit_note_tax_account = fields.Many2One( 'account.account', 'Credit Note Tax Account', domain=[ ('company', '=', Eval('company')), ('kind', 'not in', ['view', 'receivable', 'payable']), ], depends=['company'], help="GL to book for taxes when new taxes are created from channel", ) @staticmethod def default_timezone(): return 'UTC' @staticmethod def default_sequence(): return 10 def get_last_order_import_time_required(self, name): """ Returns True or False if last_order_import_time field should be required or not """ return False @classmethod def view_attributes(cls): return super(SaleChannel, cls).view_attributes() + [ ('//page[@id="configuration"]', 'states', { 'invisible': Eval('source') == 'manual', }), ('//page[@id="last_import_export_time"]', 'states', { 'invisible': Eval('source') == 'manual', }), ('//page[@id="product_defaults"]', 'states', { 'invisible': Eval('source') == 'manual', }), ('//page[@id="order_states"]', 'states', { 'invisible': Eval('source') == 'manual', }), ('//page[@id="import_export_buttons"]', 'states', { 'invisible': Eval('source') == 'manual', }) ] @classmethod def __setup__(cls): """ Setup the class before adding to pool """ super(SaleChannel, cls).__setup__() cls._buttons.update({ 'import_data_button': {}, 'export_data_button': {}, 'import_order_states_button': {}, 'import_shipping_carriers': {}, }) cls._error_messages.update({ "no_carriers_found": "Shipping carrier is not configured for code: %s", "no_tax_found": "%s (tax) of rate %f was not found.", "no_order_states_to_import": "No importable order state found\n" "HINT: Import order states from Order States tab in Channel" }) cls._order.insert(0, ('sequence', 'ASC')) @staticmethod def default_default_uom(): Uom = Pool().get('product.uom') unit = Uom.search([('name', '=', 'Unit')]) return unit and unit[0].id or None @classmethod def get_source(cls): """ Get the source """ return [('manual', 'Manual')] @staticmethod def default_last_order_import_time(): return datetime.utcnow() - relativedelta(months=1) @staticmethod def default_active(): return True @staticmethod def default_currency(): pool = Pool() Company = pool.get('company.company') Channel = pool.get('sale.channel') company_id = Channel.default_company() return company_id and Company(company_id).currency.id @staticmethod def default_company(): return Transaction().context.get('company') @fields.depends('company') def on_change_with_company_party(self, name=None): Company = Pool().get('company.company') company = self.company if not company: company = Company(SaleChannel.default_company()) # pragma: nocover return company and company.party.id or None @classmethod def get_current_channel(cls): """Helper method to get the current current_channel. """ return cls(Transaction().context['current_channel']) @classmethod @ModelView.button def import_shipping_carriers(cls, channels): """ Create shipping carriers by importing data from external channel. Since external channels are implemented by downstream modules, it is the responsibility of those channels to reuse this method or call super. :param instances: Active record list of magento instances """ raise NotImplementedError("This feature has not been implemented.") def get_shipping_carrier(self, code, silent=False): """ Search for an existing carrier by matching code and channel. If found, return its active record else raise_user_error. """ SaleCarrierChannel = Pool().get('sale.channel.carrier') try: carrier, = SaleCarrierChannel.search([ ('code', '=', code), ('channel', '=', self.id), ]) except ValueError: if silent: return None self.raise_user_error('no_carriers_found', error_args=code) else: return carrier.carrier def get_shipping_carrier_service(self, code, silent=False): """ Search for an existing carrier service by matching code and channel. If found, return its active record else raise_user_error. """ SaleCarrierChannel = Pool().get('sale.channel.carrier') try: carrier, = SaleCarrierChannel.search([ ('code', '=', code), ('channel', '=', self.id), ]) except ValueError: if silent: return None self.raise_user_error('no_carriers_found', error_args=code) else: return carrier.carrier_service def get_order_states_to_import(self): """ Return list of `sale.channel.order_state` to import orders """ OrderState = Pool().get('sale.channel.order_state') order_states_to_import = ['process_automatically', 'process_manually'] if Transaction().context.get('include_past_orders', False): order_states_to_import.append('import_as_past') order_states = OrderState.search([ ('action', 'in', order_states_to_import), ('channel', '=', self.id), ]) if not order_states: self.raise_user_error("no_order_states_to_import") return order_states def export_product_prices(self): """ Export product prices to channel Since external channels are implemented by downstream modules, it is the responsibility of those channels to reuse this method or call super. :return: List of active records of products for which prices are exported """ raise NotImplementedError( "This feature has not been implemented for %s channel yet." % self.source) def export_order_status(self): """ Export order status to external channel Since external channels are implemented by downstream modules, it is the responsibility of those channels to reuse this method or call super. :return: List of active records of orders exported """ raise NotImplementedError( "This feature has not been implemented for %s channel yet." % self.source) @classmethod def import_orders_using_cron(cls): # pragma: nocover """ Cron method to import orders from channels using cron Downstream module need not to implement this method. It will automatically call import_orders of the channel Silently pass if import_orders is not implemented """ for channel in cls.search([]): with Transaction().set_context(company=channel.company.id): try: channel.import_orders() except NotImplementedError: # Silently pass if method is not implemented pass @classmethod def export_product_prices_using_cron(cls): # pragma: nocover """ Cron method to export product prices to external channel using cron Downstream module need not to implement this method. It will automatically call export_product_prices method of the channel. Silently pass if export_product_prices is not implemented """ for channel in cls.search([]): with Transaction().set_context(company=channel.company.id): try: channel.export_product_prices() except NotImplementedError: # Silently pass if method is not implemented pass @classmethod def export_order_status_using_cron(cls): """ Export sales orders status to external channel using cron """ for channel in cls.search([]): try: channel.export_order_status() except NotImplementedError: pass def get_listings_to_export_inventory(self): """ This method returns listing, which needs inventory update Downstream module can override change its implementation :return: List of AR of `product.product.channel_listing` """ ChannelListing = Pool().get('product.product.channel_listing') cursor = Transaction().connection.cursor() if not self.last_inventory_export_time: # Return all active listings return ChannelListing.search([('channel', '=', self), ('state', '=', 'active')]) else: # Query to find listings # in which product inventory is recently updated or # listing it self got updated recently cursor.execute( """ SELECT listing.id FROM product_product_channel_listing AS listing INNER JOIN stock_move ON stock_move.product = listing.product WHERE listing.channel = %s AND listing.state = 'active' AND ( COALESCE(stock_move.write_date, stock_move.create_date) > %s OR COALESCE(listing.write_date, listing.create_date) > %s ) GROUP BY listing.id """, ( self.id, self.last_inventory_export_time, self.last_inventory_export_time, )) listing_ids = map(lambda r: r[0], cursor.fetchall()) return ChannelListing.browse(listing_ids) def export_inventory(self): """ Export inventory to external channel """ Listing = Pool().get('product.product.channel_listing') Channel = Pool().get('sale.channel') last_inventory_export_time = datetime.utcnow() channel_id = self.id listings = self.get_listings_to_export_inventory() # TODO: check if inventory export is allowed for this channel Listing.export_bulk_inventory(listings) # XXX: Exporting inventory to external channel is an expensive. # To avoid lock on sale_channel table save record after # exporting all inventory with Transaction().new_transaction() as txn: channel = Channel(channel_id) channel.last_inventory_export_time = last_inventory_export_time channel.save() txn.commit() @classmethod def export_inventory_from_cron(cls): # pragma: nocover """ Cron method to export inventory to external channel """ for channel in cls.search([]): with Transaction().set_context(company=channel.company.id): try: channel.export_inventory() except NotImplementedError: # Silently pass if method is not implemented pass def import_orders(self): """ Import orders from external channel. Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement importing or call super to delegate. :return: List of active records of sale orders that are imported """ raise NotImplementedError( "Import orders is not implemented for %s channels" % self.source) def import_order(self, order_info): """ Import specific order from external channel based on order_info. Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement importing or call super to delegate. :param order_info: The type of order_info depends on the channel. It could be an integer ID, a dictionary or anything. :return: imported sale order active record """ raise NotImplementedError( "Import order is not implemented for %s channels" % self.source) def import_products(self): """ Import Products from external channel. Since external channels are implemented by downstream modules, it is the responsibility of those channels to implement importing or call super to delegate. :return: List of active records of products that are imported """ raise NotImplementedError( "Method import_products is not implemented for %s channel yet" % self.source) # pragma: nocover def import_product(self, identifier, product_data=None): """ Import specific product 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. :param identifier: product code or sku :return: imported product active record """ raise NotImplementedError( "Method import_product is not implemented for %s channel yet" % self.source) # pragma: nocover def get_product(self, identifier, product_data=None): """ Given a SKU find the product or if it aint there create it and then return the active record of the product. This cannot be done async under any circumstances, because a product created on another transaction will not be visible to the current transaction unless the transaction is started over. :param identifier: product identifier """ return self.import_product(identifier, product_data) @classmethod @ModelView.button_action('sale_channel.wizard_import_data') def import_data_button(cls, channels): pass # pragma: nocover @classmethod @ModelView.button_action('sale_channel.wizard_export_data') def export_data_button(cls, channels): pass # pragma: nocover @classmethod @ModelView.button_action('sale_channel.wizard_import_order_states') def import_order_states_button(cls, channels): """ Import order states for current channel :param channels: List of active records of channels """ pass def import_order_states(self): """ Imports order states for current channel 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( "This feature has not been implemented for %s channel yet" % self.source) def get_default_tryton_action(self, code, name=None): """ Return default tryton_actions for this channel """ return { 'action': 'do_not_import', 'invoice_method': 'manual', 'shipment_method': 'manual', } def get_tryton_action(self, code): """ Get the tryton action corresponding to the channel state as per the predefined logic. Downstream modules need to inherit method and map states as per convenience. :param code: Code of channel state :returns: A dictionary of tryton action and shipment and invoice methods """ ChannelOrderState = Pool().get('sale.channel.order_state') try: order_state, = ChannelOrderState.search([ ('channel', '=', self.id), ('code', '=', code), ]) except ValueError: return { 'action': 'do_not_import', 'invoice_method': 'manual', 'shipment_method': 'manual', } else: return { 'action': order_state.action, 'invoice_method': order_state.invoice_method, 'shipment_method': order_state.shipment_method, } def create_order_state(self, code, name): """ This method creates order state for channel with given state code and state name. If state already exist, return same. :param code: State code used by external channel :param name: State name used by external channel :return: Active record of order state created or found """ OrderState = Pool().get('sale.channel.order_state') order_states = OrderState.search([('code', '=', code), ('channel', '=', self.id)]) if order_states: return order_states[0] values = self.get_default_tryton_action(code, name) values.update({ 'name': name, 'code': code, 'channel': self.id, }) return OrderState.create([values])[0] def get_availability_context(self): """ Return the context in which the stock availability of any product must be computed """ return { 'locations': [self.warehouse.id], } def get_availability(self, product): """ Return availability of the product within the context of this channel Availability consists of three factors: type: finite, bucket, infinite quantity: (optional) quantity available value: in_stock, limited, out_of_stock If this looks like the value in the stripe relay API, do not be confused, they are the same :) """ Listing = Pool().get('product.product.channel_listing') Product = Pool().get('product.product') listings = Listing.search([ ('channel', '=', self.id), ('product', '=', product), ]) if listings: # If there are listings, return the values from listing since # they override channel defaults for a product and channel return listings[0].get_availability() with Transaction().set_context(**self.get_availability_context()): rv = {'type': 'bucket'} quantity = Product.get_quantity([product], 'quantity')[product.id] if quantity > 0: rv['value'] = 'in_stock' else: rv['value'] = 'out_of_stock' return rv @classmethod def update_order_status_using_cron(cls): # pragma: nocover """ Cron method to update orders from channels using cron Downstream module need not to implement this method. It will automatically call update_order_status of the channel Silently pass if update_order_status is not implemented """ for channel in cls.search([]): with Transaction().set_context(company=channel.company.id): try: channel.update_order_status() except NotImplementedError: # Silently pass if method is not implemented pass def update_order_status(self): """This method is responsible for updating order status from external channel. """ if self.source == 'manual': return raise NotImplementedError( "This feature has not been implemented for %s channel yet." % self.source) def get_tax(self, name, rate, silent=False): """ Search for an existing Tax record by matching name and rate. If found return its active record else raise user error. """ TaxMapping = Pool().get('sale.channel.tax') domain = [('rate', '=', rate), ('channel', '=', self)] if name: # Search with name when it is provided # Name can be explicitly passed as None, when external # channels like magento does not provide it. domain.append(('name', '=', name)) try: mapped_tax, = TaxMapping.search(domain) except ValueError: if silent: return None self.raise_user_error('no_tax_found', error_args=(name, rate)) else: return mapped_tax.tax
class Product: "Product extension for Nereid" __name__ = "product.product" #: Decides the number of products that would be remebered. recent_list_size = 5 #: The list of fields allowed to be sent back on a JSON response from the #: application. This is validated before any product info is built #: #: The `name`, `sale_price`, `id` and `uri` are sent by default #: #: .. versionadded:: 0.3 json_allowed_fields = set(['rec_name', 'sale_price', 'id', 'uri']) uri = fields.Char('URI', select=True, states=DEFAULT_STATE2) displayed_on_eshop = fields.Boolean('Displayed on E-Shop?', select=True) long_description = fields.Text('Long Description') media = fields.One2Many( "product.media", "product", "Media", states={'invisible': Bool(Eval('use_template_images'))}, depends=['use_template_images']) images = fields.Function(fields.One2Many('nereid.static.file', None, 'Images'), getter='get_product_images') up_sells = fields.Many2Many('product.product-product.product', 'product', 'up_sell', 'Up-Sells', states=DEFAULT_STATE) cross_sells = fields.Many2Many('product.product-product.product', 'product', 'cross_sell', 'Cross-Sells', states=DEFAULT_STATE) default_image = fields.Function( fields.Many2One('nereid.static.file', 'Image'), 'get_default_image', ) use_template_description = fields.Boolean("Use template's description") use_template_images = fields.Boolean("Use template's images") @classmethod def view_attributes(cls): return super(Product, cls).view_attributes() + [ ('//page[@id="desc"]', 'states', { 'invisible': Bool(Eval('use_template_description')) }), ('//page[@id="ecomm_det"]', 'states', { 'invisible': Not(Bool(Eval('displayed_on_eshop'))) }), ('//page[@id="related_products"]', 'states', { 'invisible': Not(Bool(Eval('displayed_on_eshop'))) }) ] @classmethod def copy(cls, products, default=None): """Duplicate products """ if default is None: default = {} default = default.copy() duplicate_products = [] for index, product in enumerate(products, start=1): if product.displayed_on_eshop: default['uri'] = "%s-copy-%d" % (product.uri, index) duplicate_products.extend( super(Product, cls).copy([product], default)) return duplicate_products @classmethod def validate(cls, products): super(Product, cls).validate(products) cls.check_uri_uniqueness(products) def get_default_image(self, name): """ Returns default product image if any. """ return self.images[0].id if self.images else None @classmethod def __setup__(cls): super(Product, cls).__setup__() cls.description.states['invisible'] = Bool( Eval('use_template_description')) cls._error_messages.update({ 'unique_uri': ('URI of Product must be Unique'), }) cls.per_page = 12 @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().cursor table = TableHandler(cursor, cls, module_name) # Drop contraint for Unique URI on database # Now using validation method for that purpose table.drop_constraint('uri_uniq') super(Product, cls).__register__(module_name) @staticmethod def default_displayed_on_eshop(): return False @fields.depends('template', 'uri') def on_change_with_uri(self): """ If the URI is empty, slugify template name into URI """ if not self.uri and self.template: return slugify(self.template.name) return self.uri @staticmethod def default_use_template_description(): return True @staticmethod def default_use_template_images(): return True @classmethod def check_uri_uniqueness(cls, products): """ Ensure uniqueness of products uri. """ query = ['OR'] for product in products: # Do not check for unique uri if product is marked as # not displayed on eshop if not product.displayed_on_eshop: continue arg = [ 'AND', [('id', '!=', product.id)], [('uri', 'ilike', product.uri)] ] query.append(arg) if query != ['OR'] and cls.search(query): cls.raise_user_error('unique_uri') @classmethod @route('/product/<uri>') @route('/product/<path:path>/<uri>') def render(cls, uri, path=None): """Renders the template for a single product. :param uri: URI of the product :param path: Ignored parameter. This is used in cases where SEO friendly URL like product/category/sub-cat/sub-sub-cat/product-uri are generated """ products = cls.search([ ('displayed_on_eshop', '=', True), ('uri', '=', uri), ('template.active', '=', True), ], limit=1) if not products: return NotFound('Product Not Found') cls._add_to_recent_list(int(products[0])) return render_template('product.jinja', product=products[0]) @classmethod @route('/products/+recent', methods=['GET', 'POST']) def recent_products(cls): """ GET --- Return a list of recently visited products in JSON POST ---- Add the product to the recent list manually. This method is required if the product page is cached, or is served by a Caching Middleware like Varnish which may clear the session before sending the request to Nereid. Just as with GET the response is the AJAX of recent products """ if request.method == 'POST': cls._add_to_recent_list(request.form.get('product_id', type=int)) fields = set(request.args.getlist('fields')) or cls.json_allowed_fields fields = fields & cls.json_allowed_fields if 'sale_price' in fields: fields.remove('sale_price') response = [] if hasattr(session, 'sid'): products = cls.browse(session.get('recent-products', [])) for product in products: product_val = {} for field in fields: product_val[field] = getattr(product, field) product_val['sale_price'] = format_currency( product.sale_price(), request.nereid_currency.code) response.append(product_val) return jsonify(products=response) @classmethod def _add_to_recent_list(cls, product_id): """Adds the given product ID to the list of recently viewed products By default the list size is 5. To change this you can inherit product.product and set :attr:`recent_list_size` attribute to a non negative integer value For faster and easier access the products are stored with the ids alone this behaviour can be modified by subclassing. The deque object cannot be saved directly in the cache as its not serialisable. Hence a conversion to list is made on the fly .. versionchanged:: 0.3 If there is no session for the user this function returns an empty list. This ensures that the code is consistent with iterators that may use the returned value :param product_id: the product id to prepend to the list """ if not hasattr(session, 'sid'): current_app.logger.warning( "No session. Not saving to browsing history") return [] recent_products = deque(session.setdefault('recent-products', []), cls.recent_list_size) # XXX: If a product is already in the recently viewed list, but it # would be nice to remember the recent_products list in the order of # visits. if product_id not in recent_products: recent_products.appendleft(product_id) session['recent-products'] = list(recent_products) return recent_products @classmethod @route('/products') @route('/products/<int:page>') def render_list(cls, page=1): """ Renders the list of all products which are displayed_on_shop=True .. tip:: The implementation uses offset for pagination and could be extremely resource intensive on databases. Hence you might want to either have an alternate cache/search server based pagination or limit the pagination to a maximum page number. The base implementation does NOT limit this and could hence result in poor performance :param page: The page in pagination to be displayed """ products = Pagination(cls, [ ('displayed_on_eshop', '=', True), ('template.active', '=', True), ], page, cls.per_page) return render_template('product-list.jinja', products=products) def sale_price(self, quantity=0): """Return the Sales Price. A wrapper designed to work as a context variable in templating The price is calculated from the pricelist associated with the current user. The user in the case of guest user is logged in user. In the event that the logged in user does not have a pricelist set against the user, the guest user's pricelist is chosen. Finally if neither the guest user, nor the regsitered user has a pricelist set against them then the list price is displayed as the price of the product :param quantity: Quantity """ return self.list_price @classmethod @route('/sitemaps/product-index.xml') def sitemap_index(cls): """ Returns a Sitemap Index Page """ index = SitemapIndex(cls, [ ('displayed_on_eshop', '=', True), ('template.active', '=', True), ]) return index.render() @classmethod @route('/sitemaps/product-<int:page>.xml') def sitemap(cls, page): sitemap_section = SitemapSection(cls, [ ('displayed_on_eshop', '=', True), ('template.active', '=', True), ], page) sitemap_section.changefreq = 'daily' return sitemap_section.render() def get_absolute_url(self, **kwargs): """ Return the URL of the current product. This method works only under a nereid request context """ return url_for('product.product.render', uri=self.uri, **kwargs) def _json(self): """ Return a JSON serializable dictionary of the product """ response = { 'template': { 'name': self.template.rec_name, 'id': self.template.id, 'list_price': self.list_price, }, 'code': self.code, 'description': self.description, } return response def get_long_description(self): """ Get long description of product. If the product is set to use the template's long description, then the template long description is sent back. The returned value is a `~jinja2.Markup` object which makes it HTML safe and can be used directly in templates. It is recommended to use this method instead of trying to wrap this logic in the templates. """ if self.use_template_description: description = self.template.long_description else: description = self.long_description return Markup(description or '') def get_description(self): """ Get description of product. If the product is set to use the template's description, then the template description is sent back. The returned value is a `~jinja2.Markup` object which makes it HTML safe and can be used directly in templates. It is recommended to use this method instead of trying to wrap this logic in the templates. """ if self.use_template_description: description = self.template.description else: description = self.description return Markup(description or '') def get_product_images(self, name=None): """ Getter for `images` function field """ product_images = [] for media in self.media: if 'image' in media.static_file.mimetype: product_images.append(media.static_file.id) return product_images def get_images(self): """ Get images of product variant. If the product is set to use the template's images, then the template images is sent back. """ if self.use_template_images: return self.template.images return self.images
class SalePromotion(ModelSQL, ModelView, MatchMixin): 'Sale Promotion' __name__ = 'sale.promotion' name = fields.Char('Name', translate=True, required=True) company = fields.Many2One( 'company.company', 'Company', required=True, states={ 'readonly': Eval('id', 0) > 0, }, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], select=True) active = fields.Boolean('Active') start_date = fields.Date('Start Date', domain=[ 'OR', ('start_date', '<=', If(~Eval('end_date', None), datetime.date.max, Eval('end_date', datetime.date.max))), ('start_date', '=', None), ], depends=['end_date']) end_date = fields.Date('End Date', domain=[ 'OR', ('end_date', '>=', If(~Eval('start_date', None), datetime.date.min, Eval('start_date', datetime.date.min))), ('end_date', '=', None), ], depends=['start_date']) price_list = fields.Many2One('product.price_list', 'Price List', ondelete='CASCADE', domain=[ ('company', '=', Eval('company', -1)), ], depends=['company']) quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) unit = fields.Many2One('product.uom', 'Unit', states={ 'required': Bool(Eval('quantity', 0)), }, depends=['quantity']) unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') unit_category = fields.Function( fields.Many2One('product.uom.category', 'Unit Category'), 'on_change_with_unit_category') products = fields.Many2Many('sale.promotion-product.product', 'promotion', 'product', 'Products', domain=[ ('default_uom_category', '=', Eval('unit_category')), ], depends=['unit_category']) formula = fields.Char( 'Formula', required=True, help=('Python expression that will be evaluated with:\n' '- unit_price: the original unit_price')) @classmethod def __setup__(cls): super(SalePromotion, cls).__setup__() cls._error_messages.update({ 'invalid_formula': ('Invalid formula "%(formula)s" ' 'in promotion "%(promotion)s" ' 'with exception "%(exception)s".'), }) @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_active(): return True @fields.depends('unit') def on_change_with_unit_digits(self, name=None): if self.unit: return self.unit.digits return 2 @fields.depends('unit') def on_change_with_unit_category(self, name=None): if self.unit: return self.unit.category.id @classmethod def validate(cls, promotions): super(SalePromotion, cls).validate(promotions) for promotion in promotions: promotion.check_formula() def check_formula(self): context = self.get_context_formula(None) try: if not isinstance(self.get_unit_price(**context), Decimal): raise ValueError('Not a Decimal') except Exception, exception: self.raise_user_error( 'invalid_formula', { 'formula': self.formula, 'promotion': self.rec_name, 'exception': exception, })
class StockReturn(ModelView): "Stock Return" __name__ = "stock_return" _rec_name = 'stock_return' order_category = fields.Selection( [('purchase', u'采购'), ('return', u'采退')], 'order_category', states={'readonly': Bool(Eval('stock_return_lines'))}, required=True) categories = fields.Many2One( 'product.category', 'Categories', select=True, required=True, states={'readonly': Bool(Eval('stock_return_lines'))}) order_no_with = fields.Function( fields.One2Many('order_no', None, 'order_no_with'), 'on_change_with_order_no_with') order_no = fields.Many2One('order_no', 'Order', required=True, select=True, domain=[('id', 'in', Eval('order_no_with'))], depends=['order_no_with']) stock_return_lines = fields.One2Many( 'stock_return_lines', '', 'HrpShipmentReturnLines', states={'readonly': Equal(Eval('state'), 'done')}) state = fields.Selection([('assigned', u'未完成'), ('done', u'已完成')], 'State', select=True, required=True) confirm = fields.Boolean('confirm', states={ 'readonly': False, }) @staticmethod def default_state(): return 'assigned' @staticmethod def default_order_category(): return 'purchase' @fields.depends('categories', 'state', 'order_category') def on_change_with_order_no_with(self, name=None): if self.categories and self.state and self.order_category == 'return': ShipmentInReturn = Pool().get('stock.shipment.in.return') state = self.state if self.state == 'assigned': state = 'waiting' shipmentinreturn = ShipmentInReturn.search([('categories', '=', self.categories.id), ('state', '=', state)]) order_no_id = [] for i in shipmentinreturn: order_no_id.append(i.order_no.id) return order_no_id if self.categories and self.state and self.order_category == 'purchase': PurchaseBills = Pool().get('purchase_bills') bills = PurchaseBills.search([('state', '=', self.state), ('categories', '=', self.categories) ]) order_no_id = [] for i in bills: order_no_id.append(i.return_shipment.id) return order_no_id @fields.depends('categories', 'order_no', 'state', 'stock_return_lines', 'confirm', 'order_category') def on_change_order_no(self, name=None): if self.order_category == 'return' and self.state: ShipmentInReturn = Pool().get('stock.shipment.in.return') state = self.state if self.state == 'assigned': state = 'waiting' shipmentinreturn = ShipmentInReturn.search([ ('categories', '=', self.categories), ('state', '=', state), ('order_no', '=', self.order_no), ]) lines = [] line = 1 for each in shipmentinreturn: dict = {} dict['return_id'] = each.id dict['line'] = line dict['code'] = each.moves[0].product.code try: dict['note'] = each.moves[0].product.template.attach except: pass dict['lot'] = each.moves[0].lot.id dict['shelf_life_expiration_date'] = each.moves[ 0].lot.shelf_life_expiration_date dict['product_name'] = each.moves[0].product.template.name dict['drug_specifications'] = each.moves[ 0].product.template.drug_specifications dict['quantity'] = each.moves[0].quantity lines.append(dict) line += 1 self.stock_return_lines = lines if self.order_category == 'purchase' and self.state: PurchaseBills = Pool().get('purchase_bills') bills = PurchaseBills.search([('state', '=', self.state), ('categories', '=', self.categories), ('return_shipment', '=', self.order_no)]) lines = [] line = 1 for each in bills: dict = {} dict['purchase_id'] = each.id dict['line'] = line dict['code'] = each.product.code try: dict['note'] = each.product.template.attach except: pass dict['lot'] = each.lot.id dict[ 'shelf_life_expiration_date'] = each.lot.shelf_life_expiration_date dict['product_name'] = each.product.template.name dict[ 'drug_specifications'] = each.product.template.drug_specifications dict['quantity'] = each.shipment_quantity lines.append(dict) line += 1 self.stock_return_lines = lines
# -*- coding: utf-8 -*- """ sale.py """ from trytond.pool import PoolMeta, Pool from trytond.model import fields, ModelView, Workflow from trytond.pyson import Eval, Bool, And __all__ = ['Sale', 'SaleConfiguration'] __metaclass__ = PoolMeta STATES = { 'readonly': Eval('state') == 'done', 'required': Bool(Eval('is_dhl_de_shipping')), } INTERNATIONAL_STATES = { 'readonly': Eval('state') == 'done', 'required': And(Bool(Eval('is_dhl_de_shipping')), Bool(Eval('is_international_shipping'))), } INTERNATIONAL_DEPENDS = [ 'state', 'is_international_shipping', 'is_dhl_de_shipping' ] DHL_DE_PRODUCTS = [ (None, ''), ('BPI', 'Weltpaket'), ('EPN', 'DHL Paket'),
class MechanicalVentilation(ModelSQL, ModelView): 'Mechanical Ventilation History' __name__ = 'gnuhealth.icu.ventilation' def mv_duration(self, name): # Calculate the Mechanical Ventilation time now = datetime.now() mv_init = now mv_finnish = now if self.mv_start: mv_init = datetime.strptime(str(self.mv_start), '%Y-%m-%d %H:%M:%S') if self.mv_end: mv_finnish = datetime.strptime(str(self.mv_end), '%Y-%m-%d %H:%M:%S') delta = relativedelta(mv_finnish, mv_init) else: delta = relativedelta(now, mv_init) years_months_days = str(delta.years) + 'y ' \ + str(delta.months) + 'm ' \ + str(delta.days) + 'd' return years_months_days name = fields.Many2One('gnuhealth.inpatient.icu', 'Patient ICU Admission', required=True) ventilation = fields.Selection([(None, ''), ('none', 'None - Maintains Own'), ('nppv', 'Non-Invasive Positive Pressure'), ('ett', 'ETT'), ('tracheostomy', 'Tracheostomy')], 'Type', help="NPPV = Non-Invasive Positive " "Pressure Ventilation, BiPAP-CPAP \n" "ETT - Endotracheal Tube", sort=False) ett_size = fields.Integer( 'ETT Size', states={'invisible': Not(Equal(Eval('ventilation'), 'ett'))}) tracheostomy_size = fields.Integer( 'Tracheostomy size', states={'invisible': Not(Equal(Eval('ventilation'), 'tracheostomy'))}) mv_start = fields.DateTime('From', help="Start of Mechanical Ventilation", required=True) mv_end = fields.DateTime('To', help="End of Mechanical Ventilation", states={ 'invisible': Bool(Eval('current_mv')), 'required': Not(Bool(Eval('current_mv'))), }, depends=['current_mv']) mv_period = fields.Function(fields.Char('Duration'), 'mv_duration') current_mv = fields.Boolean('Current') remarks = fields.Char('Remarks') @classmethod def __setup__(cls): super(MechanicalVentilation, cls).__setup__() cls._constraints += [ ('check_patient_current_mv', 'patient_already_on_mv'), ] cls._error_messages.update({ 'patient_already_on_mv': 'Our records indicate that the patient' ' is already on Mechanical Ventilation !' }) def check_patient_current_mv(self): # Check for only one current mechanical ventilation on patient cursor = Transaction().cursor cursor.execute( "SELECT count(name) " "FROM " + self._table + " \ WHERE (name = %s AND current_mv)", (str(self.name.id), )) if cursor.fetchone()[0] > 1: return False return True @staticmethod def default_current_mv(): return True
class Sale: "Sale" __name__ = 'sale.sale' is_dhl_de_shipping = fields.Function( fields.Boolean('Is Shipping', readonly=True), 'get_is_dhl_de_shipping') dhl_de_product_code = fields.Selection( DHL_DE_PRODUCTS, 'DHL DE Product Code', states=STATES, depends=['state', 'is_dhl_de_shipping']) dhl_de_export_type = fields.Selection(DHL_DE_EXPORT_TYPES, 'DHL DE Export Type', states=INTERNATIONAL_STATES, depends=INTERNATIONAL_DEPENDS) dhl_de_export_type_description = fields.Char( 'Export Type Description', states={ 'required': And( Eval('state').in_(['confirmed', 'processing', 'done']), Bool(Eval('is_dhl_de_shipping')), Bool(Eval('is_international_shipping'))), 'readonly': Eval('state') == 'done', }, depends=INTERNATIONAL_DEPENDS) dhl_de_terms_of_trade = fields.Selection(DHL_DE_INCOTERMS, 'Terms of Trade (incoterms)', states=INTERNATIONAL_STATES, depends=INTERNATIONAL_DEPENDS) @classmethod def view_attributes(cls): return super(Sale, cls).view_attributes() + [ ('//page[@id="dhl_de"]', 'states', { 'invisible': ~Bool(Eval('is_dhl_de_shipping')) }), ('//group[@id="dhl_de_international"]', 'states', { 'invisible': ~Bool(Eval('is_international_shipping')) }) ] @staticmethod def default_dhl_de_product_code(): Config = Pool().get('sale.configuration') config = Config(1) return config.dhl_de_product_code @staticmethod def default_dhl_de_export_type(): Config = Pool().get('sale.configuration') config = Config(1) return config.dhl_de_export_type @staticmethod def default_dhl_de_terms_of_trade(): Config = Pool().get('sale.configuration') config = Config(1) return config.dhl_de_terms_of_trade @classmethod @ModelView.button @Workflow.transition('quotation') def quote(cls, sales): """ Downstream implementation of quote method which provides a default value to the dhl_de_export_type field. """ for sale in sales: if sale.is_dhl_de_shipping and sale.is_international_shipping: sale.set_dhl_de_export_type_description() sale.save() super(Sale, cls).quote(sales) def set_dhl_de_export_type_description(self): """ This method sets a default export type description if none is set """ if self.dhl_de_export_type_description: return if self.description: self.dhl_de_export_type_description = self.description else: self.dhl_de_export_type_description = ', '.join( map(lambda line: line.type == 'line' and line.product.name, self.lines)) def get_is_dhl_de_shipping(self, name): """ Ascertains if the sale is done using DHL (DE) """ return self.carrier and self.carrier.carrier_cost_method == 'dhl_de' @fields.depends('is_dhl_de_shipping', 'carrier') def on_change_carrier(self): """ Show/Hide dhl de Tab in view on change of carrier """ super(Sale, self).on_change_carrier() self.is_dhl_de_shipping = self.carrier and \ self.carrier.carrier_cost_method == 'dhl_de' or None def _get_shipment_sale(self, Shipment, key): """ Downstream implementation which adds dhl-specific fields to the unsaved Shipment record. """ ShipmentOut = Pool().get('stock.shipment.out') shipment = super(Sale, self)._get_shipment_sale(Shipment, key) if Shipment == ShipmentOut and self.is_dhl_de_shipping: shipment.dhl_de_product_code = self.dhl_de_product_code shipment.dhl_de_export_type = self.dhl_de_export_type shipment.dhl_de_export_type_description = \ self.dhl_de_export_type_description shipment.dhl_de_terms_of_trade = self.dhl_de_terms_of_trade return shipment
class PatientRounding(ModelSQL, ModelView): # Nursing Rounding for ICU # Inherit and append to the existing model the new functionality for ICU 'Patient Rounding' __name__ = 'gnuhealth.patient.rounding' icu_patient = fields.Boolean('ICU', help='Check this box if this is' 'an Intensive Care Unit rounding.') # Neurological assesment gcs = fields.Many2One( 'gnuhealth.icu.glasgow', 'GCS', domain=[('name', '=', Eval('name'))], depends=['name'], ) pupil_dilation = fields.Selection([('normal', 'Normal'), ('miosis', 'Miosis'), ('mydriasis', 'Mydriasis')], 'Pupil Dilation', sort=False) left_pupil = fields.Integer('L', help="size in mm of left pupil") right_pupil = fields.Integer('R', help="size in mm of right pupil") anisocoria = fields.Boolean( 'Anisocoria', on_change_with=['left_pupil', 'right_pupil'], ) pupillary_reactivity = fields.Selection([(None, ''), ('brisk', 'Brisk'), ('sluggish', 'Sluggish'), ('nonreactive', 'Nonreactive')], 'Pupillary Reactivity', sort=False) pupil_consensual_resp = fields.Boolean( 'Consensual Response', help="Pupillary Consensual Response") # Respiratory assesment # Mechanical ventilation information is on the patient ICU general info respiration_type = fields.Selection([(None, ''), ('regular', 'Regular'), ('deep', 'Deep'), ('shallow', 'Shallow'), ('labored', 'Labored'), ('intercostal', 'Intercostal')], 'Respiration', sort=False) oxygen_mask = fields.Boolean('Oxygen Mask') fio2 = fields.Integer('FiO2') peep = fields.Boolean('PEEP') peep_pressure = fields.Integer('cm H2O', help="Pressure", states={ 'invisible': Not(Bool(Eval('peep'))), 'required': Bool(Eval('peep')), }, depends=['peep']) sce = fields.Boolean('SCE', help="Subcutaneous Emphysema") lips_lesion = fields.Boolean('Lips lesion') oral_mucosa_lesion = fields.Boolean('Oral mucosa lesion') # Chest expansion characteristics chest_expansion = fields.Selection([(None, ''), ('symmetric', 'Symmetrical'), ('asymmetric', 'Asymmetrical')], 'Expansion', sort=False) paradoxical_expansion = fields.Boolean('Paradoxical', help="Paradoxical Chest Expansion") tracheal_tug = fields.Boolean('Tracheal Tug') # Trachea position trachea_alignment = fields.Selection([(None, ''), ('midline', 'Midline'), ('right', 'Deviated right'), ('left', 'Deviated left')], 'Tracheal alignment', sort=False) # Chest Drainages chest_drainages = fields.One2Many('gnuhealth.icu.chest_drainage', 'name', "Drainages") # Chest X-Ray xray = fields.Binary('Xray') # Cardiovascular assessment ecg = fields.Many2One( 'gnuhealth.icu.ecg', 'ECG', domain=[('name', '=', Eval('name'))], depends=['name'], ) venous_access = fields.Selection([(None, ''), ('none', 'None'), ('central', 'Central catheter'), ('peripheral', 'Peripheral')], 'Venous Access', sort=False) swan_ganz = fields.Boolean('Swan Ganz', help="Pulmonary Artery Catheterization - PAC -") arterial_access = fields.Boolean('Arterial Access') dialysis = fields.Boolean('Dialysis') edema = fields.Selection([(None, ''), ('none', 'None'), ('peripheral', 'Peripheral'), ('anasarca', 'Anasarca')], 'Edema', sort=False) # Blood & Skin bacteremia = fields.Boolean('Bacteremia') ssi = fields.Boolean('Surgery Site Infection') wound_dehiscence = fields.Boolean('Wound Dehiscence') cellulitis = fields.Boolean('Cellulitis') necrotizing_fasciitis = fields.Boolean('Necrotizing fasciitis') # Abdomen & Digestive vomiting = fields.Selection([(None, ''), ('none', 'None'), ('vomiting', 'Vomiting'), ('hematemesis', 'Hematemesis')], 'Vomiting', sort=False) bowel_sounds = fields.Selection([(None, ''), ('normal', 'Normal'), ('increased', 'Increased'), ('decreased', 'Decreased'), ('absent', 'Absent')], 'Bowel Sounds', sort=False) stools = fields.Selection([(None, ''), ('normal', 'Normal'), ('constipation', 'Constipation'), ('diarrhea', 'Diarrhea'), ('melena', 'Melena')], 'Stools', sort=False) peritonitis = fields.Boolean('Peritonitis signs') def on_change_with_anisocoria(self): if (self.left_pupil == self.right_pupil): return False else: return True @staticmethod def default_pupil_dilation(): return 'normal'
class SaleSecondaryMixin: __slots__ = () sale_secondary_uom = fields.Many2One('product.uom', "Sale Secondary UOM", domain=[ ('category', '!=', Eval('default_uom_category')), ], depends=['default_uom_category']) sale_secondary_uom_factor = fields.Float( "Sale Secondary UOM Factor", digits=uom_conversion_digits, states={ 'required': Bool(Eval('sale_secondary_uom')), 'invisible': ~Eval('sale_secondary_uom'), }, depends=['sale_secondary_uom'], help="The coefficient for the formula:\n" "1 (sale unit) = coefficient (secondary unit)") sale_secondary_uom_rate = fields.Float( "Sale Secondary UOM Rate", digits=uom_conversion_digits, states={ 'required': Bool(Eval('sale_secondary_uom')), 'invisible': ~Eval('sale_secondary_uom'), }, depends=['sale_secondary_uom'], help="The coefficient for the formula:\n" "coefficient (sale unit) = 1 (secondary unit)") @fields.depends('sale_secondary_uom_factor') def on_change_sale_secondary_uom_factor(self): if not self.sale_secondary_uom_factor: self.sale_secondary_uom_rate = None else: self.sale_secondary_uom_rate = round( 1. / self.sale_secondary_uom_factor, uom_conversion_digits[1]) @fields.depends('sale_secondary_uom_rate') def on_change_sale_secondary_uom_rate(self): if not self.sale_secondary_uom_rate: self.sale_secondary_uom_factor = None else: self.sale_secondary_uom_factor = round( 1. / self.sale_secondary_uom_rate, uom_conversion_digits[1]) @property def sale_secondary_uom_normal_rate(self): uom = self.sale_secondary_uom rate = self.sale_secondary_uom_rate if self.sale_uom and rate and uom: if self.sale_uom.accurate_field == 'factor': rate *= self.sale_uom.factor else: rate /= self.sale_uom.rate if uom.accurate_field == 'factor': rate /= uom.factor else: rate *= uom.rate return rate @property def sale_secondary_uom_normal_factor(self): uom = self.sale_secondary_uom factor = self.sale_secondary_uom_factor if uom and factor and self.sale_uom: if uom.accurate_field == 'factor': factor *= uom.factor else: factor /= uom.rate if self.sale_uom.accurate_field == 'factor': factor /= self.sale_uom.factor else: factor *= self.sale_uom.rate return factor @classmethod def validate(cls, records): super().validate(records) for record in records: record.check_sale_secondary_uom_factor_and_rate() def check_sale_secondary_uom_factor_and_rate(self): factor = self.sale_secondary_uom_factor rate = self.sale_secondary_uom_rate if factor and rate: if ((rate != round(1. / factor, uom_conversion_digits[1])) and factor != round(1. / rate, uom_conversion_digits[1])): raise UOMValidationError( gettext( 'sale_secondary_unit' '.msg_sale_secondary_uom_incompatible_factor_rate', record=self))
class AddSalePaymentView(BaseCreditCardViewMixin, ModelView): """ View for adding Sale Payments """ __name__ = 'sale.payment.add_view' sale = fields.Many2One( 'sale.sale', 'Sale', required=True, readonly=True ) party = fields.Many2One('party.party', 'Party', readonly=True) gateway = fields.Many2One( 'payment_gateway.gateway', 'Gateway', required=True, domain=[('users', '=', Eval('user'))], depends=['user'] ) currency_digits = fields.Function( fields.Integer('Currency Digits'), 'get_currency_digits' ) method = fields.Function( fields.Char('Payment Gateway Method'), 'get_method' ) use_existing_card = fields.Boolean( 'Use existing Card?', states={ 'invisible': Eval('method') != 'credit_card' }, depends=['method'] ) payment_profile = fields.Many2One( 'party.payment_profile', 'Payment Profile', domain=[ ('party', '=', Eval('party')), ('gateway', '=', Eval('gateway')), ], states={ 'required': And( Eval('method') == 'credit_card', Bool(Eval('use_existing_card')) ), 'invisible': ~Bool(Eval('use_existing_card')) }, depends=['method', 'use_existing_card', 'party', 'gateway'] ) amount = fields.Numeric( 'Amount', digits=(16, Eval('currency_digits', 2)), required=True, depends=['currency_digits'], ) reference = fields.Char( 'Reference', states={ 'invisible': Not(Eval('method') == 'manual'), } ) user = fields.Many2One( "res.user", "Tryton User", readonly=True ) @classmethod def __setup__(cls): super(AddSalePaymentView, cls).__setup__() INV = Or( Eval('method') == 'manual', And( Eval('method') == 'credit_card', Bool(Eval('use_existing_card')) ) ) STATE1 = { 'required': And( ~Bool(Eval('use_existing_card')), Eval('method') == 'credit_card' ), 'invisible': INV } DEPENDS = ['use_existing_card', 'method'] cls.owner.states.update(STATE1) cls.owner.depends.extend(DEPENDS) cls.number.states.update(STATE1) cls.number.depends.extend(DEPENDS) cls.expiry_month.states.update(STATE1) cls.expiry_month.depends.extend(DEPENDS) cls.expiry_year.states.update(STATE1) cls.expiry_year.depends.extend(DEPENDS) cls.csc.states.update(STATE1) cls.csc.depends.extend(DEPENDS) cls.swipe_data.states = {'invisible': INV} cls.swipe_data.depends = ['method'] def get_currency_digits(self, name): return self.sale.currency_digits if self.sale else 2 def get_method(self, name=None): """ Return the method based on the gateway """ return self.gateway.method @fields.depends('gateway') def on_change_gateway(self): if self.gateway: return { 'method': self.gateway.method, } return {}
class Attachment(metaclass=PoolMeta): __name__ = 'ir.attachment' cryptolog_signer = fields.Many2One( 'party.party', 'Cryptolog Signer', ondelete='RESTRICT', states={'readonly': Bool(Eval('cryptolog_status'))}, depends=['cryptolog_status']) cryptolog_id = fields.Char('Cryptolog ID', readonly=True) cryptolog_url = fields.Char('Cryptolog URL', readonly=True) cryptolog_status = fields.Selection([ ('', ''), ('issued', 'Issued'), ('ready', 'Ready'), ('expired', 'Expired'), ('canceled', 'Canceled'), ('failed', 'Failed'), ('completed', 'Completed'), ], 'Cryptolog Status', readonly=True) cryptolog_data = fields.Function( fields.Binary( 'Signed Document', filename='name', states={'invisible': Eval('cryptolog_status') != 'completed'}, depends=['cryptolog_status']), 'cryptolog_get_documents') cryptolog_logs = fields.Text('Cryptolog Logs', readonly=True) @classmethod def __setup__(cls): super(Attachment, cls).__setup__() cls._buttons.update({ 'cryptolog_request_transaction': {}, 'cryptolog_get_transaction_info': {} }) @classmethod def cryptolog_headers(cls): return {'Content-Type': 'application/xml'} @classmethod def cryptolog_basic_auth(cls): assert (config.get(CONFIG_SECTION, 'auth_mode') == 'basic') username = config.get(CONFIG_SECTION, 'username') assert username password = config.get(CONFIG_SECTION, 'password') assert password return requests.auth.HTTPBasicAuth(username, password) def append_log(self, method, response): self.cryptolog_logs = self.cryptolog_logs or '' self.cryptolog_logs += '%s @ %s\n%s\n\n' % ( method, datetime.datetime.utcnow(), response) @classmethod @ModelView.button def cryptolog_request_transaction(cls, attachments): # for now we support only one record attachment, = attachments url = config.get(CONFIG_SECTION, 'url') assert url verify = True if config.get(CONFIG_SECTION, 'no_verify') == '1': verify = False method = 'requester.requestTransaction' headers = cls.cryptolog_headers() auth = cls.cryptolog_basic_auth() signer = attachment.cryptolog_signer data = { 'documents': [{ 'documentType': 'pdf', 'name': attachment.name, 'content': xmlrpc.client.Binary(attachment.data) }], 'signers': [{ 'firstname': '', 'lastname': signer.full_name, 'emailAddress': signer.email, 'phoneNum': signer.phone }], 'mustContactFirstSigner': True, 'finalDocSent': True } successURL = config.get(CONFIG_SECTION, 'success-url') if successURL is not None: successURL = successURL.format(att=attachment) data['signers'][0]['successURL'] = successURL failURL = config.get(CONFIG_SECTION, 'fail-url') if failURL is not None: failURL = failURL.format(att=attachment) data['signers'][0]['failURL'] = failURL cancelURL = config.get(CONFIG_SECTION, 'cancel-url') if cancelURL is not None: cancelURL = cancelURL.format(att=attachment) data['signers'][0]['cancelURL'] = cancelURL data = xmlrpc.client.dumps((data, ), method) req = requests.post(url, headers=headers, auth=auth, data=data, verify=verify) if req.status_code > 299: raise Exception(req.content) response, _ = xmlrpc.client.loads(req.content) attachment.cryptolog_status = 'issued' attachment.append_log(method, response) attachment.cryptolog_id = response[0]['id'] attachment.cryptolog_url = response[0]['url'] attachment.save() @classmethod @ModelView.button def cryptolog_get_transaction_info(cls, attachments): attachment, = attachments url = config.get(CONFIG_SECTION, 'url') assert url verify = True if config.get(CONFIG_SECTION, 'no_verify') == '1': verify = False method = 'requester.getTransactionInfo' headers = cls.cryptolog_headers() auth = cls.cryptolog_basic_auth() data = xmlrpc.client.dumps((attachment.cryptolog_id, ), method) req = requests.post(url, headers=headers, auth=auth, data=data, verify=verify) response, _ = xmlrpc.client.loads(req.content) attachment.append_log(method, response) attachment.cryptolog_status = response[0]['status'] attachment.save() def cryptolog_get_documents(self, name): # tryton trick (extra param on context to retrieve file size) if self.cryptolog_id and self.cryptolog_status == 'completed': if Transaction().context.get('%s.%s' % (self.__name__, name)) == \ 'size': # does not make sense to retrieve the doc juste for the size return 1024 url = config.get(CONFIG_SECTION, 'url') verify = True if config.get(CONFIG_SECTION, 'no_verify') == '1': verify = False method = 'requester.getDocuments' headers = self.cryptolog_headers() auth = self.cryptolog_basic_auth() data = xmlrpc.client.dumps((self.cryptolog_id, ), method) req = requests.post(url, headers=headers, auth=auth, data=data, verify=verify) response, _ = xmlrpc.client.loads(req.content) return response[0][0]['content']
class View(ModelSQL, ModelView): "View" __name__ = 'ir.ui.view' _rec_name = 'model' model = fields.Char('Model', select=True, states={ 'required': Eval('type') != 'board', }) priority = fields.Integer('Priority', required=True, select=True) type = fields.Selection([ (None, ''), ('tree', 'Tree'), ('form', 'Form'), ('graph', 'Graph'), ('calendar', 'Calendar'), ('board', 'Board'), ('list-form', "List Form"), ], 'View Type', select=True, domain=[ If(Bool(Eval('inherit')), ('type', '=', None), ('type', '!=', None)), ], depends=['inherit']) type_string = type.translated('type') data = fields.Text('Data') name = fields.Char('Name', states={ 'invisible': ~(Eval('module') & Eval('name')), }, depends=['module'], readonly=True) arch = fields.Function(fields.Text('View Architecture', states={ 'readonly': Bool(Eval('name')), }, depends=['name']), 'get_arch', setter='set_arch') inherit = fields.Many2One('ir.ui.view', 'Inherited View', select=True, ondelete='CASCADE') field_childs = fields.Char('Children Field', states={ 'invisible': Eval('type') != 'tree', }, depends=['type']) module = fields.Char('Module', states={ 'invisible': ~Eval('module'), }, readonly=True) domain = fields.Char('Domain', states={ 'invisible': ~Eval('inherit'), }, depends=['inherit']) # AKE : Force usage of MemoryCache for non serializable data _get_rng_cache = MemoryCache('ir_ui_view.get_rng') @classmethod def __setup__(cls): super(View, cls).__setup__() cls._order.insert(0, ('priority', 'ASC')) cls._buttons.update({ 'show': { 'readonly': Eval('type') != 'form', 'depends': ['type'], }, }) @staticmethod def default_priority(): return 16 @staticmethod def default_module(): return Transaction().context.get('module') or '' def get_rec_name(self, name): return '%s (%s)' % (self.model, self.inherit.rec_name if self.inherit else self.type_string) @classmethod @ModelView.button_action('ir.act_view_show') def show(cls, views): pass @classmethod @lru_cache(maxsize=10) def get_rng(cls, type_): key = (cls.__name__, type_) rng = cls._get_rng_cache.get(key) if rng is None: if type_ == 'list-form': type_ = 'form' rng_name = os.path.join(os.path.dirname(__file__), type_ + '.rng') with open(rng_name, 'rb') as fp: rng = etree.fromstring(fp.read()) cls._get_rng_cache.set(key, rng) return rng @property def rng_type(self): if self.inherit: return self.inherit.rng_type return self.type @classmethod def validate(cls, views): super(View, cls).validate(views) cls.check_xml(views) @classmethod def check_xml(cls, views): "Check XML" for view in views: if not view.arch: continue xml = view.arch.strip() if not xml: continue try: try: encoded = xml.encode('utf-8') except UnicodeEncodeError: encoded = xml tree = etree.fromstring(encoded) except Exception: # JCA : print faulty xml try: import pprint pprint.pprint(xml) except: print(xml) raise if hasattr(etree, 'RelaxNG'): validator = etree.RelaxNG(etree=cls.get_rng(view.rng_type)) if not validator.validate(tree): error_log = '\n'.join( map(str, validator.error_log.filter_from_errors())) raise XMLError( gettext('ir.msg_view_invalid_xml', name=view.rec_name), error_log) root_element = tree.getroottree().getroot() # validate pyson attributes validates = { 'states': fields.states_validate, } def encode(element): for attr in ('states', 'domain', 'spell', 'colors'): if not element.get(attr): continue try: value = PYSONDecoder().decode(element.get(attr)) validates.get(attr, lambda a: True)(value) except Exception as e: error_log = '%s: <%s %s="%s"/>' % ( e, element.get('id') or element.get('name'), attr, element.get(attr)) raise XMLError( gettext('ir.msg_view_invalid_xml', name=view.rec_name), error_log) from e for child in element: encode(child) encode(root_element) def get_arch(self, name): value = None if self.name and self.module: path = os.path.join(self.module, 'view', self.name + '.xml') try: with file_open(path, subdir='modules', mode='r', encoding='utf-8') as fp: value = fp.read() except IOError: pass if not value: value = self.data return value @classmethod def set_arch(cls, views, name, value): cls.write(views, {'data': value}) @classmethod def delete(cls, views): super(View, cls).delete(views) # Restart the cache ModelView._fields_view_get_cache.clear() @classmethod def create(cls, vlist): views = super(View, cls).create(vlist) # Restart the cache ModelView._fields_view_get_cache.clear() return views @classmethod def write(cls, views, values, *args): super(View, cls).write(views, values, *args) # Restart the cache ModelView._fields_view_get_cache.clear()
_CT_PARTY_STATES = { 'invisible': Not(In(Eval('ct_kind_analytic', ''), _PARTY + _PRODUCT)), } _DT_PRODUCT_STATES = { 'invisible': Not(In(Eval('dt_kind_analytic', ''), _PRODUCT)), } _CT_PRODUCT_STATES = { 'invisible': Not(In(Eval('ct_kind_analytic', ''), _PRODUCT)), } _ACC_PRODUCT_STATES = { 'required': Bool(Eval('product')), 'invisible': And(Not(In(Eval('dt_kind_analytic', ''), _PRODUCT)), Not(In(Eval('ct_kind_analytic', ''), _PRODUCT))), } class LineWest(ModelSQL, ModelView): "Lines of business operations west standart" _name = "ekd.account.move.line.west" _description = __doc__ _inherits = {'ekd.account.move.line': 'line'} line = fields.Many2One('ekd.account.move.line', 'Entries', ondelete="CASCADE",
class Category: __metaclass__ = PoolMeta __name__ = 'product.category' customs = fields.Boolean('Customs', select=True, states={ 'readonly': Bool(Eval('childs', [0])) | Bool(Eval('parent')), }, depends=['parent']) tariff_codes_parent = fields.Boolean( "Use Parent's Tariff Codes", states={ 'invisible': ~Eval('customs', False), }, depends=['customs'], help='Use the tariff codes defined on the parent category') tariff_codes = fields.One2Many('product-customs.tariff.code', 'product', 'Tariff Codes', order=[('sequence', 'ASC'), ('id', 'ASC')], states={ 'invisible': (Eval('tariff_codes_parent', False) | ~Eval('customs', False)), }, depends=['tariff_codes_parent', 'customs']) @classmethod def __setup__(cls): super(Category, cls).__setup__() cls.parent.domain = [('customs', '=', Eval('customs', False)), cls.parent.domain or []] cls.parent.depends.append('customs') cls.parent.states['required'] = Or( cls.parent.states.get('required', False), Eval('tariff_codes_parent', False)) cls.parent.depends.append('tariff_codes_parent') @classmethod def default_customs(cls): return False @classmethod def default_tariff_codes_parent(cls): return False @fields.depends('parent', 'customs') def on_change_with_customs(self): if self.parent: return self.parent.customs return self.customs def get_tariff_code(self, pattern): if not self.tariff_codes_parent: for link in self.tariff_codes: if link.tariff_code.match(pattern): return link.tariff_code else: return self.parent.get_tariff_code(pattern) @classmethod def view_attributes(cls): return super(Category, cls).view_attributes() + [ ('/form/notebook/page[@id="customs"]', 'states', { 'invisible': ~Eval('customs', False), }), ] @classmethod def delete(cls, categories): pool = Pool() Product_TariffCode = pool.get('product-customs.tariff.code') products = [str(t) for t in categories] super(Category, cls).delete(categories) for products in grouped_slice(products): product_tariffcodes = Product_TariffCode.search([ 'product', 'in', list(products), ]) Product_TariffCode.delete(product_tariffcodes)
class ProductProductAttribute(ModelSQL, ModelView): "Product's Product Attribute" __name__ = 'product.product.attribute' template = fields.Many2One("product.template", "Template", required=True, select=True, ondelete='CASCADE', domain=[ If(Bool(Eval('product')), ('products', '=', Eval('product')), ()), ], depends=['product']) product = fields.Many2One("product.product", "Variant", select=True, domain=[ If(Bool(Eval('template')), ('template', '=', Eval('template')), ()), ], depends=['template']) 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"), 'on_change_with_attribute_set', searcher='search_attribute_set') value = fields.Function(fields.Char('Attribute Value'), getter='get_value') value_char = fields.Char("Value Char", translate=True, 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('product', '_parent_product.template', 'attribute_set') def on_change_product(self): if self.product: self.template = self.product.template self.attribute_set = self.template.attribute_set @fields.depends('attribute') def on_change_attribute(self): self.attribute_type = self.get_attribute_type() def get_attribute_type(self, name=None): """ Returns type of attribute """ if self.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 getattr(self, 'value_' + self.attribute_type) @fields.depends('template', '_parent_template.id', '_parent_template.attribute_set') def on_change_with_attribute_set(self, name=None): """ Returns attribute set for corresponding product's template """ if self.template and self.template.attribute_set: return self.template.attribute_set.id @classmethod def search_attribute_set(cls, name, clause): return [ (('template.attribute_set', ) + tuple(clause[1:])), ]