class ShipmentOut(metaclass=PoolMeta): __name__ = 'stock.shipment.out' carrier = fields.Many2One('carrier', 'Carrier', states={ 'readonly': ~Eval('state').in_(['draft', 'waiting', 'assigned', 'packed']), }, depends=['state']) cost_currency = fields.Many2One('currency.currency', 'Cost Currency', states={ 'invisible': ~Eval('carrier'), 'required': Bool(Eval('carrier')), 'readonly': ~Eval('state').in_(['draft', 'waiting', 'assigned', 'packed']), }, depends=['carrier', 'state']) cost = fields.Numeric('Cost', digits=price_digits, states={ 'invisible': ~Eval('carrier'), 'readonly': ~Eval('state').in_(['draft', 'waiting', 'assigned', 'packed']), }, depends=['carrier', 'state']) cost_invoice_line = fields.Many2One('account.invoice.line', 'Cost Invoice Line', readonly=True) @classmethod def __setup__(cls): super(ShipmentOut, cls).__setup__() cls._error_messages.update({ 'missing_account_revenue': ('Missing "Account Revenue" on ' 'product "%s".'), }) def _get_carrier_context(self): return {} def get_carrier_context(self): return self._get_carrier_context() @fields.depends(methods=['on_change_inventory_moves']) def on_change_carrier(self): self.on_change_inventory_moves() @fields.depends('carrier', 'customer', 'inventory_moves', methods=['_get_carrier_context']) def on_change_inventory_moves(self): try: super(ShipmentOut, self).on_change_inventory_moves() except AttributeError: pass if not self.carrier: return with Transaction().set_context(self._get_carrier_context()): cost, currency_id = self.carrier.get_sale_price() self.cost = cost.quantize( Decimal(1) / 10 ** self.__class__.cost.digits[1]) self.cost_currency = currency_id def _get_cost_tax_rule_pattern(self): 'Get tax rule pattern for invoice line' return {} def get_cost_invoice_line(self, invoice): pool = Pool() Product = pool.get('product.product') Currency = pool.get('currency.currency') InvoiceLine = pool.get('account.invoice.line') if not self.cost: return {} invoice_line = InvoiceLine() product = self.carrier.carrier_product invoice_line.type = 'line' invoice_line.product = product party = invoice.party party_context = {} if party.lang: party_context['language'] = party.lang.code with Transaction().set_context(party_context): invoice_line.description = Product(product.id).rec_name invoice_line.quantity = 1 # XXX invoice_line.unit = product.sale_uom.id cost = self.cost if invoice.currency != self.cost_currency: with Transaction().set_context(date=invoice.currency_date): cost = Currency.compute(self.cost_currency, cost, invoice.currency, round=False) invoice_line.unit_price = cost.quantize( Decimal(1) / 10 ** InvoiceLine.unit_price.digits[1]) taxes = [] pattern = self._get_cost_tax_rule_pattern() for tax in product.customer_taxes_used: if party.customer_tax_rule: tax_ids = party.customer_tax_rule.apply(tax, pattern) if tax_ids: taxes.extend(tax_ids) continue taxes.append(tax.id) if party.customer_tax_rule: tax_ids = party.customer_tax_rule.apply(None, pattern) if tax_ids: taxes.extend(tax_ids) invoice_line.taxes = taxes invoice_line.account = product.account_revenue_used if not invoice_line.account: self.raise_user_error('missing_account_revenue', error_args=(product.rec_name,)) return invoice_line
class EMBEmployeeDetails(Workflow, ModelSQL, ModelView): 'EMB employee details' __name__ = 'emb.employee.details' salary_code = fields.Char('Salary Code', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state']) employee = fields.Many2One('company.employee', 'Employee', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state']) date = fields.Date('Dated', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state']) bill_month = fields.Char('Embalming Bill for the month of', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state']) emb_fee = fields.One2Many( 'emb.bill', 'bill', 'E.M.B Fee', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state'] ) state = fields.Selection([ ('draft', 'Draft'), ('confirm', 'Confirm'), ('cancel_emp', 'Cancel'), ('f_to_hod', 'Forwarded to HoD'), ('f_to_ao', 'Forwarded to AO'), ('cancel_hod', 'Cancelled by HoD'), ('approve_ao', 'Approved by AO'), ('cancel_ao', 'Cancelled by AO') ], 'Status', states={ 'readonly': ~Eval('state').in_(['draft']) }, depends=['state'], readonly=True) remarks = fields.Char('Remarks', states={ 'required': Eval('state').in_(['cancel_ao']), 'invisible': ~Eval('state').in_(['cancel_ao']) }) @classmethod def __setup__(cls): super().__setup__() cls._transitions |= set(( ('draft', 'confirm'), ('confirm', 'f_to_hod'), ('cancel', 'cancel_emp'), ('f_to_hod', 'f_to_ao'), ('f_to_hod', 'cancel_hod'), ('f_to_ao', 'approve_ao'), ('f_to_ao', 'cancel_ao'), )) cls._buttons.update({ 'submit_to_hod': { 'invisible':~Eval('state').in_( ['draft']), 'depends':['state'], }, 'confirm': { 'invisible':~Eval('state').in_( ['draft']), 'depends':['state'], }, 'cancel': { 'invisible':~Eval('state').in_( ['confirm']), 'depends':['state'], }, 'cancel_submission_hod': { 'invisible':~Eval('state').in_( ['f_to_hod']), 'depends':['state'], }, 'submit_to_ao': { 'invisible':~Eval('state').in_( ['approve_hod']), 'depends':['state'], }, 'approve_for_ao': { 'invisible':~Eval('state').in_( ['f_to_ao']), 'depends':['state'], }, 'cancel_submission_ao': { 'invisible':~Eval('state').in_( ['f_to_ao']), 'depends':['state'], }, }) @staticmethod def default_state(): return 'draft' @classmethod @ModelView.button @Workflow.transition('f_to_hod') def submit_to_hod(cls, records): pass @classmethod @ModelView.button @Workflow.transition('f_to_ao') def submit_to_ao(cls, records): pass @classmethod @ModelView.button @Workflow.transition('confirm') def confirm(cls, records): pass @classmethod @ModelView.button @Workflow.transition('cancel') def cancel(cls, records): pass @classmethod @ModelView.button @Workflow.transition('cancel_hod') def cancel_submission_hod(cls, records): pass @classmethod @ModelView.button @Workflow.transition('approve_ao') def approve_for_ao(cls, records): pass @classmethod @ModelView.button @Workflow.transition('cancel_ao') def cancel_submission_ao(cls, records): pass @staticmethod def default_employee(): pool = Pool() User = pool.get('res.user') user = User(Transaction().user) employee = user.employee return employee.id if employee else None @fields.depends('employee') def on_change_employee(self): self.salary_code=self.employee.salary_code if self.employee else '' @classmethod def default_date(cls): present_date = datetime.now() return present_date @classmethod def default_bill_month(cls): month = datetime.today().strftime("%B") return month
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' ) 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)
class Period(Workflow, ModelSQL, ModelView): 'Period' __name__ = 'tmi.period' name = fields.Char('Name', required=True) start_date = fields.Date('Starting Date', required=True, states=_STATES, domain=[('start_date', '<=', Eval('end_date', None))], depends=_DEPENDS + ['end_date'], select=True) end_date = fields.Date('Ending Date', required=True, states=_STATES, domain=[('end_date', '>=', Eval('start_date', None))], depends=_DEPENDS + ['start_date'], select=True) year = fields.Many2One('tmi.year', 'Year', required=True, states=_STATES, depends=_DEPENDS, select=True) state = fields.Selection([ ('open', 'Open'), ('close', 'Close'), ], 'State', readonly=True, required=True) type = fields.Selection([ ('standard', 'Standard'), ('adjustment', 'Adjustment'), ], 'Type', required=True, states=_STATES, depends=_DEPENDS, select=True) icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __setup__(cls): super(Period, cls).__setup__() cls._order.insert(0, ('start_date', 'ASC')) cls._error_messages.update({ 'no_period_date': 'No period defined for date "%s".', 'modify_del_period_moves': ('You can not modify/delete ' 'period "%s" because it has moves.'), 'create_period_closed_year': ('You can not create ' 'a period on year "%s" because it is closed.'), 'open_period_closed_year': ('You can not open period ' '"%(period)s" because its year "%(year)s" is ' 'closed.'), 'close_period_non_posted_move': ('You can not close period ' '"%(period)s" because there are non posted moves ' '"%(move)s" in this period.'), 'periods_overlap': ('"%(first)s" and "%(second)s" periods ' 'overlap.'), 'check_move_sequence': ('Period "%(first)s" and "%(second)s" ' 'have the same sequence.'), 'year_dates': ('Dates of period "%s" are outside ' 'are outside it\'s year dates.'), }) cls._transitions |= set(( ('open', 'close'), ('close', 'locked'), ('close', 'open'), )) cls._buttons.update({ 'close': { 'invisible': Eval('state') != 'open', 'depends': ['state'], }, 'reopen': { 'invisible': Eval('state') != 'close', 'depends': ['state'], }, 'lock': { 'invisible': Eval('state') != 'close', 'depends': ['state'], }, }) @staticmethod def default_state(): return 'open' @staticmethod def default_type(): return 'standard' def get_icon(self, name): return { 'open': 'tryton-open', 'close': 'tryton-close', 'locked': 'tryton-readonly', }.get(self.state) @classmethod def validate(cls, periods): super(Period, cls).validate(periods) for period in periods: period.check_dates() period.check_year_dates() @classmethod def get_period_ids(cls, name): pool = Pool() Period = pool.get('tmi.period') context = Transaction().context period = None if name.startswith('start_'): period_ids = [] if context.get('start_period'): period = Period(context['start_period']) elif name.startswith('end_'): period_ids = [] if context.get('end_period'): period = Period(context['end_period']) else: periods = Period.search([ ('year', '=', context.get('year')), ('type', '=', 'standard'), ], order=[('start_date', 'DESC')], limit=1) if periods: period, = periods if period: periods = Period.search([ ('year', '=', context.get('year')), ('end_date', '<=', period.start_date), ]) if period.start_date == period.end_date: periods.append(period) if periods: period_ids = [p.id for p in periods] if name.startswith('end_'): # Always include ending period period_ids.append(period.id) return period_ids def check_dates(self): if self.type != 'standard': return True transaction = Transaction() connection = transaction.connection transaction.database.lock(connection, self._table) table = self.__table__() cursor = connection.cursor() cursor.execute( *table.select(table.id, where=(((table.start_date <= self.start_date) & (table.end_date >= self.start_date)) | ((table.start_date <= self.end_date) & (table.end_date >= self.end_date)) | ((table.start_date >= self.start_date) & (table.end_date <= self.end_date))) & (table.year == self.year.id) & (table.type == 'standard') & (table.id != self.id))) period_id = cursor.fetchone() if period_id: overlapping_period = self.__class__(period_id[0]) self.raise_user_error('periods_overlap', { 'first': self.rec_name, 'second': overlapping_period.rec_name, }) def check_year_dates(self): if (self.start_date < self.year.start_date or self.end_date > self.year.end_date): self.raise_user_error('year_dates', (self.rec_name, )) @classmethod def find(cls, date=None, exception=True, test_state=True): ''' Return the period at the date or the current date. If exception is set the function will raise an exception if no period is found. If test_state is true, it will search on non-closed periods ''' pool = Pool() Date = pool.get('ir.date') Lang = pool.get('ir.lang') if not date: date = Date.today() clause = [ ('start_date', '<=', date), ('end_date', '>=', date), ('type', '=', 'standard'), ] if test_state: clause.append(('state', '=', 'open')) periods = cls.search(clause, order=[('start_date', 'DESC')], limit=1) if not periods: if exception: lang = Lang.get() cls.raise_user_error('no_period_date', lang.strftime(date)) else: return None return periods[0].id @classmethod def _check(cls, periods): Move = Pool().get('tmi.move') moves = Move.search([ ('period', 'in', [p.id for p in periods]), ], limit=1) if moves: cls.raise_user_error('modify_del_period_moves', (moves[0].period.rec_name, )) @classmethod def search(cls, args, offset=0, limit=None, order=None, count=False, query=False): args = args[:] def process_args(args): i = 0 while i < len(args): # add test for xmlrpc and pyson that doesn't handle tuple if ((isinstance(args[i], tuple) or (isinstance(args[i], list) and len(args[i]) > 2 and args[i][1] in OPERATORS)) and args[i][0] in ('start_date', 'end_date') and isinstance(args[i][2], (list, tuple))): if not args[i][2][0]: args[i] = ('id', '!=', '0') else: period = cls(args[i][2][0]) args[i] = (args[i][0], args[i][1], getattr(period, args[i][2][1])) elif isinstance(args[i], list): process_args(args[i]) i += 1 process_args(args) return super(Period, cls).search(args, offset=offset, limit=limit, order=order, count=count, query=query) @classmethod def create(cls, vlist): FiscalYear = Pool().get('tmi.year') vlist = [x.copy() for x in vlist] for vals in vlist: if vals.get('year'): year = FiscalYear(vals['year']) if year.state != 'open': cls.raise_user_error('create_period_closed_year', (year.rec_name, )) return super(Period, cls).create(vlist) @classmethod def write(cls, *args): Move = Pool().get('tmi.move') actions = iter(args) args = [] for periods, values in zip(actions, actions): for key, value in values.items(): if key in ('start_date', 'end_date', 'year'): def modified(period): if key in ['start_date', 'end_date']: return getattr(period, key) != value else: return period.year.id != value cls._check(list(filter(modified, periods))) break if values.get('state') == 'open': for period in periods: if period.year.state != 'open': cls.raise_user_error( 'open_period_closed_year', { 'period': period.rec_name, 'year': period.year.rec_name, }) args.extend((periods, values)) super(Period, cls).write(*args) @classmethod def delete(cls, periods): cls._check(periods) super(Period, cls).delete(periods) @classmethod @ModelView.button @Workflow.transition('close') def close(cls, periods): transaction = Transaction() database = transaction.database connection = transaction.connection Period = Pool().get('tmi.period') # Lock period to be sure no new period will be created in between. database.lock(connection, Period._table) pass @classmethod @ModelView.button @Workflow.transition('open') def reopen(cls, periods): "Re-open period" pass @classmethod @ModelView.button @Workflow.transition('locked') def lock(cls, periods): pass
class Line(ModelSQL, ModelView): 'Analytic Line' __name__ = 'analytic_account.line' name = fields.Char('Name', required=True) debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)), required=True, depends=['currency_digits']) credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)), required=True, depends=['currency_digits']) currency = fields.Function( fields.Many2One('currency.currency', 'Currency'), 'on_change_with_currency') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') company = fields.Function(fields.Many2One('company.company', 'Company'), 'on_change_with_company') account = fields.Many2One('analytic_account.account', 'Account', required=True, select=True, domain=[ ('type', '!=', 'view'), [ 'OR', ('company', '=', None), ('company', '=', Eval('company', -1)), ], ], depends=['company']) move_line = fields.Many2One('account.move.line', 'Account Move Line', ondelete='CASCADE', required=True) journal = fields.Many2One('account.journal', 'Journal', required=True, select=True) date = fields.Date('Date', required=True) reference = fields.Char('Reference') party = fields.Many2One('party.party', 'Party') active = fields.Boolean('Active', select=True) @classmethod def __setup__(cls): super(Line, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('credit_debit', Check(t, (t.credit * t.debit == 0) & (t.credit + t.debit >= 0)), 'Wrong credit/debit values.'), ] cls._error_messages.update({ 'line_on_view_account': ('You can not create a move line using ' 'view account "%s".'), 'line_on_inactive_account': ('You can not create a move line ' 'using inactive account "%s".'), }) cls._order.insert(0, ('date', 'ASC')) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(Line, cls).__register__(module_name) table = TableHandler(cls, module_name) # Migration from 1.2 currency has been changed in function field table.not_null_action('currency', action='remove') @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() @staticmethod def default_active(): return True @staticmethod def default_debit(): return Decimal(0) @staticmethod def default_credit(): return Decimal(0) @fields.depends('move_line') def on_change_with_currency(self, name=None): if self.move_line: return self.move_line.account.company.currency.id @fields.depends('move_line') def on_change_with_currency_digits(self, name=None): if self.move_line: return self.move_line.account.company.currency.digits return 2 @fields.depends('move_line') def on_change_with_company(self, name=None): if self.move_line: return self.move_line.account.company.id @staticmethod def query_get(table): ''' Return SQL clause for analytic line depending of the context. table is the SQL instance of the analytic_account_line table. ''' clause = table.active if Transaction().context.get('start_date'): clause &= table.date >= Transaction().context['start_date'] if Transaction().context.get('end_date'): clause &= table.date <= Transaction().context['end_date'] return clause @classmethod def validate(cls, lines): super(Line, cls).validate(lines) for line in lines: line.check_account() def check_account(self): if self.account.type == 'view': self.raise_user_error('line_on_view_account', (self.account.rec_name, )) if not self.account.active: self.raise_user_error('line_on_inactive_account', (self.account.rec_name, ))
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" "coefficient (sale unit) = 1 (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" "1 (sale unit) = coefficient (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))
from dateutil.relativedelta import relativedelta from trytond.model import ModelView, ModelSQL, Workflow, fields from trytond.wizard import Wizard, StateView, StateAction, Button from trytond.tools import datetime_strftime from trytond.pyson import Eval, If, PYSONEncoder from trytond.transaction import Transaction from trytond.pool import Pool from trytond.const import OPERATORS __all__ = [ 'Year', 'Period', ] _STATES = STATES = { 'readonly': Eval('state') != 'open', } _DEPENDS = DEPENDS = ['state'] class Year(Workflow, ModelSQL, ModelView): 'Tmi Year' __name__ = 'tmi.year' name = fields.Char('Name', size=None, required=True, depends=DEPENDS) start_date = fields.Date('Starting Date', required=True, states=STATES, domain=[('start_date', '<=', Eval('end_date', None))], depends=DEPENDS + ['end_date']) end_date = fields.Date('Ending Date',
class StockMoveSerialNumber(ModelSQL, ModelView): 'Stock Move Serial Number' __name__ = 'stock.move.serial_number' input_move = fields.Many2One('stock.move', 'Input Stock Move', ondelete='CASCADE', select=True, required=True, states={ 'readonly' : Eval('state') != 'draft', }) output_move = fields.Many2One('stock.move', 'Output Stock Move', ondelete='SET NULL', select=True, states={ 'readonly' : Eval('state') == 'completed', }) state = fields.Selection([('draft', 'Draft'), ('stored', 'Stored'), ('completed', 'Completed')], 'State', readonly=True, required=True) serial_number = fields.Char('Serial Number', required=True) sequence = fields.Integer('Sequence') product = fields.Function(fields.Many2One('product.product', 'Product', readonly=True), 'get_product', searcher="search_product") input_date = fields.Function(fields.Date("Input Date", readonly=True), 'get_input_date', searcher="search_input_date") output_date = fields.Function(fields.Date("Output Date", readonly=True), 'get_output_date', searcher="search_output_date") @classmethod def __setup__(cls): super(StockMoveSerialNumber, cls).__setup__() cls._error_messages.update({ 'already_stored' : 'Serial number "%s" already stored', 'duplicate_serial_numbers' : '"%s", Duplicate Serial Number' }) def get_rec_name(self, name=None): return ("%s - %s" % (self.serial_number, self.product.rec_name)) def get_product(self, name=None): return self.input_move.product.id def get_input_date(self, name=None): return self.input_move.effective_date def get_output_date(self, name=None): return (self.output_move.effective_date if self.output_move else None) @classmethod def search_product(cls, name, clause): field, operator, value = clause if (field == 'product') and (operator == 'ilike'): return ['OR', ('input_move.product.code', operator, value), ('input_move.product.template.name', operator, value)] else: return [(field.replace('product', 'input_move.product'), operator, value)] @classmethod def search_input_date(cls, name, clause): return [('input_move.effective_date',) + tuple(clause[1:])] @classmethod def search_output_date(cls, name, clause): return [('output_move.effective_date',) + tuple(clause[1:])] @classmethod def search_rec_name(cls, name, clause): _, operator, value = clause return ['OR', ('input_move.product.code', operator, value), ('input_move.product.template.name', operator, value), ('serial_number', operator, value)] @classmethod def default_state(cls): return 'draft' def __get_state(self): state = 'draft' if self.input_move.state == 'done': state = 'stored' if self.output_move and (self.output_move.state == 'done'): state = 'completed' return state def set_state(self): state = self.__get_state() if self.state != state: self.write([self], {'state' : state}) @classmethod def is_valid_serial_number(cls, stock_move, serial_number): if type(stock_move) == int: stock_move = Pool().get('stock.move')(stock_move) result = cls.search([('serial_number', '=', serial_number), ('product', '=', stock_move.product), ('state', 'in', ('draft', 'stored'))]) return (len(result) == 0) @classmethod def create(cls, vlist): serial_numbers = [] for record in vlist: serial_number = record['serial_number'] if serial_number in serial_numbers: cls.raise_user_error('duplicate_serial_numbers', serial_number) else: serial_numbers.append(serial_number) if not cls.is_valid_serial_number(record['input_move'], serial_number): cls.raise_user_error('already_stored', serial_number) return super(StockMoveSerialNumber, cls).create(vlist) or vlist @classmethod def write(cls, *args): serial_numbers = [] current_record = args[0] for argument in args: if type(argument) == dict: if 'serial_number' in argument: serial_number = argument['serial_number'] if serial_number in serial_numbers: cls.raise_user_error('duplicate_serial_numbers', serial_number) else: serial_numbers.append(serial_number) if not cls.is_valid_serial_number(current_record.input_move, serial_number): cls.raise_user_error('already_stored', serial_number) else: current_record = argument[0] super(StockMoveSerialNumber, cls).write(*args)
class StockMove: __name__ = 'stock.move' input_serial_numbers = fields.One2Many('stock.move.serial_number', 'input_move', 'Input Serial Numbers', states={ 'readonly': Or( In(Eval('state'), ['cancel', 'assigned', 'done']), Not(Bool(Eval('product'))) ), 'invisible' : Not(Bool(Eval('input_type_stock_move'))) }, depends=['input_type_stock_move', 'state']) output_serial_numbers = fields.One2Many('stock.move.serial_number', 'output_move', 'Output Serial Numbers', states={ 'readonly': Or( In(Eval('state'), ['cancel', 'assigned', 'done']), Not(Bool(Eval('product'))) ), 'invisible' : Not(Bool(Eval('output_type_stock_move'))) }, depends=['output_type_stock_move', 'state'], add_remove=[('state', '=', 'stored'), ('output_move', '=', None), ('input_move.product', '=', Eval('product'))]) input_type_stock_move = fields.Function(fields.Boolean('Input Type Stock Move'), 'on_change_with_input_type_stock_move') output_type_stock_move = fields.Function(fields.Boolean('Output Type Stock Move'), 'on_change_with_output_type_stock_move') @classmethod def __setup__(cls): super(StockMove, cls).__setup__() cls._error_messages.update({ 'invalid_serial_number' : 'Serial number "%s" already stored' }) @fields.depends('from_location', 'shipment') def on_change_with_input_type_stock_move(self, name=None): if self.from_location: location_type = self.from_location.type if location_type == 'production': return True if self.shipment: shipment_type = self.shipment.__name__ if (shipment_type == 'stock.shipment.in') and (location_type == 'supplier'): return True if (shipment_type == 'stock.shipment.out.return') and (location_type == 'customer'): return True return False @fields.depends('to_location', 'shipment') def on_change_with_output_type_stock_move(self, name=None): if self.to_location and self.shipment: location_type = self.to_location.type shipment_type = self.shipment.__name__ if (shipment_type == 'stock.shipment.out') and (location_type == 'customer'): return True if (shipment_type == 'stock.shipment.in.return') and (location_type == 'supplier'): return True return False @fields.depends('product', 'input_serial_numbers') def on_change_product(self): changes = super(StockMove, self).on_change_product() changes['output_serial_numbers'] = [] if self.product: for record in self.input_serial_numbers: if not StockMoveSerialNumber.is_valid_serial_number(self, record.serial_number): self.raise_user_error('invalid_serial_number', record.serial_number) return changes @classmethod def search_rec_name(cls, name, clause): try: _, operator, value = clause return [('id', operator, int(value))] except ValueError: return super(StockMove, cls).search_rec_name(name, clause) @classmethod def do(cls, moves): super(StockMove, cls).do(moves) for move in moves: for serial_number in move.input_serial_numbers: serial_number.set_state() for serial_number in move.output_serial_numbers: serial_number.set_state()
def __setup__(cls): super().__setup__() cls.producible.states = { 'invisible': ~Eval('type').in_(cls.get_producible_types()), }
def view_attributes(cls): return super().view_attributes() + [ ('//page[@id="production"]', 'states', { 'invisible': ~Eval('producible'), })]
def test_on_create(self): 'Test on_create' pool = Pool() Model = pool.get('ir.model') Trigger = pool.get('ir.trigger') Triggered = pool.get('test.triggered') model, = Model.search([ ('model', '=', 'test.triggered'), ]) trigger, = Trigger.create([{ 'name': 'Test', 'model': model.id, 'on_create': True, 'condition': 'true', 'action': 'test.trigger_action|trigger', }]) triggered, = Triggered.create([{ 'name': 'Test', }]) self.run_tasks() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Trigger with condition condition = PYSONEncoder().encode( Eval('self', {}).get('name') == 'Bar') Trigger.write([trigger], { 'condition': condition, }) # Matching condition triggered, = Triggered.create([{ 'name': 'Bar', }]) self.run_tasks() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Non matching condition triggered, = Triggered.create([{ 'name': 'Foo', }]) self.run_tasks() self.assertEqual(TRIGGER_LOGS, []) # With limit number Trigger.write([trigger], { 'condition': 'true', 'limit_number': 1, }) triggered, = Triggered.create([{ 'name': 'Test', }]) self.run_tasks() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # With minimum delay Trigger.write([trigger], { 'limit_number': 0, 'minimum_time_delay': datetime.timedelta(hours=1), }) triggered, = Triggered.create([{ 'name': 'Test', }]) self.run_tasks() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Restart the cache on the get_triggers method of ir.trigger Trigger._get_triggers_cache.clear()
def test_on_time(self): 'Test on_time' pool = Pool() Model = pool.get('ir.model') Trigger = pool.get('ir.trigger') Triggered = pool.get('test.triggered') TriggerLog = pool.get('ir.trigger.log') model, = Model.search([ ('model', '=', 'test.triggered'), ]) trigger, = Trigger.create([{ 'name': 'Test', 'model': model.id, 'on_time': True, 'condition': 'true', 'action': 'test.trigger_action|trigger', }]) triggered, = Triggered.create([{ 'name': 'Test', }]) Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Trigger with condition condition = PYSONEncoder().encode( Eval('self', {}).get('name') == 'Bar') Trigger.write([trigger], { 'condition': condition, }) # Matching condition Triggered.write([triggered], { 'name': 'Bar', }) Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Non matching condition Triggered.write([triggered], { 'name': 'Foo', }) Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, []) # With limit number Trigger.write([trigger], { 'condition': 'true', 'limit_number': 1, }) Trigger.trigger_time() Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() # Delete trigger logs of limit number test TriggerLog.delete(TriggerLog.search([ ('trigger', '=', trigger.id), ])) # With minimum delay Trigger.write([trigger], { 'limit_number': 0, 'minimum_time_delay': datetime.timedelta.max, }) Trigger.trigger_time() Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger)]) TRIGGER_LOGS.pop() Transaction().delete = {} # Delete trigger logs of previous minimum delay test TriggerLog.delete(TriggerLog.search([ ('trigger', '=', trigger.id), ])) Trigger.write([trigger], { 'minimum_time_delay': datetime.timedelta(seconds=1), }) Trigger.trigger_time() # Make time pass by moving back in time the log creation trigger_log = TriggerLog.__table__() cursor = Transaction().connection.cursor() cursor.execute(*trigger_log.update( [trigger_log.create_date], [datetime.datetime.now() - datetime.timedelta(days=1)], where=trigger_log.record_id == triggered.id)) Trigger.trigger_time() self.assertEqual(TRIGGER_LOGS, [([triggered], trigger), ([triggered], trigger)]) TRIGGER_LOGS.pop() TRIGGER_LOGS.pop() Transaction().delete = {} # Restart the cache on the get_triggers method of ir.trigger Trigger._get_triggers_cache.clear()
class PaymentTransaction: """ Implement the authorize and capture methods """ __name__ = 'payment_gateway.transaction' gift_card = fields.Many2One('gift_card.gift_card', 'Gift Card', domain=[('state', '=', 'active')], states={ 'required': Eval('method') == 'gift_card', 'readonly': Eval('state') != 'draft' }, select=True) @classmethod def __setup__(cls): super(PaymentTransaction, cls).__setup__() cls._error_messages.update({ 'insufficient_amount': 'Card %s is found to have insufficient amount', 'negative_amount': 'The amount to be entered cannot be negative.', }) cls._buttons['authorize']['invisible'] = \ cls._buttons['authorize']['invisible'] & ~( (Eval('state') == 'draft') & (Eval('method') == 'gift_card') ) cls._buttons['capture']['invisible'] = \ cls._buttons['capture']['invisible'] & ~( (Eval('state') == 'draft') & (Eval('method') == 'gift_card') ) cls._buttons['settle']['invisible'] = \ cls._buttons['settle']['invisible'] & ~( (Eval('state') == 'authorized') & (Eval('method') == 'gift_card') ) def validate_gift_card_amount(self, available_amount): """ Validates that gift card has sufficient amount to pay """ if self.amount < 0: # TODO: # Put this bit in payment_gateway. self.raise_user_error("negative_amount") if available_amount < self.amount: self.raise_user_error("insufficient_amount", self.gift_card.number) def authorize_self(self): """ Authorize using gift card for the specific transaction. """ if self.method == 'gift_card': self.validate_gift_card_amount(self.gift_card.amount_available) return super(PaymentTransaction, self).authorize_self() def capture_self(self): """ Capture using gift card for the specific transaction. """ if self.method == 'gift_card': self.validate_gift_card_amount(self.gift_card.amount_available) return super(PaymentTransaction, self).capture_self() def settle_self(self): """ Settle using gift card for the specific transaction. """ if self.method == 'gift_card': # Ignore authorized amount as settlement will be done for # previously authorized amount available_amount = \ self.gift_card.amount - self.gift_card.amount_captured self.validate_gift_card_amount(available_amount) return super(PaymentTransaction, self).settle_self() @classmethod @ModelView.button @Workflow.transition('posted') def post(cls, transactions): """ Complete the transactions by creating account moves and post them. This method is likely to end in failure if the initial configuration of the journal and fiscal periods have not been done. You could alternatively use the safe_post instance method to try to post the record, but ignore the error silently. """ rv = super(PaymentTransaction, cls).post(transactions) for transaction in transactions: if transaction.gift_card and \ transaction.gift_card.amount_available == 0: transaction.gift_card.state = 'used' transaction.gift_card.save() return rv
class HrSalaryRule(ModelSQL, ModelView): 'Salary Rule' __name__ = 'hr.salary.rule' name = fields.Char('Name', required=True) code = fields.Char('Code', required=True) active = fields.Boolean('Active') category = fields.Many2One('hr.salary.rule.category', 'Category') condition_select = fields.Selection([ ('always_true', 'Always True'), ('range', 'Range'), ('python_code', 'Python Code'), ], 'Condition Type', required=True) condition_range_max = fields.Float( 'Maximum Range', states={'invisible': ~Eval('condition_select').in_(['range'])}, depends=['condition_select']) condition_range_min = fields.Float( 'Minimum Range', states={'invisible': ~Eval('condition_select').in_(['range'])}, depends=['condition_select']) amount_select = fields.Selection([('percentage', 'Percentage'), ('fix', 'Fix'), ('code', 'Code')], 'Amount Type', required=True) amount_fix = fields.Float( 'Fix Amount', states={'invisible': ~Eval('amount_select').in_(['fix'])}, depends=['amount_select']) amount_percentage = fields.Float( 'Percentage(%)', states={'invisible': ~Eval('amount_select').in_(['percentage'])}, depends=['amount_select']) percentage_based = fields.Many2One( 'hr.salary.rule', 'Percentage Based on', states={'invisible': ~Eval('amount_select').in_(['percentage'])}, depends=['amount_select']) note = fields.Text('Description') priority = fields.Integer('Priority', required=True) @staticmethod def default_active(): return True @classmethod def validate(cls, records): super(HrSalaryRule, cls).validate(records) for record in records: if record.amount_select == 'amount_percentage': if record.amount_percentage not in range(0, 101): cls.raise_user_error('Invalid Amount') @staticmethod def default_condition_select(): return 'always_true' def check(self, payslip, employee, contract): if self.condition_select == 'always_true': return True if self.condition_select == 'range': pass #TODO: Write the code for range here return True if self.condition_select == 'python_code': method_name = "check_" + str(self.code) if hasattr(self, method_name): method = getattr(self, method_name) res = method(payslip, employee, contract) return res else: #TODO: Raise error that the rule has python_code as condition but there is no method return True def calculate(self, payslip, employee, contract): method_name = "calculate_" + str(self.code) if hasattr(self, method_name): method = getattr(self, method_name) print(method, "methodmethod") res = method(payslip, employee, contract) print(res, "RESESE") return res def calculate_GROSS(self, payslip, employee, contract): amount = 0 payslip_line = Pool().get('hr.payslip.line') payslip_lines = payslip_line.search([('category.code', 'in', ('BASIC', 'ALW'))]) if payslip_lines: for line in payslip_lines: if line.payslip == payslip: amount += line.amount return amount def calculate_BASIC(self, payslip, employee, contract): return self.calculate_NEW_BASIC(payslip, employee, contract) def calculate_NET(self, payslip, employee, categories): amount = 0 ''' Takes Parameters - payslip, employee and #categories returns the value of Non Practicing Allowance to be added ''' # TODO: categories to be corrected gross = self.calculate_GROSS(payslip, employee, categories) payslip_line = Pool().get('hr.payslip.line') payslip_lines = payslip_line.search([('category.code', '=', 'DED')]) if payslip_lines: for line in payslip_lines: if line.payslip == payslip: amount += line.amount return gross - amount def calculate_NPA(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Non Practicing Allowance to be added ''' if employee.designation.name in ( "Scientist I", "Scientist I (Absorption)", "Scientist II", "Scientist II (Absorption)", "Scientist III", "Scientist III (Absorption)", "Scientist IV", "Scientist IV (Absorption)", "Scientist V (Absorption)", "Assistant Professor", "Associate Professor", "professor", "Lecturer", "Principal", "Director", ): # TODO: Check for faculty designations, might # coincide with nursing faculty. Requires only MBBS npa = (0.2 * contract.basic) return npa else: npa = 0 return npa def calculate_NEW_BASIC(self, payslip, employee, contract): npa = self.calculate_NPA(payslip, employee, contract) if npa: # TODO: change the rules for new_basic if any new_basic = npa + contract.basic if new_basic > 23750: new_basic = 23750 return new_basic else: new_basic = contract.basic return new_basic def calculate_DA(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Dearness Allowance to be added ''' if self.calculate_NEW_BASIC(payslip, employee, contract): # TODO: change the rules for new_basic dear_alw = (0.12 * self.calculate_NEW_BASIC(payslip, employee, contract)) return dear_alw else: dear_alw = (0.12 * contract.basic) return dear_alw def calculate_NURSING_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Nursing Allowance to be added ''' if employee.designation.name in ( "Assistant Nursing Superintendent", "Chief Nursing Officer", "Deputy Nursing Superintendent", "Nursing Officer", "Nursing Superintendent", "Sr. Nursing Officer", "Public Health Nurse", "Public Health Nurse (Supervisor)", "Tutor in Nursing", "Senior Nursing Tutor", "Senior Nursing Officer", ): nurse_alw = 7200 return nurse_alw return None def calculate_TRANSPORT_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Transport Allowance to be added ''' # TODO: Write the condition if the employee is handicapped # if employee.handicap_status == True: trans_alw = 0 if int(employee.grade_pay.name) >= 5400: trans_alw = 7200 + self.calculate_DA(payslip, employee, contract) elif int(employee.grade_pay.name) < 5400: trans_alw = 3600 + self.calculate_DA(payslip, employee, contract) elif (employee.pay_in_band) > 17: trans_alw = 14400 + self.calculate_DA(payslip, employee, contract) elif (employee.designation.name) == 'Centre Chief': trans_alw = 15750 + self.calculate_DA(payslip, employee, contract) if employee.category == "ph": trans = trans_alw * 2 trans_alw = trans else: trans_alw = trans_alw return trans_alw def calculate_HPCA(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Hospital Patient Care Allowance to be added ''' if employee.group == "C" or employee.group == "D": if employee.grade_pay >= 1800 and \ employee.grade_pay <= 2800: hpca = 4100 return hpca return None def calculate_ACAD_ALW(self, payslip, employee, contract): # TODO: Check for Faculty designations ''' Takes Parameters - payslip, employee and contract returns the value of Academic Allowance to be added ''' if employee.designation.name in ( "Assistant Professor", "Associate Professor", "Professor", "Lecturer", "Principal", "Director", ): acad_alw = 22500 return acad_alw return None def calculate_UNIFORM_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Uniform Allowance to be added ''' if employee.designation.name in ( "Assistant Nursing Superintendent", "Chief Nursing Officer", "Deputy Nursing Superintendent", "Nursing Officer", "Sr. Nursing Officer", "Nursing Superintendent", "Public Health Nurse", "Public Health Nurse (Supervisor)", "Tutor in Nursing", "Senior Nursing Tutor", "Senior Nursing Officer", ): uni_alw = 1800 return uni_alw return None def calculate_DEP_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Deputation Allowance to be added ''' pass def calculate_TOOL_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Tool Allowance to be added ''' if employee.group == "D" and employee.department == "Engineering Services Division (ESD)": if employee.designation.name in ( "Plumber", "Meson", "Electrician", "Wireman", "Foreman", "Mechanic", ): tool_alw = 10 return tool_alw elif employee.designation.name == "Carpenter": tool_alw = 15 return tool_alw elif employee.designation.name == "Mali": tool_alw = 180 return tool_alw return None def calculate_OT(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Overtime Allowance to be added ''' pass def calculate_QPAY(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Qualification Pay Allowance to be added ''' pass def calculate_DRESS_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of Dress Allowance to be added ''' if datetime.date.today().month == 7: if employee.designation.name in ( "Hospital Attendant Grade I", "Hospital Attendant Grade II", "Hospital Attendant Grade III", "Sanitary Attendant Grade I", "Sanitary Attendant Grade II", "Sanitary Attendant Grade III", ): dress_alw = 5000 return dress_alw else: dress_alw = 0 return dress_alw def calculate_ICU_ALW(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of ICU Allowance to be added ''' pass def calculate_CHTA(self, payslip, employee, contract): pass def calculate_EHS(self, payslip, employee, contract): ''' Takes parameters - payslip, employee and contract returns the value of EHS to be deducted ''' if int(employee.grade_pay.name) >= 7600: ehs = 1000 return ehs elif int(employee.grade_pay.name) >= 4600 and \ int(employee.grade_pay.name) <= 6600: ehs = 650 return ehs elif int(employee.grade_pay.name) == 4200: ehs = 450 return ehs elif int(employee.grade_pay.name) < 4200: ehs = 250 return ehs def calculate_KU(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of KARMCHARI UNION to be deducted ''' if employee.employee_group in ("C", "D"): karmchari_ded = 10 return karmchari_ded return None def calculate_OA(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of OFFICER ASSOCIATION to be deducted ''' if employee.employee_group in ("A", "B"): officer_ded = 20 return officer_ded return None def calculate_NU(self, payslip, employee, contract): ''' Takes Parameters - payslip, employee and contract returns the value of NURSING UNION to be deducted ''' if employee.designation.name in ( "Assistant Nursing Superintendent", "Chief Nursing Officer", "Deputy Nursing Superintendent", "Nursing Officer", "Nursing Superintendent", "Sr. Nursing Officer", "Public Health Nurse", "Public Health Nurse (Supervisor)", "Tutor in Nursing", "Senior Nursing Tutor", "Senior Nursing Officer", ): nurse_ded = 20 return nurse_ded return None def calculate_FAC_FUND_CLUB(self, payslip, employee, contract): # TODO : In which month Faculty Fund will be deducted ''' Takes Parameters - payslip, employee and contract returns the value of Faculity Fund to be deducted ''' if employee.employee_group in ("A", "B", "C", "D"): faculity_fund = 500 return faculity_fund return None def calculate_EIS_DED(self, payslip, employee, contract): # TODO : In which year EIS deduction stopped ''' Takes Parameters - payslip, employee and contract returns the value of EIS to be deducted ''' if employee.employee_group == 'A': eis = 100 return eis elif employee.employee_group == 'B': eis = 60 return eis elif employee.employee_group == 'C': eis = 30 return eis elif employee.employee_group == 'D': eis = 20 return eis def check_BASIC(self, payslip, employee, contract): return contract.basic def check_NET(self, payslip, employee, categories): #categories.name.BASIC + categories.ALW + categories.DED return True def check_NPA(self, payslip, employee, contract): if employee.employee_group == 'A': npa = (0.2 * contract.basic) return contract.basic + npa def check_NEW_BASIC(self, payslip, employee, contract): pass def check_UNIFORM_ALW(self, payslip, employee, contract): pass def check_DA(self, payslip, employee, contract): pass def check_NURSE_ALW(self, payslip, employee, contract): pass def check_TRANSPORT_ALW(self, payslip, employee, contract): pass def check_HPCA(self, payslip, employee, contract): pass def check_ACAD_ALW(self, payslip, employee, contract): pass def check_DEP_ALW(self, payslip, employee, contract): pass def check_TOOL_ALW(self, payslip, employee, contract): pass def check_QPAY(self, payslip, employee, contract): pass def check_DRESS_ALW(self, payslip, employee, contract): pass def check_CHTA(self, payslip, employee, contract): pass def check_EHS(self, payslip, employee, contract): pass def check_ASSN_FEE(self, payslip, employee, contract): pass def check_KARAMCHARI(self, payslip, employee, contract): pass def check_NURSE_UNION(self, payslip, employee, contract): pass def check_OF_ASSN(self, payslip, employee, contract): pass def check_FAC_CLUB(self, payslip, employee, contract): pass def check_RDA(self, payslip, employee, contract): pass def check_LEAVES(self, payslip, employee, contract): pass def check_COURT_RECOVERY(self, payslip, employee, contract): pass
class Product(metaclass=PoolMeta): __name__ = 'product.product' # TODO: Phantom should only be available with products whose UOM is 'unit' phantom = fields.Boolean('Phantom', states={ 'invisible': ~Bool(Eval('boms', [])), })
class PatientGeneticRisk(ModelSQL, ModelView): 'Patient Genetic Information' __name__ = 'gnuhealth.patient.genetic.risk' patient = fields.Many2One('gnuhealth.patient', 'Patient', select=True) disease_gene = fields.Many2One('gnuhealth.disease.gene', 'Gene', required=True) natural_variant = fields.Many2One('gnuhealth.gene.variant', 'Variant', domain=[('name', '=', Eval('disease_gene'))], depends=['disease_gene']) variant_phenotype = fields.Many2One('gnuhealth.gene.variant.phenotype',\ 'Phenotype', domain=[('variant', '=', Eval('natural_variant'))], depends=['natural_variant']) onset = fields.Integer('Onset', help="Age in years") notes = fields.Char("Notes") healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health prof', help="Health professional") institution = fields.Many2One('gnuhealth.institution', 'Institution') @staticmethod def default_institution(): HealthInst = Pool().get('gnuhealth.institution') institution = HealthInst.get_institution() return institution @classmethod def create_genetics_pol(cls,genetic_info): """ Adds an entry in the person Page of Life related to this genetic finding """ Pol = Pool().get('gnuhealth.pol') pol = [] vals = { 'page': str(uuid4()), 'person': genetic_info.patient.name.id, 'age': genetic_info.onset and str(genetic_info.onset) + 'y' or '', 'federation_account': genetic_info.patient.name.federation_account, 'page_type':'medical', 'medical_context':'genetics', 'relevance':'important', 'gene':genetic_info.disease_gene.rec_name, 'natural_variant':genetic_info.natural_variant and \ genetic_info.natural_variant.aa_change, 'summary': genetic_info.notes, 'author': genetic_info.healthprof and genetic_info.healthprof.name.rec_name, 'node': genetic_info.institution and genetic_info.institution.name.rec_name } if (genetic_info.variant_phenotype): vals['health_condition_text'] = vals['health_condition_text'] = \ genetic_info.variant_phenotype.phenotype.rec_name pol.append(vals) Pol.create(pol) @classmethod def create(cls, vlist): # Execute first the creation of PoL genetic_info = super(PatientGeneticRisk, cls).create(vlist) cls.create_genetics_pol(genetic_info[0]) return genetic_info @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, ('patient',) + tuple(clause[1:]), ('disease_gene',) + tuple(clause[1:]), ('variant_phenotype',) + tuple(clause[1:]), ]
config.payment_group_sequence.id) return super(Group, cls).create(vlist) @classmethod def copy(cls, groups, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', None) return super(Group, cls).copy(groups, default=default) _STATES = { 'readonly': Eval('state') != 'draft', } _DEPENDS = ['state'] class Payment(Workflow, ModelSQL, ModelView): 'Payment' __name__ = 'account.payment' company = fields.Many2One( 'company.company', 'Company', required=True, select=True, states=_STATES, domain=[ ('id', If(Eval('context', {}).contains('company'), '=',
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_sale_channel'): sale_channel = Channel( Transaction().context.get('current_sale_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.delivery_mode == 'ship': return self.sale.channel.ship_from_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 Payment(Workflow, ModelSQL, ModelView): 'Payment' __name__ = 'account.payment' company = fields.Many2One( 'company.company', 'Company', required=True, select=True, states=_STATES, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], depends=_DEPENDS) journal = fields.Many2One('account.payment.journal', 'Journal', required=True, states=_STATES, domain=[ ('company', '=', Eval('company', -1)), ], depends=_DEPENDS + ['company']) currency = fields.Function(fields.Many2One('currency.currency', 'Currency'), 'on_change_with_currency', searcher='search_currency') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') kind = fields.Selection(KINDS, 'Kind', required=True, states=_STATES, depends=_DEPENDS) party = fields.Many2One('party.party', 'Party', required=True, states=_STATES, depends=_DEPENDS) date = fields.Date('Date', required=True, states=_STATES, depends=_DEPENDS) amount = fields.Numeric('Amount', required=True, digits=(16, Eval('currency_digits', 2)), states=_STATES, depends=_DEPENDS + ['currency_digits']) line = fields.Many2One( 'account.move.line', 'Line', ondelete='RESTRICT', domain=[ ('move.company', '=', Eval('company', -1)), If( Eval('kind') == 'receivable', ['OR', ('debit', '>', 0), ('credit', '<', 0)], ['OR', ('credit', '>', 0), ('debit', '<', 0)], ), [ 'OR', ('account.type.receivable', '=', True), ('account.type.payable', '=', True), ], ('party', 'in', [Eval('party', None), None]), If( Eval('state') == 'draft', [ ('reconciliation', '=', None), ], []), [ 'OR', ('second_currency', '=', Eval('currency', None)), [ ('account.company.currency', '=', Eval('currency', None)), ('second_currency', '=', None), ], ], ('move_state', '=', 'posted'), ], states=_STATES, depends=_DEPENDS + ['party', 'currency', 'kind', 'company']) description = fields.Char('Description', states=_STATES, depends=_DEPENDS) origin = fields.Reference("Origin", selection='get_origin', select=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) group = fields.Many2One('account.payment.group', 'Group', readonly=True, ondelete='RESTRICT', states={ 'required': Eval('state').in_(['processing', 'succeeded']), }, domain=[ ('company', '=', Eval('company', -1)), ], depends=['state', 'company']) state = fields.Selection([ ('draft', 'Draft'), ('approved', 'Approved'), ('processing', 'Processing'), ('succeeded', 'Succeeded'), ('failed', 'Failed'), ], 'State', readonly=True, select=True) @classmethod def __setup__(cls): super(Payment, cls).__setup__() cls._order.insert(0, ('date', 'DESC')) cls._transitions |= set(( ('draft', 'approved'), ('approved', 'processing'), ('processing', 'succeeded'), ('processing', 'failed'), ('approved', 'draft'), ('succeeded', 'failed'), ('failed', 'succeeded'), )) cls._buttons.update({ 'draft': { 'invisible': Eval('state') != 'approved', 'icon': 'tryton-back', 'depends': ['state'], }, 'approve': { 'invisible': Eval('state') != 'draft', 'icon': 'tryton-forward', 'depends': ['state'], }, 'succeed': { 'invisible': ~Eval('state').in_(['processing', 'failed']), 'icon': 'tryton-ok', 'depends': ['state'], }, 'fail': { 'invisible': ~Eval('state').in_(['processing', 'succeeded']), 'icon': 'tryton-cancel', 'depends': ['state'], }, }) cls.__rpc__.update({ 'approve': RPC(readonly=False, instantiate=0, fresh_session=True), }) @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_kind(): return 'payable' @staticmethod def default_date(): pool = Pool() Date = pool.get('ir.date') return Date.today() @staticmethod def default_state(): return 'draft' @fields.depends('journal') def on_change_with_currency(self, name=None): if self.journal: return self.journal.currency.id @fields.depends('journal') def on_change_with_currency_digits(self, name=None): if self.journal: return self.journal.currency.digits return 2 @classmethod def search_currency(cls, name, clause): return [('journal.' + clause[0], ) + tuple(clause[1:])] @fields.depends('kind') def on_change_kind(self): self.line = None @fields.depends('party') def on_change_party(self): self.line = None @fields.depends('line') def on_change_line(self): if self.line: self.date = self.line.maturity_date self.amount = self.line.payment_amount @classmethod def _get_origin(cls): 'Return list of Model names for origin Reference' return [] @classmethod def get_origin(cls): Model = Pool().get('ir.model') models = cls._get_origin() models = Model.search([ ('model', 'in', models), ]) return [(None, '')] + [(m.model, m.name) for m in models] @classmethod def delete(cls, payments): for payment in payments: if payment.state != 'draft': raise AccessError( gettext('account_payment.msg_payment_delete_draft', payment=payment.rec_name)) super(Payment, cls).delete(payments) @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, payments): pass @classmethod @ModelView.button @Workflow.transition('approved') def approve(cls, payments): pass @classmethod @Workflow.transition('processing') def process(cls, payments, group): pool = Pool() Group = pool.get('account.payment.group') if payments: group = group() cls.write(payments, { 'group': group.id, }) process_method = getattr( Group, 'process_%s' % group.journal.process_method, None) if process_method: process_method(group) group.save() return group @classmethod @ModelView.button @Workflow.transition('succeeded') def succeed(cls, payments): pass @classmethod @ModelView.button @Workflow.transition('failed') def fail(cls, payments): pass
class Year(Workflow, ModelSQL, ModelView): 'Tmi Year' __name__ = 'tmi.year' name = fields.Char('Name', size=None, required=True, depends=DEPENDS) start_date = fields.Date('Starting Date', required=True, states=STATES, domain=[('start_date', '<=', Eval('end_date', None))], depends=DEPENDS + ['end_date']) end_date = fields.Date('Ending Date', required=True, states=STATES, domain=[('end_date', '>=', Eval('start_date', None))], depends=DEPENDS + ['start_date']) periods = fields.One2Many('tmi.period', 'year', 'Periods', states=STATES, depends=DEPENDS) state = fields.Selection([ ('open', 'Open'), ('close', 'Close'), ], 'State', readonly=True, required=True) icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __setup__(cls): super(Year, cls).__setup__() cls._order.insert(0, ('start_date', 'ASC')) cls._error_messages.update({ 'no_year_date': 'No year defined for "%s".', 'year_overlaps': ('Year "%(first)s" and ' '"%(second)s" overlap.'), 'close_error': ('You can not close year "%s" until you ' 'close all previous years.'), 'reopen_error': ('You can not reopen year "%s" until ' 'you reopen all later years.'), }) cls._transitions |= set(( ('open', 'close'), ('close', 'open'), )) cls._buttons.update({ 'create_period': { 'invisible': ((Eval('state') != 'open') | Eval('periods', [0])), 'depends': ['state'], }, 'close': { 'invisible': Eval('state') != 'open', 'depends': ['state'], }, 'reopen': { 'invisible': Eval('state') != 'close', 'depends': ['state'], }, }) @staticmethod def default_state(): return 'open' def get_icon(self, name): return { 'open': 'tryton-open', 'close': 'tryton-close', }.get(self.state) @classmethod def validate(cls, years): super(Year, cls).validate(years) for year in years: year.check_dates() def check_dates(self): transaction = Transaction() connection = transaction.connection transaction.database.lock(connection, self._table) cursor = connection.cursor() table = self.__table__() cursor.execute( *table.select(table.id, where=(((table.start_date <= self.start_date) & (table.end_date >= self.start_date)) | ((table.start_date <= self.end_date) & (table.end_date >= self.end_date)) | ((table.start_date >= self.start_date) & (table.end_date <= self.end_date))) & (table.id != self.id))) second_id = cursor.fetchone() if second_id: second = self.__class__(second_id[0]) self.raise_user_error('year_overlaps', { 'first': self.rec_name, 'second': second.rec_name, }) @classmethod def delete(cls, years): Period = Pool().get('tmi.period') Period.delete([p for f in years for p in f.periods]) super(Year, cls).delete(years) @classmethod @ModelView.button def create_period(cls, years, interval=1): ''' Create periods for the years with month interval ''' Period = Pool().get('tmi.period') to_create = [] for year in years: period_start_date = year.start_date while period_start_date < year.end_date: period_end_date = period_start_date + \ relativedelta(months=interval - 1) + \ relativedelta(day=31) if period_end_date > year.end_date: period_end_date = year.end_date name = datetime_strftime(period_start_date, '%Y-%m') if name != datetime_strftime(period_end_date, '%Y-%m'): name += ' - ' + datetime_strftime(period_end_date, '%Y-%m') to_create.append({ 'name': name, 'start_date': period_start_date, 'end_date': period_end_date, 'year': year.id, 'type': 'standard', }) period_start_date = period_end_date + relativedelta(days=1) if to_create: Period.create(to_create) @classmethod @ModelView.button def create_period_3(cls, years): ''' Create periods for the years with 3 months interval ''' cls.create_period(years, interval=3) @classmethod def find(cls, date=None, exception=True): ''' Return the year for the at the date or the current date. If exception is set the function will raise an exception if any year is found. ''' pool = Pool() Lang = pool.get('ir.lang') Date = pool.get('ir.date') if not date: date = Date.today() years = cls.search([ ('start_date', '<=', date), ('end_date', '>=', date), ], order=[('start_date', 'DESC')], limit=1) if not years: if exception: lang = Lang.get() cls.raise_user_error('no_year_date', lang.strftime(date)) else: return None return years[0].id @classmethod @ModelView.button @Workflow.transition('close') def close(cls, years): ''' Close a year ''' pool = Pool() Period = pool.get('tmi.period') transaction = Transaction() database = transaction.database connection = transaction.connection # Lock period to be sure no new period will be created in between. database.lock(connection, Period._table) for year in years: if cls.search([ ('end_date', '<=', year.start_date), ('state', '=', 'open'), ]): cls.raise_user_error('close_error', (year.rec_name, )) periods = Period.search([ ('year', '=', year.id), ]) Period.close(periods) @classmethod @ModelView.button @Workflow.transition('open') def reopen(cls, years): ''' Re-open a year ''' for year in years: if cls.search([ ('start_date', '>=', year.end_date), ('state', '!=', 'open'), ]): cls.raise_user_error('reopen_error')
class Group(ModelSQL, ModelView): 'Payment Group' __name__ = 'account.payment.group' _rec_name = 'number' number = fields.Char('Number', required=True, readonly=True) company = fields.Many2One( 'company.company', 'Company', required=True, readonly=True, select=True, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ]) journal = fields.Many2One('account.payment.journal', 'Journal', required=True, readonly=True, domain=[ ('company', '=', Eval('company', -1)), ], depends=['company']) kind = fields.Selection(KINDS, 'Kind', required=True, readonly=True) payments = fields.One2Many('account.payment', 'group', 'Payments', readonly=True) @classmethod def __register__(cls, module_name): table_h = cls.__table_handler__(module_name) # Migration from 3.8: rename reference into number if table_h.column_exist('reference'): table_h.column_rename('reference', 'number') super(Group, cls).__register__(module_name) @staticmethod def default_company(): return Transaction().context.get('company') @classmethod def create(cls, vlist): pool = Pool() Sequence = pool.get('ir.sequence') Config = pool.get('account.configuration') vlist = [v.copy() for v in vlist] config = Config(1) for values in vlist: if values.get('number') is None: values['number'] = Sequence.get_id( config.payment_group_sequence.id) return super(Group, cls).create(vlist) @classmethod def copy(cls, groups, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', None) return super(Group, cls).copy(groups, default=default)
class PaymentTermLine(sequence_ordered(), ModelSQL, ModelView): 'Payment Term Line' __name__ = 'account.invoice.payment_term.line' payment = fields.Many2One('account.invoice.payment_term', 'Payment Term', required=True, ondelete="CASCADE") type = fields.Selection([ ('fixed', 'Fixed'), ('percent', 'Percentage on Remainder'), ('percent_on_total', 'Percentage on Total'), ('remainder', 'Remainder'), ], 'Type', required=True) ratio = fields.Numeric('Ratio', digits=(14, 10), states={ 'invisible': ~Eval('type').in_(['percent', 'percent_on_total']), 'required': Eval('type').in_(['percent', 'percent_on_total']), }, depends=['type']) divisor = fields.Numeric('Divisor', digits=(10, 14), states={ 'invisible': ~Eval('type').in_(['percent', 'percent_on_total']), 'required': Eval('type').in_(['percent', 'percent_on_total']), }, depends=['type']) amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)), states={ 'invisible': Eval('type') != 'fixed', 'required': Eval('type') == 'fixed', }, depends=['type', 'currency_digits']) currency = fields.Many2One('currency.currency', 'Currency', states={ 'invisible': Eval('type') != 'fixed', 'required': Eval('type') == 'fixed', }, depends=['type']) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') relativedeltas = fields.One2Many( 'account.invoice.payment_term.line.delta', 'line', 'Deltas') @classmethod def __register__(cls, module_name): sql_table = cls.__table__() super(PaymentTermLine, cls).__register__(module_name) cursor = Transaction().connection.cursor() table = cls.__table_handler__(module_name) # Migration from 3.8: rename percentage into ratio if table.column_exist('percentage'): cursor.execute(*sql_table.update( columns=[sql_table.ratio], values=[sql_table.percentage / 100])) table.drop_column('percentage') @staticmethod def default_currency_digits(): return 2 @staticmethod def default_type(): return 'remainder' @classmethod def default_relativedeltas(cls): if Transaction().user == 0: return [] return [{}] @fields.depends('type') def on_change_type(self): if self.type != 'fixed': self.amount = Decimal('0.0') self.currency = None if self.type not in ('percent', 'percent_on_total'): self.ratio = Decimal('0.0') self.divisor = Decimal('0.0') @fields.depends('ratio') def on_change_ratio(self): if not self.ratio: self.divisor = Decimal('0.0') else: self.divisor = self.round(1 / self.ratio, self.__class__.divisor.digits[1]) @fields.depends('divisor') def on_change_divisor(self): if not self.divisor: self.ratio = Decimal('0.0') else: self.ratio = self.round(1 / self.divisor, self.__class__.ratio.digits[1]) @fields.depends('currency') def on_change_with_currency_digits(self, name=None): if self.currency: return self.currency.digits return 2 def get_date(self, date): for relativedelta_ in self.relativedeltas: date += relativedelta_.get() return date def get_value(self, remainder, amount, currency): Currency = Pool().get('currency.currency') if self.type == 'fixed': fixed = Currency.compute(self.currency, self.amount, currency) return fixed.copy_sign(amount) elif self.type == 'percent': return currency.round(remainder * self.ratio) elif self.type == 'percent_on_total': return currency.round(amount * self.ratio) elif self.type == 'remainder': return currency.round(remainder) return None @staticmethod def round(number, digits): quantize = Decimal(10) ** -Decimal(digits) return Decimal(number).quantize(quantize) @classmethod def validate(cls, lines): super(PaymentTermLine, cls).validate(lines) cls.check_ratio_and_divisor(lines) @classmethod def check_ratio_and_divisor(cls, lines): "Check consistency between ratio and divisor" # Use a copy because on_change will change the records for line in cls.browse(lines): if line.type not in ('percent', 'percent_on_total'): continue if line.ratio is None or line.divisor is None: raise PaymentTermValidationError( gettext('account_invoice' '.msg_payment_term_invalid_ratio_divisor', line=line.rec_name)) ratio = line.ratio divisor = line.divisor line.on_change_ratio() line.on_change_divisor() if (line.divisor != divisor) or (line.ratio != ratio): raise PaymentTermValidationError( gettext('account_invoice' '.msg_payment_term_invalid_ratio_divisor', line=line.rec_name))
class SaleLine: "SaleLine" __name__ = 'sale.line' gift_card_delivery_mode = fields.Function( fields.Selection([ ('virtual', 'Virtual'), ('physical', 'Physical'), ('combined', 'Combined'), ], 'Gift Card Delivery Mode', states={ 'invisible': ~Bool(Eval('is_gift_card')), }, depends=['is_gift_card']), 'on_change_with_gift_card_delivery_mode') is_gift_card = fields.Function(fields.Boolean('Gift Card'), 'on_change_with_is_gift_card') gift_cards = fields.One2Many('gift_card.gift_card', "sale_line", "Gift Cards", readonly=True) message = fields.Text("Message", states={'invisible': ~Bool(Eval('is_gift_card'))}) recipient_email = fields.Char( "Recipient Email", states={ 'invisible': ~(Bool(Eval('is_gift_card')) & (Eval('gift_card_delivery_mode').in_(['virtual', 'combined']))), 'required': (Bool(Eval('is_gift_card')) & (Eval('gift_card_delivery_mode').in_(['virtual', 'combined']))), }, depends=['gift_card_delivery_mode', 'is_gift_card']) recipient_name = fields.Char("Recipient Name", states={ 'invisible': ~Bool(Eval('is_gift_card')), }, depends=['is_gift_card']) allow_open_amount = fields.Function( fields.Boolean("Allow Open Amount?", states={'invisible': ~Bool(Eval('is_gift_card'))}, depends=['is_gift_card']), 'on_change_with_allow_open_amount') gc_price = fields.Many2One( 'product.product.gift_card.price', "Gift Card Price", states={ 'required': (~Bool(Eval('allow_open_amount')) & Bool(Eval('is_gift_card'))), 'invisible': ~(~Bool(Eval('allow_open_amount')) & Bool(Eval('is_gift_card'))) }, depends=['allow_open_amount', 'is_gift_card', 'product'], domain=[('product', '=', Eval('product'))]) @fields.depends('product') def on_change_with_allow_open_amount(self, name=None): if self.product: return self.product.allow_open_amount @fields.depends('gc_price', 'unit_price') def on_change_gc_price(self, name=None): res = {} if self.gc_price: res['unit_price'] = self.gc_price.price return res @classmethod def __setup__(cls): super(SaleLine, cls).__setup__() cls.unit_price.states['readonly'] = (~Bool(Eval('allow_open_amount')) & Bool(Eval('is_gift_card'))) cls._error_messages.update({ 'amounts_out_of_range': 'Gift card amount must be within %s %s and %s %s' }) @fields.depends('product', 'is_gift_card') def on_change_with_gift_card_delivery_mode(self, name=None): """ Returns delivery mode of the gift card product """ if not (self.product and self.is_gift_card): return None return self.product.gift_card_delivery_mode @classmethod def copy(cls, lines, default=None): if default is None: default = {} default['gift_cards'] = None return super(SaleLine, cls).copy(lines, default=default) @fields.depends('product') def on_change_with_is_gift_card(self, name=None): """ Returns boolean value to tell if product is gift card or not """ return self.product and self.product.is_gift_card def get_invoice_line(self, invoice_type): """ Pick up liability account from gift card configuration for invoices """ GiftCardConfiguration = Pool().get('gift_card.configuration') lines = super(SaleLine, self).get_invoice_line(invoice_type) if lines and self.is_gift_card: liability_account = GiftCardConfiguration(1).liability_account if not liability_account: self.raise_user_error( "Liability Account is missing from Gift Card " "Configuration") for invoice_line in lines: invoice_line.account = liability_account return lines @fields.depends('is_gift_card') def on_change_is_gift_card(self): ModelData = Pool().get('ir.model.data') if self.is_gift_card: return { 'product': None, 'description': 'Gift Card', 'unit': ModelData.get_id('product', 'uom_unit'), } return { 'description': None, 'unit': None, } def create_gift_cards(self): ''' Create the actual gift card for this line ''' GiftCard = Pool().get('gift_card.gift_card') if not self.is_gift_card: # Not a gift card line return None product = self.product if product.allow_open_amount and not (product.gc_min <= self.unit_price <= product.gc_max): self.raise_user_error("amounts_out_of_range", (self.sale.currency.code, product.gc_min, self.sale.currency.code, product.gc_max)) # XXX: Do not consider cancelled ones in the gift cards. # card could have been cancelled for reasons like wrong message ? quantity_created = len(self.gift_cards) if self.sale.gift_card_method == 'order': quantity = self.quantity - quantity_created else: # On invoice paid quantity_paid = 0 for invoice_line in self.invoice_lines: if invoice_line.invoice.state == 'paid': invoice_line.quantity quantity_paid += invoice_line.quantity # Remove already created gift cards quantity = quantity_paid - quantity_created if not quantity > 0: # No more gift cards to create return gift_cards = GiftCard.create([{ 'amount': self.unit_price, 'sale_line': self.id, 'message': self.message, 'recipient_email': self.recipient_email, 'recipient_name': self.recipient_name, 'origin': '%s,%d' % (self.sale.__name__, self.sale.id), } for each in range(0, int(quantity))]) GiftCard.activate(gift_cards) return gift_cards
class MoveLine: __metaclass__ = PoolMeta __name__ = 'account.move.line' payment_amount = fields.Function(fields.Numeric( 'Payment Amount', digits=(16, If(Bool(Eval('second_currency_digits')), Eval('second_currency_digits', 2), Eval('currency_digits', 2))), states={ 'invisible': ~Eval('payment_kind'), }, depends=['payment_kind', 'second_currency_digits', 'currency_digits']), 'get_payment_amount', searcher='search_payment_amount') payments = fields.One2Many('account.payment', 'line', 'Payments', readonly=True, states={ 'invisible': ~Eval('payment_kind'), }, depends=['payment_kind']) payment_kind = fields.Function(fields.Selection([ (None, ''), ] + KINDS, 'Payment Kind'), 'get_payment_kind', searcher='search_payment_kind') @classmethod def __setup__(cls): super(MoveLine, cls).__setup__() cls._buttons.update({ 'pay': { 'invisible': ~Eval('payment_kind').in_(dict(KINDS).keys()), }, }) @classmethod def get_payment_amount(cls, lines, name): amounts = {} for line in lines: if line.account.kind not in ('payable', 'receivable'): amounts[line.id] = None continue if line.second_currency: amount = abs(line.amount_second_currency) else: amount = abs(line.credit - line.debit) for payment in line.payments: if payment.state != 'failed': amount -= payment.amount amounts[line.id] = amount return amounts @classmethod def search_payment_amount(cls, name, clause): pool = Pool() Payment = pool.get('account.payment') Account = pool.get('account.account') _, operator, value = clause Operator = fields.SQL_OPERATORS[operator] table = cls.__table__() payment = Payment.__table__() account = Account.__table__() payment_amount = Sum(Coalesce(payment.amount, 0)) main_amount = Abs(table.credit - table.debit) - payment_amount second_amount = Abs(table.amount_second_currency) - payment_amount amount = Case((table.second_currency == Null, main_amount), else_=second_amount) value = cls.payment_amount.sql_format(value) query = table.join( payment, type_='LEFT', condition=(table.id == payment.line) & (payment.state != 'failed')).join( account, condition=table.account == account.id).select( table.id, where=account.kind.in_(['payable', 'receivable']), group_by=(table.id, account.kind, table.second_currency), having=Operator(amount, value)) return [('id', 'in', query)] def get_payment_kind(self, name): return self.account.kind if self.account.kind in dict(KINDS) else None @classmethod def search_payment_kind(cls, name, clause): return [('account.kind', ) + tuple(clause[1:])] @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', None) return super(MoveLine, cls).copy(lines, default=default) @classmethod @ModelView.button_action('account_payment.act_pay_line') def pay(cls, lines): pass
class GiftCard(Workflow, ModelSQL, ModelView): "Gift Card" __name__ = 'gift_card.gift_card' _rec_name = 'number' number = fields.Char('Number', select=True, readonly=True, required=True, help='Number of the gift card') origin = fields.Reference('Origin', selection='get_origin', select=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) currency = fields.Many2One('currency.currency', 'Currency', required=True, states={'readonly': Eval('state') != 'draft'}, depends=['state']) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)), states={'readonly': Eval('state') != 'draft'}, depends=['state', 'currency_digits'], required=True) amount_authorized = fields.Function( fields.Numeric("Amount Authorized", digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount') amount_captured = fields.Function( fields.Numeric("Amount Captured", digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount') amount_available = fields.Function( fields.Numeric("Amount Available", digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount') state = fields.Selection([ ('draft', 'Draft'), ('active', 'Active'), ('canceled', 'Canceled'), ('used', 'Used'), ], 'State', readonly=True, required=True) sale_line = fields.Many2One('sale.line', "Sale Line", readonly=True) sale = fields.Function(fields.Many2One('sale.sale', "Sale"), 'get_sale') payment_transactions = fields.One2Many("payment_gateway.transaction", "gift_card", "Payment Transactions", readonly=True) message = fields.Text("Message") recipient_email = fields.Char( "Recipient Email", states={'readonly': Eval('state') != 'draft'}) recipient_name = fields.Char("Recipient Name", states={'readonly': Eval('state') != 'draft'}) is_email_sent = fields.Boolean("Is Email Sent ?", readonly=True) comment = fields.Text('Comment') def get_sale(self, name): """ Return sale for gift card using sale line associated with it """ return self.sale_line and self.sale_line.sale.id or None @staticmethod def default_currency(): """ Set currency of current company as default currency """ Company = Pool().get('company.company') return Transaction().context.get('company') and \ Company(Transaction().context.get('company')).currency.id or None def get_amount(self, name): """ Returns authorzied, captured and available amount for the gift card """ PaymentTransaction = Pool().get('payment_gateway.transaction') if name == 'amount_authorized': return sum([ t.amount for t in PaymentTransaction.search([( 'state', '=', 'authorized'), ('gift_card', '=', self.id)]) ]) if name == 'amount_captured': return sum([ t.amount for t in PaymentTransaction.search([( 'state', 'in', ['posted', 'done']), ('gift_card', '=', self.id)]) ]) if name == 'amount_available': return self.amount - sum([ t.amount for t in PaymentTransaction.search([( 'state', 'in', ['authorized', 'posted', 'done'] ), ('gift_card', '=', self.id)]) ]) @staticmethod def default_state(): return 'draft' @fields.depends('currency') def on_change_with_currency_digits(self, name=None): if self.currency: return self.currency.digits return 2 @classmethod def __setup__(cls): super(GiftCard, cls).__setup__() table = cls.__table__() cls._sql_constraints = [('number_uniq', Unique(table, table.number), 'The number of the gift card must be unique.') ] cls._error_messages.update({ 'deletion_not_allowed': "Gift cards can not be deleted in active state" }) cls._transitions |= set(( ('draft', 'active'), ('active', 'canceled'), ('draft', 'canceled'), ('canceled', 'draft'), )) cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['draft', 'active']), }, 'draft': { 'invisible': ~Eval('state').in_(['canceled']), 'icon': If( Eval('state') == 'cancel', 'tryton-clear', 'tryton-go-previous'), }, 'activate': { 'invisible': Eval('state') != 'draft', } }) @classmethod def create(cls, vlist): Sequence = Pool().get('ir.sequence') Configuration = Pool().get('gift_card.configuration') vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('number'): values['number'] = Sequence.get_id( Configuration(1).number_sequence.id) return super(GiftCard, cls).create(vlist) @classmethod def copy(cls, gift_cards, default=None): if default is None: default = {} default = default.copy() default['number'] = None default['sale_line'] = None default['state'] = cls.default_state() default['payment_transactions'] = None return super(GiftCard, cls).copy(gift_cards, default=default) @classmethod @ModelView.button @Workflow.transition('active') def activate(cls, gift_cards): """ Set gift cards to active state """ for gift_card in gift_cards: if gift_card.recipient_email and not gift_card.is_email_sent: gift_card.send_gift_card_as_email() @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, gift_cards): """ Set gift cards back to draft state """ pass @classmethod @ModelView.button @Workflow.transition('canceled') def cancel(cls, gift_cards): """ Cancel gift cards """ pass @classmethod def get_origin(cls): return [ (None, ''), ('sale.sale', 'Sale'), ] @classmethod def delete(cls, gift_cards): """ It should not be possible to delete gift cards in active state """ for gift_card in gift_cards: if gift_card.state == 'active': cls.raise_user_error("deletion_not_allowed") return super(GiftCard, cls).delete(gift_cards) def _get_subject_for_email(self): """ Returns the text to use as subject of email """ return "Gift Card - %s" % self.number def _get_email_templates(self): """ Returns a tuple of the form: (html_template, text_template) """ env = Environment( loader=PackageLoader('trytond.modules.gift_card', 'emails')) return (env.get_template('gift_card_html.html'), env.get_template('gift_card_text.html')) def send_gift_card_as_email(self): """ Send gift card as an attachment in the email """ EmailQueue = Pool().get('email.queue') GiftCardReport = Pool().get('gift_card.gift_card', type='report') ModelData = Pool().get('ir.model.data') Group = Pool().get('res.group') group_id = ModelData.get_id("gift_card", "gift_card_email_receivers") bcc_emails = map( lambda user: user.email, filter(lambda user: user.email, Group(group_id).users)) if not self.recipient_email: # pragma: no cover return # Try to generate report twice # This is needed as sometimes `unoconv` fails to convert report to pdf for try_count in range(2): try: val = GiftCardReport.execute([self.id], {}) break except: # pragma: no cover if try_count == 0: continue else: return subject = self._get_subject_for_email() html_template, text_template = self._get_email_templates() sender = config.get('email', 'from') email_gift_card = render_email( sender, self.recipient_email, subject, html_template=html_template, text_template=text_template, attachments={"%s.%s" % (val[3], val[0]): val[1]}, card=self, ) EmailQueue.queue_mail(sender, [self.recipient_email] + bcc_emails, email_gift_card.as_string()) self.is_email_sent = True self.save()
class Carrier(metaclass=PoolMeta): __name__ = 'carrier' weight_uom = fields.Many2One( 'product.uom', 'Weight Uom', domain=[('category', '=', Id('product', 'uom_cat_weight'))], states={ 'invisible': Eval('carrier_cost_method') != 'weight', 'required': Eval('carrier_cost_method') == 'weight', 'readonly': Bool(Eval('weight_price_list', [])), }, depends=['carrier_cost_method', 'weight_price_list'], help="The unit of weight criteria of the price list.") weight_uom_digits = fields.Function(fields.Integer('Weight Uom Digits'), 'on_change_with_weight_uom_digits') weight_currency = fields.Many2One( 'currency.currency', 'Currency', states={ 'invisible': Eval('carrier_cost_method') != 'weight', 'required': Eval('carrier_cost_method') == 'weight', 'readonly': Bool(Eval('weight_price_list', [])), }, depends=['carrier_cost_method', 'weight_price_list'], help="The currency of the price.") weight_currency_digits = fields.Function( fields.Integer('Weight Currency Digits'), 'on_change_with_weight_currency_digits') weight_price_list = fields.One2Many( 'carrier.weight_price_list', 'carrier', 'Price List', states={ 'invisible': Eval('carrier_cost_method') != 'weight', 'readonly': ~(Eval('weight_uom', 0) & Eval('weight_currency', 0)), }, depends=['carrier_cost_method', 'weight_uom', 'weight_currency'], help="Add price to the carrier service.") @classmethod def __setup__(cls): super(Carrier, cls).__setup__() selection = ('weight', 'Weight') if selection not in cls.carrier_cost_method.selection: cls.carrier_cost_method.selection.append(selection) @staticmethod def default_weight_uom_digits(): return 2 @staticmethod def default_weight_currency_digits(): Company = Pool().get('company.company') company = Transaction().context.get('company') if company: return Company(company).currency.digits return 2 @fields.depends('weight_uom') def on_change_with_weight_uom_digits(self, name=None): if self.weight_uom: return self.weight_uom.digits return 2 @fields.depends('weight_currency') def on_change_with_weight_currency_digits(self, name=None): if self.weight_currency: return self.weight_currency.digits return 2 def compute_weight_price(self, weight): "Compute price based on weight" for line in reversed(self.weight_price_list): if line.weight < weight: return line.price return Decimal(0) def get_sale_price(self): price, currency_id = super(Carrier, self).get_sale_price() if self.carrier_cost_method == 'weight': weight_price = Decimal(0) for weight in Transaction().context.get('weights', []): weight_price += self.compute_weight_price(weight) return weight_price, self.weight_currency.id return price, currency_id def get_purchase_price(self): price, currency_id = super(Carrier, self).get_purchase_price() if self.carrier_cost_method == 'weight': weight_price = Decimal(0) for weight in Transaction().context.get('weights', []): weight_price += self.compute_weight_price(weight) return weight_price, self.weight_currency.id return price, currency_id
class GiftCardRedeemStart(ModelView): "Gift Card Redeem Start View" __name__ = 'gift_card.redeem.start' description = fields.Text('Description', required=True) gateway = fields.Many2One('payment_gateway.gateway', 'Gateway', required=True, domain=[ ('method', '=', 'gift_card'), ]) gift_card = fields.Many2One('gift_card.gift_card', 'Gift Card', readonly=True) party = fields.Many2One('party.party', 'Party', required=True) amount = fields.Numeric('Amount', digits=(16, 2), required=True) address = fields.Many2One('party.address', 'Billing Address', required=True, domain=[('party', '=', Eval('party'))], depends=['party']) currency = fields.Many2One('currency.currency', 'Currency', required=True) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') @staticmethod def default_currency(): """ Set currency of current company as default currency """ Company = Pool().get('company.company') return Transaction().context.get('company') and \ Company(Transaction().context.get('company')).currency.id or None @fields.depends('currency') def on_change_with_currency_digits(self, name=None): if self.currency: return self.currency.digits return 2 @fields.depends('party') def on_change_with_address(self, name=None): # pragma: no cover """ This method returns one of the following, once the party is set -: * If the party has invoice addresses, the first among them is shown by default. * If the party has no invoice addresses but has addresses, the first among those addresses is shown. * If the party has no addresses, the user gets to select them. """ Address = Pool().get('party.address') if self.party is None: # If the party is removed altogether return None try: address, = Address.search([('party', '=', self.party.id)], order=[('invoice', 'DESC')], limit=1) except ValueError: return None return address.id
class Complaint(Workflow, ModelSQL, ModelView): 'Customer Complaint' __name__ = 'sale.complaint' _rec_name = 'number' _states = { 'readonly': Eval('state') != 'draft', } _depends = ['state'] number = fields.Char('Number', readonly=True, select=True) reference = fields.Char('Reference', select=True) date = fields.Date('Date', states=_states, depends=_depends) customer = fields.Many2One('party.party', 'Customer', required=True, states=_states, depends=_depends) address = fields.Many2One('party.address', 'Address', domain=[('party', '=', Eval('customer'))], states=_states, depends=_depends + ['customer']) company = fields.Many2One('company.company', 'Company', required=True, states=_states, depends=_depends) employee = fields.Many2One('company.employee', 'Employee', states=_states, depends=_depends) type = fields.Many2One('sale.complaint.type', 'Type', required=True, states=_states, depends=_depends) origin = fields.Reference('Origin', selection='get_origin', states={ 'readonly': ((Eval('state') != 'draft') | Bool(Eval('actions', [0]))), 'required': Bool(Eval('origin_model')), }, depends=['state', 'customer', 'origin_model', 'company']) origin_id = fields.Function(fields.Integer('Origin ID'), 'on_change_with_origin_id') origin_model = fields.Function(fields.Char('Origin Model'), 'on_change_with_origin_model') description = fields.Text('Description', states=_states, depends=_depends) actions = fields.One2Many('sale.complaint.action', 'complaint', 'Actions', states={ 'readonly': ((Eval('state') != 'draft') | (If(~Eval('origin_id', 0), 0, Eval('origin_id', 0)) <= 0)), }, depends=['state', 'origin_model', 'origin_id']) state = fields.Selection([ ('draft', 'Draft'), ('waiting', 'Waiting'), ('approved', 'Approved'), ('rejected', 'Rejected'), ('done', 'Done'), ('cancelled', 'Cancelled'), ], 'State', readonly=True, required=True) @classmethod def __setup__(cls): super(Complaint, cls).__setup__() cls._order.insert(0, ('date', 'DESC')) cls._error_messages.update({ 'delete_draft': ('Complaint "%s" must be in draft ' 'to be deleted.'), }) cls._transitions |= set(( ('draft', 'waiting'), ('waiting', 'draft'), ('waiting', 'approved'), ('waiting', 'rejected'), ('approved', 'done'), ('draft', 'cancelled'), ('waiting', 'cancelled'), ('done', 'draft'), ('cancelled', 'draft'), )) cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['draft', 'waiting']), }, 'draft': { 'invisible': ~Eval('state').in_( ['waiting', 'done', 'cancelled']), 'icon': If(Eval('state').in_(['done', 'cancelled']), 'tryton-clear', 'tryton-go-previous'), }, 'wait': { 'invisible': ~Eval('state').in_(['draft']), }, 'approve': { 'invisible': (~Eval('state').in_(['waiting']) & Eval('context', {}).get('groups', []).contains( Id('sale', 'group_sale_admin'))), }, 'reject': { 'invisible': (~Eval('state').in_(['waiting']) & Eval('context', {}).get('groups', []).contains( Id('sale', 'group_sale_admin'))), }, 'process': { 'invisible': ~Eval('state').in_(['approved']), }, }) origin_domain = [] for model, domain in cls._origin_domains().iteritems(): origin_domain = If(Eval('origin_model') == model, domain, origin_domain) cls.origin.domain = [origin_domain] actions_domains = cls._actions_domains() actions_domain = [('action', 'in', actions_domains.pop(None))] for model, actions in actions_domains.iteritems(): actions_domain = If(Eval('origin_model') == model, [('action', 'in', actions)], actions_domain) cls.actions.domain = [actions_domain] @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') table_h = TableHandler(cls, module_name) # Migration from 3.8: rename reference into number if (table_h.column_exist('reference') and not table_h.column_exist('number')): table_h.column_rename('reference', 'number') super(Complaint, cls).__register__(module_name) @classmethod def _origin_domains(cls): return { 'sale.sale': [ ('party', '=', Eval('customer')), ('company', '=', Eval('company')), ('state', 'in', ['confirmed', 'processing', 'done']), ], 'sale.line': [ ('sale.party', '=', Eval('customer')), ('sale.company', '=', Eval('company')), ('sale.state', 'in', ['confirmed', 'processing', 'done']), ], 'account.invoice': [ ('party', '=', Eval('customer')), ('company', '=', Eval('company')), ('type', '=', 'out'), ('state', 'in', ['posted', 'paid']), ], 'account.invoice.line': [ ('invoice.party', '=', Eval('customer')), ('invoice.company', '=', Eval('company')), ('invoice.type', '=', 'out'), ('invoice.state', 'in', ['posted', 'paid']), ], } @classmethod def _actions_domains(cls): return { None: [], 'sale.sale': ['sale_return'], 'sale.line': ['sale_return'], 'account.invoice': ['credit_note'], 'account.invoice.line': ['credit_note'], } @staticmethod def default_date(): pool = Pool() Date = pool.get('ir.date') return Date.today() @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_state(): return 'draft' @fields.depends('type') def get_origin(self): if self.type: origin = self.type.origin return [('', ''), (origin.model, origin.name)] else: return [] @fields.depends('origin') def on_change_with_origin_id(self, name=None): if self.origin: return self.origin.id @fields.depends('origin') def on_change_with_origin_model(self, name=None): if self.origin: return self.origin.__class__.__name__ @classmethod def create(cls, vlist): pool = Pool() Sequence = pool.get('ir.sequence') Configuration = pool.get('sale.configuration') config = Configuration(1) vlist = [v.copy() for v in vlist] for values in vlist: if values.get('number') is None: values['number'] = Sequence.get_id( config.complaint_sequence.id) return super(Complaint, cls).create(vlist) @classmethod def copy(cls, complaints, default=None): if default is None: default = {} default = default.copy() default['number'] = None return super(Complaint, cls).copy(complaints, default=default) @classmethod def delete(cls, complaints): for complaint in complaints: if complaint.state != 'draft': cls.raise_user_error('delete_draft', complaint.rec_name) super(Complaint, cls).delete(complaints) @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, complaints): pass @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, complaints): pass @classmethod @ModelView.button @Workflow.transition('waiting') def wait(cls, complaints): pass @classmethod @ModelView.button @Workflow.transition('approved') def approve(cls, complaints): pass @classmethod @ModelView.button @Workflow.transition('rejected') def reject(cls, complaints): pass @classmethod @ModelView.button @Workflow.transition('done') def process(cls, complaints): results = defaultdict(list) actions = defaultdict(list) for complaint in complaints: for action in complaint.actions: if action.result: continue result = action.do() results[result.__class__].append(result) actions[result.__class__].append(action) for kls, records in results.iteritems(): kls.save(records) for action, record in zip(actions[kls], records): action.result = record Action.save(sum(actions.values(), []))
class SaleOpportunityEmployeeMonthly(ModelSQL, ModelView): 'Sale Opportunity per Employee per Month' __name__ = 'sale.opportunity_employee_monthly' year = fields.Char('Year') month = fields.Integer('Month') employee = fields.Many2One('company.employee', 'Employee') number = fields.Integer('Number') converted = fields.Integer('Converted') conversion_rate = fields.Function( fields.Float('Conversion Rate', help='In %'), 'get_conversion_rate') lost = fields.Integer('Lost') company = fields.Many2One('company.company', 'Company') currency = fields.Function( fields.Many2One('currency.currency', 'Currency'), 'get_currency') currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits') amount = fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']) converted_amount = fields.Numeric('Converted Amount', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']) conversion_amount_rate = fields.Function( fields.Float('Conversion Amount Rate', help='In %'), 'get_conversion_amount_rate') @classmethod def __setup__(cls): super(SaleOpportunityEmployeeMonthly, cls).__setup__() cls._order.insert(0, ('year', 'DESC')) cls._order.insert(1, ('month', 'DESC')) cls._order.insert(2, ('employee', 'ASC')) @staticmethod def _converted_state(): return ['converted'] @staticmethod def _lost_state(): return ['lost'] @classmethod def table_query(cls): Opportunity = Pool().get('sale.opportunity') type_id = FIELDS[cls.id._type].sql_type(cls.id)[0] type_year = FIELDS[cls.year._type].sql_type(cls.year)[0] return ('SELECT CAST(id AS ' + type_id + ') AS id, create_uid, ' 'create_date, write_uid, write_date, ' 'CAST(year AS ' + type_year + ') AS year, month, ' 'employee, company, number, converted, lost, amount, ' 'converted_amount ' 'FROM (' 'SELECT EXTRACT(MONTH FROM start_date) + ' 'EXTRACT(YEAR FROM start_date) * 100 + ' 'employee * 1000000 AS id, ' 'MAX(create_uid) AS create_uid, ' 'MAX(create_date) AS create_date, ' 'MAX(write_uid) AS write_uid, ' 'MAX(write_date) AS write_date, ' 'EXTRACT(YEAR FROM start_date) AS year, ' 'EXTRACT(MONTH FROM start_date) AS month, ' 'employee, ' 'company, ' 'COUNT(1) AS number, ' 'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x for x in cls._converted_state()) + ') ' 'THEN 1 ELSE 0 END) AS converted, ' 'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x for x in cls._lost_state()) + ') ' 'THEN 1 ELSE 0 END) AS lost, ' 'SUM(amount) AS amount, ' 'SUM(CASE WHEN state IN (' + ','.join("'%s'" % x for x in cls._converted_state()) + ') ' 'THEN amount ELSE 0 END) AS converted_amount ' 'FROM "' + Opportunity._table + '" ' 'GROUP BY year, month, employee, company) ' 'AS "' + cls._table + '"', []) def get_conversion_rate(self, name): if self.number: return float(self.converted) / self.number * 100.0 else: return 0.0 def get_currency(self, name): return self.company.currency.id def get_currency_digits(self, name): return self.company.currency.digits def get_conversion_amount_rate(self, name): if self.amount: return float(self.converted_amount) / float(self.amount) * 100.0 else: return 0.0