class InvoiceEdi(ModelSQL, ModelView): 'Account Invoice Edi Reception Header' __name__ = 'invoice.edi' company = fields.Many2One('company.company', 'Company', readonly=True) number = fields.Char('Number', readonly=True) invoice = fields.Many2One('account.invoice', 'Invoice') type_ = fields.Selection([('380', 'Invoice'), ('381', 'Credit'), ('383', 'Charge Note')], 'Document Type', readonly=True) function_ = fields.Selection([(None, ''), ('9', 'Original'), ('31', 'Copy')], 'Message Function', readonly=True) invoice_date = fields.Date('Invoice Date', readonly=True) service_date = fields.Date('Service Date', readonly=True) start_period_date = fields.Date('Start Period', readonly=True) end_period_date = fields.Date('End Period', readonly=True) payment_type_value = fields.Selection([(None, ''), ('42', 'Account Bank'), ('10', 'cash'), ('20', 'check'), ('60', 'Bank Note'), ('14E', 'Bank Draft'), ('30', 'Credit Transfer')], 'Payment Type Value', readonly=True) payment_type = fields.Many2One('account.payment.type', 'Payment Type', readonly=True) factoring = fields.Boolean('Factoring', readonly=True) currency_code = fields.Char('Currency Code', readonly=True) currency = fields.Many2One('currency.currency', 'Currency', readonly=True) maturity_dates = fields.One2Many('invoice.edi.maturity_date', 'invoice_edi', 'Maturity Dates', readonly=True) discounts = fields.One2Many('invoice.edi.discount', 'invoice_edi', 'Discounts', readonly=True) net_amount = fields.Numeric('Net Amount', digits=(16, 2), readonly=True) gross_amount = fields.Numeric('Gross Amount', digits=(16, 2), readonly=True) base_amount = fields.Numeric('Base Amount', digits=(16, 2), readonly=True) total_amount = fields.Numeric('Total Amount', digits=(16, 2), readonly=True) tax_amount = fields.Numeric('Tax Amount', digits=(16, 2), readonly=True) discount_amount = fields.Numeric('Discount Amount', digits=(16, 2), readonly=True) charge_amount = fields.Numeric('Charge Amount', digits=(16, 2), readonly=True) taxes = fields.One2Many('invoice.edi.tax', 'edi_invoice', 'Taxes', readonly=True) lines = fields.One2Many('invoice.edi.line', 'edi_invoice', 'Lines') suppliers = fields.One2Many('invoice.edi.supplier', 'edi_invoice', 'Supplier', readonly=True) references = fields.One2Many('invoice.edi.reference', 'edi_invoice', 'References', readonly=True) state = fields.Function(fields.Selection([('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State'), 'get_state', searcher='search_state') differences_state = fields.Function( fields.Selection([(None, ''), ('ok', 'Ok'), ('difference', 'Difference')], 'Difference State'), 'get_differences_state') difference_amount = fields.Function( fields.Numeric('Differences', digits=(16, 2)), 'get_difference_amount') party = fields.Function(fields.Many2One('party.party', 'Invoice Party'), 'get_party', searcher='search_party') manual_party = fields.Many2One('party.party', 'Manual Party') comment = fields.Text('Comment') @classmethod def __setup__(cls): super(InvoiceEdi, cls).__setup__() cls._order = [ ('invoice_date', 'DESC'), ('id', 'DESC'), ] cls._buttons.update({'create_invoices': {}, 'search_references': {}}) @staticmethod def default_state(): return 'draft' @staticmethod def default_company(): return Transaction().context.get('company') def get_state(self, name): if self.invoice: return 'confirmed' return 'draft' @classmethod def search_state(cls, name, clause): if clause[-1] == 'draft': return [('invoice', clause[1], None)] return [('invoice', '!=', None)] def get_differences_state(self, name): if not self.invoice: return if self.invoice.total_amount != self.total_amount: return 'difference' return 'ok' def get_difference_amount(self, name): if not self.invoice: return return self.total_amount - self.invoice.total_amount @classmethod def search_party(cls, name, clause): return [ 'OR', ('manual_party', ) + tuple(clause[1:]), [('suppliers.type_', '=', 'NADSU'), ('suppliers.party', ) + tuple(clause[1:])] ] def get_party(self, name): if self.manual_party: return self.manual_party.id for s in self.suppliers: if s.type_ == 'NADSU': return s.party and s.party.id def read_INV(self, message): self.number = message.pop(0) if message else '' self.type_ = message.pop(0) if message else '' self.function_ = '9' if message: self.function_ = message.pop(0) def read_TXT(self, message): self.comment = message.pop(0) if message else '' def read_DTM(self, message): self.invoice_date = to_date(message.pop(0)[0:8]) if message else None if message: self.service_date = to_date(message.pop(0)[0:8]) if message: period = message.pop(0) self.start_period_date = to_date(period[0:8]) self.end_period_date = to_date(period[8:]) def read_PAI(self, message): payment_type = message.pop(0) if message else '' self.payment_type_value = payment_type factoring = False if message: factoring = message.pop(0) self.factoring = factoring def read_RFF(self, message): REF = Pool().get('invoice.edi.reference') ref = REF() ref.type_ = message.pop(0) if message else '' ref.value = message.pop(0) if message else '' if message: ref.line_number = message.pop(0) ref.search_reference() if not getattr(self, 'references', False): self.references = [] self.references += (ref, ) def read_CUX(self, message): message.pop(0) self.currency_code = message.pop(0) if message else '' def read_PAT(self, message): MaturityDate = Pool().get('invoice.edi.maturity_date') line = MaturityDate() line.type_ = message.pop(0) if message else '' line.maturity_date = to_date(message.pop(0)) if message else None if message: line.amount = to_decimal(message.pop(0)) if not getattr(self, 'maturity_dates', False): self.maturity_dates = [] self.maturity_dates += (line, ) def read_ALC(self, message): Discount = Pool().get('invoice.edi.discount') discount = Discount() discount.type_ = message.pop(0) if message else '' sequence = message.pop(0) if message else '' discount.sequence = int(sequence) or None discount.discount = message.pop(0) if message else '' discount.percent = to_decimal( message.pop(0)) if message else Decimal(0) discount.amount = to_decimal(message.pop(0)) if message else Decimal(0) if not getattr(self, 'discounts', False): self.discounts = [] self.discounts += (discount, ) def read_MOARES(self, message): self.net_amount = to_decimal(message.pop(0)) if message else Decimal(0) self.gross_amount = to_decimal( message.pop(0)) if message else Decimal(0) self.base_amount = to_decimal( message.pop(0)) if message else Decimal(0) self.total_amount = to_decimal( message.pop(0)) if message else Decimal(0) self.tax_amount = to_decimal(message.pop(0)) if message else Decimal(0) if message: self.discount_amount = to_decimal(message.pop(0)) if message: self.charge_amount = to_decimal(message.pop(0)) def read_TAXRES(self, message): Tax = Pool().get('invoice.edi.tax') tax = Tax() tax.type_ = message.pop(0) tax.percent = to_decimal(message.pop(0)) if message else Decimal(0) tax.tax_amount = to_decimal(message.pop(0)) if message else Decimal(0) print(message) if message: tax.base_amount = to_decimal(message.pop(0)) tax.search_tax() if not getattr(self, 'taxes', False): self.taxes = [] self.taxes += (tax, ) def read_CNTRES(self, message): pass @classmethod def import_edi_file(cls, data): pool = Pool() Invoice = pool.get('invoice.edi') InvoiceLine = pool.get('invoice.edi.line') SupplierEdi = pool.get('invoice.edi.supplier') Configuration = pool.get('invoice.edi.configuration') config = Configuration(1) separator = config.separator invoice = None invoice_line = None document_type = data.pop(0) if document_type == 'INVOIC_D_93A_UN_EAN007': return for line in data: line = line.replace('\n', '').replace('\r', '') line = line.split(separator) msg_id = line.pop(0) if msg_id == 'INV': invoice = Invoice() invoice.read_INV(line) elif msg_id == 'LIN': # if invoice_line: # invoice_line.search_related(invoice) invoice_line = InvoiceLine() invoice_line.read_LIN(line) if not getattr(invoice, 'lines', False): invoice.lines = [] invoice.lines += (invoice_line, ) elif 'LIN' in msg_id: if hasattr(invoice_line, 'read_%s' % msg_id): getattr(invoice_line, 'read_%s' % msg_id)(line) elif msg_id in [x[0] for x in SUPPLIER_TYPE]: supplier = SupplierEdi() if hasattr(supplier, 'read_%s' % msg_id): getattr(supplier, 'read_%s' % msg_id)(line) supplier.search_party() if not getattr(invoice, 'suppliers', False): invoice.suppliers = [] invoice.suppliers += (supplier, ) elif 'NAD' in msg_id: continue else: if hasattr(invoice, 'read_%s' % msg_id): getattr(invoice, 'read_%s' % msg_id)(line) # invoice_line.search_related(invoice) return invoice def add_attachment(self, attachment, filename=None): pool = Pool() Attachment = pool.get('ir.attachment') if not filename: filename = datetime.now().strftime("%y/%m/%d %H:%M:%S") attach = Attachment(name=filename, type='data', data=attachment.encode('utf8'), resource=self) attach.save() @classmethod def import_edi_files(cls, invoices=None): pool = Pool() Configuration = pool.get('invoice.edi.configuration') configuration = Configuration(1) source_path = os.path.abspath(configuration.edi_files_path or DEFAULT_FILES_LOCATION) files = [ os.path.join(source_path, fp) for fp in os.listdir(source_path) if os.path.isfile(os.path.join(source_path, fp)) ] files_to_delete = [] to_save = [] attachments = dict() for fname in files: if fname[-4:].lower() not in KNOWN_EXTENSIONS: continue with codecs.open(fname, 'rb', 'latin-1') as fp: data = fp.readlines() invoice = cls.import_edi_file(data) basename = os.path.basename(fname) if invoice: attachments[invoice] = ("\n".join(data), basename) to_save.append(invoice) files_to_delete.append(fname) if to_save: cls.save(to_save) with Transaction().set_user(0, set_context=True): for invoice, (data, basename) in attachments.items(): invoice.add_attachment(data, basename) if files_to_delete: for file in files_to_delete: os.remove(file) cls.search_references(to_save) def get_invoice(self): pool = Pool() Invoice = pool.get('account.invoice') invoice = Invoice() invoice.currency = Invoice.default_currency() invoice.company = self.company invoice.party = self.party invoice.on_change_party() invoice.reference = self.number invoice.invoice_date = self.invoice_date invoice.type = 'in' invoice.on_change_type() invoice.account = invoice.on_change_with_account() invoice.state = 'draft' return invoice def get_discount_invoice_line(self, description, amount, taxes=None): Config = Pool().get('account.configuration') config = Config(1) Line = Pool().get('account.invoice.line') line = Line() line.invoice_type = 'in' line.party = self.party line.type = 'line' line.description = description line.quantity = 1 line.unit_price = amount line.gross_unit_price = amount line.account = config.default_product_account_expense line.taxes = taxes return line @classmethod @ModelView.button def search_references(cls, edi_invoices): for edi_invoice in edi_invoices: if edi_invoice.invoice: continue invoice = edi_invoice.get_invoice() invoice.lines = [] for eline in edi_invoice.lines: eline.search_related(edi_invoice) eline.save() @classmethod @ModelView.button def create_invoices(cls, edi_invoices): pool = Pool() Invoice = pool.get('account.invoice') invoices = [] to_save = [] for edi_invoice in edi_invoices: if edi_invoice.invoice: continue invoice = edi_invoice.get_invoice() invoice.lines = [] for eline in edi_invoice.lines: line = eline.get_line() invoice.lines += (line, ) invoice.on_change_lines() invoice.on_change_lines() invoice.on_change_type() invoice.use_edi = True edi_invoice.invoice = invoice invoices.append(invoice) to_save.append(edi_invoice) Invoice.save(invoices) Invoice.validate(invoices) Invoice.draft(invoices) cls.save(to_save)
class BOMInput(ModelSQL, ModelView): "Bill of Material Input" __name__ = 'production.bom.input' bom = fields.Many2One('production.bom', 'BOM', required=True, select=1, ondelete='CASCADE') product = fields.Many2One('product.product', 'Product', required=True) uom_category = fields.Function( fields.Many2One('product.uom.category', 'Uom Category'), 'on_change_with_uom_category') uom = fields.Many2One('product.uom', 'Uom', required=True, domain=[ ('category', '=', Eval('uom_category')), ]) quantity = fields.Float('Quantity', digits='uom', required=True, domain=[ 'OR', ('quantity', '>=', 0), ('quantity', '=', None), ]) @classmethod def __setup__(cls): super(BOMInput, cls).__setup__() cls.product.domain = [('type', 'in', cls.get_product_types())] cls.__access__.add('bom') @classmethod def __register__(cls, module): super().__register__(module) table_h = cls.__table_handler__(module) # Migration from 6.0: remove unique constraint table_h.drop_constraint('product_bom_uniq') @classmethod def get_product_types(cls): return ['goods', 'assets'] @fields.depends('product', 'uom') def on_change_product(self): if self.product: category = self.product.default_uom.category if not self.uom or self.uom.category != category: self.uom = self.product.default_uom else: self.uom = None @fields.depends('product') def on_change_with_uom_category(self, name=None): if self.product: return self.product.default_uom.category.id def get_rec_name(self, name): return self.product.rec_name @classmethod def search_rec_name(cls, name, clause): return [('product.rec_name', ) + tuple(clause[1:])] @classmethod def validate(cls, boms): super(BOMInput, cls).validate(boms) for bom in boms: bom.check_bom_recursion() def check_bom_recursion(self): ''' Check BOM recursion ''' self.product.check_bom_recursion() def compute_quantity(self, factor): return self.uom.ceil(self.quantity * factor)
class SaleOpportunity(Workflow, ModelSQL, ModelView, AttachmentCopyMixin, NoteCopyMixin): 'Sale Opportunity' __name__ = "sale.opportunity" _history = True _rec_name = 'number' _states_start = { 'readonly': Eval('state') != 'lead', } _states_stop = { 'readonly': Eval('state').in_(['converted', 'won', 'lost', 'cancelled']), } number = fields.Char('Number', readonly=True, required=True, select=True) reference = fields.Char('Reference', select=True) party = fields.Many2One( 'party.party', "Party", select=True, states={ 'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']), 'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']), }, context={ 'company': Eval('company', -1), }, depends={'company'}) contact = fields.Many2One('party.contact_mechanism', "Contact", context={ 'company': Eval('company', -1), }, search_context={ 'related_party': Eval('party'), }, depends=['party', 'company']) address = fields.Many2One('party.address', 'Address', domain=[('party', '=', Eval('party'))], select=True, states=_states_stop) company = fields.Many2One( 'company.company', 'Company', required=True, select=True, states={ 'readonly': _states_stop['readonly'] | Eval('party', True), }, domain=[ ('id', If(In('company', Eval('context', {})), '=', '!='), Get(Eval('context', {}), 'company', 0)), ]) currency = fields.Function( fields.Many2One('currency.currency', "Currency"), 'on_change_with_currency') amount = Monetary("Amount", currency='currency', digits='currency', states=_states_stop, help='Estimated revenue amount.') payment_term = fields.Many2One('account.invoice.payment_term', 'Payment Term', states={ 'readonly': In(Eval('state'), ['converted', 'lost', 'cancelled']), }) employee = fields.Many2One( 'company.employee', 'Employee', states={ 'readonly': _states_stop['readonly'], 'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']), }, domain=[('company', '=', Eval('company'))]) start_date = fields.Date('Start Date', required=True, select=True, states=_states_start) end_date = fields.Date('End Date', select=True, states=_states_stop) description = fields.Char('Description', states=_states_stop) comment = fields.Text('Comment', states=_states_stop) lines = fields.One2Many('sale.opportunity.line', 'opportunity', 'Lines', states=_states_stop) conversion_probability = fields.Float( 'Conversion Probability', digits=(1, 4), required=True, domain=[ ('conversion_probability', '>=', 0), ('conversion_probability', '<=', 1), ], states={ 'readonly': ~Eval('state').in_(['opportunity', 'lead', 'converted']), }, help="Percentage between 0 and 100.") lost_reason = fields.Text('Reason for loss', states={ 'invisible': Eval('state') != 'lost', }) sales = fields.One2Many('sale.sale', 'origin', 'Sales') converted_by = employee_field( "Converted By", states=['converted', 'won', 'lost', 'cancelled']) state = fields.Selection([ ('lead', "Lead"), ('opportunity', "Opportunity"), ('converted', "Converted"), ('won', "Won"), ('lost', "Lost"), ('cancelled', "Cancelled"), ], "State", required=True, select=True, sort=False, readonly=True) del _states_start del _states_stop @classmethod def __register__(cls, module_name): pool = Pool() Sale = pool.get('sale.sale') transaction = Transaction() cursor = transaction.connection.cursor() update = transaction.connection.cursor() sql_table = cls.__table__() sale = Sale.__table__() table = cls.__table_handler__(module_name) number_exists = table.column_exist('number') # Migration from 3.8: rename reference into number if table.column_exist('reference') and not number_exists: table.column_rename('reference', 'number') number_exists = True super(SaleOpportunity, cls).__register__(module_name) table = cls.__table_handler__(module_name) # Migration from 3.4: replace sale by origin if table.column_exist('sale'): cursor.execute(*sql_table.select( sql_table.id, sql_table.sale, where=sql_table.sale != Null)) for id_, sale_id in cursor: update.execute( *sale.update(columns=[sale.origin], values=['%s,%s' % (cls.__name__, id_)], where=sale.id == sale_id)) table.drop_column('sale') # Migration from 4.0: change probability into conversion probability if table.column_exist('probability'): cursor.execute( *sql_table.update([sql_table.conversion_probability], [sql_table.probability / 100.0])) table.drop_constraint('check_percentage') table.drop_column('probability') # Migration from 4.2: make employee not required table.not_null_action('employee', action='remove') # Migration from 5.0: drop required on description table.not_null_action('description', action='remove') @classmethod def __setup__(cls): super(SaleOpportunity, cls).__setup__() cls._order.insert(0, ('start_date', 'DESC')) cls._transitions |= set(( ('lead', 'opportunity'), ('lead', 'lost'), ('lead', 'cancelled'), ('lead', 'converted'), ('opportunity', 'converted'), ('opportunity', 'lead'), ('opportunity', 'lost'), ('opportunity', 'cancelled'), ('converted', 'won'), ('converted', 'lost'), ('won', 'converted'), ('lost', 'converted'), ('lost', 'lead'), ('cancelled', 'lead'), )) cls._buttons.update({ 'lead': { 'invisible': ~Eval('state').in_(['cancelled', 'lost', 'opportunity']), 'icon': If( Eval('state').in_(['cancelled', 'lost']), 'tryton-undo', 'tryton-back'), 'depends': ['state'], }, 'opportunity': { 'pre_validate': [ If(~Eval('party'), ('party', '!=', None), ()), If(~Eval('employee'), ('employee', '!=', None), ()), ], 'invisible': ~Eval('state').in_(['lead']), 'depends': ['state'], }, 'convert': { 'invisible': ~Eval('state').in_(['opportunity']), 'depends': ['state'], }, 'lost': { 'invisible': ~Eval('state').in_(['lead', 'opportunity']), 'depends': ['state'], }, 'cancel': { 'invisible': ~Eval('state').in_(['lead', 'opportunity']), 'depends': ['state'], }, }) @staticmethod def default_state(): return 'lead' @staticmethod def default_start_date(): Date = Pool().get('ir.date') return Date.today() @staticmethod def default_conversion_probability(): return 0.5 @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_employee(): return Transaction().context.get('employee') @classmethod def default_payment_term(cls): PaymentTerm = Pool().get('account.invoice.payment_term') payment_terms = PaymentTerm.search(cls.payment_term.domain) if len(payment_terms) == 1: return payment_terms[0].id @classmethod def view_attributes(cls): return super().view_attributes() + [ ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')), ] @classmethod def get_resources_to_copy(cls, name): return { 'sale.sale', } @classmethod def create(cls, vlist): pool = Pool() Config = pool.get('sale.configuration') config = Config(1) vlist = [x.copy() for x in vlist] default_company = cls.default_company() for vals in vlist: if vals.get('number') is None: vals['number'] = config.get_multivalue( 'sale_opportunity_sequence', company=vals.get('company', default_company)).get() return super(SaleOpportunity, cls).create(vlist) @classmethod def copy(cls, opportunities, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('number', None) default.setdefault('sales', None) default.setdefault('converted_by') return super(SaleOpportunity, cls).copy(opportunities, default=default) @fields.depends('company') def on_change_with_currency(self, name=None): if self.company: return self.company.currency.id @fields.depends('party') def on_change_party(self): if self.party and self.party.customer_payment_term: self.payment_term = self.party.customer_payment_term else: self.payment_term = self.default_payment_term() def _get_sale_opportunity(self): ''' Return sale for an opportunity ''' Sale = Pool().get('sale.sale') return Sale( description=self.description, party=self.party, contact=self.contact, payment_term=self.payment_term, company=self.company, invoice_address=self.address, shipment_address=self.address, currency=self.company.currency, comment=self.comment, sale_date=None, origin=self, warehouse=Sale.default_warehouse(), ) def create_sale(self): ''' Create a sale for the opportunity and return the sale ''' sale = self._get_sale_opportunity() sale_lines = [] for line in self.lines: sale_lines.append(line.get_sale_line(sale)) sale.lines = sale_lines return sale @classmethod def delete(cls, opportunities): # Cancel before delete cls.cancel(opportunities) for opportunity in opportunities: if opportunity.state != 'cancelled': raise AccessError( gettext('sale_opportunity.msg_opportunity_delete_cancel', opportunity=opportunity.rec_name)) super(SaleOpportunity, cls).delete(opportunities) @classmethod @ModelView.button @Workflow.transition('lead') def lead(cls, opportunities): pass @classmethod @ModelView.button @Workflow.transition('opportunity') def opportunity(cls, opportunities): pass @classmethod @ModelView.button @Workflow.transition('converted') @set_employee('converted_by') def convert(cls, opportunities): pool = Pool() Sale = pool.get('sale.sale') sales = [o.create_sale() for o in opportunities if not o.sales] Sale.save(sales) for sale in sales: sale.origin.copy_resources_to(sale) @property def is_forecast(self): pool = Pool() Date = pool.get('ir.date') with Transaction().set_context(company=self.company.id): today = Date.today() return self.end_date or datetime.date.max > today @classmethod @Workflow.transition('won') def won(cls, opportunities): pool = Pool() Date = pool.get('ir.date') for company, c_opportunities in groupby(opportunities, key=lambda o: o.company): with Transaction().set_context(company=company.id): today = Date.today() cls.write([o for o in c_opportunities if o.is_forecast], { 'end_date': today, 'state': 'won', }) @classmethod @ModelView.button @Workflow.transition('lost') def lost(cls, opportunities): pool = Pool() Date = pool.get('ir.date') for company, c_opportunities in groupby(opportunities, key=lambda o: o.company): with Transaction().set_context(company=company.id): today = Date.today() cls.write([o for o in c_opportunities if o.is_forecast], { 'end_date': today, 'state': 'lost', }) @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, opportunities): pool = Pool() Date = pool.get('ir.date') for company, c_opportunities in groupby(opportunities, key=lambda o: o.company): with Transaction().set_context(company=company.id): today = Date.today() cls.write([o for o in c_opportunities if o.is_forecast], { 'end_date': today, 'state': 'cancelled', }) @staticmethod def _sale_won_states(): return ['confirmed', 'processing', 'done'] @staticmethod def _sale_lost_states(): return ['cancelled'] def is_won(self): sale_won_states = self._sale_won_states() sale_lost_states = self._sale_lost_states() end_states = sale_won_states + sale_lost_states return (self.sales and all(s.state in end_states for s in self.sales) and any(s.state in sale_won_states for s in self.sales)) def is_lost(self): sale_lost_states = self._sale_lost_states() return (self.sales and all(s.state in sale_lost_states for s in self.sales)) @property def sale_amount(self): pool = Pool() Currency = pool.get('currency.currency') if not self.sales: return sale_lost_states = self._sale_lost_states() amount = 0 for sale in self.sales: if sale.state not in sale_lost_states: amount += Currency.compute(sale.currency, sale.untaxed_amount, self.currency) return amount @classmethod def process(cls, opportunities): won = [] lost = [] converted = [] for opportunity in opportunities: sale_amount = opportunity.sale_amount if opportunity.amount != sale_amount: opportunity.amount = sale_amount if opportunity.is_won(): won.append(opportunity) elif opportunity.is_lost(): lost.append(opportunity) elif (opportunity.state != 'converted' and opportunity.sales): converted.append(opportunity) cls.save(opportunities) if won: cls.won(won) if lost: cls.lost(lost) if converted: cls.convert(converted)
class Work(sequence_ordered(), tree(separator='\\'), ModelSQL, ModelView): 'Work Effort' __name__ = 'project.work' name = fields.Char('Name', required=True, select=True) type = fields.Selection([('project', 'Project'), ('task', 'Task')], 'Type', required=True, select=True) company = fields.Many2One('company.company', 'Company', required=True, select=True) party = fields.Many2One('party.party', 'Party', states={ 'invisible': Eval('type') != 'project', }, depends=['type']) party_address = fields.Many2One('party.address', 'Contact Address', domain=[('party', '=', Eval('party'))], states={ 'invisible': Eval('type') != 'project', }, depends=['party', 'type']) timesheet_works = fields.One2Many('timesheet.work', 'origin', 'Timesheet Works', readonly=True, size=1) timesheet_available = fields.Function( fields.Boolean('Available on timesheets'), 'get_timesheet_available', setter='set_timesheet_available') timesheet_start_date = fields.Function(fields.Date( 'Timesheet Start', states={ 'invisible': ~Eval('timesheet_available'), }, depends=['timesheet_available']), 'get_timesheet_date', setter='set_timesheet_date') timesheet_end_date = fields.Function(fields.Date( 'Timesheet End', states={ 'invisible': ~Eval('timesheet_available'), }, depends=['timesheet_available']), 'get_timesheet_date', setter='set_timesheet_date') timesheet_duration = fields.Function( fields.TimeDelta( 'Duration', 'company_work_time', help="Total time spent on this work and the sub-works"), 'get_total') effort_duration = fields.TimeDelta('Effort', 'company_work_time', help="Estimated Effort for this work") total_effort = fields.Function( fields.TimeDelta( 'Total Effort', 'company_work_time', help="Estimated total effort for this work and the sub-works"), 'get_total') progress = fields.Float('Progress', domain=[ 'OR', ('progress', '=', None), [ ('progress', '>=', 0), ('progress', '<=', 1), ], ], help='Estimated progress for this work') total_progress = fields.Function( fields.Float( 'Total Progress', digits=(16, 4), help='Estimated total progress for this work and the sub-works', states={ 'invisible': Eval('total_progress', None) == None, }), 'get_total') comment = fields.Text('Comment') parent = fields.Many2One('project.work', 'Parent', left='left', right='right', ondelete='RESTRICT', domain=[ ('company', '=', Eval('company', -1)), ], depends=['company']) left = fields.Integer('Left', required=True, select=True) right = fields.Integer('Right', required=True, select=True) children = fields.One2Many('project.work', 'parent', 'Children', domain=[ ('company', '=', Eval('company', -1)), ], depends=['company']) state = fields.Selection([ ('opened', 'Opened'), ('done', 'Done'), ], 'State', required=True, select=True) @staticmethod def default_type(): return 'task' @classmethod def default_company(cls): return Transaction().context.get('company') @staticmethod def default_state(): return 'opened' @classmethod def default_left(cls): return 0 @classmethod def default_right(cls): return 0 @classmethod def __register__(cls, module_name): TimesheetWork = Pool().get('timesheet.work') cursor = Transaction().connection.cursor() table_project_work = cls.__table_handler__(module_name) project = cls.__table__() timesheet = TimesheetWork.__table__() work_exist = table_project_work.column_exist('work') add_parent = (not table_project_work.column_exist('parent') and work_exist) add_company = (not table_project_work.column_exist('company') and work_exist) add_name = (not table_project_work.column_exist('name') and work_exist) super(Work, cls).__register__(module_name) # Migration from 3.4: change effort into timedelta effort_duration if table_project_work.column_exist('effort'): cursor.execute(*project.select( project.id, project.effort, where=project.effort != Null)) for id_, effort in cursor.fetchall(): duration = datetime.timedelta(hours=effort) cursor.execute( *project.update([project.effort_duration], [duration], where=project.id == id_)) table_project_work.drop_column('effort') # Migration from 3.6: add parent, company, drop required on work, # fill name if add_parent: second_project = cls.__table__() query = project.join( timesheet, condition=project.work == timesheet.id).join( second_project, condition=timesheet.parent == second_project.work).select( project.id, second_project.id) cursor.execute(*query) for id_, parent in cursor.fetchall(): cursor.execute(*project.update([project.parent], [parent], where=project.id == id_)) cls._rebuild_tree('parent', None, 0) if add_company: cursor.execute(*project.join( timesheet, condition=project.work == timesheet.id).select( project.id, timesheet.company)) for id_, company in cursor.fetchall(): cursor.execute(*project.update([project.company], [company], where=project.id == id_)) table_project_work.not_null_action('work', action='remove') if add_name: cursor.execute(*project.join( timesheet, condition=project.work == timesheet.id).select( project.id, timesheet.name)) for id_, name in cursor.fetchall(): cursor.execute(*project.update([project.name], [name], where=project.id == id_)) # Migration from 4.0: remove work if work_exist: table_project_work.drop_constraint('work_uniq') update = Transaction().connection.cursor() cursor.execute(*project.select( project.id, project.work, where=project.work != Null)) for project_id, work_id in cursor: update.execute(*timesheet.update( [timesheet.origin, timesheet.name], ['%s,%s' % (cls.__name__, project_id), Null], where=timesheet.id == work_id)) table_project_work.drop_column('work') @classmethod def index_set_field(cls, name): index = super(Work, cls).index_set_field(name) if name in {'timesheet_start_date', 'timesheet_end_date'}: index = cls.index_set_field('timesheet_available') + 1 return index @classmethod def validate(cls, works): super(Work, cls).validate(works) for work in works: work.check_state() def check_state(self): if (self.state == 'opened' and (self.parent and self.parent.state == 'done')): raise WorkValidationError( gettext('project.msg_work_invalid_parent_state', child=self.rec_name, parent=self.parent.rec_name)) if self.state == 'done': for child in self.children: if child.state == 'opened': raise WorkValidationError( gettext('project.msg_work_invalid_children_state', parent=self.rec_name, child=child.rec_name)) @property def effort_hours(self): if not self.effort_duration: return 0 return self.effort_duration.total_seconds() / 60 / 60 @property def total_effort_hours(self): if not self.total_effort: return 0 return self.total_effort.total_seconds() / 60 / 60 @property def timesheet_duration_hours(self): if not self.timesheet_duration: return 0 return self.timesheet_duration.total_seconds() / 60 / 60 @classmethod def default_timesheet_available(cls): return False def get_timesheet_available(self, name): return bool(self.timesheet_works) @classmethod def set_timesheet_available(cls, projects, name, value): pool = Pool() Timesheet = pool.get('timesheet.work') to_create = [] to_delete = [] for project in projects: if not project.timesheet_works and value: to_create.append({ 'origin': str(project), 'company': project.company.id, }) elif project.timesheet_works and not value: to_delete.extend(project.timesheet_works) if to_create: Timesheet.create(to_create) if to_delete: Timesheet.delete(to_delete) def get_timesheet_date(self, name): if self.timesheet_works: func = { 'timesheet_start_date': min, 'timesheet_end_date': max, }[name] return func(getattr(w, name) for w in self.timesheet_works) @classmethod def set_timesheet_date(cls, projects, name, value): pool = Pool() Timesheet = pool.get('timesheet.work') timesheets = [w for p in projects for w in p.timesheet_works] if timesheets: Timesheet.write(timesheets, { name: value, }) @classmethod def sum_tree(cls, works, values, parents): result = values.copy() works = set((w.id for w in works)) leafs = works - set(parents.values()) while leafs: for work in leafs: works.remove(work) parent = parents.get(work) if parent in result: result[parent] += result[work] next_leafs = set(works) for work in works: parent = parents.get(work) if not parent: continue if parent in next_leafs and parent in works: next_leafs.remove(parent) leafs = next_leafs return result @classmethod def get_total(cls, works, names): cursor = Transaction().connection.cursor() table = cls.__table__() works = cls.search([ ('parent', 'child_of', [w.id for w in works]), ]) work_ids = [w.id for w in works] parents = {} for sub_ids in grouped_slice(work_ids): where = reduce_ids(table.id, sub_ids) cursor.execute(*table.select(table.id, table.parent, where=where)) parents.update(cursor.fetchall()) if 'total_progress' in names and 'total_effort' not in names: names = list(names) names.append('total_effort') result = {} for name in names: values = getattr(cls, '_get_%s' % name)(works) result[name] = cls.sum_tree(works, values, parents) if 'total_progress' in names: digits = cls.total_progress.digits[1] total_progress = result['total_progress'] total_effort = result['total_effort'] for work in works: if total_effort[work.id]: total_progress[work.id] = round( total_progress[work.id] / (total_effort[work.id].total_seconds() / 60 / 60), digits) else: total_effort[work.id] = None return result @classmethod def _get_total_effort(cls, works): return {w.id: w.effort_duration or datetime.timedelta() for w in works} @classmethod def _get_timesheet_duration(cls, works): durations = {} for work in works: value = datetime.timedelta() for timesheet_work in work.timesheet_works: if timesheet_work.duration: value += timesheet_work.duration durations[work.id] = value return durations @classmethod def _get_total_progress(cls, works): return {w.id: w.effort_hours * (w.progress or 0) for w in works} @classmethod def copy(cls, project_works, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('children', None) return super().copy(project_works, default=default) @classmethod def delete(cls, project_works): TimesheetWork = Pool().get('timesheet.work') # Get the timesheet works linked to the project works timesheet_works = [ w for pw in project_works for w in pw.timesheet_works ] super(Work, cls).delete(project_works) if timesheet_works: with Transaction().set_context(_check_access=False): TimesheetWork.delete(timesheet_works) @classmethod def search_global(cls, text): for record, rec_name, icon in super(Work, cls).search_global(text): icon = icon or 'tryton-project' yield record, rec_name, icon
class FiscalYear(Workflow, ModelSQL, ModelView): 'Fiscal Year' __name__ = 'account.fiscalyear' name = fields.Char('Name', size=None, required=True) start_date = fields.Date('Starting Date', required=True, states=STATES, domain=[('start_date', '<=', Eval('end_date', None))]) end_date = fields.Date('Ending Date', required=True, states=STATES, domain=[('end_date', '>=', Eval('start_date', None))]) periods = fields.One2Many('account.period', 'fiscalyear', 'Periods', states=STATES, domain=[ ('company', '=', Eval('company')), ], order=[('start_date', 'ASC'), ('id', 'ASC')]) state = fields.Selection([ ('open', 'Open'), ('close', 'Close'), ('locked', 'Locked'), ], 'State', readonly=True, required=True, sort=False) post_move_sequence = fields.Many2One( 'ir.sequence', "Post Move Sequence", required=True, domain=[ ('sequence_type', '=', Id('account', 'sequence_type_account_move')), ('company', '=', Eval('company')), ]) company = fields.Many2One('company.company', "Company", required=True, select=True) icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __setup__(cls): super(FiscalYear, cls).__setup__() cls._order.insert(0, ('start_date', 'DESC')) cls._transitions |= set(( ('open', 'close'), ('close', 'locked'), ('close', 'open'), )) cls._buttons.update({ 'create_periods': { 'invisible': ((Eval('state') != 'open') | Eval('periods', [0])), 'depends': ['state'], }, 'close': { 'invisible': Eval('state') != 'open', 'depends': ['state'], }, 'reopen': { 'invisible': Eval('state') != 'close', 'depends': ['state'], }, 'lock_': { 'invisible': Eval('state') != 'close', 'depends': ['state'], }, }) cls.__rpc__.update({ 'create_period': RPC(readonly=False, instantiate=0), }) @staticmethod def default_state(): return 'open' @staticmethod def default_company(): return Transaction().context.get('company') def get_icon(self, name): return { 'open': 'tryton-account-open', 'close': 'tryton-account-close', 'locked': 'tryton-account-block', }.get(self.state) @classmethod def validate_fields(cls, fiscalyears, field_names): super().validate_fields(fiscalyears, field_names) cls.check_dates(fiscalyears, field_names) cls.check_post_move_sequence(fiscalyears, field_names) @classmethod def check_dates(cls, fiscalyears, field_names=None): if field_names and not ( field_names & {'start_date', 'end_date', 'state', 'company'}): return transaction = Transaction() connection = transaction.connection cls.lock() cursor = connection.cursor() table = cls.__table__() for year in fiscalyears: cursor.execute( *table.select(table.id, where=(((table.start_date <= year.start_date) & (table.end_date >= year.start_date)) | ((table.start_date <= year.end_date) & (table.end_date >= year.end_date)) | ((table.start_date >= year.start_date) & (table.end_date <= year.end_date))) & (table.company == year.company.id) & (table.id != year.id))) second_id = cursor.fetchone() if second_id: second = cls(second_id[0]) raise FiscalYearDatesError( gettext('account.msg_fiscalyear_overlap', first=year.rec_name, second=second.rec_name)) if year.state == 'open': fiscalyears = cls.search([ ('start_date', '>=', year.end_date), ('state', 'in', ['close', 'locked']), ('company', '=', year.company.id), ], limit=1, order=[('start_date', 'ASC')]) if fiscalyears: fiscalyear, = fiscalyears raise FiscalYearDatesError( gettext('account.msg_open_fiscalyear_earlier', open=year.rec_name, close=fiscalyear.rec_name)) @classmethod def check_post_move_sequence(cls, fiscalyears, field_names=None): if field_names and 'post_move_sequence' not in field_names: return for fiscalyear in fiscalyears: sequence = fiscalyear.post_move_sequence years = cls.search([ ('post_move_sequence', '=', sequence.id), ('id', '!=', fiscalyear.id), ], limit=1) if years: raise FiscalYearSequenceError( gettext( 'account.msg_fiscalyear_different_post_move_sequence', first=fiscalyear.rec_name, second=years[0].rec_name)) @classmethod def write(cls, *args): pool = Pool() Move = pool.get('account.move') actions = iter(args) for fiscalyears, values in zip(actions, actions): if values.get('post_move_sequence'): for fiscalyear in fiscalyears: if (fiscalyear.post_move_sequence and fiscalyear.post_move_sequence.id != values['post_move_sequence']): if Move.search([ ('period.fiscalyear', '=', fiscalyear.id), ('state', '=', 'posted'), ]): raise AccessError( gettext( 'account.' 'msg_change_fiscalyear_post_move_sequence', fiscalyear=fiscalyear.rec_name)) super(FiscalYear, cls).write(*args) @classmethod def delete(cls, fiscalyears): Period = Pool().get('account.period') Period.delete([p for f in fiscalyears for p in f.periods]) super(FiscalYear, cls).delete(fiscalyears) @classmethod def create_period(cls, fiscalyears, interval=1, end_day=31): ''' Create periods for the fiscal years with month interval ''' Period = Pool().get('account.period') to_create = [] for fiscalyear in fiscalyears: period_start_date = fiscalyear.start_date while period_start_date < fiscalyear.end_date: month_offset = 1 if period_start_date.day < end_day else 0 period_end_date = ( period_start_date + relativedelta(months=interval - month_offset) + relativedelta(day=end_day)) if period_end_date > fiscalyear.end_date: period_end_date = fiscalyear.end_date name = period_start_date.strftime('%Y-%m') if name != period_end_date.strftime('%Y-%m'): name += ' - ' + period_end_date.strftime('%Y-%m') to_create.append({ 'name': name, 'start_date': period_start_date, 'end_date': period_end_date, 'fiscalyear': fiscalyear.id, 'type': 'standard', }) period_start_date = period_end_date + relativedelta(days=1) if to_create: Period.create(to_create) @classmethod @ModelView.button_action('account.act_create_periods') def create_periods(cls, fiscalyears): pass @classmethod def find(cls, company_id, date=None, exception=True): ''' Return the fiscal year for the company_id at the date or the current date. If exception is set the function will raise an exception if any fiscal year is found. ''' pool = Pool() Lang = pool.get('ir.lang') Date = pool.get('ir.date') if not date: with Transaction().set_context(company=company_id): date = Date.today() fiscalyears = cls.search([ ('start_date', '<=', date), ('end_date', '>=', date), ('company', '=', company_id), ], order=[('start_date', 'DESC')], limit=1) if not fiscalyears: if exception: lang = Lang.get() raise FiscalYearNotFoundError( gettext('account.msg_no_fiscalyear_date', date=lang.strftime(date))) else: return None return fiscalyears[0].id def get_deferral(self, account): 'Computes deferrals for accounts' pool = Pool() Currency = pool.get('currency.currency') Deferral = pool.get('account.account.deferral') if not account.type: return if not account.deferral: if not Currency.is_zero(self.company.currency, account.balance): raise FiscalYearCloseError( gettext( 'account' '.msg_close_fiscalyear_account_balance_not_zero', account=account.rec_name)) else: deferral = Deferral() deferral.account = account deferral.fiscalyear = self deferral.debit = account.debit deferral.credit = account.credit deferral.line_count = account.line_count deferral.amount_second_currency = account.amount_second_currency return deferral @classmethod @ModelView.button @Workflow.transition('close') def close(cls, fiscalyears): ''' Close a fiscal year ''' pool = Pool() Period = pool.get('account.period') Account = pool.get('account.account') Deferral = pool.get('account.account.deferral') # Prevent create new fiscal year or period cls.lock() Period.lock() deferrals = [] for fiscalyear in fiscalyears: if cls.search([ ('end_date', '<=', fiscalyear.start_date), ('state', '=', 'open'), ('company', '=', fiscalyear.company.id), ]): raise FiscalYearCloseError( gettext('account.msg_close_fiscalyear_earlier', fiscalyear=fiscalyear.rec_name)) periods = Period.search([ ('fiscalyear', '=', fiscalyear.id), ]) Period.close(periods) with Transaction().set_context(fiscalyear=fiscalyear.id, date=None, cumulate=True, journal=None): accounts = Account.search([ ('company', '=', fiscalyear.company.id), ]) for account in accounts: deferral = fiscalyear.get_deferral(account) if deferral: deferrals.append(deferral) Deferral.save(deferrals) @classmethod @ModelView.button @Workflow.transition('open') def reopen(cls, fiscalyears): ''' Re-open a fiscal year ''' Deferral = Pool().get('account.account.deferral') for fiscalyear in fiscalyears: if cls.search([ ('start_date', '>=', fiscalyear.end_date), ('state', '!=', 'open'), ('company', '=', fiscalyear.company.id), ]): raise FiscalYearReOpenError( gettext('account.msg_reopen_fiscalyear_later', fiscalyear=fiscalyear.rec_name)) deferrals = Deferral.search([ ('fiscalyear', '=', fiscalyear.id), ]) Deferral.delete(deferrals) @classmethod @ModelView.button @Workflow.transition('locked') def lock_(cls, fiscalyears): pool = Pool() Period = pool.get('account.period') periods = Period.search([ ('fiscalyear', 'in', [f.id for f in fiscalyears]), ]) Period.lock_(periods)
class DeathCertificate(ModelSQL, ModelView): __name__ = 'gnuhealth.death_certificate' serializer = fields.Text('Doc String', readonly=True) document_digest = fields.Char('Digest', readonly=True, help="Original Document Digest") digest_status = fields.Function(fields.Boolean('Altered', states={ 'invisible': Not(Equal(Eval('state'),'done')), }, help="This field will be set whenever parts of" \ " the main original document has been changed." \ " Please note that the verification is done only on selected" \ " fields." ), 'check_digest') serializer_current = fields.Function( fields.Text('Current Doc', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digest_current = fields.Function( fields.Char('Current Hash', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digital_signature = fields.Text('Digital Signature', readonly=True) @classmethod def __setup__(cls): super(DeathCertificate, cls).__setup__() cls._buttons.update({ 'generate_death_certificate': { 'invisible': Not(Equal(Eval('state'), 'signed')), }, }) ''' Allow calling the set_signature method via RPC ''' cls.__rpc__.update({ 'set_signature': RPC(readonly=False), }) @classmethod @ModelView.button def generate_death_certificate(cls, certificates): certificate = certificates[0] HealthProf = Pool().get('gnuhealth.healthprofessional') # Change the state of the certificate to "Done" serial_doc = cls.get_serial(certificate) signing_hp = HealthProf.get_health_professional() if not signing_hp: cls.raise_user_error( "No health professional associated to this user !") cls.write( certificates, { 'serializer': serial_doc, 'document_digest': HealthCrypto().gen_hash(serial_doc), 'state': 'done', }) @classmethod def get_serial(cls, certificate): underlying_conds = [] for condition in certificate.underlying_conditions: cond = [] cond = [ str(condition.condition.rec_name), condition.interval, condition.unit_of_time ] underlying_conds.append(cond) data_to_serialize = { 'certificate': str(certificate.code) or '', 'Date': str(certificate.dod) or '', 'HP': certificate.signed_by \ and str(certificate.signed_by.rec_name) or '', 'Person': str(certificate.name.rec_name), 'Person_dob':str(certificate.name.dob) or '', 'Person_ID': str(certificate.name.ref) or '', 'Cod': str(certificate.cod.rec_name), 'Underlying_conditions': underlying_conds or '', 'Autopsy': certificate.autopsy, 'Type_of_death': str(certificate.type_of_death), 'Place_of_death': str(certificate.place_of_death), 'Country': str(certificate.country.rec_name) or '', 'Country_subdivision': certificate.country_subdivision \ and str(certificate.country_subdivision.rec_name) or '', 'Observations': str(certificate.observations), } serialized_doc = str(HealthCrypto().serialize(data_to_serialize)) return serialized_doc @classmethod def set_signature(cls, data, signature): """ Set the clearsigned signature """ doc_id = data['id'] cls.write([cls(doc_id)], { 'digital_signature': signature, }) def check_digest(self, name): result = '' serial_doc = self.get_serial(self) if (name == 'digest_status' and self.document_digest): if (HealthCrypto().gen_hash(serial_doc) == self.document_digest): result = False else: ''' Return true if the document has been altered''' result = True if (name == 'digest_current'): result = HealthCrypto().gen_hash(serial_doc) if (name == 'serializer_current'): result = serial_doc return result # Hide the group holding all the digital signature until signed @classmethod def view_attributes(cls): return [('//group[@id="group_current_string"]', 'states', { 'invisible': ~Eval('digest_status'), })]
class PatientPrescriptionOrder(ModelSQL, ModelView): """ Add the serialized and hash fields to the prescription order document""" __name__ = 'gnuhealth.prescription.order' serializer = fields.Text('Doc String', readonly=True) document_digest = fields.Char('Digest', readonly=True, help="Original Document Digest") digest_status = fields.Function(fields.Boolean('Altered', states={ 'invisible': Not(Equal(Eval('state'),'done')), }, help="This field will be set whenever parts of" \ " the main original document has been changed." \ " Please note that the verification is done only on selected" \ " fields." ), 'check_digest') serializer_current = fields.Function( fields.Text('Current Doc', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digest_current = fields.Function( fields.Char('Current Hash', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digital_signature = fields.Text('Digital Signature', readonly=True) @staticmethod def default_state(): return 'draft' @classmethod def __setup__(cls): super(PatientPrescriptionOrder, cls).__setup__() cls._buttons.update({ 'generate_prescription': { 'invisible': Equal(Eval('state'), 'validated'), }, 'create_prescription': { 'invisible': Or(Equal(Eval('state'), 'done'), Equal(Eval('state'), 'validated')) }, }) ''' Allow calling the set_signature method via RPC ''' cls.__rpc__.update({ 'set_signature': RPC(readonly=False), }) @classmethod @ModelView.button def generate_prescription(cls, prescriptions): prescription = prescriptions[0] # Change the state of the prescription to "Validated" serial_doc = cls.get_serial(prescription) cls.write( prescriptions, { 'serializer': serial_doc, 'document_digest': HealthCrypto().gen_hash(serial_doc), 'state': 'validated', }) @classmethod def get_serial(cls, prescription): presc_line = [] for line in prescription.prescription_line: line_elements = [ line.medicament and line.medicament.name.name or '', line.dose or '', line.route and line.route.name or '', line.form and line.form.name or '', line.indication and line.indication.name or '', line.short_comment or '' ] presc_line.append(line_elements) data_to_serialize = { 'Prescription': str(prescription.prescription_id) or '', 'Date': str(prescription.prescription_date) or '', 'HP': str(prescription.healthprof.rec_name), 'Patient': str(prescription.patient.rec_name), 'Patient_ID': str(prescription.patient.name.ref) or '', 'Prescription_line': str(presc_line), 'Notes': str(prescription.notes), } serialized_doc = str(HealthCrypto().serialize(data_to_serialize)) return serialized_doc @classmethod def set_signature(cls, data, signature): """ Set the clearsigned signature """ doc_id = data['id'] cls.write([cls(doc_id)], { 'digital_signature': signature, }) def check_digest(self, name): result = '' serial_doc = self.get_serial(self) if (name == 'digest_status' and self.document_digest): if (HealthCrypto().gen_hash(serial_doc) == self.document_digest): result = False else: ''' Return true if the document has been altered''' result = True if (name == 'digest_current'): result = HealthCrypto().gen_hash(serial_doc) if (name == 'serializer_current'): result = serial_doc return result # Hide the group holding validation information when state is # not validated @classmethod def view_attributes(cls): return [('//group[@id="prescription_digest"]', 'states', { 'invisible': Not(Eval('state') == 'validated'), })]
class MoveLineTemplate(ModelSQL, ModelView): 'Account Move Line Template' __name__ = 'account.move.line.template' move = fields.Many2One('account.move.template', 'Move', required=True) operation = fields.Selection([ ('debit', 'Debit'), ('credit', 'Credit'), ], 'Operation', required=True) amount = fields.Char( 'Amount', required=True, help="A python expression that will be evaluated with the keywords.") account = fields.Many2One('account.account', 'Account', required=True, domain=[ ('type', '!=', None), ('closed', '!=', True), ('company', '=', Eval('_parent_move', {}).get('company', -1)), ]) party = fields.Char('Party', states={ 'required': Eval('party_required', False), 'invisible': ~Eval('party_required', False), }, depends=['party_required'], help="The name of the 'Party' keyword.") party_required = fields.Function(fields.Boolean('Party Required'), 'on_change_with_party_required') description = fields.Char( 'Description', help="Keywords values substitutions are identified " "by braces ('{' and '}').") taxes = fields.One2Many('account.tax.line.template', 'line', 'Taxes') @fields.depends('account') def on_change_with_party_required(self, name=None): if self.account: return self.account.party_required return False def get_line(self, values): 'Return the move line for the keyword values' pool = Pool() Line = pool.get('account.move.line') Keyword = pool.get('account.move.template.keyword') line = Line() amount = simple_eval(decistmt(self.amount), functions={'Decimal': Decimal}, names=values) amount = self.move.company.currency.round(amount) if self.operation == 'debit': line.debit = amount else: line.credit = amount line.account = self.account if self.party: line.party = values.get(self.party) if self.description: line.description = self.description.format( **dict(Keyword.format_values(self.move, values))) line.tax_lines = [t.get_line(values) for t in self.taxes] return line
class User(metaclass=PoolMeta): __name__ = 'res.user' main_company = fields.Many2One( 'company.company', 'Main Company', help="Grant access to the company and its children.") company = fields.Many2One('company.company', 'Current Company', domain=[('parent', 'child_of', [Eval('main_company')], 'parent')], depends=['main_company'], help="Select the company to work for.") companies = fields.Function( fields.One2Many('company.company', None, 'Companies'), 'get_companies') employees = fields.Many2Many( 'res.user-company.employee', 'user', 'employee', 'Employees', help="Add employees to grant the user access to them.") employee = fields.Many2One( 'company.employee', 'Current Employee', domain=[ ('company', '=', Eval('company', -1)), ('id', 'in', Eval('employees', [])), ], depends=['company', 'employees'], help="Select the employee to make the user behave as such.") @classmethod def __setup__(cls): super(User, cls).__setup__() cls._context_fields.insert(0, 'company') cls._context_fields.insert(0, 'employee') @staticmethod def default_main_company(): return Transaction().context.get('company') @classmethod def default_company(cls): return cls.default_main_company() @classmethod def get_companies(cls, users, name): Company = Pool().get('company.company') companies = {} company_childs = {} for user in users: companies[user.id] = [] company = None if user.company: company = user.company elif user.main_company: company = user.main_company if company: if company in company_childs: company_ids = company_childs[company] else: company_ids = list( map( int, Company.search([ ('parent', 'child_of', [company.id]), ]))) company_childs[company] = company_ids if company_ids: companies[user.id].extend(company_ids) return companies def get_status_bar(self, name): status = super(User, self).get_status_bar(name) if self.company: status += ' - %s [%s]' % (self.company.rec_name, self.company.currency.name) return status @fields.depends('main_company') def on_change_main_company(self): self.company = self.main_company self.employee = None @fields.depends('company', 'employees') def on_change_company(self): Employee = Pool().get('company.employee') self.employee = None if self.company and self.employees: employees = Employee.search([ ('id', 'in', [e.id for e in self.employees]), ('company', '=', self.company.id), ]) if employees: self.employee = employees[0] @classmethod def _get_preferences(cls, user, context_only=False): res = super(User, cls)._get_preferences(user, context_only=context_only) if not context_only: res['main_company'] = None if user.main_company: res['main_company'] = user.main_company.id res['main_company.rec_name'] = user.main_company.rec_name res['employees'] = [e.id for e in user.employees] if user.employee: res['employee'] = user.employee.id res['employee.rec_name'] = user.employee.rec_name if user.company: res['company'] = user.company.id res['company.rec_name'] = user.company.rec_name return res @classmethod def get_preferences_fields_view(cls): pool = Pool() Company = pool.get('company.company') res = super(User, cls).get_preferences_fields_view() res = copy.deepcopy(res) def convert2selection(definition, name): del definition[name]['relation'] definition[name]['type'] = 'selection' selection = [] definition[name]['selection'] = selection return selection if 'company' in res['fields']: selection = convert2selection(res['fields'], 'company') selection.append((None, '')) user = cls(Transaction().user) if user.main_company: companies = Company.search([ ('parent', 'child_of', [user.main_company.id], 'parent'), ]) for company in companies: selection.append((company.id, company.rec_name)) return res @classmethod def read(cls, ids, fields_names=None): Company = Pool().get('company.company') user_id = Transaction().user if user_id == 0 and 'user' in Transaction().context: user_id = Transaction().context['user'] result = super(User, cls).read(ids, fields_names=fields_names) if (fields_names and (('company' in fields_names and 'company' in Transaction().context) or ('employee' in fields_names and 'employee' in Transaction().context))): values = None if int(user_id) in ids: for vals in result: if vals['id'] == int(user_id): values = vals break if values: if ('company' in fields_names and 'company' in Transaction().context): main_company_id = values.get('main_company') if not main_company_id: main_company_id = cls.read( [user_id], ['main_company'])[0]['main_company'] companies = Company.search([ ('parent', 'child_of', [main_company_id]), ]) company_id = Transaction().context['company'] if ((company_id and company_id in list(map(int, companies))) or not company_id or Transaction().user == 0): values['company'] = company_id if ('employee' in fields_names and 'employee' in Transaction().context): employees = values.get('employees') if not employees: employees = cls.read([user_id], ['employees'])[0]['employees'] employee_id = Transaction().context['employee'] if ((employee_id and employee_id in employees) or not employee_id or Transaction().user == 0): values['employee'] = employee_id return result @classmethod def write(cls, *args): pool = Pool() Rule = pool.get('ir.rule') super(User, cls).write(*args) # Restart the cache on the domain_get method Rule._domain_get_cache.clear()
class DocumentRequestCash(ModelSQL, ModelView): _name = 'ekd.document.head.request' budget_ref = fields.Function(fields.Many2One("ekd.account.budget", 'Budget'), 'get_budget_ref', setter='set_budget_ref') budget = fields.Many2One('ekd.account.budget', 'Budget', states=_RECEIVED_STATES, depends=_RECEIVED_DEPENDS) lines = fields.One2Many('ekd.document.line.request', 'requestcash', 'Lines Request', states=_RECEIVED_STATES, depends=_RECEIVED_DEPENDS, context={ 'document_ref': Eval('document_ref'), 'budget_ref': Eval('budget_ref'), 'budget': Eval('budget'), 'currency_digits': Eval('currency_digits') }) document_ref = fields.Reference( 'Base', selection='documents_get', select=1, states={'readonly': Not(In(Eval('state_doc'), ['empty', 'draft']))}, depends=['state_doc'], on_change=['document_ref']) def __init__(self): super(DocumentRequestCash, self).__init__() def get_budget_ref(self, ids, name): if not ids: return res = {}.fromkeys(ids, False) for line in self.browse(ids): if line.budget: res[line.id] = line.budget.id elif line.document_ref: model, model_id = line.document_ref.split(',') if model_id == '0': continue if model: if model == 'ekd.account.budget': res[line.id] = int(model_id) elif model == 'ekd.project': model_obj = self.pool.get(model) model_brw = model_obj.browse(int(model_id)) if model_brw and model_brw.budget: res[line.id] = model_brw.budget.id else: raise Exception('Error', 'Unknown-model: %s' % (model)) return res def set_budget_ref(self, id, name, value): if not value: return self.write(id, { 'budget': value, }) def on_change_document_ref(self, value): res = {} if value.get('document_ref'): model, model_id = value.get('document_ref').split(',') if model_id == '0': return res if model == 'ekd.project': model_obj = self.pool.get(model) model_ids = model_obj.browse(int(model_id)) if model_ids.manager: res['manager'] = model_ids.manager.id if model_ids.employee: res['employee'] = model_ids.employee.id if model_ids.budget: res['budget_ref'] = model_ids.budget.id res['budget'] = model_ids.budget.id elif model == 'ekd.account.budget': res['budget_ref'] = int(model_id) res['budget'] = int(model_id) return res
class Location(DeactivableMixin, tree(), ModelSQL, ModelView): "Stock Location" __name__ = 'stock.location' _default_warehouse_cache = Cache('stock.location.default_warehouse', context=False) name = fields.Char("Name", size=None, required=True, states=STATES, depends=DEPENDS, translate=True) code = fields.Char("Code", size=None, states=STATES, depends=DEPENDS, select=True, help="The internal identifier used for the location.") address = fields.Many2One("party.address", "Address", states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), }, depends=['type', 'active']) type = fields.Selection([ ('supplier', 'Supplier'), ('customer', 'Customer'), ('lost_found', 'Lost and Found'), ('warehouse', 'Warehouse'), ('storage', 'Storage'), ('production', 'Production'), ('drop', 'Drop'), ('view', 'View'), ], 'Location type', states=STATES, depends=DEPENDS) type_string = type.translated('type') parent = fields.Many2One("stock.location", "Parent", select=True, left="left", right="right", states={ 'invisible': Eval('type') == 'warehouse', }, depends=['type'], help="Used to add structure above the location.") left = fields.Integer('Left', required=True, select=True) right = fields.Integer('Right', required=True, select=True) childs = fields.One2Many("stock.location", "parent", "Children", help="Used to add structure below the location.") flat_childs = fields.Boolean( "Flat Children", help="Check to enforce a single level of children with no " "grandchildren.") warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'), 'get_warehouse') input_location = fields.Many2One("stock.location", "Input", states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), 'required': Eval('type') == 'warehouse', }, domain=[ ('type', '=', 'storage'), [ 'OR', ('parent', 'child_of', [Eval('id')]), ('parent', '=', None), ], ], depends=['type', 'active', 'id'], help="Where incoming stock is received.") output_location = fields.Many2One( "stock.location", "Output", states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), 'required': Eval('type') == 'warehouse', }, domain=[('type', '=', 'storage'), [ 'OR', ('parent', 'child_of', [Eval('id')]), ('parent', '=', None) ]], depends=['type', 'active', 'id'], help="Where outgoing stock is sent from.") storage_location = fields.Many2One( "stock.location", "Storage", states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), 'required': Eval('type') == 'warehouse', }, domain=[('type', 'in', ['storage', 'view']), [ 'OR', ('parent', 'child_of', [Eval('id')]), ('parent', '=', None) ]], depends=['type', 'active', 'id'], help="The top level location where stock is stored.") picking_location = fields.Many2One( 'stock.location', 'Picking', states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), }, domain=[ ('type', '=', 'storage'), ('parent', 'child_of', [Eval('storage_location', -1)]), ], depends=['type', 'active', 'storage_location'], help="Where stock is picked from.\n" "Leave empty to use the storage location.") quantity = fields.Function(fields.Float( 'Quantity', help="The amount of stock in the location."), 'get_quantity', searcher='search_quantity') forecast_quantity = fields.Function(fields.Float( 'Forecast Quantity', help="The amount of stock expected to be in the location."), 'get_quantity', searcher='search_quantity') cost_value = fields.Function( fields.Numeric('Cost Value', help="The value of the stock in the location."), 'get_cost_value') @classmethod def __setup__(cls): super(Location, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) parent_domain = [[ 'OR', ('parent.flat_childs', '=', False), ('parent', '=', None), ]] childs_domain = [ If(Eval('flat_childs', False), ('childs', '=', None), ()), ] childs_mapping = cls._childs_domain() for type_, allowed_parents in cls._parent_domain().items(): parent_domain.append( If(Eval('type') == type_, ('type', 'in', allowed_parents), ())) childs_domain.append( If( Eval('type') == type_, ('type', 'in', childs_mapping[type_]), ())) cls.parent.domain = parent_domain cls.childs.domain = childs_domain cls.childs.depends.extend(['flat_childs', 'type']) @classmethod def _parent_domain(cls): '''Returns a dict with location types as keys and a list of allowed parent location types as values''' return { 'customer': ['customer'], 'supplier': ['supplier'], 'production': ['production'], 'lost_found': ['lost_found'], 'view': ['warehouse', 'view', 'storage'], 'storage': ['warehouse', 'view', 'storage'], 'warehouse': [''], } @classmethod def _childs_domain(cls): childs_domain = {} for type_, allowed_parents in cls._parent_domain().items(): for parent in allowed_parents: childs_domain.setdefault(parent, []) childs_domain[parent].append(type_) return childs_domain @classmethod def __register__(cls, module_name): super(Location, cls).__register__(module_name) table = cls.__table_handler__(module_name) table.index_action(['left', 'right'], 'add') @classmethod def validate(cls, locations): super(Location, cls).validate(locations) inactives = [] for location in locations: location.check_type_for_moves() if not location.active: inactives.append(location) cls.check_inactive(inactives) def check_type_for_moves(self): """ Check locations with moves have types compatible with moves. """ invalid_move_types = ['warehouse', 'view'] Move = Pool().get('stock.move') if self.type in invalid_move_types: # Use root to compute for all companies with Transaction().set_user(0): moves = Move.search([ [ 'OR', ('to_location', '=', self.id), ('from_location', '=', self.id), ], ('state', 'not in', ['staging', 'draft']), ]) if moves: raise LocationValidationError( gettext('stock.msg_location_invalid_type_for_moves', location=self.rec_name, type=self.type_string)) @classmethod def check_inactive(cls, locations): "Check inactive location are empty" assert all(not l.active for l in locations) empty = cls.get_empty_locations(locations) non_empty = set(locations) - set(empty) if non_empty: raise LocationValidationError( gettext('stock.msg_location_inactive_not_empty', location=next(iter(non_empty)).rec_name)) @classmethod def get_empty_locations(cls, locations=None): pool = Pool() Move = pool.get('stock.move') if locations is None: locations = cls.search([]) if not locations: return [] location_ids = list(map(int, locations)) # Use root to compute for all companies # and ensures inactive locations are in the query with Transaction().set_user(0), \ Transaction().set_context(active_test=False): query = Move.compute_quantities_query(location_ids, with_childs=True) quantities = Move.compute_quantities(query, location_ids, with_childs=True) empty = set(location_ids) for (location_id, product), quantity in quantities.items(): if quantity: empty.discard(location_id) for sub_ids in grouped_slice(list(empty)): sub_ids = list(sub_ids) moves = Move.search([ ('state', 'not in', ['done', 'cancel']), [ 'OR', ('from_location', 'in', sub_ids), ('to_location', 'in', sub_ids), ], ]) for move in moves: for location in [move.from_location, move.to_location]: empty.discard(location.id) return cls.browse(empty) @staticmethod def default_left(): return 0 @staticmethod def default_right(): return 0 @classmethod def default_flat_childs(cls): return False @staticmethod def default_type(): return 'storage' @classmethod def check_xml_record(cls, records, values): return True def get_warehouse(self, name): # Order by descending left to get the first one in the tree with Transaction().set_context(active_test=False): locations = self.search([ ('parent', 'parent_of', [self.id]), ('type', '=', 'warehouse'), ], order=[('left', 'DESC')]) if locations: return locations[0].id @classmethod def get_default_warehouse(cls): warehouse = Transaction().context.get('warehouse') if warehouse: return warehouse warehouse = cls._default_warehouse_cache.get(None, -1) if warehouse == -1: warehouses = cls.search([ ('type', '=', 'warehouse'), ], limit=2) if len(warehouses) == 1: warehouse = warehouses[0].id else: warehouse = None cls._default_warehouse_cache.set(None, warehouse) return warehouse @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, (cls._rec_name, ) + tuple(clause[1:]), ('code', ) + tuple(clause[1:]), ] @classmethod def get_quantity(cls, locations, name): pool = Pool() Product = pool.get('product.product') Date_ = pool.get('ir.date') trans_context = Transaction().context def valid_context(name): return (trans_context.get(name) is not None and isinstance(trans_context[name], int)) if not any(map(valid_context, ['product', 'product_template'])): return {l.id: None for l in locations} context = {} if (name == 'quantity' and (trans_context.get( 'stock_date_end', datetime.date.max) > Date_.today())): context['stock_date_end'] = Date_.today() if name == 'forecast_quantity': context['forecast'] = True if not trans_context.get('stock_date_end'): context['stock_date_end'] = datetime.date.max if trans_context.get('product') is not None: grouping = ('product', ) grouping_filter = ([trans_context['product']], ) key = trans_context['product'] else: grouping = ('product.template', ) grouping_filter = ([trans_context['product_template']], ) key = trans_context['product_template'] pbl = {} for sub_locations in grouped_slice(locations): location_ids = [l.id for l in sub_locations] with Transaction().set_context(context): pbl.update( Product.products_by_location( location_ids, grouping=grouping, grouping_filter=grouping_filter, with_childs=trans_context.get('with_childs', True))) return dict((loc.id, pbl.get((loc.id, key), 0)) for loc in locations) @classmethod def search_quantity(cls, name, domain): _, operator_, operand = domain operator_ = { '=': operator.eq, '>=': operator.ge, '>': operator.gt, '<=': operator.le, '<': operator.lt, '!=': operator.ne, 'in': lambda v, l: v in l, 'not in': lambda v, l: v not in l, }.get(operator_, lambda v, l: False) ids = [] for location in cls.search([]): if operator_(getattr(location, name), operand): ids.append(location.id) return [('id', 'in', ids)] @classmethod def get_cost_value(cls, locations, name): pool = Pool() Product = pool.get('product.product') Template = pool.get('product.template') trans_context = Transaction().context cost_values = {l.id: None for l in locations} def valid_context(name): return (trans_context.get(name) is not None and isinstance(trans_context[name], int)) if not any(map(valid_context, ['product', 'product_template'])): return cost_values def get_record(): if trans_context.get('product') is not None: return Product(trans_context['product']) else: return Template(trans_context['product_template']) context = {} if 'stock_date_end' in trans_context: # Use the last cost_price of the day context['_datetime'] = datetime.datetime.combine( trans_context['stock_date_end'], datetime.time.max) # The date could be before the product creation record = get_record() if record.create_date > context['_datetime']: return cost_values with Transaction().set_context(context): cost_price = get_record().cost_price # The template may have more than one product if cost_price is not None: for location in locations: cost_values[location.id] = (Decimal(str(location.quantity)) * cost_price) return cost_values @classmethod def _set_warehouse_parent(cls, locations): ''' Set the parent of child location of warehouse if not set ''' to_update = set() to_save = [] for location in locations: if location.type == 'warehouse': if not location.input_location.parent: to_update.add(location.input_location) if not location.output_location.parent: to_update.add(location.output_location) if not location.storage_location.parent: to_update.add(location.storage_location) if to_update: for child_location in to_update: child_location.parent = location to_save.append(child_location) to_update.clear() cls.save(to_save) @classmethod def create(cls, vlist): locations = super(Location, cls).create(vlist) cls._set_warehouse_parent(locations) cls._default_warehouse_cache.clear() return locations @classmethod def write(cls, *args): super(Location, cls).write(*args) locations = sum(args[::2], []) cls._set_warehouse_parent(locations) cls._default_warehouse_cache.clear() ids = [l.id for l in locations] warehouses = cls.search([('type', '=', 'warehouse'), [ 'OR', ('storage_location', 'in', ids), ('input_location', 'in', ids), ('output_location', 'in', ids), ]]) fields = ('storage_location', 'input_location', 'output_location') wh2childs = {} for warehouse in warehouses: in_out_sto = (getattr(warehouse, f).id for f in fields) for location in locations: if location.id not in in_out_sto: continue childs = wh2childs.setdefault( warehouse.id, cls.search([ ('parent', 'child_of', warehouse.id), ])) if location not in childs: raise LocationValidationError( gettext('stock.msg_location_child_of_warehouse', location=location.rec_name, warehouse=warehouse.rec_name)) @classmethod def delete(cls, *args): super().delete(*args) cls._default_warehouse_cache.clear() @classmethod def copy(cls, locations, default=None): if default is None: default = {} else: default = default.copy() res = [] for location in locations: if location.type == 'warehouse': wh_default = default.copy() wh_default['type'] = 'view' wh_default['input_location'] = None wh_default['output_location'] = None wh_default['storage_location'] = None wh_default['childs'] = None new_location, = super(Location, cls).copy([location], default=wh_default) with Transaction().set_context( cp_warehouse_locations={ 'input_location': location.input_location.id, 'output_location': location.output_location.id, 'storage_location': location.storage_location.id, }, cp_warehouse_id=new_location.id): cls.copy(location.childs, default={'parent': new_location.id}) cls.write([new_location], { 'type': 'warehouse', }) else: new_location, = super(Location, cls).copy([location], default=default) warehouse_locations = Transaction().context.get( 'cp_warehouse_locations') or {} if location.id in warehouse_locations.values(): cp_warehouse = cls( Transaction().context['cp_warehouse_id']) for field, loc_id in warehouse_locations.items(): if loc_id == location.id: cls.write([cp_warehouse], { field: new_location.id, }) res.append(new_location) return res
class DocumentRequestCashLine(ModelSQL, ModelView): _name = 'ekd.document.line.request' budget_ref = fields.Function( fields.Many2One("ekd.account.budget", 'Budget'), 'get_budget_ref') # Ошибка в домене ?????????????? budget_line = fields.Many2One( 'ekd.account.budget.line', 'Budget Line', states={'readonly': Not(Bool(Eval('budget_ref')))}, domain=[ # "('budget','=', context.get('budget_ref',False))", ('budget', '=', Eval('budget_ref')), ('direct_line', '=', 'expense'), ('type_line', '=', 'line') ], on_change=['budget_line'], depends=['budget_ref']) amount_budget = fields.Function( fields.Numeric('Amount in Budget', digits=(16, 2)), 'get_budget') # name = fields.Char('Description') # analytic = fields.Many2One('ekd.account.analytic', 'Analytic Account') def default_budget_ref(self): #raise Exception(str(Transaction().context)) if Transaction().context.get('budget_ref'): return Transaction().context.get('budget_ref') elif Transaction().context.get('budget'): return Transaction().context.get('budget') return False def get_budget(self, ids, names): res = {} for line in self.browse(ids): for name in names: res.setdefault(name, {}.fromkeys(ids, Decimal('0.0'))) if name == 'amount_budget' and line.budget_line: res[name][line.id] = line.budget_line.amount return res def get_budget_ref(self, ids, name): context = Transaction().context if context.get('budget_ref'): return {}.fromkeys(ids, context.get('budget_ref')) res = {}.fromkeys(ids, False) for line in self.browse(ids): if line.requestcash.budget_ref: res[line.id] = line.requestcash.budget_ref.id #raise Exception(str(res)) return res def on_change_budget_line(self, vals): if vals.get('budget_line'): budget_line_obj = self.pool.get('ekd.account.budget.line') budget_line = budget_line_obj.browse(vals.get('budget_line')) return { 'name': budget_line.name, 'analytic': budget_line.analytic.id, 'amount_budget': budget_line.amount, 'amount': budget_line.amount } else: return { 'analytic': False, 'amount_budget': Decimal('0.0'), 'amount': Decimal('0.0') } def on_change_analytic(self, vals): if vals.get('budget_line'): return {} elif vals.get('analytic'): analytic_obj = self.pool.get('ekd.account.analytic') analytic_id = analytic_obj.browse(vals.get('analytic')) if vals.get('name'): return { 'name': "%s - (%s)" % (vals.get('name'), analytic_id.name) } else: return {'name': analytic_id.name} else: return {}
class DictSchemaMixin(object): __slots__ = () _rec_name = 'string' name = fields.Char(lazy_gettext('ir.msg_dict_schema_name'), required=True) string = fields.Char(lazy_gettext('ir.msg_dict_schema_string'), translate=True, required=True) help = fields.Text(lazy_gettext('ir.msg_dict_schema_help'), translate=True) type_ = fields.Selection([ ('boolean', lazy_gettext('ir.msg_dict_schema_boolean')), ('integer', lazy_gettext('ir.msg_dict_schema_integer')), ('char', lazy_gettext('ir.msg_dict_schema_char')), ('float', lazy_gettext('ir.msg_dict_schema_float')), ('numeric', lazy_gettext('ir.msg_dict_schema_numeric')), ('date', lazy_gettext('ir.msg_dict_schema_date')), ('datetime', lazy_gettext('ir.msg_dict_schema_datetime')), ('selection', lazy_gettext('ir.msg_dict_schema_selection')), ('multiselection', lazy_gettext('ir.msg_dict_schema_multiselection')), ], lazy_gettext('ir.msg_dict_schema_type'), required=True) digits = fields.Integer(lazy_gettext('ir.msg_dict_schema_digits'), states={ 'invisible': ~Eval('type_').in_(['float', 'numeric']), }, depends=['type_']) domain = fields.Char(lazy_gettext('ir.msg_dict_schema_domain')) selection = fields.Text( lazy_gettext('ir.msg_dict_schema_selection'), states={ 'invisible': ~Eval('type_').in_(['selection', 'multiselection']), }, translate=True, depends=['type_'], help=lazy_gettext('ir.msg_dict_schema_selection_help')) selection_sorted = fields.Boolean( lazy_gettext('ir.msg_dict_schema_selection_sorted'), states={ 'invisible': ~Eval('type_').in_(['selection', 'multiselection']), }, depends=['type_'], help=lazy_gettext('ir.msg_dict_schema_selection_sorted_help')) selection_json = fields.Function( fields.Char(lazy_gettext('ir.msg_dict_schema_selection_json'), states={ 'invisible': ~Eval('type_').in_(['selection', 'multiselection']), }, depends=['type_']), 'get_selection_json') _relation_fields_cache = Cache('_dict_schema_mixin.get_relation_fields') @classmethod def __setup__(cls): super(DictSchemaMixin, cls).__setup__() cls.__rpc__.update({ 'get_keys': RPC(instantiate=0), }) @staticmethod def default_digits(): return 2 @staticmethod def default_selection_sorted(): return True @fields.depends('name', 'string') def on_change_string(self): if not self.name and self.string: self.name = slugify(self.string.lower(), hyphenate='_') @classmethod def validate(cls, schemas): super(DictSchemaMixin, cls).validate(schemas) cls.check_domain(schemas) cls.check_selection(schemas) @classmethod def check_domain(cls, schemas): for schema in schemas: if not schema.domain: continue try: value = PYSONDecoder().decode(schema.domain) except Exception: raise DomainError( gettext('ir.msg_dict_schema_invalid_domain', schema=schema.rec_name)) if not isinstance(value, list): raise DomainError( gettext('ir.msg_dict_schema_invalid_domain', schema=schema.rec_name)) @classmethod def check_selection(cls, schemas): for schema in schemas: if schema.type_ not in {'selection', 'multiselection'}: continue try: dict(json.loads(schema.get_selection_json())) except Exception: raise SelectionError( gettext('ir.msg_dict_schema_invalid_selection', schema=schema.rec_name)) def get_selection_json(self, name=None): db_selection = self.selection or '' selection = [[w.strip() for w in v.split(':', 1)] for v in db_selection.splitlines() if v] return json.dumps(selection, separators=(',', ':')) @classmethod def get_keys(cls, records): pool = Pool() Config = pool.get('ir.configuration') keys = [] for record in records: new_key = { 'id': record.id, 'name': record.name, 'string': record.string, 'help': record.help, 'type': record.type_, 'domain': record.domain, 'sequence': getattr(record, 'sequence', record.name), } if record.type_ in {'selection', 'multiselection'}: with Transaction().set_context(language=Config.get_language()): english_key = cls(record.id) selection = OrderedDict( json.loads(english_key.selection_json)) selection.update(dict(json.loads(record.selection_json))) new_key['selection'] = list(selection.items()) new_key['sort'] = record.selection_sorted elif record.type_ in ('float', 'numeric'): new_key['digits'] = (16, record.digits) keys.append(new_key) return keys @classmethod def get_relation_fields(cls): if not config.get('dict', cls.__name__, default=True): return {} fields = cls._relation_fields_cache.get(cls.__name__) if fields is not None: return fields keys = cls.get_keys(cls.search([])) fields = {k['name']: k for k in keys} cls._relation_fields_cache.set(cls.__name__, fields) return fields @classmethod def create(cls, vlist): records = super().create(vlist) cls._relation_fields_cache.clear() return records @classmethod def write(cls, *args): super().write(*args) cls._relation_fields_cache.clear() @classmethod def delete(cls, records): super().delete(records) cls._relation_fields_cache.clear()
class InvoiceEdiLine(ModelSQL, ModelView): 'Invoice Edi Line' __name__ = 'invoice.edi.line' edi_invoice = fields.Many2One('invoice.edi', 'Invoice', ondelete='CASCADE') code = fields.Char('Code') code_type = fields.Selection([ (None, ''), ('EAN', 'EAN'), ('EAN8', 'EAN8'), ('EAN13', 'EAN13'), ('EAN14', 'EAN14'), ('DUN14', 'DUN14'), ], 'Code Type') sequence = fields.Integer('Sequence') supplier_code = fields.Char('Supplier Code') purchaser_code = fields.Char('Purchaser Code') lot_number = fields.Char('Lot Number') serial_number = fields.Char('Serial Number') customer_code = fields.Char('Customer Code') producer_code = fields.Char('Producer Code') national_code = fields.Char('National Code') hibc_code = fields.Char('Healh Industry Bar Code') description = fields.Char('Description') characteristic = fields.Selection([(None, ''), ('M', 'Goods'), ('C', 'C')], 'Characteristic') qualifier = fields.Selection([(None, ''), ('F', 'Free Description')], 'Qualifier ') quantities = fields.One2Many('invoice.edi.line.quantity', 'line', 'Quantities') delivery_date = fields.Char('Delivery Date') base_amount = fields.Numeric('Base Amount', digits=(16, 2)) total_amount = fields.Numeric('Total Amount', digits=(16, 2)) unit_price = fields.Numeric('Unit Price', digits=(16, 4)) gross_price = fields.Numeric('Gross Price', digits=(16, 4)) references = fields.One2Many('invoice.edi.reference', 'edi_invoice_line', 'References') taxes = fields.One2Many('invoice.edi.tax', 'line', 'Taxes') discounts = fields.One2Many('invoice.edi.discount', 'invoice_edi_line', 'Discounts') product = fields.Many2One('product.product', 'Product') quantity = fields.Function(fields.Numeric('Quantity', digits=(16, 4)), 'invoiced_quantity') invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line') note = fields.Text('Note') @classmethod def __setup__(cls): super(InvoiceEdiLine, cls).__setup__() def search_related(self, edi_invoice): pool = Pool() Barcode = pool.get('product.code') REF = Pool().get('invoice.edi.reference') # ('barcode', '=', self.code_type) Remove this from domain after some # received domain = [('number', '=', self.code)] barcode = Barcode.search(domain, limit=1) if not barcode: return product = barcode[0].product self.product = product purchases = [ x.origin for x in edi_invoice.references if x.type_ == 'ON' and x.origin ] self.references = [] for purchase in purchases: for move in purchase.moves: if move.state != 'done': continue if move.product == product: # TODO: check for quantity? ref = REF() ref.type_ = 'move' ref.origin = 'stock.move,%s' % move.id self.references += (ref, ) def get_line(self): if self.edi_invoice.type_ == '381': # CREDIT: return self.get_line_credit() if self.references is None or len(self.references) != 1: raise UserError( gettext('account_invoice_edi.confirm_invoice_with_reference', line=self.description)) move, = self.references invoice_lines = [x for x in move.origin.invoice_lines if not x.invoice] if not invoice_lines or len(invoice_lines) != 1: raise UserError( gettext('account_invoice_edi.confirm_invoice_with_invoice', line=self.description)) invoice_line, = invoice_lines # JUst invoice wat system expect to see differences. # invoice_line.gross_unit_price = self.gross_price or self.unit_price # invoice_line.unit_price = self.unit_price # if self.unit_price and self.gross_price: # invoice_line.discount = Decimal(1 - # self.unit_price/self.gross_price).quantize(Decimal('.01')) # else: # invoice_line.unit_price = Decimal( # self.base_amount / self.quantity).quantize(Decimal('0.0001')) self.invoice_line = invoice_line return invoice_line def get_line_credit(self): move = self.references and self.references[0] invoice_lines = [ x for x in move and move.origin.invoice_lines or [] if not x.invoice ] invoice_line = invoice_lines and invoice_lines[0] Line = Pool().get('account.invoice.line') line = Line() line.product = self.product line.invoice_type = 'in' line.quantity = self.quantity if self.base_amount < 0: line.quantity = -self.quantity line.party = self.edi_invoice.party line.type = 'line' line.on_change_product() line.on_change_account() line.gross_unit_price = self.gross_price or self.unit_price line.unit_price = self.unit_price if self.unit_price and self.gross_price: line.discount = Decimal(1 - self.unit_price / self.gross_price).quantize(Decimal('.01')) else: line.unit_price = Decimal( self.base_amount / self.quantity).quantize(Decimal('0.0001')) if invoice_line: line.origin = invoice_line self.invoice_line = line return line def invoiced_quantity(self, name): for q in self.quantities: if q.type_ == '47': return q.quantity return Decimal('0') def read_LIN(self, message): def _get_code_type(code): for code_type in ('EAN8', 'EAN13', 'EAN'): check_code_ean = 'check_code_' + code_type.lower() if getattr(barcodenumber, check_code_ean)(code): return code_type if len(code) == 14: return 'EAN14' # TODO DUN14 self.code = message.pop(0) if message else '' code_type = message.pop(0) if message else '' if code_type == 'EN': self.code_type = _get_code_type(self.code) # Some times the provider send the EAN13 without left zeros # and the EAN is an EAN13 but the check fail becasue it have # less digits. if self.code_type == 'EN' and len(self.code) < 13: code = self.code.zfill(13) if getattr(barcodenumber, 'check_code_ean13')(code): self.code = code self.code_type = 'EAN13' if message: self.sequence = int(message.pop(0)) def read_PIALIN(self, message): self.supplier_code = message.pop(0) if message else '' if message: self.purchaser_code = message.pop(0) if message: self.lot_number = message.pop(0) if message: self.serial_number = message.pop(0) if message: self.customer_code = message.pop(0) if message: self.producer_code = message.pop(0) if message: self.national_code = message.pop(0) if message: self.hibc_code = message.pop(0) def read_IMDLIN(self, message): self.description = message.pop(0) if message else '' self.characteristic = message.pop(0) if message else '' self.qualifier = message.pop(0) if message else '' def read_QTYLIN(self, message): QTY = Pool().get('invoice.edi.line.quantity') qty = QTY() qty.type_ = message.pop(0) if message else '' qty.quantity = to_decimal(message.pop(0), 4) if message else Decimal(0) if qty.type_ == '47': self.quantity = qty.quantity if message: qty.uom_char = message.pop(0) if not getattr(self, 'quantities', False): self.quantities = [] self.quantities += (qty, ) def read_DTMLINE(self, message): self.delivery_date = to_date(message.pop(0)) if message else None def read_MOALIN(self, message): self.base_amount = to_decimal( message.pop(0)) if message else Decimal(0) if message: self.total_amount = to_decimal(message.pop(0)) def read_PRILIN(self, message): type_ = message.pop(0) if type_ == 'AAA': self.unit_price = to_decimal(message.pop(0), 4) if message else Decimal(0) elif type_ == 'AAB': self.gross_price = to_decimal(message.pop(0), 4) if message else Decimal(0) def read_RFFLIN(self, message): REF = Pool().get('invoice.edi.reference') ref = REF() ref.type_ = message.pop(0) if message else '' ref.value = message.pop(0) if message else '' ref.search_reference() if message: ref.line_number = message.pop(0) if message else '' if not getattr(self, 'references', False): self.references = [] self.references += (ref, ) def read_TAXLIN(self, message): Tax = Pool().get('invoice.edi.tax') tax = Tax() tax.type_ = message.pop(0) if message else '' tax.percent = to_decimal(message.pop(0)) if message else Decimal(0) if message: tax.tax_amount = to_decimal(message.pop(0)) if not getattr(self, 'taxes', False): self.taxes = [] self.taxes += (tax, ) def read_TXTLIN(self, message): self.note = message.pop(0) if message else '' def read_ALCLIN(self, message): Discount = Pool().get('invoice.edi.discount') discount = Discount() discount.type_ = message.pop(0) if message else '' discount.sequence = int(message.pop(0) or 0) if message else 0 discount.discount = message.pop(0) if message else '' discount.percent = to_decimal( message.pop(0)) if message else Decimal(0) discount.amount = to_decimal(message.pop(0)) if message else Decimal(0) if not getattr(self, 'discounts', False): self.discounts = [] self.discounts += (discount, )
class PatientDisabilityAssessment(ModelSQL, ModelView): 'Patient Disability Information' __name__ = 'gnuhealth.patient.disability_assessment' patient = fields.Many2One('gnuhealth.patient', 'Patient', required=True) assessment_date = fields.Date('Date') assessment = fields.Char('Code') crutches = fields.Boolean('Crutches') wheelchair = fields.Boolean('Wheelchair') uxo = fields.Function(fields.Boolean('UXO'), 'get_uxo_status') amputee = fields.Function(fields.Boolean('Amputee'), 'get_amputee_status') amputee_since = fields.Function(fields.Date('Since'), 'get_amputee_date') notes = fields.Text('Notes', help="Extra Information") hand_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Hand', sort=False) visual_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Visual', sort=False) speech_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Speech', sort=False) hearing_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Hearing', sort=False) cognitive_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Cognitive', sort=False) locomotor_function = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'Mobility', sort=False) activity_participation = fields.Selection([ (None, ''), ('0', 'No impairment'), ('1', 'Mild impairment'), ('2', 'Moderate impairment'), ('3', 'Severe impairment'), ('4', 'Complete impairment'), ], 'A & P', sort=False) body_functions = fields.One2Many('gnuhealth.body_function.assessment', 'assessment', 'Body Functions Impairments') body_structures = fields.One2Many('gnuhealth.body_structure.assessment', 'assessment', 'Body Structures Impairments') activity_and_participation = fields.One2Many( 'gnuhealth.activity.assessment', 'assessment', 'Activities and Participation Impairments') environmental_factor = fields.One2Many('gnuhealth.environment.assessment', 'assessment', 'Environmental Factors Barriers') healthprof = fields.Many2One('gnuhealth.healthprofessional', 'Health Prof', help="Authorized health professional") def get_uxo_status(self, name): return self.patient.uxo def get_amputee_status(self, name): return self.patient.amputee def get_amputee_date(self, name): return self.patient.amputee_since @staticmethod def default_assessment_date(): return date.today() @staticmethod def default_healthprof(): pool = Pool() HealthProf = pool.get('gnuhealth.healthprofessional') hp = HealthProf.get_health_professional() return hp
class AccountLiquidation(ModelSQL, ModelView): 'Account Liquidation' __name__ = 'account.liquidation' _rec_name = 'number' company = fields.Many2One( 'company.company', 'Company', required=True, states=_STATES, select=True, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], depends=_DEPENDS) type = fields.Selection(_TYPE, 'Type', select=True, required=True, states={ 'readonly': ((Eval('state') != 'draft')), }, depends=['state']) number = fields.Char('Number', size=None, select=True) reference = fields.Char('Reference', size=None, states=_STATES, depends=_DEPENDS) description = fields.Char('Description', size=None, states=_STATES, depends=_DEPENDS) state = fields.Selection([ ('draft', 'Draft'), ('validated', 'Confirm'), ('posted', 'Posted'), ], 'State', readonly=True) liquidation_date = fields.Date('Liquidation Date', states={ 'readonly': Eval('state').in_(['posted', 'cancel']), 'required': Eval('state').in_(['posted']), }, depends=['state']) accounting_date = fields.Date('Accounting Date', states=_STATES, depends=_DEPENDS) party = fields.Many2One('party.party', 'Party', required=True, states=_STATES, depends=_DEPENDS) party_lang = fields.Function(fields.Char('Party Language'), 'on_change_with_party_lang') liquidation_address = fields.Many2One('party.address', 'Liquidation Address', required=True, states=_STATES, depends=['state', 'party'], domain=[('party', '=', Eval('party')) ]) 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') currency_date = fields.Function(fields.Date('Currency Date'), 'on_change_with_currency_date') journal = fields.Many2One('account.journal', 'Journal', required=True, states=_STATES, depends=_DEPENDS) move = fields.Many2One('account.move', 'Move', readonly=True) account = fields.Many2One('account.account', 'Account', required=True, states=_STATES, depends=_DEPENDS) taxes = fields.One2Many('account.liquidation.tax', 'liquidation', 'Tax Lines', states=_STATES, depends=_DEPENDS) comment = fields.Text('Comment', states=_STATES, depends=_DEPENDS) untaxed_amount = fields.Function(fields.Numeric( 'Untaxed', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount', searcher='search_untaxed_amount') tax_amount = fields.Function(fields.Numeric( 'Tax', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount', searcher='search_tax_amount') total_amount = fields.Function(fields.Numeric( 'Total liquidation', digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']), 'get_amount', searcher='search_total_amount') @classmethod def __setup__(cls): super(AccountLiquidation, cls).__setup__() cls._error_messages.update({ 'delete_liquidation': 'You can not delete a liquidation that is posted!', 'no_liquidation_sequence': ('There is no liquidation sequence for ' 'liquidation "%(liquidation)s" on the period/fiscal year ' '"%(period)s".'), }) cls._buttons.update({ 'validate_liquidation': { 'invisible': Eval('state') != 'draft', }, 'post': { 'invisible': (Eval('state') == 'posted'), 'readonly': ~Eval('taxes', [0]), #'readonly': Not(Bool(Eval('taxes')) }, }) cls._order.insert(0, ('liquidation_date', 'DESC')) @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 @fields.depends('party') def on_change_with_party_lang(self, name=None): Config = Pool().get('ir.configuration') if self.party: if self.party.lang: return self.party.lang.code return Config.get_language() def get_tax_context(self): context = {} if self.party and self.party.lang: context['language'] = self.party.lang.code return context @classmethod def get_amount(cls, invoices, names): pool = Pool() InvoiceTax = pool.get('account.liquidation.tax') Move = pool.get('account.move') MoveLine = pool.get('account.move.line') cursor = Transaction().cursor untaxed_amount = dict((i.id, _ZERO) for i in invoices) tax_amount = dict((i.id, _ZERO) for i in invoices) total_amount = dict((i.id, _ZERO) for i in invoices) type_name = cls.tax_amount._field.sql_type().base tax = InvoiceTax.__table__() to_round = False for sub_ids in grouped_slice(invoices): red_sql = reduce_ids(tax.liquidation, sub_ids) cursor.execute( *tax.select(tax.liquidation, Coalesce(Sum(tax.amount), 0).as_(type_name), where=red_sql, group_by=tax.liquidation)) for invoice_id, sum_ in cursor.fetchall(): # SQLite uses float for SUM if not isinstance(sum_, Decimal): sum_ = Decimal(str(sum_)) to_round = True tax_amount[invoice_id] = sum_ # Float amount must be rounded to get the right precision if to_round: for invoice in invoices: tax_amount[invoice.id] = invoice.currency.round( tax_amount[invoice.id]) invoices_move = set() invoices_no_move = set() for invoice in invoices: if invoice.move: invoices_move.add(invoice.id) else: invoices_no_move.add(invoice.id) invoices_move = cls.browse(invoices_move) invoices_no_move = cls.browse(invoices_no_move) type_name = cls.total_amount._field.sql_type().base invoice = cls.__table__() move = Move.__table__() line = MoveLine.__table__() to_round = False for sub_ids in grouped_slice(invoices_move): red_sql = reduce_ids(invoice.id, sub_ids) cursor.execute( *invoice.join(move, condition=invoice.move == move.id).join( line, condition=move.id == line.move).select( invoice.id, Coalesce( Sum( Case((line.second_currency == invoice.currency, Abs(line.amount_second_currency) * Sign(line.debit - line.credit)), else_=line.debit - line.credit)), 0).cast(type_name), where=(invoice.account == line.account) & red_sql, group_by=invoice.id)) for invoice_id, sum_ in cursor.fetchall(): # SQLite uses float for SUM if not isinstance(sum_, Decimal): sum_ = Decimal(str(sum_)) to_round = True total_amount[invoice_id] = sum_ for invoice in invoices_move: # Float amount must be rounded to get the right precision if to_round: total_amount[invoice.id] = invoice.currency.round( total_amount[invoice.id]) untaxed_amount[invoice.id] = (total_amount[invoice.id] - tax_amount[invoice.id]) for invoice in invoices_no_move: total_amount[invoice.id] = (untaxed_amount[invoice.id] + tax_amount[invoice.id]) result = { 'untaxed_amount': untaxed_amount, 'tax_amount': tax_amount, 'total_amount': total_amount, } for key in result.keys(): if key not in names: del result[key] return result @staticmethod def default_currency(): Company = Pool().get('company.company') company_id = Transaction().context.get('company') if company_id: return Company(company_id).currency.id @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_journal(): pool = Pool() Journal = pool.get('account.journal') journal = Journal.search([('type', '=', 'expense')]) for j in journal: return j.id @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() def set_number(self): pool = Pool() Period = pool.get('account.period') Sequence = pool.get('ir.sequence.strict') Date = pool.get('ir.date') if self.number: return test_state = True accounting_date = self.accounting_date or self.liquidation_date period_id = Period.find(self.company.id, date=accounting_date, test_state=test_state) period = Period(period_id) sequence = period.get_invoice_sequence(self.type) if not sequence: self.raise_user_error('no_liquidation_sequence', { 'liquidation': self.rec_name, 'period': period.rec_name, }) with Transaction().set_context( date=self.liquidation_date or Date.today()): number = Sequence.get_id(sequence.id) vals = {'number': number} if (not self.liquidation_date and self.type in ('out_liquidation')): vals['liquidation_date'] = Transaction().context['date'] self.write([self], vals) @classmethod def delete(cls, liquidations): if not liquidations: return True for liquidation in liquidations: if liquidation.state == 'posted': cls.raise_user_error('delete_liquidation') return super(AccountLiquidation, cls).delete(liquidations) def prepare_liquidation_lines(self): pool = Pool() Period = pool.get('account.period') Move = pool.get('account.move') Liquidation = pool.get('account.liquidation') amount = Decimal(0.0) move_lines = [] line_move_ids = [] move, = Move.create([{ 'period': Period.find(self.company.id, date=self.liquidation_date), 'journal': self.journal.id, 'date': self.liquidation_date, 'origin': str(self), }]) self.write([self], { 'move': move.id, }) for tax in self.taxes: amount += tax.amount if self.type == 'out_liquidation': debit = Decimal('0.00') credit = amount else: debit = self.total_amount credit = Decimal('0.00') move_lines.append({ 'description': self.number, 'debit': debit, 'credit': credit, 'account': self.account.id, 'move': move.id, 'journal': self.journal.id, 'period': Period.find(self.company.id, date=self.liquidation_date), }) if self.taxes: for tax in self.taxes: if self.type == 'out_liquidation': debit = tax.amount credit = Decimal('0.00') move_lines.append({ 'description': tax.description, 'debit': debit, 'credit': credit, 'account': tax.account.id, 'move': move.id, 'journal': self.journal.id, 'party': self.party, 'period': Period.find(self.company.id, date=self.liquidation_date), }) return move_lines def posted(self, move_lines): pool = Pool() Move = pool.get('account.move') MoveLine = pool.get('account.move.line') created_lines = MoveLine.create(move_lines) Move.post([self.move]) return True @classmethod @ModelView.button @Workflow.transition('validated') def validate_invoice(cls, liquidations): for liquidation in liquidations: invoice.create_move() @classmethod @ModelView.button def post(cls, liquidations): for liquidation in liquidations: liquidation.set_number() move_lines = liquidation.prepare_liquidation_lines() liquidation.posted(move_lines) cls.write(liquidations, {'state': 'posted'})
class DiseaseNotification(ModelView, ModelSQL): 'Disease Notification' __name__ = 'gnuhealth.disease_notification' active = fields.Boolean('Active') patient = fields.Many2One('gnuhealth.patient', 'Patient', required=True, states=RO_SAVED) status = fields.Selection(NOTIFICATION_STATES, 'Status', required=True, sort=False) status_display = fields.Function(fields.Char('State'), 'get_selection_display') name = fields.Char('Code', size=18, states={'readonly': True}, required=True) tracking_code = fields.Char('Case Tracking Code', select=True) date_notified = fields.DateTime('Date reported', required=True, states=RO_SAVED) date_received = fields.DateTime( 'Date received', states=RO_NEW, help='Date received the National Surveillance Unit') diagnosis = fields.Many2One('gnuhealth.pathology', 'Suspected Diagnosis', states=RO_STATE_END, required=False) diagnosis_confirmed = fields.Many2One( 'gnuhealth.pathology', 'Confirmed Diagnosis', required=False, states={'invisible': Eval('id', 0) < 0}) symptoms = fields.One2Many('gnuhealth.disease_notification.symptom', 'name', 'Symptoms', states=RO_STATE_END) date_onset = fields.Date('Date of Onset', help='Date of onset of the illness') epi_week_onset = fields.Function( fields.Char('Epi. Week of onset', size=8, help='Week of onset (epidemiological)'), 'epi_week') date_seen = fields.Date('Date Seen', help='Date seen by a medical officer') reporting_facility = fields.Many2One( 'gnuhealth.institution', 'Reporting facility', states={'invisible': Bool(Eval('reporting_facility_other'))}) reporting_facility_other = fields.Char( 'Other Reporting location', help='Used when the report came from an institution not found above', states={'invisible': Bool(Eval('reporting_facility'))}) encounter = fields.Many2One('gnuhealth.encounter', 'Clinical Encounter', domain=[('patient', '=', Eval('patient')), ('start_time', '<', Eval('date_notified'))]) specimen_taken = fields.Boolean('Samples Taken') specimens = fields.One2Many('gnuhealth.disease_notification.specimen', 'notification', 'Samples', states=ONLY_IF_LAB) hospitalized = fields.Boolean('Admitted to hospital') admission_date = fields.Date('Date admitted', states=ONLY_IF_ADMITTED) hospital = fields.Many2One('gnuhealth.institution', 'Hospital', states=ONLY_IF_ADMITTED) ward = fields.Char('Ward', states=ONLY_IF_ADMITTED) deceased = fields.Boolean('Deceased') date_of_death = fields.Date('Date of Death', states=ONLY_IF_DEAD) healthprof = fields.Many2One('gnuhealth.healthprofessional', 'Reported by') comments = fields.Text('Additional comments') comments_short = fields.Function(fields.Char('Comments'), 'short_comment') risk_factors = fields.One2Many( 'gnuhealth.disease_notification.risk_disease', 'notification', 'Risk Factors', help="Other conditions of merit") hx_travel = fields.Boolean('Recent Foreign Travels', help="History of Overseas travel in the last" " 4 - 6 weeks") hx_locations = fields.One2Many( 'gnuhealth.disease_notification.travel', 'notification', 'Places visited', states={'invisible': ~Eval('hx_travel', False)}) age = fields.Function( fields.Char('Age', size=8, help='age at date of onset'), 'get_patient_age') sex = fields.Function(fields.Selection(SEX_OPTIONS, 'Sex'), 'get_patient_field', searcher='search_patient_field') puid = fields.Function(fields.Char('UPI', size=12), 'get_patient_field', searcher='search_patient_field') state_changes = fields.One2Many( 'gnuhealth.disease_notification.statechange', 'notification', 'Status Changes', order=[('create_date', 'DESC')], readonly=True) ir_received = fields.Boolean('IR Received') # medical_record_num = fields.Function(fields.Char('Medical Record Numbers'), # 'get_patient_field', # searcher='search_patient_field') @classmethod def __setup__(cls): super(DiseaseNotification, cls).__setup__() cls._order = [('date_onset', 'DESC')] cls._sql_error_messages = { 'unique_name': 'There is another notification with this code' } cls._sql_constraints = [('name_uniq', 'UNIQUE(name)', 'The code must be unique.')] @classmethod def get_patient_field(cls, instances, name): return dict([(x.id, getattr(x.patient, name)) for x in instances]) @classmethod def get_patient_age(cls, instances, name): ''' Uses the age function in the database to calculate the age at the date specified. :param: instance_refs - a list of tuples with (id, ref_date) ''' c = Transaction().cursor tbl = cls.__table__() qry = "\n".join([ "SET intervalstyle TO 'iso_8601';", "SELECT a.id as id, btrim(lower(" "regexp_replace(AGE(a.date_onset, c.dob)::varchar, " "'([YMD])', '\\1 ', 'g')), 'p ') as showage ", "from " + str(tbl) + " as a ", " inner join gnuhealth_patient as b on a.patient=b.id", " inner join party_party c on b.name=c.id" " where a.id in %s ;" ]) qry_parm = tuple(map(int, instances)) c.execute(qry, (qry_parm, )) outx = c.fetchall() outd = dict([x for x in outx]) return outd @classmethod def order_puid(cls, tables): table, _ = tables[None] return [Column(table, 'patient')] # not really a UPI/PUID sort, but good enough @classmethod def short_comment(cls, instances, name): return dict( map( lambda x: (x.id, x.comments and ' '.join(x.comments.split( '\n'))[:40] or ''), instances)) @classmethod def search_patient_field(cls, field_name, clause): return replace_clause_column(clause, 'patient.%s' % field_name) _rec_name = 'name' # @classmethod # def get_rec_name(cls, records, name): # return dict([(x.id, x.name) for x in records]) @classmethod def search_rec_name(cls, field_name, clause): _, operand, val = clause return ['OR', ('patient.puid', operand, val), ('name', operand, val)] @fields.depends('reporting_facility') def on_change_reporting_facility(self, *arg, **kwarg): return {'reporting_facility_other': ''} @fields.depends('diagnosis', 'name') def on_change_with_name(self): curname = self.name if self.diagnosis: newcode = '%s:' % self.diagnosis.code if curname: newcode = '%s:%s' % (self.diagnosis.code, curname) return newcode elif curname and ':' in curname: return curname[curname.index(':') + 1:] @fields.depends('diagnosis_confirmed', 'status') def on_change_diagnosis_confirmed(self): if self.diagnosis_confirmed and self.status == 'suspected': return {'status': 'confirmed'} else: return {} @fields.depends('encounter') def on_change_with_date_seen(self): return self.encounter.start_time.date() if self.encounter else None @staticmethod def default_healthprof(): healthprof_model = Pool().get('gnuhealth.healthprofessional') return healthprof_model.get_health_professional() @staticmethod def default_status(): return 'waiting' @staticmethod def default_active(): return True @classmethod def create(cls, vlist): pool = Pool() Sequence = pool.get('ir.sequence') Config = pool.get('gnuhealth.sequences') config = Config(1) vlist = [x.copy() for x in vlist] for values in vlist: val_name = values.get('name', '') if not val_name or val_name.endswith(':'): newcode = Sequence.get_id(config.notification_sequence.id) values['name'] = '%s%s' % (values['name'], newcode) elif ':' in val_name and not values.get('diagnosis', False): values['name'] = val_name[val_name.index(':') + 1:] if values.get('state_changes', False): pass else: values['state_changes'] = [('create', [{ 'orig_state': None, 'target_state': values['status'], 'healthprof': values['healthprof'] }])] return super(DiseaseNotification, cls).create(vlist) @classmethod def write(cls, records, values, *args): '''create a NotificationStateChange when the status changes''' healthprof = DiseaseNotification.default_healthprof() to_make = [] irecs = iter((records, values) + args) for recs, vals in zip(irecs, irecs): newstate = vals.get('status', False) if newstate: for rec in recs: if rec.status != newstate: to_make.append({ 'notification': rec.id, 'orig_state': rec.status, 'target_state': newstate, 'healthprof': healthprof }) return_val = super(DiseaseNotification, cls).write(records, values, *args) # nsc = Notification State Change if to_make: nsc = Pool().get('gnuhealth.disease_notification.statechange') nsc.create(to_make) return return_val @classmethod def validate(cls, records): now = {date: date.today(), datetime: datetime.now(), type(None): None} date_fields = [ 'date_onset', 'date_seen', 'date_notified', 'admission_date', 'date_of_death' ] # we need to ensure that none of these fields are in the future for rec in records: if rec.encounter: if rec.encounter.patient != rec.patient: cls.raise_user_error('Invalid encounter selected.' 'Different patient') for fld in date_fields: val = getattr(rec, fld) if val and val > now[type(val)]: val_name = getattr(cls, fld).string cls.raise_user_error('%s cannot be in the future', (val_name, )) @classmethod def copy(cls, records, default=None): if default is None: default = {} default = default.copy() default.update(diagnosis=None, state_changes=[]) if 'name' in default: del default['name'] return super(DiseaseNotification, cls).copy(records, default=default) @classmethod def epi_week(cls, instances, name): def ewcalc(k): return (k.id, epiweek_str(k.date_onset) if k.date_onset else '') if name == 'epi_week_onset': return dict(map(ewcalc, instances)) @classmethod def get_selection_display(cls, instances, field_name): real_field = field_name[:0 - len('_display')] field_selections = cls._fields[real_field].selection xdict = dict(filter(lambda x: x[0], field_selections)) return dict( map(lambda x: (x.id, xdict.get(getattr(x, real_field), '')), instances))
class AccountLiquidationTax(ModelSQL, ModelView): 'Account Liquidation Tax' __name__ = 'account.liquidation.tax' _rec_name = 'description' liquidation = fields.Many2One('account.liquidation', 'liquidation', ondelete='CASCADE', select=True) description = fields.Char('Description', size=None, required=True) sequence = fields.Integer('Sequence') sequence_number = fields.Function(fields.Integer('Sequence Number'), 'get_sequence_number') account = fields.Many2One('account.account', 'Account', required=True, domain=[ ('kind', '!=', 'view'), ('company', '=', Eval('_parent_liquidation', {}).get('company', 0)), ]) base = fields.Numeric('Base', required=True, digits=(16, Eval('_parent_liquidation', {}).get('currency_digits', 2))) amount = fields.Numeric('Amount', required=True, digits=(16, Eval('_parent_liquidation', {}).get('currency_digits', 2)), depends=['tax', 'base', 'manual']) manual = fields.Boolean('Manual') base_code = fields.Many2One('account.tax.code', 'Base Code', domain=[ ('company', '=', Eval('_parent_liquidation', {}).get('company', 0)), ]) base_sign = fields.Numeric('Base Sign', digits=(2, 0), required=True) tax_code = fields.Many2One('account.tax.code', 'Tax Code', domain=[ ('company', '=', Eval('_parent_liquidation', {}).get('company', 0)), ]) tax_sign = fields.Numeric('Tax Sign', digits=(2, 0), required=True) tax = fields.Many2One('account.tax', 'Tax', states={ 'readonly': ~Eval('manual', False), }, depends=['manual']) tipo = fields.Char('Tipo de retencion') @classmethod def __setup__(cls): super(AccountLiquidationTax, cls).__setup__() cls._order.insert(0, ('sequence', 'ASC')) cls._error_messages.update({ 'modify': ('You can not modify tax "%(tax)s" from liquidation ' '"%(liquidation)s" because it is posted or paid.'), 'create': ('You can not add line "%(line)s" to liquidation ' '"%(liquidation)s" because it is posted, paid or canceled.'), 'invalid_account_company': ('You can not create liquidation ' '"%(liquidation)s" on company "%(liquidation_company)s" using ' 'account "%(account)s" from company ' '"%(account_company)s".'), 'invalid_base_code_company': ('You can not create liquidation ' '"%(liquidation)s" on company "%(liquidation_company)s" ' 'using base tax code "%(base_code)s" from company ' '"%(base_code_company)s".'), 'invalid_tax_code_company': ('You can not create liquidation ' '"%(liquidation)s" on company "%(liquidation_company)s" using tax ' 'code "%(tax_code)s" from company ' '"%(tax_code_company)s".'), }) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().cursor table = TableHandler(cursor, cls, module_name) super(AccountLiquidationTax, cls).__register__(module_name) # Migration from 2.4: drop required on sequence table.not_null_action('sequence', action='remove') @staticmethod def order_sequence(tables): table, _ = tables[None] return [table.sequence == None, table.sequence] @staticmethod def default_base(): return Decimal('0.0') @staticmethod def default_amount(): return Decimal('0.0') @staticmethod def default_manual(): return True @staticmethod def default_base_sign(): return Decimal('1') @staticmethod def default_tax_sign(): return Decimal('1') @fields.depends('tax', '_parent_liquidation.party', '_parent_liquidation.type') def on_change_tax(self): Tax = Pool().get('account.tax') changes = {} if self.tax: if self.liquidation: context = self.liquidation.get_tax_context() else: context = {} with Transaction().set_context(**context): tax = Tax(self.tax.id) changes['description'] = tax.description if self.liquidation and self.liquidation.type: liquidation_type = self.liquidation.type else: liquidation_type = 'out_liquidation' if liquidation_type in ('out_liquidation', 'in_liquidation'): changes['base_code'] = (tax.invoice_base_code.id if tax.invoice_base_code else None) changes['base_sign'] = tax.invoice_base_sign changes['tax_code'] = (tax.invoice_tax_code.id if tax.invoice_tax_code else None) changes['tax_sign'] = tax.invoice_tax_sign changes['account'] = tax.invoice_account.id return changes @fields.depends('tax', 'base', 'amount', 'manual') def on_change_with_amount(self): Tax = Pool().get('account.tax') transaction = Transaction() company = transaction.context['company'] Company = Pool().get('company.company') companies = Company.search([('id', '=', company)]) for c in companies: company = c if self.tax and self.manual: tax = self.tax base = self.base or Decimal(0) for values in Tax.compute([tax], base, 1): if (values['tax'] == tax and values['base'] == base): amount = company.currency.round(values['amount']) return amount return company.currency.round(self.amount) @classmethod def check_modify(cls, taxes): ''' Check if the taxes can be modified ''' for tax in taxes: if tax.liquidation.state in ('posted', 'paid'): cls.raise_user_error('modify') def get_sequence_number(self, name): i = 1 for tax in self.liquidation.taxes: if tax == self: return i i += 1 return 0 @classmethod def delete(cls, taxes): cls.check_modify(taxes) super(AccountLiquidationTax, cls).delete(taxes) @classmethod def write(cls, *args): taxes = sum(args[0::2], []) cls.check_modify(taxes) super(AccountLiquidationTax, cls).write(*args) @classmethod def create(cls, vlist): Liquidation = Pool().get('account.liquidation') liquidation_ids = [] for vals in vlist: if vals.get('Liquidation'): liquidation_ids.append(vals['liquidation']) for liquidation in Liquidation.browse(liquidation_ids): if liquidation.state in ('posted'): cls.raise_user_error('create') return super(AccountLiquidationTax, cls).create(vlist) @classmethod def validate(cls, taxes): super(AccountLiquidationTax, cls).validate(taxes) for tax in taxes: tax.check_company() def check_company(self): company = self.liquidation.company if self.account.company != company: self.raise_user_error( 'invalid_account_company', { 'liquidation': self.liquidation.rec_name, 'liquidation_company': self.liquidation.company.rec_name, 'account': self.account.rec_name, 'account_company': self.account.company.rec_name, }) if self.base_code: if self.base_code.company != company: self.raise_user_error( 'invalid_base_code_company', { 'liquidation': self.liquidation.rec_name, 'liquidation_company': self.liquidation.company.rec_name, 'base_code': self.base_code.rec_name, 'base_code_company': self.base_code.company.rec_name, }) if self.tax_code: if self.tax_code.company != company: self.raise_user_error( 'invalid_tax_code_company', { 'liquidation': self.liquidation.rec_name, 'liquidation_company': self.liquidation.company.rec_name, 'tax_code': self.tax_code.rec_name, 'tax_code_company': self.tax_code.company.rec_name, }) def get_move_line(self): ''' Return a list of move lines values for liquidation tax ''' Currency = Pool().get('currency.currency') res = {} if not self.amount: return [] res['description'] = self.description if self.liquidation.currency != self.liquidation.company.currency: with Transaction().set_context( date=self.liquidation.currency_date): amount = Currency.compute(self.liquidation.currency, self.amount, self.liquidation.company.currency) res['amount_second_currency'] = self.amount res['second_currency'] = self.liquidation.currency.id else: amount = self.amount res['amount_second_currency'] = None res['second_currency'] = None if self.liquidation.type in ('in_liquidation', 'out_credit_note'): if amount >= Decimal('0.0'): res['debit'] = amount res['credit'] = Decimal('0.0') else: res['debit'] = Decimal('0.0') res['credit'] = -amount if res['amount_second_currency']: res['amount_second_currency'] = \ - res['amount_second_currency'] else: if amount >= Decimal('0.0'): res['debit'] = Decimal('0.0') res['credit'] = amount if res['amount_second_currency']: res['amount_second_currency'] = \ - res['amount_second_currency'] else: res['debit'] = -amount res['credit'] = Decimal('0.0') res['account'] = self.account.id if self.account.party_required: res['party'] = self.liquidation.party.id if self.tax_code: res['tax_lines'] = [('create', [{ 'code': self.tax_code.id, 'amount': amount * self.tax_sign, 'tax': self.tax and self.tax.id or None }])] return [res]
class PatientEvaluation(ModelSQL, ModelView): __name__ = 'gnuhealth.patient.evaluation' serializer = fields.Text('Doc String', readonly=True) document_digest = fields.Char('Digest', readonly=True, help="Original Document Digest") digest_status = fields.Function(fields.Boolean('Altered', states={ 'invisible': Not(Equal(Eval('state'),'signed')), }, help="This field will be set whenever parts of" \ " the main original document has been changed." \ " Please note that the verification is done only on selected" \ " fields." ), 'check_digest') serializer_current = fields.Function( fields.Text('Current Doc', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digest_current = fields.Function( fields.Char('Current Hash', states={ 'invisible': Not(Bool(Eval('digest_status'))), }), 'check_digest') digital_signature = fields.Text('Digital Signature', readonly=True) @classmethod def __setup__(cls): super(PatientEvaluation, cls).__setup__() cls._buttons.update({ 'sign_evaluation': { 'invisible': Not(Equal(Eval('state'), 'done')), }, }) ''' Allow calling the set_signature method via RPC ''' cls.__rpc__.update({ 'set_signature': RPC(readonly=False), }) @classmethod @ModelView.button def sign_evaluation(cls, evaluations): evaluation = evaluations[0] HealthProf = Pool().get('gnuhealth.healthprofessional') # Change the state of the evaluation to "Signed" # Include signing health professional serial_doc = cls.get_serial(evaluation) signing_hp = HealthProf.get_health_professional() if not signing_hp: cls.raise_user_error( "No health professional associated to this user !") cls.write( evaluations, { 'serializer': serial_doc, 'document_digest': HealthCrypto().gen_hash(serial_doc), 'state': 'signed', }) @classmethod def get_serial(cls, evaluation): signs_symptoms = [] secondary_conditions = [] diagnostic_hypotheses = [] procedures = [] for sign_symptom in evaluation.signs_and_symptoms: finding = [] finding = [ sign_symptom.clinical.rec_name, sign_symptom.sign_or_symptom, ] signs_symptoms.append(finding) for secondary_condition in evaluation.secondary_conditions: sc = [] sc = [secondary_condition.pathology.rec_name] secondary_conditions.append(sc) for ddx in evaluation.diagnostic_hypothesis: dx = [] dx = [ddx.pathology.rec_name] diagnostic_hypotheses.append(dx) for procedure in evaluation.actions: proc = [] proc = [procedure.procedure.rec_name] procedures.append(proc) data_to_serialize = { 'Patient': str(evaluation.patient.rec_name) or '', 'Start': str(evaluation.evaluation_start) or '', 'End': str(evaluation.evaluation_endtime) or '', 'Initiated_by': str(evaluation.healthprof.rec_name), 'Signed_by': evaluation.signed_by and str(evaluation.signed_by.rec_name) or '', 'Specialty': evaluation.specialty and str(evaluation.specialty.rec_name) or '', 'Visit_type': str(evaluation.visit_type) or '', 'Urgency': str(evaluation.urgency) or '', 'Information_source': str(evaluation.information_source) or '', 'Reliable_info': evaluation.reliable_info, 'Chief_complaint': str(evaluation.chief_complaint) or '', 'Present_illness': str(evaluation.present_illness) or '', 'Evaluation_summary': str(evaluation.evaluation_summary), 'Signs_and_Symptoms': signs_symptoms or '', 'Glycemia': evaluation.glycemia or '', 'Hba1c': evaluation.hba1c or '', 'Total_Cholesterol': evaluation.cholesterol_total or '', 'HDL': evaluation.hdl or '', 'LDL': evaluation.ldl or '', 'TAG': evaluation.ldl or '', 'Systolic': evaluation.systolic or '', 'Diastolic': evaluation.diastolic or '', 'BPM': evaluation.bpm or '', 'Respiratory_rate': evaluation.respiratory_rate or '', 'Osat': evaluation.osat or '', 'BPM': evaluation.bpm or '', 'Malnutrition': evaluation.malnutrition, 'Dehydration': evaluation.dehydration, 'Temperature': evaluation.temperature, 'Weight': evaluation.weight or '', 'Height': evaluation.height or '', 'BMI': evaluation.bmi or '', 'Head_circ': evaluation.head_circumference or '', 'Abdominal_cir': evaluation.abdominal_circ or '', 'Hip': evaluation.hip or '', 'WHR': evaluation.whr or '', 'Abdominal_cir': evaluation.abdominal_circ or '', 'Loc': evaluation.loc or '', 'Loc_eyes': evaluation.loc_eyes or '', 'Loc_verbal': evaluation.loc_verbal or '', 'Loc_motor': evaluation.loc_motor or '', 'Tremor': evaluation.tremor, 'Violent': evaluation.violent, 'Mood': str(evaluation.mood) or '', 'Orientation': evaluation.orientation, 'Orientation': evaluation.orientation, 'Memory': evaluation.memory, 'Knowledge_current_events': evaluation.knowledge_current_events, 'Judgment': evaluation.judgment, 'Abstraction': evaluation.abstraction, 'Vocabulary': evaluation.vocabulary, 'Calculation': evaluation.calculation_ability, 'Object_recognition': evaluation.object_recognition, 'Praxis': evaluation.praxis, 'Diagnosis': evaluation.diagnosis and str(evaluation.diagnosis.rec_name) or '', 'Secondary_conditions': secondary_conditions or '', 'DDX': diagnostic_hypotheses or '', 'Info_Diagnosis': str(evaluation.info_diagnosis) or '', 'Treatment_plan': str(evaluation.directions) or '', 'Procedures': procedures or '', 'Institution': evaluation.institution and str(evaluation.institution.rec_name) or '', 'Derived_from': evaluation.derived_from and str(evaluation.derived_from.rec_name) or '', 'Derived_to': evaluation.derived_to and str(evaluation.derived_to.rec_name) or '', } serialized_doc = str(HealthCrypto().serialize(data_to_serialize)) return serialized_doc @classmethod def set_signature(cls, data, signature): """ Set the clearsigned signature """ doc_id = data['id'] cls.write([cls(doc_id)], { 'digital_signature': signature, }) def check_digest(self, name): result = '' serial_doc = str(self.get_serial(self)) if (name == 'digest_status' and self.document_digest): if (HealthCrypto().gen_hash(serial_doc) == self.document_digest): result = False else: ''' Return true if the document has been altered''' result = True if (name == 'digest_current'): result = HealthCrypto().gen_hash(serial_doc) if (name == 'serializer_current'): result = serial_doc return result # Hide the group holding all the digital signature until signed @classmethod def view_attributes(cls): return [('//group[@id="group_digital_signature"]', 'states', { 'invisible': ~Eval('digital_signature') }), ('//group[@id="group_current_string"]', 'states', { 'invisible': ~Eval('digest_status'), })]
class MoveLine: __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 User(ModelSQL, ModelView): 'Web User' __name__ = 'web.user' _rec_name = 'email' email = fields.Char('E-mail', required=True, select=True) email_valid = fields.Boolean('E-mail Valid') email_token = fields.Char('E-mail Token', select=True) password_hash = fields.Char('Password Hash') password = fields.Function(fields.Char('Password'), 'get_password', setter='set_password') reset_password_token = fields.Char('Reset Password Token', select=True) reset_password_token_expire = fields.Timestamp( 'Reset Password Token Expire') active = fields.Boolean('Active') party = fields.Many2One('party.party', 'Party') @classmethod def __setup__(cls): super(User, cls).__setup__() table = cls.__table__() cls._sql_constraints += [ ('email_unique', Unique(table, table.email), 'E-mail must be unique'), ] cls._buttons.update({ 'validate_email': { 'readonly': Eval('email_valid', False), }, 'reset_password': { 'readonly': ~Eval('email_valid', False), }, }) @staticmethod def default_active(): return True @classmethod def default_email_valid(cls): return False def get_password(self, name): return 'x' * 10 @classmethod def set_password(cls, users, name, value): pool = Pool() User = pool.get('res.user') if value == 'x' * 10: return if Transaction().user and value: User.validate_password(value, users) to_write = [] for user in users: to_write.extend([[user], { 'password_hash': cls.hash_password(value), }]) cls.write(*to_write) @classmethod def _format_email(cls, users): for user in users: email = user.email.lower() if email != user.email: user.email = email cls.save(users) @classmethod def create(cls, vlist): users = super(User, cls).create(vlist) cls._format_email(users) return users @classmethod def write(cls, *args): super(User, cls).write(*args) users = sum(args[0:None:2], []) cls._format_email(users) @classmethod def authenticate(cls, email, password): pool = Pool() Attempt = pool.get('web.user.authenticate.attempt') email = email.lower() # Prevent brute force attack Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1) users = cls.search([('email', '=', email)]) if users: user, = users if cls.check_password(password, user.password_hash): Attempt.remove(email) return user Attempt.add(email) @staticmethod def hash_method(): return 'bcrypt' if bcrypt else 'sha1' @classmethod def hash_password(cls, password): '''Hash given password in the form <hash_method>$<password>$<salt>...''' if not password: return '' return getattr(cls, 'hash_' + cls.hash_method())(password) @classmethod def check_password(cls, password, hash_): if not hash_: return False hash_method = hash_.split('$', 1)[0] return getattr(cls, 'check_' + hash_method)(password, hash_) @classmethod def hash_sha1(cls, password): salt = ''.join(random.sample(string.ascii_letters + string.digits, 8)) salted_password = password + salt if isinstance(salted_password, unicode): salted_password = salted_password.encode('utf-8') hash_ = hashlib.sha1(salted_password).hexdigest() return '$'.join(['sha1', hash_, salt]) @classmethod def check_sha1(cls, password, hash_): if isinstance(password, unicode): password = password.encode('utf-8') hash_method, hash_, salt = hash_.split('$', 2) salt = salt or '' if isinstance(salt, unicode): salt = salt.encode('utf-8') assert hash_method == 'sha1' return hash_ == hashlib.sha1(password + salt).hexdigest() @classmethod def hash_bcrypt(cls, password): if isinstance(password, unicode): password = password.encode('utf-8') hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8') return '$'.join(['bcrypt', hash_]) @classmethod def check_bcrypt(cls, password, hash_): if isinstance(password, unicode): password = password.encode('utf-8') hash_method, hash_ = hash_.split('$', 1) if isinstance(hash_, unicode): hash_ = hash_.encode('utf-8') assert hash_method == 'bcrypt' return hash_ == bcrypt.hashpw(password, hash_) def new_session(self): pool = Pool() Session = pool.get('web.user.session') return Session.add(self) @classmethod def get_user(cls, session): pool = Pool() Session = pool.get('web.user.session') return Session.get_user(session) @classmethod @ModelView.button def validate_email(cls, users, from_=None): for user in users: user.set_email_token() cls.save(users) _send_email(from_, users, cls.get_email_validation) def set_email_token(self, nbytes=None): self.email_token = token_hex(nbytes) def get_email_validation(self): return get_email('web.user.email_validation', self, self.languages) def get_email_validation_url(self, url=None): if url is None: url = config.get('web', 'email_validation_url') return _add_params(url, token=self.email_token) @classmethod def validate_email_url(cls, url): parts = urlparse.urlsplit(url) tokens = filter(None, urlparse.parse_qs(parts.query).get('token', [None])) return cls.validate_email_token(tokens) @classmethod def validate_email_token(cls, tokens): users = cls.search([ ('email_token', 'in', tokens), ]) cls.write(users, { 'email_valid': True, 'email_token': None, }) return bool(users) @classmethod @ModelView.button def reset_password(cls, users, from_=None): now = datetime.datetime.now() # Prevent abusive reset def reset(user): return not (user.reset_password_token_expire and user.reset_password_token_expire > now) users = filter(reset, users) for user in users: user.set_reset_password_token() cls.save(users) _send_email(from_, users, cls.get_email_reset_password) def set_reset_password_token(self, nbytes=None): self.reset_password_token = token_hex(nbytes) self.reset_password_token_expire = ( datetime.datetime.now() + datetime.timedelta(seconds=config.getint( 'session', 'web_timeout_reset', default=24 * 60 * 60))) def clear_reset_password_token(self): self.reset_password_token = None self.reset_password_token_expire = None def get_email_reset_password(self): return get_email('web.user.email_reset_password', self, self.languages) def get_email_reset_password_url(self, url=None): if url is None: url = config.get('web', 'reset_password_url') return _add_params(url, token=self.reset_password_token, email=self.email) @classmethod def set_password_url(cls, url, password): parts = urlparse.urlsplit(url) query = urlparse.parse_qs(parts.query) email = query.get('email', [None])[0] token = query.get('token', [None])[0] return cls.set_password_token(email, token, password) @classmethod def set_password_token(cls, email, token, password): pool = Pool() Attempt = pool.get('web.user.authenticate.attempt') email = email.lower() # Prevent brute force attack Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1) users = cls.search([ ('email', '=', email), ]) if users: user, = users if user.reset_password_token == token: now = datetime.datetime.now() expire = user.reset_password_token_expire user.clear_reset_password_token() if expire > now: user.password = password user.save() Attempt.remove(email) return True Attempt.add(email) return False @property def languages(self): pool = Pool() Language = pool.get('ir.lang') if self.party and self.party.lang: languages = [self.party.lang] else: languages = Language.search([ ('code', '=', Transaction().language), ]) return languages
class EmailTemplate(ModelSQL, ModelView): "Email Template" __name__ = 'ir.email.template' model = fields.Many2One('ir.model', "Model", required=True) name = fields.Char("Name", required=True, translate=True) recipients = fields.Many2One( 'ir.model.field', "Recipients", states={ 'invisible': Bool(Eval('recipients_pyson')), }, depends=['recipients_pyson'], help="The field that contains the recipient(s).") recipients_pyson = fields.Char( "Recipients", states={ 'invisible': Bool(Eval('recipients')), }, depends=['recipients'], help="A PYSON expression that generates a list of recipients " 'with the record represented by "self".') recipients_secondary = fields.Many2One( 'ir.model.field', "Secondary Recipients", states={ 'invisible': Bool(Eval('recipients_secondary_pyson')), }, depends=['recipients_secondary_pyson'], help="The field that contains the secondary recipient(s).") recipients_secondary_pyson = fields.Char( "Secondary Recipients", states={ 'invisible': Bool(Eval('recipients_secondary')), }, depends=['recipients_secondary'], help="A PYSON expression that generates a list " 'of secondary recipients with the record represented by "self".') recipients_hidden = fields.Many2One( 'ir.model.field', "Hidden Recipients", states={ 'invisible': Bool(Eval('recipients_hidden_pyson')), }, depends=['recipients_hidden_pyson'], help="The field that contains the secondary recipient(s).") recipients_hidden_pyson = fields.Char( "Hidden Recipients", states={ 'invisible': Bool(Eval('recipients_hidden')), }, depends=['recipients_hidden'], help="A PYSON expression that generates a list of hidden recipients " 'with the record represented by "self".') subject = fields.Char("Subject", translate=True) body = fields.Text("Body", translate=True) reports = fields.Many2Many('ir.email.template-ir.action.report', 'template', 'report', "Reports", domain=[ ('model', '=', Eval('model_name')), ], depends=['model_name']) model_name = fields.Function(fields.Char("Model Name"), 'on_change_with_model_name') @classmethod def __setup__(cls): super().__setup__() for field in [ 'recipients', 'recipients_secondary', 'recipients_hidden', ]: field = getattr(cls, field) field.domain = [('model', '=', Eval('model')), [ 'OR', ('relation', 'in', cls.email_models()), [ ('model.model', 'in', cls.email_models()), ('name', '=', 'id'), ], ]] field.depends.add('model') cls.__rpc__.update({ 'get': RPC(instantiate=0), 'get_default': RPC(), }) @fields.depends('model') def on_change_with_model_name(self, name=None): if self.model: return self.model.model @classmethod def validate_fields(cls, templates, field_names): super().validate_fields(templates, field_names) cls.check_subject(templates, field_names) cls.check_body(templates, field_names) cls.check_fields_pyson(templates, field_names) @classmethod def check_subject(cls, templates, field_names=None): if field_names and 'subject' not in field_names: return for template in templates: if not template.subject: continue try: TextTemplate(template.subject) except Exception as exception: raise EmailTemplateError( gettext('ir.msg_email_template_invalid_subject', template=template.rec_name, exception=exception)) from exception @classmethod def check_body(self, templates, field_names=None): if field_names and 'body' not in field_names: return for template in templates: if not template.body: continue try: TextTemplate(template.body) except Exception as exception: raise EmailTemplateError( gettext('ir.msg_email_template_invalid_body', template=template.rec_name, exception=exception)) from exception @classmethod def check_fields_pyson(cls, templates, field_names=None): pyson_fields = { 'recipients_pyson', 'recipients_secondary_pyson', 'recipients_hidden_pyson', } if field_names: pyson_fields &= field_names if not pyson_fields: return encoder = PYSONDecoder(noeval=True) for template in templates: for field in pyson_fields: value = getattr(template, field) if not value: continue try: pyson = encoder.decode(value) except Exception as exception: raise EmailTemplateError( gettext('ir.msg_email_template_invalid_field_pyson', template=template.rec_name, field=cls.__names__(field)['field'], exception=exception)) from exception if not isinstance(pyson, list) and pyson.types() != {list}: raise EmailTemplateError( gettext( 'ir.msg_email_template_invalid_field_pyson_type', template=template.rec_name, field=cls.__names__(field)['field'], )) def get(self, record): pool = Pool() Model = pool.get(self.model.model) record = Model(int(record)) values = {} for attr, key in [ ('recipients', 'to'), ('recipients_secondary', 'cc'), ('recipients_hidden', 'bcc'), ]: field = getattr(self, attr) try: if field: if field.name == 'id': value = record else: value = getattr(record, field.name, None) if value: values[key] = self.get_addresses(value) else: value = getattr(self, attr + '_pyson') if value: value = self.eval(record, value) if value: values[key] = self.get_addresses(value) except AccessError: continue if self.subject: try: values['subject'] = (TextTemplate(self.subject).generate( **self.get_context(record)).render()) except AccessError: pass if self.body: try: values['body'] = (TextTemplate( self.body).generate(**self.get_context(record)).render()) except AccessError: pass if self.reports: values['reports'] = [r.id for r in self.reports] return values def get_context(self, record): pool = Pool() User = pool.get('res.user') return { 'context': Transaction().context, 'user': User(Transaction().user), 'record': record, 'format_date': Report.format_date, 'format_datetime': Report.format_datetime, 'format_timedelta': Report.format_timedelta, 'format_currency': Report.format_currency, 'format_number': Report.format_number, } def eval(self, record, pyson, _env=None): 'Evaluate the pyson with the record' if _env is None: env = {} else: env = _env.copy() env['context'] = Transaction().context env['self'] = EvalEnvironment(record, record.__class__) return PYSONDecoder(env).decode(pyson) @classmethod def _get_default_exclude(cls, record): return ['create_uid', 'write_uid'] @classmethod def get_default(cls, model, record): pool = Pool() Field = pool.get('ir.model.field') Model = pool.get(model) record = Model(int(record)) values = {} fields = Field.search([ ('model.model', '=', model), ('name', 'not in', cls._get_default_exclude(record)), [ 'OR', ('relation', 'in', cls.email_models()), [ ('model.model', 'in', cls.email_models()), ('name', '=', 'id'), ], ], ]) addresses = set() for field in fields: try: if field.name == 'id': value = record else: value = getattr(record, field.name) addresses.update(cls.get_addresses(value)) except AccessError: pass values['to'] = list(addresses) try: values['subject'] = '%s: %s' % (Model.__names__()['model'], record.rec_name) except AccessError: pass return values @classmethod def email_models(cls): return ['res.user'] @classmethod def get_addresses(cls, value): if isinstance(value, (list, tuple)): addresses = (cls._get_address(v) for v in value) else: addresses = [cls._get_address(value)] return [ _formataddr((name, email)) for name, email in filter(None, addresses) if email ] @classmethod def _get_address(cls, record): pool = Pool() User = pool.get('res.user') if isinstance(record, str): return (None, record) elif isinstance(record, User) and record.email: return (record.name, record.email) @classmethod def get_languages(cls, value): pool = Pool() Configuration = pool.get('ir.configuration') Lang = pool.get('ir.lang') if isinstance(value, (list, tuple)): languagues = {cls._get_language(v) for v in value} else: languagues = {cls._get_language(value)} languagues = list(filter(None, languagues)) if not languagues: return Lang.search([ ('code', '=', Configuration.get_language()), ], limit=1) return languagues @classmethod def _get_language(cls, record): pool = Pool() User = pool.get('res.user') if isinstance(record, User) and record.language: return record.language
class Category(CompanyMultiValueMixin, metaclass=PoolMeta): __name__ = 'product.category' accounting = fields.Boolean('Accounting', select=True, states={ 'readonly': Bool(Eval('childs', [0])) | Bool(Eval('parent')), }, depends=['parent'], help="Check to convert into accounting category.") account_parent = fields.Boolean('Use Parent\'s accounts', states={ 'invisible': ~Eval('accounting', False), }, depends=['accounting'], help="Use the accounts defined on the parent category.") accounts = fields.One2Many( 'product.category.account', 'category', "Accounts") account_expense = fields.MultiValue(fields.Many2One('account.account', 'Account Expense', domain=[ ('kind', '=', 'expense'), ('company', '=', Eval('context', {}).get('company', -1)), ], states={ 'invisible': (~Eval('context', {}).get('company') | Eval('account_parent') | ~Eval('accounting', False)), }, depends=['account_parent', 'accounting'])) account_revenue = fields.MultiValue(fields.Many2One('account.account', 'Account Revenue', domain=[ ('kind', '=', 'revenue'), ('company', '=', Eval('context', {}).get('company', -1)), ], states={ 'invisible': (~Eval('context', {}).get('company') | Eval('account_parent') | ~Eval('accounting', False)), }, depends=['account_parent', 'accounting'])) taxes_parent = fields.Boolean('Use the Parent\'s Taxes', states={ 'invisible': ~Eval('accounting', False), }, depends=['accounting'], help="Use the taxes defined on the parent category.") customer_taxes = fields.Many2Many('product.category-customer-account.tax', 'category', 'tax', 'Customer Taxes', order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')], domain=[('parent', '=', None), ['OR', ('group', '=', None), ('group.kind', 'in', ['sale', 'both'])], ], states={ 'invisible': (~Eval('context', {}).get('company') | Eval('taxes_parent') | ~Eval('accounting', False)), }, depends=['taxes_parent', 'accounting'], help="The taxes to apply when selling products of this category.") supplier_taxes = fields.Many2Many('product.category-supplier-account.tax', 'category', 'tax', 'Supplier Taxes', order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')], domain=[('parent', '=', None), ['OR', ('group', '=', None), ('group.kind', 'in', ['purchase', 'both'])], ], states={ 'invisible': (~Eval('context', {}).get('company') | Eval('taxes_parent') | ~Eval('accounting', False)), }, depends=['taxes_parent', 'accounting'], help="The taxes to apply when purchasing products of this category.") customer_taxes_used = fields.Function(fields.One2Many('account.tax', None, 'Customer Taxes Used'), 'get_taxes') supplier_taxes_used = fields.Function(fields.One2Many('account.tax', None, 'Supplier Taxes Used'), 'get_taxes') @classmethod def __setup__(cls): super(Category, cls).__setup__() cls._error_messages.update({ 'missing_account': ('There is no ' '"%(field)s" defined on the category "%(name)s"'), }) cls.parent.domain = [ ('accounting', '=', Eval('accounting', False)), cls.parent.domain or []] cls.parent.depends.append('accounting') cls.parent.states['required'] = Or( cls.parent.states.get('required', False), Eval('account_parent', False) | Eval('taxes_parent', False)) cls.parent.depends.extend(['account_parent', 'taxes_parent']) @classmethod def multivalue_model(cls, field): pool = Pool() if field in {'account_expense', 'account_revenue'}: return pool.get('product.category.account') return super(Category, cls).multivalue_model(field) @classmethod def default_accounting(cls): return False @classmethod def default_account_expense(cls, **pattern): pool = Pool() Configuration = pool.get('account.configuration') config = Configuration(1) account = config.get_multivalue( 'default_category_account_expense', **pattern) return account.id if account else None @classmethod def default_account_revenue(cls, **pattern): pool = Pool() Configuration = pool.get('account.configuration') config = Configuration(1) account = config.get_multivalue( 'default_category_account_revenue', **pattern) return account.id if account else None def get_account(self, name, **pattern): if self.account_parent: return self.parent.get_account(name, **pattern) else: return self.get_multivalue(name[:-5], **pattern) def get_taxes(self, name): if self.taxes_parent: return [x.id for x in getattr(self.parent, name)] else: return [x.id for x in getattr(self, name[:-5])] @fields.depends('parent', 'accounting') def on_change_with_accounting(self): if self.parent: return self.parent.accounting return self.accounting @fields.depends('account_expense') def on_change_account_expense(self): if self.account_expense: self.supplier_taxes = self.account_expense.taxes else: self.supplier_taxes = [] @fields.depends('account_revenue') def on_change_account_revenue(self): if self.account_revenue: self.customer_taxes = self.account_revenue.taxes else: self.customer_taxes = [] @classmethod def view_attributes(cls): return super(Category, cls).view_attributes() + [ ('/form/notebook/page[@id="accounting"]', 'states', { 'invisible': ~Eval('accounting', False), }), ] @property @account_used('account_expense') def account_expense_used(self): pass @property @account_used('account_revenue') def account_revenue_used(self): pass
class Email(ResourceAccessMixin, ModelSQL, ModelView): "Email" __name__ = 'ir.email' user = fields.Function(fields.Char("User"), 'get_user') at = fields.Function(fields.DateTime("At"), 'get_at') recipients = fields.Char("Recipients", readonly=True) recipients_secondary = fields.Char("Secondary Recipients", readonly=True) recipients_hidden = fields.Char("Hidden Recipients", readonly=True) addresses = fields.One2Many('ir.email.address', 'email', "Addresses", readonly=True) subject = fields.Char("Subject", readonly=True) body = fields.Text("Body", readonly=True) @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('create_date', 'DESC')) cls.__rpc__.update({ 'send': RPC(readonly=False, result=int), 'complete': RPC(check_access=False), }) del cls.__rpc__['create'] def get_user(self, name): return self.create_uid.rec_name def get_at(self, name): return self.create_date.replace(microsecond=0) @classmethod def send(cls, to='', cc='', bcc='', subject='', body='', files=None, record=None, reports=None, attachments=None): pool = Pool() User = pool.get('res.user') ActionReport = pool.get('ir.action.report') Attachment = pool.get('ir.attachment') transaction = Transaction() user = User(transaction.user) Model = pool.get(record[0]) record = Model(record[1]) body_html = HTML_EMAIL % { 'subject': subject, 'body': body, 'signature': user.signature or '', } content = MIMEMultipart('alternative') if html2text: body_text = HTML_EMAIL % { 'subject': subject, 'body': body, 'signature': '', } converter = html2text.HTML2Text() body_text = converter.handle(body_text) if user.signature: body_text += '\n-- \n' + converter.handle(user.signature) part = MIMEText(body_text, 'plain', _charset='utf-8') content.attach(part) part = MIMEText(body_html, 'html', _charset='utf-8') content.attach(part) if files or reports or attachments: msg = MIMEMultipart('mixed') msg.attach(content) if files is None: files = [] else: files = list(files) for report_id in (reports or []): report = ActionReport(report_id) Report = pool.get(report.report_name, type='report') ext, content, _, title = Report.execute([record.id], { 'action_id': report.id, }) name = '%s.%s' % (title, ext) if isinstance(content, str): content = content.encode('utf-8') files.append((name, content)) if attachments: files += [(a.name, a.data) for a in Attachment.browse(attachments)] for name, data in files: mimetype, _ = mimetypes.guess_type(name) if mimetype: attachment = MIMENonMultipart(*mimetype.split('/')) attachment.set_payload(data) encode_base64(attachment) else: attachment = MIMEApplication(data) attachment.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', name)) msg.attach(attachment) else: msg = content from_ = config.get('email', 'from') set_from_header(msg, from_, user.email or from_) msg['To'] = ', '.join(formataddr(a) for a in getaddresses([to])) msg['Cc'] = ', '.join(formataddr(a) for a in getaddresses([cc])) msg['Subject'] = Header(subject, 'utf-8') to_addrs = list( filter( None, map(str.strip, _get_emails(to) + _get_emails(cc) + _get_emails(bcc)))) sendmail_transactional(from_, to_addrs, msg, datamanager=SMTPDataManager(strict=True)) email = cls(recipients=to, recipients_secondary=cc, recipients_hidden=bcc, addresses=[{ 'address': a } for a in to_addrs], subject=subject, body=body, resource=record) email.save() with Transaction().set_context(_check_access=False): attachments_ = [] for name, data in files: attachments_.append( Attachment(resource=email, name=name, data=data)) Attachment.save(attachments_) return email @classmethod def complete(cls, text, limit): limit = int(limit) if not limit > 0: raise ValueError('limit must be > 0: %r' % (limit, )) emails = getaddresses([text]) if not emails: return [] name, email = map(str.strip, emails[-1]) if not name and not email: return [] s = StringMatcher() try: s.set_seq2(_formataddr((name, email))) except UnicodeEncodeError: return [] def generate(name, email): for name, email in cls._match(name, email): try: address = _formataddr((name, email)) except UnicodeEncodeError: continue s.set_seq1(address) yield (s.ratio(), address, ', '.join( map(_formataddr, emails[:-1] + [(name, email)]))) return heapq.nlargest(limit, generate(name, email)) @classmethod def _match(cls, name, email): pool = Pool() User = pool.get('res.user') domain = ['OR'] for field in ['name', 'login', 'email']: for value in [name, email]: if value and len(value) >= 3: domain.append( (field, 'ilike', '%' + escape_wildcard(value) + '%')) for user in User.search([ ('email', '!=', ''), domain, ], order=[]): yield user.name, user.email
class Subscription(ModelSQL, ModelView): "Subscription" __name__ = 'sale.subscription' student = fields.Many2One('party.party', "Student", required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state', 'company'], domain=[ 'AND', [('is_student', '=', True)], [('company', '=', Eval('company', -1))], ], help="The student who subscribe.") origin = fields.Reference('Origin', selection='get_origin', select=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) invoices = fields.One2Many('account.invoice', 'subscription_origin', 'Invoices') amount = fields.Function( fields.Numeric( 'Total', digits=(16, Eval('currency_digits', 2)), ), 'get_amount') registration_date = fields.Date('Fecha de inscripcion', required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) section = fields.Char('Section', required=False, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'get_currency_digits') receivable = fields.Function( fields.Numeric( 'Receivable', digits=(16, 2), ), 'get_receivable_payable') enrolment = fields.Many2One('product.product', 'Enrolment', domain=[ 'AND', [('is_enrolment', '=', True)], [('company', '=', Eval('company', -1))], ], required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state', 'company']) unit_price_enrolment = fields.Numeric( "Enrolment Price", digits=(16, 2), required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state'], ) grade = fields.Function(fields.Many2One('sale.subscription.service', 'Grade'), 'get_grade', searcher='search_grade') @classmethod def search_grade(cls, name, clause): return [('lines.service', ) + tuple(clause[1:])] @fields.depends('enrolment', 'currency', 'party', 'start_date') def on_change_enrolment(self): pool = Pool() Product = pool.get('product.product') if not self.enrolment: return party = None quantity = 1 party_context = {} if self.party: party = self.party if party.lang: party_context['language'] = party.lang.code enrolment = self.enrolment category = enrolment.sale_uom.category if self.enrolment: unit = enrolment.sale_uom unit_digits = enrolment.sale_uom.digits with Transaction().set_context(self._get_context_sale_price()): self.unit_price_enrolment = Product.get_sale_price( [enrolment], quantity or 0)[enrolment.id] if self.unit_price_enrolment: self.unit_price_enrolment = self.unit_price_enrolment.quantize( Decimal(1) / 10**self.__class__.unit_price_enrolment.digits[1]) def _get_context_sale_price(self): context = {} '''if getattr(self, 'subscription', None): if getattr('currency', None): context['currency'] = self.currency.id if getattr('party', None): context['customer'] = self.party.id if getattr('start_date', None): context['sale_date'] = self.start_date''' if self.currency: context['currency'] = self.currency.id if self.party: context['customer'] = self.party.id if self.start_date: context['sale_date'] = self.start_date if self.enrolment: context['uom'] = self.enrolment.default_uom.id return context @classmethod def __setup__(cls): super(Subscription, cls).__setup__() cls.party.depends = ['company', 'state'] cls.party.states = {'readonly': Eval('state') != 'draft'} cls.company.domain = [] cls.party.domain = [ 'AND', [('is_subscriber', '=', True)], [('company', '=', Eval('company', -1))], ] cls.lines.required = True cls.end_date.required = True cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['draft', 'quotation']), 'icon': 'tryton-cancel', }, 'draft': { 'invisible': Eval('state').in_(['draft', 'closed']), 'icon': If( Eval('state') == 'canceled', 'tryton-clear', 'tryton-go-previous'), }, 'quote': { 'pre_validate': [ 'OR', ('registration_date', '!=', None), ('enrolment', '!=', None), ('lines', '!=', []), ], 'invisible': Eval('state') != 'draft', 'icon': 'tryton-go-next', }, 'run': { #'invisible': Eval('state') == 'running', 'invisible': Eval('state').in_(['running', 'canceled']), 'icon': 'tryton-go-next', }, }) cls._transitions |= set(( ('draft', 'canceled'), ('draft', 'quotation'), ('draft', 'running'), ('quotation', 'canceled'), ('quotation', 'draft'), ('quotation', 'running'), ('running', 'draft'), ('running', 'closed'), ('canceled', 'draft'), )) cls._error_messages.update({ 'missing_account_enrolment': ("You need to define an enrolment account."), }) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [ bool_op, ('party', ) + tuple(clause[1:]), ('student', ) + tuple(clause[1:]), ] def get_receivable_payable(self, name): amount = self.student.receivable return amount def get_grade(self, name): return self.lines[0].service.id if self.lines else None def get_amount(self, name): amount = Decimal('0.0') if self.lines: for line in self.lines: if line.unit_price is not None: amount += line.unit_price return amount def _create_party_address(self, party): pool = Pool() Party = pool.get('party.party') PartyAddress = pool.get('party.address') address = PartyAddress(party=party, city="Guatemala") address.save() #print "PARTY ID: " + str(address.id) +"ADDRESS: " + str(address.city) + "PARTY: " + str(address.party) #print "PARTY ADDRESSES: " + str(party.addresses) return address def _get_invoice(self, invoice_date=None): pool = Pool() Date = pool.get('ir.date') Invoice = pool.get('account.invoice') address = self.student.address_get(type='invoice') if address is None: self._create_party_address(self.student) invoice = Invoice( company=self.company, type='out', party=self.student, invoice_address=self.student.address_get(type='invoice'), currency=self.currency, invoice_date=invoice_date, accounting_date=invoice_date, reference='MAT: ' + self.number, description=mes_actual(invoice_date), account=self.party.account_receivable, subscription_origin=self, ) invoice.on_change_type() invoice.payment_term = self.payment_term return invoice @classmethod def _check_enrolment(cls, party=None, reference=None): pool = Pool() Invoice = pool.get('account.invoice') invoices = Invoice.search([ ('party', '=', party), ('reference', '=', reference), ('is_enrolment', '=', True), ]) if invoices: return True return False def compute_next_invoice_date(self): start_date = self.invoice_start_date or self.start_date date = self.next_invoice_date or self.start_date rruleset = self.invoice_recurrence.rruleset(start_date) dt = datetime.datetime.combine(date, datetime.time()) inc = (start_date == date) and not self.next_invoice_date next_date = rruleset.after(dt, inc=inc) return next_date.date() def create_enrolment_invoice(self): 'Create and return an enrolment invoice' pool = Pool() Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') Uom = pool.get('product.uom') AccountConfiguration = pool.get('account.configuration') account_config = AccountConfiguration(1) pool = Pool() Invoice = pool.get('account.invoice') address = self.student.address_get(type='invoice') if address is None: self._create_party_address(self.student) invoice = Invoice( company=self.company, type='out', party=self.student, invoice_address=self.student.address_get(type='invoice'), currency=self.currency, reference='MAT: ' + str(self.number), description='INS - ' + str(self.registration_date.year), invoice_date=self.registration_date, accounting_date=self.registration_date, is_enrolment=True, subscription_origin=self, ) invoice.on_change_type() invoice.payment_term = self.payment_term default_enrolment_account = account_config.get_multivalue( 'default_enrolment_account') default_enrolment_revenue = account_config.get_multivalue( 'default_enrolment_revenue') if default_enrolment_account is None: self.raise_user_error('missing_account_enrolment') if default_enrolment_revenue is None: self.raise_user_error('missing_account_enrolment') invoice.account = default_enrolment_account invoice.save() invoice.update_taxes([invoice]) line = InvoiceLine() line.invoice_type = 'out' line.type = 'line' line.quantity = 1 line.unit = self.enrolment.default_uom line.unit_price = self.unit_price_enrolment line.product = self.enrolment line.description = self.enrolment.name line.party = self.student line.account = default_enrolment_revenue line.invoice = invoice if not line.account: cls.raise_user_error('missing_account_revenue', { 'product': enrolment.rec_name, }) taxes = [] pattern = line._get_tax_rule_pattern() party = invoice.party for tax in line.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) line.taxes = taxes line.save() return invoice @classmethod def generate_invoice(cls, date=None, party=None, enrolment=None): pool = Pool() Date = pool.get('ir.date') Consumption = pool.get('sale.subscription.line.consumption') Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') Subscription = pool.get('sale.subscription') company = Transaction().context.get('company') if date is None: date = Date.today() consumptions = Consumption.search( [('invoice_line', '=', None), ('line.subscription.next_invoice_date', '<=', date), ('line.subscription.state', 'in', ['running', 'closed']), ('company', '=', company)], order=[ ('line.subscription.id', 'DESC'), ]) def keyfunc(consumption): return consumption.line.subscription invoices = {} lines = {} if consumptions: invoice_date = consumptions[0].date for subscription, consumptions in groupby(consumptions, key=keyfunc): invoices[subscription] = invoice = subscription._get_invoice( invoice_date) lines[subscription] = Consumption.get_invoice_lines( consumptions, invoice) all_invoices = invoices.values() Invoice.save(all_invoices) all_invoice_lines = [] for subscription, invoice in invoices.items(): invoice_lines, _ = lines[subscription] for line in invoice_lines: line.invoice = invoice all_invoice_lines.extend(invoice_lines) InvoiceLine.save(all_invoice_lines) all_consumptions = [] for values in lines.values(): for invoice_line, consumptions in zip(*values): for consumption in consumptions: assert not consumption.invoice_line consumption.invoice_line = invoice_line all_consumptions.append(consumption) Consumption.save(all_consumptions) Invoice.update_taxes(all_invoices) subscriptions = cls.search([('next_invoice_date', '<=', date), ('company', '=', company)]) for subscription in subscriptions: if subscription.state == 'running': while subscription.next_invoice_date <= date: subscription.next_invoice_date = ( subscription.compute_next_invoice_date()) else: subscription.next_invoice_date = None for subscription in subscriptions: # check invoice enrolment party = subscription.student.id subscription_reference = 'MAT: ' + str(subscription.number) exist_enrolment = subscription._check_enrolment( party, subscription_reference) if not exist_enrolment: subscription.create_enrolment_invoice() cls.save(subscriptions) @classmethod def generate_global_invoice(cls, date=None, party=None, enrolment=None): pool = Pool() Date = pool.get('ir.date') Consumption = pool.get('sale.subscription.line.consumption') Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') Subscription = pool.get('sale.subscription') if date is None: date = Date.today() consumptions = Consumption.search([ ('invoice_line', '=', None), ('line.subscription.next_invoice_date', '<=', date), ('line.subscription.state', 'in', ['running', 'closed']), ], order=[ ('line.subscription.id', 'DESC'), ]) def keyfunc(consumption): return consumption.line.subscription invoices = {} lines = {} if consumptions: invoice_date = consumptions[0].date for subscription, consumptions in groupby(consumptions, key=keyfunc): invoices[subscription] = invoice = subscription._get_invoice( invoice_date) lines[subscription] = Consumption.get_invoice_lines( consumptions, invoice) all_invoices = invoices.values() Invoice.save(all_invoices) all_invoice_lines = [] for subscription, invoice in invoices.items(): invoice_lines, _ = lines[subscription] for line in invoice_lines: line.invoice = invoice all_invoice_lines.extend(invoice_lines) InvoiceLine.save(all_invoice_lines) all_consumptions = [] for values in lines.values(): for invoice_line, consumptions in zip(*values): for consumption in consumptions: assert not consumption.invoice_line consumption.invoice_line = invoice_line all_consumptions.append(consumption) Consumption.save(all_consumptions) Invoice.update_taxes(all_invoices) subscriptions = cls.search([ ('next_invoice_date', '<=', date), ]) for subscription in subscriptions: if subscription.state == 'running': while subscription.next_invoice_date <= date: subscription.next_invoice_date = ( subscription.compute_next_invoice_date()) else: subscription.next_invoice_date = None cls.save(subscriptions) @classmethod @ModelView.button @Workflow.transition('running') @process_opportunity def run(cls, subscriptions): cls.set_number(subscriptions) pool = Pool() Line = pool.get('sale.subscription.line') Subscription = pool.get('sale.subscription') Party = pool.get('party.party') lines = [] state = 'none' party = '' enrolment = '' for subscription in subscriptions: subscription.state = 'running' start_date = subscription.start_date state = subscription.state if not subscription.next_invoice_date: subscription.next_invoice_date = ( subscription.compute_next_invoice_date()) for line in subscription.lines: if (line.next_consumption_date is None and not line.consumed): line.next_consumption_date = ( line.compute_next_consumption_date()) lines.extend(subscription.lines) party = subscription.party.id enrolment = subscription.enrolment Line.save(lines) cls.save(subscriptions) Line.generate_consumption(date=start_date) Subscription.generate_invoice(date=start_date, party=party, enrolment=enrolment) @classmethod def _get_origin(cls): 'Return list of Model names for origin Reference' return ['sale.subscription', 'sale.opportunity'] @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 @process_opportunity def delete(cls, subscriptions): super(Subscription, cls).delete(subscriptions) @classmethod @process_opportunity def cancel(cls, subscriptions): super(Subscription, cls).cancel(subscriptions) @classmethod @process_opportunity def draft(cls, subscriptions): super(Subscription, cls).draft(subscriptions) @classmethod @process_opportunity def quote(cls, subscriptions): super(Subscription, cls).quote(subscriptions) @classmethod @process_opportunity def process(cls, subscriptions): super(Subscription, cls).process(subscriptions) @classmethod def default_invoice_recurrence(cls): Recurrence = Pool().get('sale.subscription.recurrence.rule.set') recurrences = Recurrence.search([]) if recurrences: return recurrences[0].id return None @classmethod def default_registration_date(cls): Date = Pool().get('ir.date') date = Date.today() return date @classmethod def default_start_date(cls): Date = Pool().get('ir.date') today = Date.today() first_date = date(today.year, 1, 1) return first_date @classmethod def default_end_date(cls): Date = Pool().get('ir.date') today = Date.today() end_date = date(today.year, 10, 1) return end_date @classmethod def search(cls, domain, offset=0, limit=None, order=None, count=False, query=False): transaction = Transaction().context student = transaction.get('student') subscriber = transaction.get('subscriber') course = transaction.get('course') domain = domain[:] if student is not None: domain = [domain, ('student', '=', student)] if subscriber is not None: domain = [domain, ('party', '=', subscriber)] if course is not None: domain = [domain, ('lines.service', '=', course)] records = super(Subscription, cls).search(domain, offset=offset, limit=limit, order=order, count=count, query=query) if Transaction().user: # Clear the cache as it was not cleaned for confidential cache = Transaction().get_cache() cache.pop(cls.__name__, None) return records
class Module(ModelSQL, ModelView): "Module" __name__ = "ir.module" name = fields.Char("Name", readonly=True, required=True) version = fields.Function(fields.Char('Version'), 'get_version') dependencies = fields.One2Many('ir.module.dependency', 'module', 'Dependencies', readonly=True) parents = fields.Function(fields.One2Many('ir.module', None, 'Parents'), 'get_parents') childs = fields.Function(fields.One2Many('ir.module', None, 'Childs'), 'get_childs') state = fields.Selection([ ('not activated', 'Not Activated'), ('activated', 'Activated'), ('to upgrade', 'To be upgraded'), ('to remove', 'To be removed'), ('to activate', 'To be activated'), ], string='State', readonly=True) @classmethod def __setup__(cls): super(Module, cls).__setup__() table = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(table, table.name), 'The name of the module must be unique!'), ] cls._order.insert(0, ('name', 'ASC')) cls.__rpc__.update({ 'on_write': RPC(instantiate=0), }) cls._buttons.update({ 'activate': { 'invisible': Eval('state') != 'not activated', 'depends': ['state'], }, 'activate_cancel': { 'invisible': Eval('state') != 'to activate', 'depends': ['state'], }, 'deactivate': { 'invisible': Eval('state') != 'activated', 'depends': ['state'], }, 'deactivate_cancel': { 'invisible': Eval('state') != 'to remove', 'depends': ['state'], }, 'upgrade': { 'invisible': Eval('state') != 'activated', 'depends': ['state'], }, 'upgrade_cancel': { 'invisible': Eval('state') != 'to upgrade', 'depends': ['state'], }, }) @classmethod def __register__(cls, module_name): pool = Pool() ModelData = pool.get('ir.model.data') sql_table = cls.__table__() model_data_sql_table = ModelData.__table__() cursor = Transaction().connection.cursor() # Migration from 3.6: remove double module old_table = 'ir_module_module' if backend.TableHandler.table_exist(old_table): backend.TableHandler.table_rename(old_table, cls._table) super(Module, cls).__register__(module_name) # Migration from 4.0: rename installed to activated cursor.execute(*sql_table.update([sql_table.state], ['activated'], where=sql_table.state == 'installed')) cursor.execute( *sql_table.update([sql_table.state], ['not activated'], where=sql_table.state == 'uninstalled')) # Migration from 4.6: register buttons on ir module button_fs_ids = [ 'module_activate_button', 'module_activate_cancel_button', 'module_deactivate_button', 'module_deactivate_cancel_button', 'module_upgrade_button', 'module_upgrade_cancel_button', ] cursor.execute(*model_data_sql_table.update( [model_data_sql_table.module], ['ir'], where=((model_data_sql_table.module == 'res') & (model_data_sql_table.fs_id.in_(button_fs_ids))))) @staticmethod def default_state(): return 'not activated' def get_version(self, name): return get_module_info(self.name).get('version', '') @classmethod def get_parents(cls, modules, name): parent_names = list( set(d.name for m in modules for d in m.dependencies)) parents = cls.search([ ('name', 'in', parent_names), ]) name2id = dict((m.name, m.id) for m in parents) return dict( (m.id, [name2id[d.name] for d in m.dependencies]) for m in modules) @classmethod def get_childs(cls, modules, name): child_ids = dict((m.id, []) for m in modules) name2id = dict((m.name, m.id) for m in modules) childs = cls.search([ ('dependencies.name', 'in', list(name2id.keys())), ]) for child in childs: for dep in child.dependencies: if dep.name in name2id: child_ids[name2id[dep.name]].append(child.id) return child_ids @classmethod def delete(cls, records): for module in records: if module.state in ( 'activated', 'to upgrade', 'to remove', 'to activate', ): raise AccessError(gettext('ir.msg_module_delete_state')) return super(Module, cls).delete(records) @classmethod def on_write(cls, modules): dependencies = set() def get_parents(module): parents = set(p.id for p in module.parents) for p in module.parents: parents.update(get_parents(p)) return parents def get_childs(module): childs = set(c.id for c in module.childs) for c in module.childs: childs.update(get_childs(c)) return childs for module in modules: dependencies.update(get_parents(module)) dependencies.update(get_childs(module)) return list(dependencies) @classmethod @ModelView.button @filter_state('not activated') def activate(cls, modules): modules_activated = set(modules) def get_parents(module): parents = set(p for p in module.parents) for p in module.parents: parents.update(get_parents(p)) return parents for module in modules: modules_activated.update( (m for m in get_parents(module) if m.state == 'not activated')) cls.write(list(modules_activated), { 'state': 'to activate', }) @classmethod @ModelView.button @filter_state('activated') def upgrade(cls, modules): modules_activated = set(modules) def get_childs(module): childs = set(c for c in module.childs) for c in module.childs: childs.update(get_childs(c)) return childs for module in modules: modules_activated.update( (m for m in get_childs(module) if m.state == 'activated')) cls.write(list(modules_activated), { 'state': 'to upgrade', }) @classmethod @ModelView.button @filter_state('to activate') def activate_cancel(cls, modules): cls.write(modules, { 'state': 'not activated', }) @classmethod @ModelView.button @filter_state('activated') def deactivate(cls, modules): pool = Pool() Module = pool.get('ir.module') Dependency = pool.get('ir.module.dependency') module_table = Module.__table__() dep_table = Dependency.__table__() cursor = Transaction().connection.cursor() for module in modules: cursor.execute(*dep_table.join( module_table, condition=(dep_table.module == module_table.id) ).select( module_table.state, module_table.name, where=(dep_table.name == module.name) & NotIn(module_table.state, ['not activated', 'to remove']))) res = cursor.fetchall() if res: raise DeactivateDependencyError( gettext('ir.msg_module_deactivate_dependency'), '\n'.join('\t%s: %s' % (x[0], x[1]) for x in res)) cls.write(modules, {'state': 'to remove'}) @classmethod @ModelView.button @filter_state('to remove') def deactivate_cancel(cls, modules): cls.write(modules, {'state': 'not activated'}) @classmethod @ModelView.button @filter_state('to upgrade') def upgrade_cancel(cls, modules): cls.write(modules, {'state': 'activated'}) @classmethod def update_list(cls): 'Update the list of available packages' count = 0 module_names = get_module_list() modules = cls.search([]) name2id = dict((m.name, m.id) for m in modules) cls.delete([ m for m in modules if m.state != 'activated' and m.name not in module_names ]) # iterate through activated modules and mark them as being so for name in module_names: if name in name2id: module = cls(name2id[name]) tryton = get_module_info(name) cls._update_dependencies(module, tryton.get('depends', [])) continue tryton = get_module_info(name) if not tryton: continue module, = cls.create([{ 'name': name, 'state': 'not activated', }]) count += 1 cls._update_dependencies(module, tryton.get('depends', [])) return count @classmethod def _update_dependencies(cls, module, depends=None): pool = Pool() Dependency = pool.get('ir.module.dependency') Dependency.delete( [x for x in module.dependencies if x.name not in depends]) if depends is None: depends = [] # Restart Browse Cache for deleted dependencies module = cls(module.id) dependency_names = [x.name for x in module.dependencies] to_create = [] for depend in depends: if depend not in dependency_names: to_create.append({ 'module': module.id, 'name': depend, }) if to_create: Dependency.create(to_create)
class BOMInput(ModelSQL, ModelView): "Bill of Material Input" __name__ = 'production.bom.input' _rec_name = 'product' bom = fields.Many2One('production.bom', 'BOM', required=True, select=1, ondelete='CASCADE') product = fields.Many2One('product.product', 'Product', required=True, domain=[ ('type', '!=', 'service'), ]) uom_category = fields.Function( fields.Many2One('product.uom.category', 'Uom Category'), 'on_change_with_uom_category') uom = fields.Many2One('product.uom', 'Uom', required=True, domain=[ ('category', '=', Eval('uom_category')), ], depends=['uom_category']) unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') quantity = fields.Float('Quantity', required=True, domain=[('quantity', '>=', 0)], digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) @classmethod def __setup__(cls): super(BOMInput, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('product_bom_uniq', Unique(t, t.product, t.bom), 'product_bom_uniq'), ] cls._error_messages.update({ 'product_bom_uniq': 'Product must be unique per BOM.', 'recursive_bom': 'You can not create recursive BOMs.', }) @fields.depends('product', 'uom') def on_change_product(self): if self.product: uoms = self.product.default_uom.category.uoms if (not self.uom or self.uom not in uoms): self.uom = self.product.default_uom self.unit_digits = self.product.default_uom.digits else: self.uom = None self.unit_digits = 2 @fields.depends('product') def on_change_with_uom_category(self, name=None): if self.product: return self.product.default_uom.category.id @fields.depends('uom') def on_change_with_unit_digits(self, name=None): if self.uom: return self.uom.digits return 2 @classmethod def validate(cls, boms): super(BOMInput, cls).validate(boms) for bom in boms: bom.check_bom_recursion() def check_bom_recursion(self): ''' Check BOM recursion ''' self.product.check_bom_recursion() def compute_quantity(self, factor): return self.uom.round(self.quantity * factor)
class Sale: "Sale" __name__ = 'sale.sale' endicia_mailclass = fields.Many2One( 'endicia.mailclass', 'MailClass', states={ 'readonly': ~Eval('state').in_(['draft', 'quotation']), }, depends=['state']) endicia_mailpiece_shape = fields.Selection( MAILPIECE_SHAPES, 'Endicia MailPiece Shape', states={ 'readonly': ~Eval('state').in_(['draft', 'quotation']), }, depends=['state']) is_endicia_shipping = fields.Function( fields.Boolean('Is Endicia Shipping?', readonly=True), 'get_is_endicia_shipping') @classmethod def view_attributes(cls): return super(Sale, cls).view_attributes() + [ ('//page[@id="endicia"]', 'states', { 'invisible': ~Bool(Eval('is_endicia_shipping')) }) ] def _get_weight_uom(self): """ Returns uom for endicia """ UOM = Pool().get('product.uom') if self.is_endicia_shipping: # Endicia by default uses this uom return UOM.search([('symbol', '=', 'oz')])[0] return super(Sale, self)._get_weight_uom() @staticmethod def default_endicia_mailclass(): Config = Pool().get('sale.configuration') config = Config(1) return config.endicia_mailclass and config.endicia_mailclass.id or None @classmethod def __setup__(cls): super(Sale, cls).__setup__() cls._error_messages.update({ 'mailclass_missing': 'Select a mailclass to ship using Endicia [USPS].' }) cls._buttons.update({ 'update_endicia_shipment_cost': { 'invisible': Eval('state') != 'quotation' } }) @fields.depends('is_endicia_shipping', 'carrier') def on_change_carrier(self): super(Sale, self).on_change_carrier() self.is_endicia_shipping = self.carrier and \ self.carrier.carrier_cost_method == 'endicia' or None def _get_carrier_context(self): "Pass sale in the context" context = super(Sale, self)._get_carrier_context() if not self.carrier.carrier_cost_method == 'endicia': return context context = context.copy() context['sale'] = self.id return context def on_change_lines(self): """Pass a flag in context which indicates the get_sale_price method of endicia carrier not to calculate cost on each line change """ with Transaction().set_context({'ignore_carrier_computation': True}): return super(Sale, self).on_change_lines() def apply_endicia_shipping(self): "Add a shipping line to sale for endicia" Currency = Pool().get('currency.currency') if self.carrier and self.carrier.carrier_cost_method == 'endicia': if not self.endicia_mailclass: self.raise_user_error('mailclass_missing') with Transaction().set_context(self._get_carrier_context()): shipment_cost_usd = self.carrier.get_sale_price() if not shipment_cost_usd[0]: return # Convert the shipping cost to sale currency from USD usd, = Currency.search([('code', '=', 'USD')]) shipment_cost = Currency.compute(usd, shipment_cost_usd[0], self.currency) self.add_shipping_line( shipment_cost, '%s - %s' % (self.carrier.party.name, self.endicia_mailclass.name)) @classmethod def quote(cls, sales): res = super(Sale, cls).quote(sales) cls.update_endicia_shipment_cost(sales) return res @classmethod @ModelView.button def update_endicia_shipment_cost(cls, sales): "Updates the shipping line with new value if any" for sale in sales: sale.apply_endicia_shipping() def create_shipment(self, shipment_type): Shipment = Pool().get('stock.shipment.out') with Transaction().set_context(ignore_carrier_computation=True): # disable `carrier cost computation`(default behaviour) as cost # should only be computed after updating mailclass else error may # occur, with improper mailclass. shipments = super(Sale, self).create_shipment(shipment_type) if shipment_type == 'out' and shipments and self.carrier and \ self.carrier.carrier_cost_method == 'endicia': Shipment.write( shipments, { 'endicia_mailclass': self.endicia_mailclass.id, 'endicia_mailpiece_shape': self.endicia_mailpiece_shape, 'is_endicia_shipping': self.is_endicia_shipping, }) return shipments def get_endicia_shipping_cost(self, mailclass=None): """Returns the calculated shipping cost as sent by endicia :param mailclass: endicia mailclass for which cost to be fetched :returns: The shipping cost in USD """ Carrier = Pool().get('carrier') EndiciaConfiguration = Pool().get('endicia.configuration') endicia_credentials = EndiciaConfiguration(1).get_endicia_credentials() carrier, = Carrier.search(['carrier_cost_method', '=', 'endicia']) if not mailclass and not self.endicia_mailclass: self.raise_user_error('mailclass_missing') from_address = self._get_ship_from_address() to_address = self.shipment_address to_zip = to_address.zip if to_address.country and to_address.country.code == 'US': # Domestic to_zip = to_zip and to_zip[:5] else: # International to_zip = to_zip and to_zip[:15] # Endicia only support 1 decimal place in weight weight_oz = "%.1f" % self.package_weight calculate_postage_request = CalculatingPostageAPI( mailclass=mailclass or self.endicia_mailclass.value, MailpieceShape=self.endicia_mailpiece_shape, weightoz=weight_oz, from_postal_code=from_address.zip and from_address.zip[:5], to_postal_code=to_zip, to_country_code=to_address.country and to_address.country.code, accountid=endicia_credentials.account_id, requesterid=endicia_credentials.requester_id, passphrase=endicia_credentials.passphrase, test=endicia_credentials.is_test, ) # Logging. logger.debug('Making Postage Request for shipping cost of' 'Sale ID: {0} and Carrier ID: {1}'.format( self.id, carrier.id)) logger.debug('--------POSTAGE REQUEST--------') logger.debug(str(calculate_postage_request.to_xml())) logger.debug('--------END REQUEST--------') try: response = calculate_postage_request.send_request() except RequestError, e: self.raise_user_error(unicode(e)) # Logging. logger.debug('--------POSTAGE RESPONSE--------') logger.debug(str(response)) logger.debug('--------END RESPONSE--------') return self.fetch_endicia_postage_rate( objectify_response(response).PostagePrice)
class SaleOpportunityLine(sequence_ordered(), ModelSQL, ModelView): 'Sale Opportunity Line' __name__ = "sale.opportunity.line" _history = True _states = { 'readonly': Eval('opportunity_state').in_( ['converted', 'won', 'lost', 'cancelled']), } opportunity = fields.Many2One('sale.opportunity', 'Opportunity', ondelete='CASCADE', select=True, required=True, states={ 'readonly': _states['readonly'] & Bool(Eval('opportunity')), }) opportunity_state = fields.Function( fields.Selection('get_opportunity_states', "Opportunity State"), 'on_change_with_opportunity_state') product = fields.Many2One('product.product', 'Product', required=True, domain=[('salable', '=', True)], states=_states) quantity = fields.Float("Quantity", digits='unit', required=True, states=_states) unit = fields.Many2One('product.uom', 'Unit', required=True, states=_states) del _states @classmethod def __setup__(cls): super().__setup__() cls.__access__.add('opportunity') @classmethod def get_opportunity_states(cls): pool = Pool() Opportunity = pool.get('sale.opportunity') return Opportunity.fields_get(['state'])['state']['selection'] @fields.depends('opportunity', '_parent_opportunity.state') def on_change_with_opportunity_state(self, name=None): if self.opportunity: return self.opportunity.state @fields.depends('product', 'unit') def on_change_product(self): if not self.product: return category = self.product.sale_uom.category if not self.unit or self.unit.category != category: self.unit = self.product.sale_uom def get_sale_line(self, sale): ''' Return sale line for opportunity line ''' SaleLine = Pool().get('sale.line') sale_line = SaleLine( type='line', product=self.product, sale=sale, description=None, ) sale_line.on_change_product() self._set_sale_line_quantity(sale_line) sale_line.on_change_quantity() return sale_line def _set_sale_line_quantity(self, sale_line): sale_line.quantity = self.quantity sale_line.unit = self.unit def get_rec_name(self, name): pool = Pool() Lang = pool.get('ir.lang') lang = Lang.get() return (lang.format_number_symbol( self.quantity or 0, self.unit, digits=self.unit.digits) + ' %s @ %s' % (self.product.rec_name, self.opportunity.rec_name)) @classmethod def search_rec_name(cls, name, clause): return [('product.rec_name', ) + tuple(clause[1:])]
class AnalyticAccount(ModelSQL, ModelView): 'Analytic Account' _name = 'ekd.account.analytic' _description = __doc__ company = fields.Many2One('company.company', 'Company') model_ref = fields.Reference('Reference To', selection='get_model_ref', select=1) name = fields.Char('Name', required=True, select=1) code = fields.Char('Code', select=1) full_code = fields.Function(fields.Char('Full Code', select=1), 'get_full_code') full_name = fields.Function(fields.Char('Full name', select=1), 'get_full_name') active = fields.Boolean('Active', select=2) level = fields.Integer('Level Analysts', select=1) party = fields.Many2One('party.party', 'Party') currency = fields.Many2One('currency.currency', 'Currency', required=True) currency_digits = fields.Function( fields.Integer('Currency Digits', on_change_with=['currency']), 'get_currency_digits') type = fields.Selection([ ('root', 'Root'), ('view', 'View'), ('normal', 'Normal'), ('consolidation', 'Consolidation'), ], 'Type Struct', required=True) kind_analytic = fields.Selection( [ ('cost', 'Cost'), ('cash_flow', 'Cash Flow'), ('expense', 'Expense'), ('income', 'Income'), ('expense_future', 'Expense future period'), ('income_future', 'Incomes future period'), ('type_business', 'Type Business'), ('type_payment_budget', 'Types of payments to the budget'), ], 'Kind analityc', states={ 'invisible': Not(In(Eval('type'), ['root', 'view'])), 'required': In(Eval('type'), ['root', 'view']), }) root = fields.Many2One('ekd.account.analytic', 'Root', select=2, domain=[('parent', '=', False)], states={ 'invisible': Equal(Eval('type'), 'root'), 'required': Not(Equal(Eval('type'), 'root')), }) parent = fields.Many2One('ekd.account.analytic', 'Parent', select=2, domain=[('parent', 'child_of', Eval('root'))], states={ 'invisible': Equal(Eval('type'), 'root'), 'required': Not(Equal(Eval('type'), 'root')), }) childs = fields.One2Many('ekd.account.analytic', 'parent', 'Children') child_consol_ids = fields.Many2Many('ekd.account.analytic.consoledate', 'parent', 'child', 'Consolidated Children') # balance = fields.Function('get_balance', digits=(16, Eval('currency_digits', 2)), # string='Balance') # credit = fields.Function('get_credit_debit', digits=(16, Eval('currency_digits', 2)), # string='Credit') # debit = fields.Function('get_credit_debit', digits=(16, Eval('currency_digits', 2)), # string='Debit') state = fields.Selection([ ('draft', 'Draft'), ('opened', 'Opened'), ('closed', 'Closed'), ], 'State', required=True) note = fields.Text('Note') display_balance = fields.Selection([ ('debit-credit', 'Debit - Credit'), ('credit-debit', 'Credit - Debit'), ], 'Display Balance', required=True) mandatory = fields.Boolean('Mandatory', states={ 'invisible': Not(Equal(Eval('type'), 'root')), }) def __init__(self): super(AnalyticAccount, self).__init__() self._constraints += [ ('check_recursion', 'recursive_accounts'), ] self._error_messages.update({ 'recursive_accounts': 'You can not create recursive accounts!', }) self._order.insert(0, ('code', 'ASC')) self._order.insert(1, ('name', 'ASC')) def default_active(self): return True def default_company(self): return Transaction().context.get('company') or False def default_currency(self): company_obj = self.pool.get('company.company') currency_obj = self.pool.get('currency.currency') context = Transaction().context if context.get('company'): company = company_obj.browse(context['company']) return company.currency.id return False def default_type(self): return 'normal' def default_state(self): return 'draft' def default_display_balance(self): return 'credit-debit' def default_mandatory(self): return False def on_change_with_currency_digits(self, vals): currency_obj = self.pool.get('currency.currency') if vals.get('currency'): currency = currency_obj.browse(vals['currency']) return currency.digits return 2 def get_currency_digits(self, ids, name): res = {} for account in self.browse(ids): res[account.id] = account.currency.digits return res def get_balance(self, ids, names): res = {} for name in names: res.setdefault(name, {}) for account_id in ids: res[name][account_id] = Decimal('0.0') return res def get_credit_debit(self, ids, names): res = {} for name in names: res.setdefault(name, {}) for account_id in ids: res[name][account_id] = Decimal('0.0') return res def get_rec_name(self, ids, name): if not ids: return {} res = {} for account in self.browse(ids): if account.code: res[account.id] = account.code + ' - ' + unicode(account.name) else: res[account.id] = unicode(account.name) return res def search_rec_name(self, name, clause): ids = self.search([('code', ) + clause[1:]], limit=1) if ids: return [('code', ) + clause[1:]] else: return [(self._rec_name, ) + clause[1:]] def get_full_name(self, ids, name): if not ids: return {} res = {} def _name(category): if category.id in res: return res[category.id] elif category.parent: return _name(category.parent) + '/' + category.name else: return category.name for category in self.browse(ids): res[category.id] = _name(category) return res def get_full_code(self, ids, name): if not ids: return {} res = {} def _name(account): if account.id in res: return res[account.id] elif account.parent: return _name(account.parent) + '.' + account.code else: return account.code for account in self.browse(ids): res[account.id] = _name(account) return res def create(self, vals): if vals.get('root'): vals['kind_analytic'] = self.browse(vals['root']).kind_analytic else: vals['kind_analytic'] = self.browse(vals['parent']).kind_analytic return super(AnalyticAccount, self).create(vals) def write(self, ids, vals): if vals.get('parent'): vals['kind_analytic'] = self.browse(vals['parent']).kind_analytic elif vals.get('root'): vals['kind_analytic'] = self.browse(vals['root']).kind_analytic return super(AnalyticAccount, self).write(ids, vals) def convert_view(self, tree): res = tree.xpath('//field[@name=\'analytic_accounts\']') if not res: return element_accounts = res[0] root_account_ids = self.search([ ('parent', '=', False), ]) if not root_account_ids: element_accounts.getparent().getparent().remove( element_accounts.getparent()) return for account_id in root_account_ids: newelement = copy.copy(element_accounts) newelement.tag = 'label' newelement.set('name', 'analytic_account_' + str(account_id)) element_accounts.addprevious(newelement) newelement = copy.copy(element_accounts) newelement.set('name', 'analytic_account_' + str(account_id)) element_accounts.addprevious(newelement) parent = element_accounts.getparent() parent.remove(element_accounts) def analytic_accounts_fields_get(self, field, fields_names=None): res = {} if fields_names is None: fields_names = [] root_account_ids = self.search([ ('parent', '=', False), ]) for account in self.browse(root_account_ids): name = 'analytic_account_' + str(account.id) if name in fields_names or not fields_names: res[name] = field.copy() #res[name]['required'] = account.mandatory res[name]['string'] = account.name res[name]['relation'] = self._name res[name]['domain'] = [('root', '=', account.id), ('type', '=', 'normal')] return res def get_model_ref(self): res = [] res.append(['party.party', 'Partner']) res.append(['company.employee', 'Employee']) res.append(['product.product', 'Product']) res.append(['ekd.document', 'Document']) res.append(['project.project', 'Project']) return res