class Task(models.Model): _name = "project.task" _description = "Task" _date_name = "date_start" _inherit = ['mail.thread', 'ir.needaction_mixin'] _mail_post_access = 'read' _order = "priority desc, sequence, date_start, name, id" @api.model def default_get(self, field_list): """ Set 'date_assign' if user_id is set. """ result = super(Task, self).default_get(field_list) if 'user_id' in result: result['date_assign'] = fields.Datetime.now() return result def _get_default_partner(self): if 'default_project_id' in self.env.context: default_project_id = self.env['project.project'].browse( self.env.context['default_project_id']) return default_project_id.exists().partner_id def _get_default_stage_id(self): """ Gives default stage_id """ project_id = self.env.context.get('default_project_id') if not project_id: return False return self.stage_find(project_id, [('fold', '=', False)]) @api.model def _read_group_stage_ids(self, stages, domain, order): search_domain = [('id', 'in', stages.ids)] if 'default_project_id' in self.env.context: search_domain = [ '|', ('project_ids', '=', self.env.context['default_project_id']) ] + search_domain stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID) return stages.browse(stage_ids) active = fields.Boolean(default=True) name = fields.Char(string='Task Title', track_visibility='always', required=True, index=True) description = fields.Html(string='Description') priority = fields.Selection([('0', 'Normal'), ('1', 'High')], default='0', index=True) sequence = fields.Integer( string='Sequence', index=True, default=10, help="Gives the sequence order when displaying a list of tasks.") stage_id = fields.Many2one('project.task.type', string='Stage', track_visibility='onchange', index=True, default=_get_default_stage_id, group_expand='_read_group_stage_ids', domain="[('project_ids', '=', project_id)]", copy=False) tag_ids = fields.Many2many('project.tags', string='Tags', oldname='categ_ids') kanban_state = fields.Selection( [('normal', 'In Progress'), ('done', 'Ready for next stage'), ('blocked', 'Blocked')], string='Kanban State', default='normal', track_visibility='onchange', required=True, copy=False, help="A task's kanban state indicates special situations affecting it:\n" " * Normal is the default situation\n" " * Blocked indicates something is preventing the progress of this task\n" " * Ready for next stage indicates the task is ready to be pulled to the next stage" ) create_date = fields.Datetime(index=True) write_date = fields.Datetime( index=True ) #not displayed in the view but it might be useful with base_action_rule module (and it needs to be defined first for that) date_start = fields.Datetime(string='Starting Date', default=fields.Datetime.now, index=True, copy=False) date_end = fields.Datetime(string='Ending Date', index=True, copy=False) date_assign = fields.Datetime(string='Assigning Date', index=True, copy=False, readonly=True) date_deadline = fields.Date(string='Deadline', index=True, copy=False) date_last_stage_update = fields.Datetime(string='Last Stage Update', default=fields.Datetime.now, index=True, copy=False, readonly=True) project_id = fields.Many2one( 'project.project', string='Project', default=lambda self: self.env.context.get('default_project_id'), index=True, track_visibility='onchange', change_default=True) notes = fields.Text(string='Notes') planned_hours = fields.Float( string='Initially Planned Hours', help= 'Estimated time to do the task, usually set by the project manager when the task is in draft state.' ) remaining_hours = fields.Float( string='Remaining Hours', digits=(16, 2), help= "Total remaining time, can be re-estimated periodically by the assignee of the task." ) user_id = fields.Many2one('res.users', string='Assigned to', default=lambda self: self.env.uid, index=True, track_visibility='always') partner_id = fields.Many2one('res.partner', string='Customer', default=_get_default_partner) manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True) company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get()) color = fields.Integer(string='Color Index') user_email = fields.Char(related='user_id.email', string='User Email', readonly=True) attachment_ids = fields.One2many( 'ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], auto_join=True, string='Attachments') # In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id displayed_image_id = fields.Many2one( 'ir.attachment', domain= "[('res_model', '=', 'project.task'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]", string='Displayed Image') legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True) legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True) legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True) employee = fields.Many2one('hr.employee', 'employee') checklist1 = fields.Many2many('project.checklist1', string='제조') checklist2 = fields.Many2many('project.checklist2', string='전장') checklist3 = fields.Many2many('project.checklist3', string='제어') checklist4 = fields.Many2many('project.checklist4', string='설계') checklist5 = fields.Many2one('project.checklist5', string='유닛') overlap = fields.Char('check', default='check') department_id = fields.Many2one('hr.department', string='부서', store=True) check_department = fields.Boolean('동일부서확인', compute='_compute_check_department', search='_search_department') @api.multi def name_get(self): result = [] for record in self: name = record.name if record.checklist5: name = '[' + str(record.checklist5.name) + ']' + record.name result.append((record.id, name)) return result @api.depends('department_id') def _compute_check_department(self): _logger.warning('check user.department_id == task.department_id') @api.multi def _search_department(self, operator, value): user_dep = self.env['hr.employee'].search([ ('user_id', '=', self.env.uid) ]).department_id.id return [('department_id', '=', user_dep)] @api.onchange('project_id') def _onchange_project(self): if self.project_id: self.partner_id = self.project_id.partner_id self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) else: self.stage_id = False @api.onchange('user_id') def _onchange_user(self): if self.user_id: self.date_start = fields.Datetime.now() @api.multi def copy(self, default=None): if default is None: default = {} if not default.get('name'): default['name'] = _("%s (copy)") % self.name if 'remaining_hours' not in default: default['remaining_hours'] = self.planned_hours return super(Task, self).copy(default) @api.constrains('date_start', 'date_end') def _check_dates(self): if any( self.filtered(lambda task: task.date_start and task.date_end and task.date_start > task.date_end)): raise ValidationError( _('Error ! Task starting date must be lower than its ending date.' )) # Override view according to the company definition @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): # read uom as admin to avoid access rights issues, e.g. for portal/share users, # this should be safe (no context passed to avoid side-effects) obj_tm = self.env.user.company_id.project_time_mode_id tm = obj_tm and obj_tm.name or 'Hours' res = super(Task, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) # read uom as admin to avoid access rights issues, e.g. for portal/share users, # this should be safe (no context passed to avoid side-effects) obj_tm = self.env.user.company_id.project_time_mode_id # using get_object to get translation value uom_hour = self.env.ref('product.product_uom_hour', False) if not obj_tm or not uom_hour or obj_tm.id == uom_hour.id: return res eview = etree.fromstring(res['arch']) # if the project_time_mode_id is not in hours (so in days), display it as a float field def _check_rec(eview): if eview.attrib.get('widget', '') == 'float_time': eview.set('widget', 'float') for child in eview: _check_rec(child) return True _check_rec(eview) res['arch'] = etree.tostring(eview) # replace reference of 'Hours' to 'Day(s)' for f in res['fields']: # TODO this NOT work in different language than english # the field 'Initially Planned Hours' should be replaced by 'Initially Planned Days' # but string 'Initially Planned Days' is not available in translation if 'Hours' in res['fields'][f]['string']: res['fields'][f]['string'] = res['fields'][f][ 'string'].replace('Hours', obj_tm.name) return res @api.model def get_empty_list_help(self, help): self = self.with_context( empty_list_help_id=self.env.context.get('default_project_id'), empty_list_help_model='project.project', empty_list_help_document_name=_("tasks")) return super(Task, self).get_empty_list_help(help) # ---------------------------------------- # Case management # ---------------------------------------- def stage_find(self, section_id, domain=[], order='sequence'): """ Override of the base.stage method Parameter of the stage search taken from the lead: - section_id: if set, stages must belong to this section or be a default stage; if not set, stages must be default stages """ # collect all section_ids section_ids = [] if section_id: section_ids.append(section_id) section_ids.extend(self.mapped('project_id').ids) search_domain = [] if section_ids: search_domain = [('|')] * (len(section_ids) - 1) for section_id in section_ids: search_domain.append(('project_ids', '=', section_id)) search_domain += list(domain) # perform search, return the first found return self.env['project.task.type'].search(search_domain, order=order, limit=1).id # ------------------------------------------------ # CRUD overrides # ------------------------------------------------ @api.model def create(self, vals): # context: no_log, because subtype already handle this context = dict(self.env.context, mail_create_nolog=True) # for default stage if vals.get('project_id') and not context.get('default_project_id'): context['default_project_id'] = vals.get('project_id') # user_id change: update date_assign if vals.get('user_id'): vals['date_assign'] = fields.Datetime.now() task = super(Task, self.with_context(context)).create(vals) user_dep = self.env['hr.employee'].search([ ('user_id', '=', self.env.uid) ]).department_id.id task.department_id = user_dep return task @api.multi def write(self, vals): now = fields.Datetime.now() # stage change: update date_last_stage_update if 'stage_id' in vals: vals['date_last_stage_update'] = now # reset kanban state when changing stage if 'kanban_state' not in vals: vals['kanban_state'] = 'normal' # user_id change: update date_assign if vals.get('user_id'): vals['date_assign'] = now result = super(Task, self).write(vals) return result # --------------------------------------------------- # Mail gateway # --------------------------------------------------- @api.multi def _track_template(self, tracking): res = super(Task, self)._track_template(tracking) test_task = self[0] changes, tracking_value_ids = tracking[test_task.id] if 'stage_id' in changes and test_task.stage_id.mail_template_id: res['stage_id'] = (test_task.stage_id.mail_template_id, { 'composition_mode': 'mass_mail' }) return res @api.multi def _track_subtype(self, init_values): self.ensure_one() if 'kanban_state' in init_values and self.kanban_state == 'blocked': return 'project.mt_task_blocked' elif 'kanban_state' in init_values and self.kanban_state == 'done': return 'project.mt_task_ready' elif 'user_id' in init_values and self.user_id: # assigned -> new return 'project.mt_task_new' elif 'stage_id' in init_values and self.stage_id and self.stage_id.sequence <= 1: # start stage -> new return 'project.mt_task_new' elif 'stage_id' in init_values: return 'project.mt_task_stage' return super(Task, self)._track_subtype(init_values) @api.multi def _notification_recipients(self, message, groups): """ Handle project users and managers recipients that can convert assign tasks and create new one directly from notification emails. """ groups = super(Task, self)._notification_recipients(message, groups) self.ensure_one() if not self.user_id: take_action = self._notification_link_helper('assign') project_actions = [{'url': take_action, 'title': _('I take it')}] else: new_action_id = self.env.ref('project.action_view_task').id new_action = self._notification_link_helper( 'new', action_id=new_action_id) project_actions = [{'url': new_action, 'title': _('New Task')}] new_group = ('group_project_user', lambda partner: bool(partner.user_ids) and any( user.has_group('project.group_project_user') for user in partner.user_ids), { 'actions': project_actions, }) return [new_group] + groups @api.model def message_get_reply_to(self, res_ids, default=None): """ Override to get the reply_to of the parent project. """ tasks = self.sudo().browse(res_ids) project_ids = tasks.mapped('project_id').ids aliases = self.env['project.project'].message_get_reply_to( project_ids, default=default) return { task.id: aliases.get(task.project_id.id, False) for task in tasks } @api.multi def email_split(self, msg): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias aliases = self.mapped('project_id.alias_name') return filter(lambda x: x.split('@')[0] not in aliases, email_list) @api.model def message_new(self, msg, custom_values=None): """ Override to updates the document according to the email. """ if custom_values is None: custom_values = {} defaults = { 'name': msg.get('subject'), 'planned_hours': 0.0, 'partner_id': msg.get('author_id') } defaults.update(custom_values) res = super(Task, self).message_new(msg, custom_values=defaults) task = self.browse(res) email_list = task.email_split(msg) partner_ids = filter( None, task._find_partner_from_emails(email_list, force_create=False)) task.message_subscribe(partner_ids) return res @api.multi def message_update(self, msg, update_vals=None): """ Override to update the task according to the email. """ if update_vals is None: update_vals = {} maps = { 'cost': 'planned_hours', } for line in msg['body'].split('\n'): line = line.strip() res = tools.command_re.match(line) if res: match = res.group(1).lower() field = maps.get(match) if field: try: update_vals[field] = float(res.group(2).lower()) except (ValueError, TypeError): pass email_list = self.email_split(msg) partner_ids = filter( None, self._find_partner_from_emails(email_list, force_create=False)) self.message_subscribe(partner_ids) return super(Task, self).message_update(msg, update_vals=update_vals) @api.multi def message_get_suggested_recipients(self): recipients = super(Task, self).message_get_suggested_recipients() for task in self.filtered('partner_id'): reason = _('Customer Email') if task.partner_id.email else _( 'Customer') task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason) return recipients @api.multi def message_get_email_values(self, notif_mail=None): res = super(Task, self).message_get_email_values(notif_mail=notif_mail) headers = {} if res.get('headers'): try: headers.update(safe_eval(res['headers'])) except Exception: pass if self.project_id: current_objects = filter( None, headers.get('X-Odoo-Objects', '').split(',')) current_objects.insert(0, 'project.project-%s, ' % self.project_id.id) headers['X-Odoo-Objects'] = ','.join(current_objects) if self.tag_ids: headers['X-Odoo-Tags'] = ','.join(self.tag_ids.mapped('name')) res['headers'] = repr(headers) return res
class ImportDigitalAtasWizard(models.TransientModel): _name = 'import.digital.atas.wizard' delegated_type_id = fields.Many2one( comodel_name='cci_international.delegated_type', string='ATA Carnet Type', required=True, domain=[('section', '=', 'ata_carnet'), ('digital', '=', True)], help='Type of eATA to Import') input_file = fields.Binary( string='XML File', required=True, help='The XML file from eATA platform', ) state = fields.Selection([('step1', 'try_import'), ('step2', 'show_errors')], default='step1') text_errors = fields.Text(string='Errors') rejected = fields.Integer(string='Rejected eATAs') imported_atas = fields.Integer(string='Imported eATAs') new_ids = [] # list of the ID of created ATA Carnets @api.multi def import_digital_atas(self): self.new_ids = [] inputdata = base64.decodestring(self.input_file) inputdata = inputdata.decode( 'utf-8' ) # 2015-03-04 : no more utf-16 but utf-8 as at the beginning # store the results self.rejected = 0 self.imported_eatas = 0 self.text_errors = '' errors = [] created_codes = [] # objects necessary for the import SaleOrder = self.env['sale.order'] DelegatedType = self.env['cci_international.delegated_type'] ResPartner = self.env['res.partner'] ResCountryGroup = self.env['res.country.group'] #CCIZip = self.env['cci.zip'] root = objectify.fromstring(inputdata) # check if there is CARNET subElements if not hasattr( root, 'CARNET' ): ### the file is empty OR this is not a certificates'file raise UserError( _('The given file is either empty or NOT an eATA XML file.\nPlease check your parameters.' )) for carnet in root.CARNET: internal_id = str(carnet.ID) if len(internal_id) > 0 and not (internal_id[-1:] == 'D'): if str(carnet.ChamberNumber) == '87': full_customer_number = str(carnet.CustomerNumber) if full_customer_number[0:4] == 'cci_': # searching partner by CustomerNumber partners = ResPartner.search([ ('id', '=', int(full_customer_number[4:])), ('active', '=', True) ]) if len(partners) == 1: partner = partners[0] existing_atas = SaleOrder.search([ ('digital_ref', '=', internal_id), ('section', '=', 'ata_carnet') ]) # the search on digital_number is not enough because we have a serie of ATA Carnet without digital_number, so search on name also if not existing_atas: existing_atas = SaleOrder.search([ ('internal_ref', '=', carnet.CarnetNumber or ''), ('section', '=', 'ata_carnet') ]) # so we search also on 'name' if not existing_atas: # extraction of countries #country_ids = [] #if hasattr(carnet.DestinationList,'Destination'): # for destination in carnet.DestinationList.Destination: # isocode3 = str(destination) # if len(isocode3) == 3: # search_country_ids = obj_country.search(cr,uid,[('isocode3','=',isocode3)]) # if len(search_country_ids) == 1: # country_ids.append(search_country_ids[0]) # search for usage if str(carnet.IntendedUseList.IntendedUse ) == 'Other': usage_name = str( carnet.IntendedUseList. IntendedUseOther) else: usage_name = str( carnet.IntendedUseList.IntendedUse) customerRef = str( carnet.CustomerRef) # to unicode if customerRef: while ' ' in customerRef: customerRef = customerRef.replace( ' ', ' ') ata_data = {} ata_data['section'] = 'ata_carnet' ata_data[ 'delegated_type_id'] = self.delegated_type_id.id ata_data['internal_ref'] = str( carnet.CarnetNumber or b'').replace(' ', '') ata_data['date_order'] = str( carnet.AcceptedDateTime )[6:10] + "-" + str( carnet.AcceptedDateTime )[3:5] + "-" + str( carnet.AcceptedDateTime)[0:2] ata_data['ata_validity_date'] = str( carnet.ExpiryDateTime )[6:10] + '-' + str( carnet.ExpiryDateTime )[3:5] + '-' + str( carnet.ExpiryDateTime)[0:2] ata_data['partner_id'] = partner.id ata_data['asker_name'] = str( carnet.Holder.Applicant) ata_data['asker_street'] = '' ata_data['asker_city'] = '' ata_data['sender_name'] = '' ata_data['sender_street'] = '' ata_data['sender_city'] = '' ata_data['state'] = 'draft' ata_data['goods_desc'] = '' ata_data['goods_value'] = float( str(carnet.TotalValue).replace( ',', '.')) ata_data['copies'] = 0 ata_data['originals'] = 1 ata_data['digital_ref'] = internal_id ata_data['client_order_ref'] = customerRef ata_data['ata_usage'] = usage_name tous_les_pays = ResCountryGroup.search([ ('name', '=', 'Tous les Pays') ]) if len(tous_les_pays) == 1: ata_data[ 'ata_area_id'] = tous_les_pays.id else: tous_les_pays = ResCountryGroup.search( [()]) ata_data[ 'ata_area_id'] = tous_les_pays[ 0].id ata_data['ata_warranty'] = 50.0 if hasattr(carnet.Deposit, 'DepositAmount'): if carnet.Deposit.DepositAmount: ata_data['warranty'] = 0.0 ata_data['own_risk'] = True ata_data[ 'internal_comments'] = u'Caution de %.2f euros' % float( str(carnet.Deposit. DepositAmount or '0,0').replace( ',', '.')) ata_data['ata_pages_initial'] = int( carnet.TotalNumberOfPages or '0') ata_data['ata_pages_additional'] = int( carnet.NumberOfExtraPages or '0') new_ata = SaleOrder.create(ata_data) self.new_ids.append(new_ata.id) self.imported_atas += 1 else: self.rejected += 1 errors.append( 'Name : %s - Already used name-number ' % (carnet.CarnetNumber or '--none--')) else: self.rejected += 1 errors.append( 'InternalID : %s - Already used digital number ' % internal_id) else: self.rejected += 1 errors.append( 'InternalID : %s - Wrong Customer ID : \'%s\'' % (str(internal_id), full_customer_number)) else: self.rejected += 1 errors.append( 'InternalID : %s - Unknown Customer Number : \'%s\'' % (str(internal_id), full_customer_number)) else: self.rejected += 1 errors.append( 'InternalID : %s - Wrong ChamberNumber : \'%s\'' % (str(internal_id), str(carnet.ChamberNumber))) else: # duplicata of an existing ATA: nothing to DO errors.append('InternalID : %s - Duplicata => ignored' % str(internal_id)) # give the result to the user self.state = 'step2' if self.rejected > 0: self.text_errors = '\n'.join(errors) else: self.text_errors = 'No ERRORS at ALL !\n\nGreat job !' print('\n-----------ERRORS---------------------\n') print(self.text_errors) return { 'name': 'Imported eATAs', 'type': 'ir.actions.act_window', 'res_model': 'sale.order', 'view_type': 'form', 'view_mode': 'tree,form', 'views': [ (self.env.ref('cci_international.view_ata_tree').id, 'tree'), (self.env.ref('cci_international.view_form_delegated').id, 'form'), ], 'domain': [('id', 'in', self.new_ids)], }
class MrpWorkorder(models.Model): _name = 'mrp.workorder' _description = 'Work Order' def _read_group_workcenter_id(self, workcenters, domain, order): workcenter_ids = self.env.context.get('default_workcenter_id') if not workcenter_ids: workcenter_ids = workcenters._search( [], order=order, access_rights_uid=SUPERUSER_ID) return workcenters.browse(workcenter_ids) name = fields.Char('Work Order', required=True, states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }) workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', required=True, states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)], 'progress': [('readonly', True)] }, group_expand='_read_group_workcenter_id', check_company=True) working_state = fields.Selection(string='Workcenter Status', related='workcenter_id.working_state', readonly=False, help='Technical: used in views only') product_id = fields.Many2one(related='production_id.product_id', readonly=True, store=True, check_company=True) product_tracking = fields.Selection(related="product_id.tracking") product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True, readonly=True) use_create_components_lots = fields.Boolean( related="production_id.picking_type_id.use_create_components_lots") production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True, check_company=True, readonly=True) production_availability = fields.Selection( string='Stock Availability', readonly=True, related='production_id.reservation_state', store=True, help='Technical: used in views and domains only.') production_state = fields.Selection(string='Production State', readonly=True, related='production_id.state', help='Technical: used in views only.') production_bom_id = fields.Many2one('mrp.bom', related='production_id.bom_id') qty_production = fields.Float('Original Production Quantity', readonly=True, related='production_id.product_qty') company_id = fields.Many2one(related='production_id.company_id') qty_producing = fields.Float(compute='_compute_qty_producing', inverse='_set_qty_producing', string='Currently Produced Quantity', digits='Product Unit of Measure') qty_remaining = fields.Float('Quantity To Be Produced', compute='_compute_qty_remaining', digits='Product Unit of Measure') qty_produced = fields.Float( 'Quantity', default=0.0, readonly=True, digits='Product Unit of Measure', copy=False, help="The number of products already handled by this work order") is_produced = fields.Boolean(string="Has Been Produced", compute='_compute_is_produced') state = fields.Selection([('pending', 'Waiting for another WO'), ('ready', 'Ready'), ('progress', 'In Progress'), ('done', 'Finished'), ('cancel', 'Cancelled')], string='Status', default='pending', copy=False, readonly=True) leave_id = fields.Many2one( 'resource.calendar.leaves', help='Slot into workcenter calendar once planned', check_company=True, copy=False) date_planned_start = fields.Datetime('Scheduled Start Date', compute='_compute_dates_planned', inverse='_set_dates_planned', states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }, store=True, tracking=True, copy=False) date_planned_finished = fields.Datetime('Scheduled End Date', compute='_compute_dates_planned', inverse='_set_dates_planned', states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }, store=True, tracking=True, copy=False) date_start = fields.Datetime('Start Date', copy=False, states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }) date_finished = fields.Datetime('End Date', copy=False, states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }) duration_expected = fields.Float('Expected Duration', digits=(16, 2), default=60.0, states={ 'done': [('readonly', True)], 'cancel': [('readonly', True)] }, help="Expected duration (in minutes)") duration = fields.Float('Real Duration', compute='_compute_duration', inverse='_set_duration', readonly=False, store=True) duration_unit = fields.Float('Duration Per Unit', compute='_compute_duration', readonly=True, store=True) duration_percent = fields.Integer('Duration Deviation (%)', compute='_compute_duration', group_operator="avg", readonly=True, store=True) progress = fields.Float('Progress Done (%)', digits=(16, 2), compute='_compute_progress') operation_id = fields.Many2one('mrp.routing.workcenter', 'Operation', check_company=True) # Should be used differently as BoM can change in the meantime worksheet = fields.Binary('Worksheet', related='operation_id.worksheet', readonly=True) worksheet_type = fields.Selection(string='Worksheet Type', related='operation_id.worksheet_type', readonly=True) worksheet_google_slide = fields.Char( 'Worksheet URL', related='operation_id.worksheet_google_slide', readonly=True) operation_note = fields.Text("Description", related='operation_id.note', readonly=True) move_raw_ids = fields.One2many('stock.move', 'workorder_id', 'Raw Moves', domain=[('raw_material_production_id', '!=', False), ('production_id', '=', False)]) move_finished_ids = fields.One2many('stock.move', 'workorder_id', 'Finished Moves', domain=[('raw_material_production_id', '=', False), ('production_id', '!=', False) ]) move_line_ids = fields.One2many( 'stock.move.line', 'workorder_id', 'Moves to Track', help= "Inventory moves for which you must scan a lot number at this work order" ) finished_lot_id = fields.Many2one( 'stock.production.lot', string='Lot/Serial Number', compute='_compute_finished_lot_id', inverse='_set_finished_lot_id', domain= "[('product_id', '=', product_id), ('company_id', '=', company_id)]", check_company=True) time_ids = fields.One2many('mrp.workcenter.productivity', 'workorder_id', copy=False) is_user_working = fields.Boolean( 'Is the Current User Working', compute='_compute_working_users', help="Technical field indicating whether the current user is working. " ) working_user_ids = fields.One2many( 'res.users', string='Working user on this work order.', compute='_compute_working_users') last_working_user_id = fields.One2many( 'res.users', string='Last user that worked on this work order.', compute='_compute_working_users') next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order", check_company=True) scrap_ids = fields.One2many('stock.scrap', 'workorder_id') scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move') production_date = fields.Datetime( 'Production Date', related='production_id.date_planned_start', store=True, readonly=False) json_popover = fields.Char('Popover Data JSON', compute='_compute_json_popover') show_json_popover = fields.Boolean('Show Popover?', compute='_compute_json_popover') consumption = fields.Selection( [('strict', 'Strict'), ('warning', 'Warning'), ('flexible', 'Flexible')], required=True, ) @api.depends('production_state', 'date_planned_start', 'date_planned_finished') def _compute_json_popover(self): previous_wo_data = self.env['mrp.workorder'].read_group( [('next_work_order_id', 'in', self.ids)], [ 'ids:array_agg(id)', 'date_planned_start:max', 'date_planned_finished:max' ], ['next_work_order_id']) previous_wo_dict = dict([(x['next_work_order_id'][0], { 'id': x['ids'][0], 'date_planned_start': x['date_planned_start'], 'date_planned_finished': x['date_planned_finished'] }) for x in previous_wo_data]) if self.ids: conflicted_dict = self._get_conflicted_workorder_ids() for wo in self: infos = [] if not wo.date_planned_start or not wo.date_planned_finished or not wo.ids: wo.show_json_popover = False wo.json_popover = False continue if wo.state in ['pending', 'ready']: previous_wo = previous_wo_dict.get(wo.id) prev_start = previous_wo and previous_wo[ 'date_planned_start'] or False prev_finished = previous_wo and previous_wo[ 'date_planned_finished'] or False if wo.state == 'pending' and prev_start and not ( prev_start > wo.date_planned_start): infos.append({ 'color': 'text-primary', 'msg': _("Waiting the previous work order, planned from %(start)s to %(end)s", start=format_datetime(self.env, prev_start, dt_format=False), end=format_datetime(self.env, prev_finished, dt_format=False)) }) if wo.date_planned_finished < fields.Datetime.now(): infos.append({ 'color': 'text-warning', 'msg': _("The work order should have already been processed.") }) if prev_start and prev_start > wo.date_planned_start: infos.append({ 'color': 'text-danger', 'msg': _("Scheduled before the previous work order, planned from %(start)s to %(end)s", start=format_datetime(self.env, prev_start, dt_format=False), end=format_datetime(self.env, prev_finished, dt_format=False)) }) if conflicted_dict.get(wo.id): infos.append({ 'color': 'text-danger', 'msg': _( "Planned at the same time than other workorder(s) at %s", wo.workcenter_id.display_name) }) color_icon = infos and infos[-1]['color'] or False wo.show_json_popover = bool(color_icon) wo.json_popover = json.dumps({ 'infos': infos, 'color': color_icon, 'icon': 'fa-exclamation-triangle' if color_icon in ['text-warning', 'text-danger'] else 'fa-info-circle', 'replan': color_icon not in [False, 'text-primary'] }) @api.depends('production_id.lot_producing_id') def _compute_finished_lot_id(self): for workorder in self: workorder.finished_lot_id = workorder.production_id.lot_producing_id def _set_finished_lot_id(self): for workorder in self: workorder.production_id.lot_producing_id = workorder.finished_lot_id @api.depends('production_id.qty_producing') def _compute_qty_producing(self): for workorder in self: workorder.qty_producing = workorder.production_id.qty_producing def _set_qty_producing(self): for workorder in self: if workorder.qty_producing != 0 and workorder.production_id.qty_producing != workorder.qty_producing: workorder.production_id.qty_producing = workorder.qty_producing workorder.production_id._set_qty_producing() # Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say # we slide a workorder on a gantt view, a single call to write is made with both # fields Changes. As the ORM doesn't batch the write on related fields and instead # makes multiple call, the constraint check_dates() is raised. # That's why the compute and set methods are needed. to ensure the dates are updated # in the same time. @api.depends('leave_id') def _compute_dates_planned(self): for workorder in self: workorder.date_planned_start = workorder.leave_id.date_from workorder.date_planned_finished = workorder.leave_id.date_to def _set_dates_planned(self): date_from = self[0].date_planned_start date_to = self[0].date_planned_finished self.mapped('leave_id').write({ 'date_from': date_from, 'date_to': date_to, }) def name_get(self): res = [] for wo in self: if len(wo.production_id.workorder_ids) == 1: res.append( (wo.id, "%s - %s - %s" % (wo.production_id.name, wo.product_id.name, wo.name))) else: res.append( (wo.id, "%s - %s - %s - %s" % (wo.production_id.workorder_ids.ids.index(wo._origin.id) + 1, wo.production_id.name, wo.product_id.name, wo.name))) return res def unlink(self): # Removes references to workorder to avoid Validation Error (self.mapped('move_raw_ids') | self.mapped('move_finished_ids')).write( {'workorder_id': False}) self.mapped('leave_id').unlink() mo_dirty = self.production_id.filtered( lambda mo: mo.state in ("confirmed", "progress", "to_close")) res = super().unlink() # We need to go through `_action_confirm` for all workorders of the current productions to # make sure the links between them are correct (`next_work_order_id` could be obsolete now). mo_dirty.workorder_ids._action_confirm() return res @api.depends('production_id.product_qty', 'qty_produced', 'production_id.product_uom_id') def _compute_is_produced(self): self.is_produced = False for order in self.filtered( lambda p: p.production_id and p.production_id.product_uom_id): rounding = order.production_id.product_uom_id.rounding order.is_produced = float_compare(order.qty_produced, order.production_id.product_qty, precision_rounding=rounding) >= 0 @api.depends('time_ids.duration', 'qty_produced') def _compute_duration(self): for order in self: order.duration = sum(order.time_ids.mapped('duration')) order.duration_unit = round(order.duration / max(order.qty_produced, 1), 2) # rounding 2 because it is a time if order.duration_expected: order.duration_percent = 100 * ( order.duration_expected - order.duration) / order.duration_expected else: order.duration_percent = 0 def _set_duration(self): def _float_duration_to_second(duration): minutes = duration // 1 seconds = (duration % 1) * 60 return minutes * 60 + seconds for order in self: old_order_duation = sum(order.time_ids.mapped('duration')) new_order_duration = order.duration if new_order_duration == old_order_duation: continue delta_duration = new_order_duration - old_order_duation if delta_duration > 0: date_start = datetime.now() - timedelta( seconds=_float_duration_to_second(delta_duration)) self.env['mrp.workcenter.productivity'].create( order._prepare_timeline_vals(delta_duration, date_start, datetime.now())) else: duration_to_remove = abs(delta_duration) timelines = order.time_ids.sorted(lambda t: t.date_start) timelines_to_unlink = self.env['mrp.workcenter.productivity'] for timeline in timelines: if duration_to_remove <= 0.0: break if timeline.duration <= duration_to_remove: duration_to_remove -= timeline.duration timelines_to_unlink |= timeline else: new_time_line_duration = timeline.duration - duration_to_remove timeline.date_start = timeline.date_end - timedelta( seconds=_float_duration_to_second( new_time_line_duration)) break timelines_to_unlink.unlink() @api.depends('duration', 'duration_expected', 'state') def _compute_progress(self): for order in self: if order.state == 'done': order.progress = 100 elif order.duration_expected: order.progress = order.duration * 100 / order.duration_expected else: order.progress = 0 def _compute_working_users(self): """ Checks whether the current user is working, all the users currently working and the last user that worked. """ for order in self: order.working_user_ids = [ (4, order.id) for order in order.time_ids.filtered( lambda time: not time.date_end).sorted( 'date_start').mapped('user_id') ] if order.working_user_ids: order.last_working_user_id = order.working_user_ids[-1] elif order.time_ids: order.last_working_user_id = order.time_ids.sorted( 'date_end')[-1].user_id else: order.last_working_user_id = False if order.time_ids.filtered( lambda x: (x.user_id.id == self.env.user.id) and (not x.date_end) and (x.loss_type in ('productive', 'performance'))): order.is_user_working = True else: order.is_user_working = False def _compute_scrap_move_count(self): data = self.env['stock.scrap'].read_group( [('workorder_id', 'in', self.ids)], ['workorder_id'], ['workorder_id']) count_data = dict((item['workorder_id'][0], item['workorder_id_count']) for item in data) for workorder in self: workorder.scrap_count = count_data.get(workorder.id, 0) @api.onchange('date_planned_finished') def _onchange_date_planned_finished(self): if self.date_planned_start and self.date_planned_finished: interval = self.workcenter_id.resource_calendar_id.get_work_duration_data( self.date_planned_start, self.date_planned_finished, domain=[('time_type', 'in', ['leave', 'other'])]) self.duration_expected = interval['hours'] * 60 @api.onchange('operation_id') def _onchange_operation_id(self): if self.operation_id: self.name = self.operation_id.name self.workcenter_id = self.operation_id.workcenter_id.id @api.onchange('date_planned_start', 'duration_expected') def _onchange_date_planned_start(self): if self.date_planned_start and self.duration_expected: self.date_planned_finished = self.workcenter_id.resource_calendar_id.plan_hours( self.duration_expected / 60.0, self.date_planned_start, compute_leaves=True, domain=[('time_type', 'in', ['leave', 'other'])]) @api.onchange('operation_id', 'workcenter_id', 'qty_production') def _onchange_expected_duration(self): self.duration_expected = self._get_duration_expected() def write(self, values): if 'production_id' in values: raise UserError( _('You cannot link this work order to another manufacturing order.' )) if 'workcenter_id' in values: for workorder in self: if workorder.workcenter_id.id != values['workcenter_id']: if workorder.state in ('progress', 'done', 'cancel'): raise UserError( _('You cannot change the workcenter of a work order that is in progress or done.' )) workorder.leave_id.resource_id = self.env[ 'mrp.workcenter'].browse( values['workcenter_id']).resource_id if 'date_planned_start' in values or 'date_planned_finished' in values: for workorder in self: start_date = fields.Datetime.to_datetime( values.get( 'date_planned_start')) or workorder.date_planned_start end_date = fields.Datetime.to_datetime( values.get('date_planned_finished') ) or workorder.date_planned_finished if start_date and end_date and start_date > end_date: raise UserError( _('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.' )) # Update MO dates if the start date of the first WO or the # finished date of the last WO is update. if workorder == workorder.production_id.workorder_ids[ 0] and 'date_planned_start' in values: if values['date_planned_start']: workorder.production_id.with_context( force_date=True).write({ 'date_planned_start': fields.Datetime.to_datetime( values['date_planned_start']) }) if workorder == workorder.production_id.workorder_ids[ -1] and 'date_planned_finished' in values: if values['date_planned_finished']: workorder.production_id.with_context( force_date=True).write({ 'date_planned_finished': fields.Datetime.to_datetime( values['date_planned_finished']) }) return super(MrpWorkorder, self).write(values) @api.model_create_multi def create(self, values): res = super().create(values) # Auto-confirm manually added workorders. # We need to go through `_action_confirm` for all workorders of the current productions to # make sure the links between them are correct. to_confirm = res.filtered(lambda wo: wo.production_id.state in ("confirmed", "progress", "to_close")) to_confirm = to_confirm.production_id.workorder_ids to_confirm._action_confirm() return res def _action_confirm(self): workorders_by_production = defaultdict( lambda: self.env['mrp.workorder']) for workorder in self: workorders_by_production[workorder.production_id] |= workorder for production, workorders in workorders_by_production.items(): workorders_by_bom = defaultdict(lambda: self.env['mrp.workorder']) bom = self.env['mrp.bom'] moves = production.move_raw_ids | production.move_finished_ids for workorder in self: if workorder.operation_id.bom_id: bom = workorder.operation_id.bom_id if not bom: bom = workorder.production_id.bom_id previous_workorder = workorders_by_bom[bom][-1:] previous_workorder.next_work_order_id = workorder.id workorders_by_bom[bom] |= workorder moves.filtered( lambda m: m.operation_id == workorder.operation_id).write( {'workorder_id': workorder.id}) exploded_boms, dummy = production.bom_id.explode( production.product_id, 1, picking_type=production.bom_id.picking_type_id) exploded_boms = {b[0]: b[1] for b in exploded_boms} for move in moves: if move.workorder_id: continue bom = move.bom_line_id.bom_id while bom and bom not in workorders_by_bom: bom_data = exploded_boms.get(bom, {}) bom = bom_data.get('parent_line') and bom_data[ 'parent_line'].bom_id or False if bom in workorders_by_bom: move.write( {'workorder_id': workorders_by_bom[bom][-1:].id}) else: move.write({ 'workorder_id': workorders_by_bom[production.bom_id][-1:].id }) for workorders in workorders_by_bom.values(): if workorders[0].state == 'pending': workorders[0].state = 'ready' for workorder in workorders: workorder._start_nextworkorder() def _get_byproduct_move_to_update(self): return self.production_id.move_finished_ids.filtered( lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))) def _start_nextworkorder(self): if self.state == 'done' and self.next_work_order_id.state == 'pending': self.next_work_order_id.state = 'ready' @api.model def gantt_unavailability(self, start_date, end_date, scale, group_bys=None, rows=None): """Get unavailabilities data to display in the Gantt view.""" workcenter_ids = set() def traverse_inplace(func, row, **kargs): res = func(row, **kargs) if res: kargs.update(res) for row in row.get('rows'): traverse_inplace(func, row, **kargs) def search_workcenter_ids(row): if row.get('groupedBy') and row.get( 'groupedBy')[0] == 'workcenter_id' and row.get('resId'): workcenter_ids.add(row.get('resId')) for row in rows: traverse_inplace(search_workcenter_ids, row) start_datetime = fields.Datetime.to_datetime(start_date) end_datetime = fields.Datetime.to_datetime(end_date) workcenters = self.env['mrp.workcenter'].browse(workcenter_ids) unavailability_mapping = workcenters._get_unavailability_intervals( start_datetime, end_datetime) # Only notable interval (more than one case) is send to the front-end (avoid sending useless information) cell_dt = (scale in ['day', 'week'] and timedelta(hours=1)) or ( scale == 'month' and timedelta(days=1)) or timedelta(days=28) def add_unavailability(row, workcenter_id=None): if row.get('groupedBy') and row.get( 'groupedBy')[0] == 'workcenter_id' and row.get('resId'): workcenter_id = row.get('resId') if workcenter_id: notable_intervals = filter( lambda interval: interval[1] - interval[0] >= cell_dt, unavailability_mapping[workcenter_id]) row['unavailabilities'] = [{ 'start': interval[0], 'stop': interval[1] } for interval in notable_intervals] return {'workcenter_id': workcenter_id} for row in rows: traverse_inplace(add_unavailability, row) return rows def button_start(self): self.ensure_one() # As button_start is automatically called in the new view if self.state in ('done', 'cancel'): return True if self.product_tracking == 'serial': self.qty_producing = 1.0 self.env['mrp.workcenter.productivity'].create( self._prepare_timeline_vals(self.duration, datetime.now())) if self.production_id.state != 'progress': self.production_id.write({ 'date_start': datetime.now(), }) if self.state == 'progress': return True start_date = datetime.now() vals = { 'state': 'progress', 'date_start': start_date, } if not self.leave_id: leave = self.env['resource.calendar.leaves'].create({ 'name': self.display_name, 'calendar_id': self.workcenter_id.resource_calendar_id.id, 'date_from': start_date, 'date_to': start_date + relativedelta(minutes=self.duration_expected), 'resource_id': self.workcenter_id.resource_id.id, 'time_type': 'other' }) vals['leave_id'] = leave.id return self.write(vals) else: if self.date_planned_start > start_date: vals['date_planned_start'] = start_date if self.date_planned_finished and self.date_planned_finished < start_date: vals['date_planned_finished'] = start_date return self.write(vals) def button_finish(self): end_date = datetime.now() for workorder in self: if workorder.state in ('done', 'cancel'): continue workorder.end_all() vals = { 'state': 'done', 'date_finished': end_date, 'date_planned_finished': end_date } if not workorder.date_start: vals['date_start'] = end_date if not workorder.date_planned_start or end_date < workorder.date_planned_start: vals['date_planned_start'] = end_date workorder.write(vals) workorder._start_nextworkorder() return True def end_previous(self, doall=False): """ @param: doall: This will close all open time lines on the open work orders when doall = True, otherwise only the one of the current user """ # TDE CLEANME timeline_obj = self.env['mrp.workcenter.productivity'] domain = [('workorder_id', 'in', self.ids), ('date_end', '=', False)] if not doall: domain.append(('user_id', '=', self.env.user.id)) not_productive_timelines = timeline_obj.browse() for timeline in timeline_obj.search(domain, limit=None if doall else 1): wo = timeline.workorder_id if wo.duration_expected <= wo.duration: if timeline.loss_type == 'productive': not_productive_timelines += timeline timeline.write({'date_end': fields.Datetime.now()}) else: maxdate = fields.Datetime.from_string( timeline.date_start) + relativedelta( minutes=wo.duration_expected - wo.duration) enddate = datetime.now() if maxdate > enddate: timeline.write({'date_end': enddate}) else: timeline.write({'date_end': maxdate}) not_productive_timelines += timeline.copy({ 'date_start': maxdate, 'date_end': enddate }) if not_productive_timelines: loss_id = self.env['mrp.workcenter.productivity.loss'].search( [('loss_type', '=', 'performance')], limit=1) if not len(loss_id): raise UserError( _("You need to define at least one unactive productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses." )) not_productive_timelines.write({'loss_id': loss_id.id}) return True def end_all(self): return self.end_previous(doall=True) def button_pending(self): self.end_previous() return True def button_unblock(self): for order in self: order.workcenter_id.unblock() return True def action_cancel(self): self.leave_id.unlink() return self.write({'state': 'cancel'}) def action_replan(self): """Replan a work order. It actually replans every "ready" or "pending" work orders of the linked manufacturing orders. """ for production in self.production_id: production._plan_workorders(replan=True) return True def button_done(self): if any(x.state in ('done', 'cancel') for x in self): raise UserError( _('A Manufacturing Order is already done or cancelled.')) self.end_all() end_date = datetime.now() return self.write({ 'state': 'done', 'date_finished': end_date, 'date_planned_finished': end_date, }) def button_scrap(self): self.ensure_one() return { 'name': _('Scrap'), 'view_mode': 'form', 'res_model': 'stock.scrap', 'view_id': self.env.ref('stock.stock_scrap_form_view2').id, 'type': 'ir.actions.act_window', 'context': { 'default_company_id': self.production_id.company_id.id, 'default_workorder_id': self.id, 'default_production_id': self.production_id.id, 'product_ids': (self.production_id.move_raw_ids.filtered( lambda x: x.state not in ('done', 'cancel')) | self.production_id.move_finished_ids.filtered( lambda x: x.state == 'done')).mapped('product_id').ids }, 'target': 'new', } def action_see_move_scrap(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( "stock.action_stock_scrap") action['domain'] = [('workorder_id', '=', self.id)] return action def action_open_wizard(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( "mrp.mrp_workorder_mrp_production_form") action['res_id'] = self.id return action @api.depends('qty_production', 'qty_produced') def _compute_qty_remaining(self): for wo in self: wo.qty_remaining = float_round( wo.qty_production - wo.qty_produced, precision_rounding=wo.production_id.product_uom_id.rounding) def _get_duration_expected(self, alternative_workcenter=False, ratio=1): self.ensure_one() if not self.workcenter_id: return self.duration_expected if not self.operation_id: duration_expected_working = ( self.duration_expected - self.workcenter_id.time_start - self.workcenter_id.time_stop ) * self.workcenter_id.time_efficiency / 100.0 if duration_expected_working < 0: duration_expected_working = 0 return self.workcenter_id.time_start + self.workcenter_id.time_stop + duration_expected_working * ratio * 100.0 / self.workcenter_id.time_efficiency qty_production = self.production_id.product_uom_id._compute_quantity( self.qty_production, self.production_id.product_id.uom_id) cycle_number = float_round(qty_production / self.workcenter_id.capacity, precision_digits=0, rounding_method='UP') if alternative_workcenter: # TODO : find a better alternative : the settings of workcenter can change duration_expected_working = ( self.duration_expected - self.workcenter_id.time_start - self.workcenter_id.time_stop ) * self.workcenter_id.time_efficiency / (100.0 * cycle_number) if duration_expected_working < 0: duration_expected_working = 0 return alternative_workcenter.time_start + alternative_workcenter.time_stop + cycle_number * duration_expected_working * 100.0 / alternative_workcenter.time_efficiency time_cycle = self.operation_id and self.operation_id.time_cycle or 60.0 return self.workcenter_id.time_start + self.workcenter_id.time_stop + cycle_number * time_cycle * 100.0 / self.workcenter_id.time_efficiency def _get_conflicted_workorder_ids(self): """Get conlicted workorder(s) with self. Conflict means having two workorders in the same time in the same workcenter. :return: defaultdict with key as workorder id of self and value as related conflicted workorder """ self.flush([ 'state', 'date_planned_start', 'date_planned_finished', 'workcenter_id' ]) sql = """ SELECT wo1.id, wo2.id FROM mrp_workorder wo1, mrp_workorder wo2 WHERE wo1.id IN %s AND wo1.state IN ('pending','ready') AND wo2.state IN ('pending','ready') AND wo1.id != wo2.id AND wo1.workcenter_id = wo2.workcenter_id AND (DATE_TRUNC('second', wo2.date_planned_start), DATE_TRUNC('second', wo2.date_planned_finished)) OVERLAPS (DATE_TRUNC('second', wo1.date_planned_start), DATE_TRUNC('second', wo1.date_planned_finished)) """ self.env.cr.execute(sql, [tuple(self.ids)]) res = defaultdict(list) for wo1, wo2 in self.env.cr.fetchall(): res[wo1].append(wo2) return res @api.model def _prepare_component_quantity(self, move, qty_producing): """ helper that computes quantity to consume (or to create in case of byproduct) depending on the quantity producing and the move's unit factor""" if move.product_id.tracking == 'serial': uom = move.product_id.uom_id else: uom = move.product_uom return move.product_uom._compute_quantity(qty_producing * move.unit_factor, uom, round=False) def _prepare_timeline_vals(self, duration, date_start, date_end=False): # Need a loss in case of the real time exceeding the expected if not self.duration_expected or duration < self.duration_expected: loss_id = self.env['mrp.workcenter.productivity.loss'].search( [('loss_type', '=', 'productive')], limit=1) if not len(loss_id): raise UserError( _("You need to define at least one productivity loss in the category 'Productivity'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses." )) else: loss_id = self.env['mrp.workcenter.productivity.loss'].search( [('loss_type', '=', 'performance')], limit=1) if not len(loss_id): raise UserError( _("You need to define at least one productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses." )) return { 'workorder_id': self.id, 'workcenter_id': self.workcenter_id.id, 'description': _('Time Tracking: %(user)s', user=self.env.user.name), 'loss_id': loss_id[0].id, 'date_start': date_start, 'date_end': date_end, 'user_id': self.env.user.id, # FIXME sle: can be inconsistent with company_id 'company_id': self.company_id.id, } def _update_finished_move(self): """ Update the finished move & move lines in order to set the finished product lot on it as well as the produced quantity. This method get the information either from the last workorder or from the Produce wizard.""" production_move = self.production_id.move_finished_ids.filtered( lambda move: move.product_id == self.product_id and move.state not in ('done', 'cancel')) if production_move and production_move.product_id.tracking != 'none': if not self.finished_lot_id: raise UserError( _('You need to provide a lot for the finished product.')) move_line = production_move.move_line_ids.filtered( lambda line: line.lot_id.id == self.finished_lot_id.id) if move_line: if self.product_id.tracking == 'serial': raise UserError( _('You cannot produce the same serial number twice.')) move_line.product_uom_qty += self.qty_producing move_line.qty_done += self.qty_producing else: location_dest_id = production_move.location_dest_id._get_putaway_strategy( self.product_id).id or production_move.location_dest_id.id move_line.create({ 'move_id': production_move.id, 'product_id': production_move.product_id.id, 'lot_id': self.finished_lot_id.id, 'product_uom_qty': self.qty_producing, 'product_uom_id': self.product_uom_id.id, 'qty_done': self.qty_producing, 'location_id': production_move.location_id.id, 'location_dest_id': location_dest_id, }) else: rounding = production_move.product_uom.rounding production_move._set_quantity_done( float_round(self.qty_producing, precision_rounding=rounding)) def _check_sn_uniqueness(self): """ Alert the user if the serial number as already been produced """ if self.product_tracking == 'serial' and self.finished_lot_id: sml = self.env['stock.move.line'].search_count([ ('lot_id', '=', self.finished_lot_id.id), ('location_id.usage', '=', 'production'), ('qty_done', '=', 1), ('state', '=', 'done') ]) if sml: raise UserError( _( 'This serial number for product %s has already been produced', self.product_id.name)) def _update_qty_producing(self, quantity): self.ensure_one() if self.qty_producing: self.qty_producing = quantity
class OeHealthPatient(models.Model): _name = 'oeh.medical.patient' _description = 'Patient Management' _inherits = { 'res.partner': 'partner_id', } MARITAL_STATUS = [ ('Single', 'Single'), ('Married', 'Married'), ('Widowed', 'Widowed'), ('Divorced', 'Divorced'), ('Separated', 'Separated'), ] SEX = [ ('Male', 'Male'), ('Female', 'Female'), ] BLOOD_TYPE = [ ('A', 'A'), ('B', 'B'), ('AB', 'AB'), ('O', 'O'), ] RH = [ ('+', '+'), ('-', '-'), ] @api.multi def _app_count(self): oe_apps = self.env['oeh.medical.appointment'] for pa in self: domain = [('patient', '=', pa.id)] app_ids = oe_apps.search(domain) apps = oe_apps.browse(app_ids) app_count = 0 for ap in apps: app_count += 1 pa.app_count = app_count return True @api.multi def _prescription_count(self): oe_pres = self.env['oeh.medical.prescription'] for pa in self: domain = [('patient', '=', pa.id)] pres_ids = oe_pres.search(domain) pres = oe_pres.browse(pres_ids) pres_count = 0 for pr in pres: pres_count += 1 pa.prescription_count = pres_count return True @api.multi def _admission_count(self): oe_admission = self.env['oeh.medical.inpatient'] for adm in self: domain = [('patient', '=', adm.id)] admission_ids = oe_admission.search(domain) admissions = oe_admission.browse(admission_ids) admission_count = 0 for ad in admissions: admission_count += 1 adm.admission_count = admission_count return True @api.multi def _vaccine_count(self): oe_vac = self.env['oeh.medical.vaccines'] for va in self: domain = [('patient', '=', va.id)] vec_ids = oe_vac.search(domain) vecs = oe_vac.browse(vec_ids) vecs_count = 0 for vac in vecs: vecs_count += 1 va.vaccine_count = vecs_count return True @api.multi def _invoice_count(self): oe_invoice = self.env['account.invoice'] for inv in self: invoice_ids = self.env['account.invoice'].search([('patient', '=', inv.id)]) invoices = oe_invoice.browse(invoice_ids) invoice_count = 0 for inv_id in invoices: invoice_count += 1 inv.invoice_count = invoice_count return True @api.multi def _patient_age(self): def compute_age_from_dates(patient_dob, patient_deceased, patient_dod): now = datetime.datetime.now() if (patient_dob): dob = datetime.datetime.strptime( patient_dob.strftime('%Y-%m-%d'), '%Y-%m-%d') if patient_deceased: dod = datetime.datetime.strptime( patient_dod.strftime('%Y-%m-%d'), '%Y-%m-%d') delta = dod - dob deceased = " (deceased)" years_months_days = str( delta.days // 365) + " years " + str( delta.days % 365) + " days" + deceased else: delta = now - dob years_months_days = str( delta.days // 365) + " years " + str( delta.days % 365) + " days" else: years_months_days = "No DoB !" return years_months_days for patient_data in self: patient_data.age = compute_age_from_dates(patient_data.dob, patient_data.deceased, patient_data.dod) return True partner_id = fields.Many2one('res.partner', string='Related Partner', required=True, ondelete='cascade', help='Partner-related data of the patient') family = fields.One2many('oeh.medical.patient.family', 'patient_id', string='Family') ssn = fields.Char(size=256, string='SSN') current_insurance = fields.Many2one( 'oeh.medical.insurance', string="Insurance", domain="[('patient','=', active_id),('state','=','Active')]", help= "Insurance information. You may choose from the different insurances belonging to the patient" ) doctor = fields.Many2one( 'oeh.medical.physician', string='Family Physician', help="Current primary care physician / family doctor", domain=[('is_pharmacist', '=', False)]) dob = fields.Date(string='Date of Birth') age = fields.Char( compute=_patient_age, size=32, string='Patient Age', help= "It shows the age of the patient in years(y), months(m) and days(d).\nIf the patient has died, the age shown is the age at time of death, the age corresponding to the date on the death certificate. It will show also \"deceased\" on the field" ) sex = fields.Selection(SEX, string='Sex', index=True) marital_status = fields.Selection(MARITAL_STATUS, string='Marital Status') blood_type = fields.Selection(BLOOD_TYPE, string='Blood Type') rh = fields.Selection(RH, string='Rh') identification_code = fields.Char( string='Patient ID', size=256, help='Patient Identifier provided by the Health Center', readonly=True) ethnic_group = fields.Many2one('oeh.medical.ethnicity', 'Ethnic group') critical_info = fields.Text( string='Important disease, allergy or procedures information', help= "Write any important information on the patient's disease, surgeries, allergies, ..." ) general_info = fields.Text(string='General Information', help="General information about the patient") genetic_risks = fields.Many2many('oeh.medical.genetics', 'oeh_genetic_risks_rel', 'patient_id', 'genetic_risk_id', string='Genetic Risks') deceased = fields.Boolean(string='Patient Deceased ?', help="Mark if the patient has died") dod = fields.Date(string='Date of Death') cod = fields.Many2one('oeh.medical.pathology', string='Cause of Death') app_count = fields.Integer(compute=_app_count, string="Appointments") prescription_count = fields.Integer(compute=_prescription_count, string="Prescriptions") admission_count = fields.Integer(compute=_admission_count, string="Admission / Discharge") vaccine_count = fields.Integer(compute=_vaccine_count, string="Vaccines") invoice_count = fields.Integer(compute=_invoice_count, string="Invoices") oeh_patient_user_id = fields.Many2one('res.users', string='Responsible Odoo User') prescription_line = fields.One2many('oeh.medical.prescription.line', 'patient', string='Medicines', readonly=True) _sql_constraints = [( 'code_oeh_patient_userid_uniq', 'unique (oeh_patient_user_id)', "Selected 'Responsible' user is already assigned to another patient !") ] @api.model def create(self, vals): sequence = self.env['ir.sequence'].next_by_code('oeh.medical.patient') vals['identification_code'] = sequence vals['is_patient'] = True health_patient = super(OeHealthPatient, self).create(vals) return health_patient @api.onchange('state_id') def onchange_state_id(self): if self.state_id: self.country_id = self.state_id.country_id.id @api.multi def print_patient_label(self): return self.env.ref( 'oehealth.action_report_patient_label').report_action(self)
class HrEmployeePrivate(models.Model): """ NB: Any field only available on the model hr.employee (i.e. not on the hr.employee.public model) should have `groups="hr.group_hr_user"` on its definition to avoid being prefetched when the user hasn't access to the hr.employee model. Indeed, the prefetch loads the data for all the fields that are available according to the group defined on them. """ _name = "hr.employee" _description = "Employee" _order = 'name' _inherit = [ 'hr.employee.base', 'mail.thread', 'mail.activity.mixin', 'resource.mixin', 'image.mixin' ] _mail_post_access = 'read' _rec_name = 'name' @api.model def _default_image(self): image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') return base64.b64encode(open(image_path, 'rb').read()) cin = fields.Char(string='CIN') passport = fields.Char(string='PASSPORT') visa_du = fields.Date(string='Visa Du ') visa_ou = fields.Date(string='Visa Ou ') # resource and user # required on the resource, make sure required="True" set in the view name = fields.Char(string="Employee Name", related='resource_id.name', store=True, readonly=False, tracking=True) user_id = fields.Many2one('res.users', 'User', related='resource_id.user_id', store=True, readonly=False) user_partner_id = fields.Many2one(related='user_id.partner_id', related_sudo=False, string="User's partner") active = fields.Boolean('Active', related='resource_id.active', default=True, store=True, readonly=False) # private partner address_home_id = fields.Many2one( 'res.partner', 'Address', help= 'Enter here the private address of the employee, not the one linked to your company.', groups="hr.group_hr_user", tracking=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") is_address_home_a_company = fields.Boolean( 'The employee address has a company linked', compute='_compute_is_address_home_a_company', ) private_email = fields.Char(related='address_home_id.email', string="Private Email", groups="hr.group_hr_user") country_id = fields.Many2one('res.country', 'Nationality (Country)', groups="hr.group_hr_user", tracking=True) gender = fields.Selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')], groups="hr.group_hr_user", default="male", tracking=True) marital = fields.Selection([('single', 'Single'), ('married', 'Married'), ('cohabitant', 'Legal Cohabitant'), ('widower', 'Widower'), ('divorced', 'Divorced')], string='Marital Status', groups="hr.group_hr_user", default='single', tracking=True) spouse_complete_name = fields.Char(string="Spouse Complete Name", groups="hr.group_hr_user", tracking=True) spouse_birthdate = fields.Date(string="Spouse Birthdate", groups="hr.group_hr_user", tracking=True) children = fields.Integer(string='Number of Children', groups="hr.group_hr_user", tracking=True) place_of_birth = fields.Char('Place of Birth', groups="hr.group_hr_user", tracking=True) country_of_birth = fields.Many2one('res.country', string="Country of Birth", groups="hr.group_hr_user", tracking=True) birthday = fields.Date('Date of Birth', groups="hr.group_hr_user", tracking=True) ssnid = fields.Char('SSN No', help='Social Security Number', groups="hr.group_hr_user", tracking=True) sinid = fields.Char('SIN No', help='Social Insurance Number', groups="hr.group_hr_user", tracking=True) identification_id = fields.Char(string='Identification No', groups="hr.group_hr_user", tracking=True) passport_id = fields.Char('Passport No', groups="hr.group_hr_user", tracking=True) bank_account_id = fields.Many2one( 'res.partner.bank', 'Bank Account Number', domain= "[('partner_id', '=', address_home_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", groups="hr.group_hr_user", tracking=True, help='Employee bank salary account') permit_no = fields.Char('Work Permit No', groups="hr.group_hr_user", tracking=True) visa_no = fields.Char('Visa No', groups="hr.group_hr_user", tracking=True) visa_expire = fields.Date('Visa Expire Date', groups="hr.group_hr_user", tracking=True) additional_note = fields.Text(string='Additional Note', groups="hr.group_hr_user", tracking=True) certificate = fields.Selection([ ('bachelor', 'Bachelor'), ('master', 'Master'), ('other', 'Other'), ], 'Certificate Level', default='other', groups="hr.group_hr_user", tracking=True) study_field = fields.Char("Field of Study", groups="hr.group_hr_user", tracking=True) study_school = fields.Char("School", groups="hr.group_hr_user", tracking=True) emergency_contact = fields.Char("Emergency Contact", groups="hr.group_hr_user", tracking=True) emergency_phone = fields.Char("Emergency Phone", groups="hr.group_hr_user", tracking=True) km_home_work = fields.Integer(string="Km Home-Work", groups="hr.group_hr_user", tracking=True) image_1920 = fields.Image(default=_default_image) phone = fields.Char(related='address_home_id.phone', related_sudo=False, readonly=False, string="Private Phone", groups="hr.group_hr_user") # employee in company child_ids = fields.One2many('hr.employee', 'parent_id', string='Direct subordinates') category_ids = fields.Many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', groups="hr.group_hr_manager", string='Tags') # misc notes = fields.Text('Notes', groups="hr.group_hr_user") color = fields.Integer('Color Index', default=0, groups="hr.group_hr_user") barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", groups="hr.group_hr_user", copy=False) pin = fields.Char( string="PIN", groups="hr.group_hr_user", copy=False, help= "PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration)." ) departure_reason = fields.Selection([('fired', 'Fired'), ('resigned', 'Resigned'), ('retired', 'Retired')], string="Departure Reason", groups="hr.group_hr_user", copy=False, tracking=True) departure_description = fields.Text(string="Additional Information", groups="hr.group_hr_user", copy=False, tracking=True) message_main_attachment_id = fields.Many2one(groups="hr.group_hr_user") _sql_constraints = [ ('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee." ), ('user_uniq', 'unique (user_id, company_id)', "A user cannot be linked to multiple employees in the same company.") ] def name_get(self): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).name_get() return self.env['hr.employee.public'].browse(self.ids).name_get() def _read(self, fields): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._read(fields) res = self.env['hr.employee.public'].browse(self.ids).read(fields) for r in res: record = self.browse(r['id']) record._update_cache({k: v for k, v in r.items() if k in fields}, validate=False) def read(self, fields, load='_classic_read'): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).read(fields, load=load) private_fields = set(fields).difference( self.env['hr.employee.public']._fields.keys()) if private_fields: raise AccessError( _('The fields "%s" you try to read is not available on the public employee profile.' ) % (','.join(private_fields))) return self.env['hr.employee.public'].browse(self.ids).read(fields, load=load) @api.model def load_views(self, views, options=None): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).load_views(views, options=options) return self.env['hr.employee.public'].load_views(views, options=options) @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): """ We override the _search because it is the method that checks the access rights This is correct to override the _search. That way we enforce the fact that calling search on an hr.employee returns a hr.employee recordset, even if you don't have access to this model, as the result of _search (the ids of the public employees) is to be browsed on the hr.employee model. This can be trusted as the ids of the public employees exactly match the ids of the related hr.employee. """ if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) return self.env['hr.employee.public']._search( args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) def get_formview_id(self, access_uid=None): """ Override this method in order to redirect many2one towards the right model depending on access_uid """ if access_uid: self_sudo = self.with_user(access_uid) else: self_sudo = self if self_sudo.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).get_formview_id(access_uid=access_uid) # Hardcode the form view for public employee return self.env.ref('hr.hr_employee_public_view_form').id def get_formview_action(self, access_uid=None): """ Override this method in order to redirect many2one towards the right model depending on access_uid """ res = super(HrEmployeePrivate, self).get_formview_action(access_uid=access_uid) if access_uid: self_sudo = self.with_user(access_uid) else: self_sudo = self if not self_sudo.check_access_rights('read', raise_exception=False): res['res_model'] = 'hr.employee.public' return res @api.constrains('pin') def _verify_pin(self): for employee in self: if employee.pin and not employee.pin.isdigit(): raise ValidationError( _("The PIN must be a sequence of digits.")) @api.onchange('job_id') def _onchange_job_id(self): if self.job_id: self.job_title = self.job_id.name @api.onchange('address_id') def _onchange_address(self): self.work_phone = self.address_id.phone self.mobile_phone = self.address_id.mobile @api.onchange('company_id') def _onchange_company(self): address = self.company_id.partner_id.address_get(['default']) self.address_id = address['default'] if address else False @api.onchange('department_id') def _onchange_department(self): if self.department_id.manager_id: self.parent_id = self.department_id.manager_id @api.onchange('user_id') def _onchange_user(self): if self.user_id: self.update(self._sync_user(self.user_id)) if not self.name: self.name = self.user_id.name @api.onchange('resource_calendar_id') def _onchange_timezone(self): if self.resource_calendar_id and not self.tz: self.tz = self.resource_calendar_id.tz def _sync_user(self, user): vals = dict( image_1920=user.image_1920, work_email=user.email, user_id=user.id, ) if user.tz: vals['tz'] = user.tz return vals @api.model def create(self, vals): if vals.get('user_id'): user = self.env['res.users'].browse(vals['user_id']) vals.update(self._sync_user(user)) vals['name'] = vals.get('name', user.name) employee = super(HrEmployeePrivate, self).create(vals) url = '/web#%s' % url_encode( { 'action': 'hr.plan_wizard_action', 'active_id': employee.id, 'active_model': 'hr.employee', 'menu_id': self.env.ref('hr.menu_hr_root').id, }) employee._message_log(body=_( '<b>Congratulations!</b> May I recommend you to setup an <a href="%s">onboarding plan?</a>' ) % (url)) if employee.department_id: self.env['mail.channel'].sudo().search([ ('subscription_department_ids', 'in', employee.department_id.id) ])._subscribe_users() return employee def write(self, vals): if 'address_home_id' in vals: account_id = vals.get('bank_account_id') or self.bank_account_id.id if account_id: self.env['res.partner.bank'].browse( account_id).partner_id = vals['address_home_id'] if vals.get('user_id'): vals.update( self._sync_user(self.env['res.users'].browse(vals['user_id']))) res = super(HrEmployeePrivate, self).write(vals) if vals.get('department_id') or vals.get('user_id'): department_id = vals['department_id'] if vals.get( 'department_id') else self[:1].department_id.id # When added to a department or changing user, subscribe to the channels auto-subscribed by department self.env['mail.channel'].sudo().search([ ('subscription_department_ids', 'in', department_id) ])._subscribe_users() return res def unlink(self): resources = self.mapped('resource_id') super(HrEmployeePrivate, self).unlink() return resources.unlink() def toggle_active(self): res = super(HrEmployeePrivate, self).toggle_active() self.filtered(lambda employee: employee.active).write({ 'departure_reason': False, 'departure_description': False, }) if len(self) == 1 and not self.active: return { 'type': 'ir.actions.act_window', 'name': _('Register Departure'), 'res_model': 'hr.departure.wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'active_id': self.id }, 'views': [[False, 'form']] } return res def generate_random_barcode(self): for employee in self: employee.barcode = '041' + "".join( choice(digits) for i in range(9)) @api.depends('address_home_id.parent_id') def _compute_is_address_home_a_company(self): """Checks that chosen address (res.partner) is not linked to a company. """ for employee in self: try: employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False except AccessError: employee.is_address_home_a_company = False # --------------------------------------------------------- # Business Methods # --------------------------------------------------------- @api.model def get_import_templates(self): return [{ 'label': _('Import Template for Employees'), 'template': '/hr/static/xls/hr_employee.xls' }] def _post_author(self): """ When a user updates his own employee's data, all operations are performed by super user. However, tracking messages should not be posted as OdooBot but as the actual user. This method is used in the overrides of `_message_log` and `message_post` to post messages as the correct user. """ real_user = self.env.context.get('binary_field_real_user') if self.env.is_superuser() and real_user: self = self.with_user(real_user) return self # --------------------------------------------------------- # Messaging # --------------------------------------------------------- def _message_log(self, **kwargs): return super(HrEmployeePrivate, self._post_author())._message_log(**kwargs) @api.returns('mail.message', lambda value: value.id) def message_post(self, **kwargs): return super(HrEmployeePrivate, self._post_author()).message_post(**kwargs) def _sms_get_partner_fields(self): return ['user_partner_id'] def _sms_get_number_fields(self): return ['mobile_phone']
class JobPosting(models.Model): _name = "job.posting" manpower_req = fields.Selection([('in_house','In House'),('site','Client')], string="Manpower Requisition", default="in_house") posting_type = fields.Selection([('new','New'),('replacement','Replacement')], string="Job Posting Type", default="new") freshers = fields.Boolean('Allow freshers to apply?') req_min_exp = fields.Integer(string='Mininum Experience') req_max_exp = fields.Integer(string='Maximum Experience') ctc_min = fields.Float('Minimum CTC') ctc_max = fields.Float('Maximum CTC') opening_date = fields.Date('Opening Date') closing_date = fields.Date('Closing Date') process = fields.Many2one('interview.round','Process') skills = fields.One2many('skillset.child','skill','Skills and Competencies') replacement_emp = fields.One2many('replacement.child','replacement_id','Replacement Employees') jobprocessone2many = fields.One2many('job.process.child','process_id','') job_visible = fields.Boolean('Job') upload_po = fields.Binary('Upload PO', attachment=True) designation = fields.Many2one('hr.job', string="Designation") location = fields.Many2one('site.master', string="Site") no_of_openings = fields.Integer('No of Openings') department_id = fields.Many2one('hr.department', string="Department") job_description = fields.Text("Job Description(JD)") client_name = fields.Char(string="Client Name") client_location = fields.Many2one('site.master', string="Client Location") client_city = fields.Char(string="Client City") project_name = fields.Char(string="Project Name") project_duration = fields.Char('Project Duration') no_of_replacement = fields.Integer('No. Of Replacement') client_address = fields.Text('Client Address') client_mail_id = fields.Char('Client Mail ID') hr_access = fields.Boolean('HR Access') hr_manager = fields.Many2one('hr.employee','HR Manager',default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)) hr_spoc_id = fields.Many2one('hr.employee','HR Spoc',track_visibility='onchange') spoc_user_id = fields.Many2one('res.users','Spoc User') status = fields.Selection([('draft','Draft'),('open','Requisition'),('initiate','Pending'),('done','Closed')], string="Status", default="draft") initiated = fields.Boolean('initiated') @api.multi def view_rounds(self): if self.process: self.write({'job_visible':True}) search_record = self.env['round.child'].search([('round_id','=',self.process.id)]) if not search_record: raise ValidationError(_("Kindly add rounds against this Process!")) if self.jobprocessone2many: for i in self.jobprocessone2many: i.unlink() for each in search_record: self.env['job.process.child'].create({'name':each.name,'stage_id':each.stage_id.id,'process_id':self.id}) return True @api.onchange('hr_spoc_id') def onchange_hr_spoc_id(self): data = {} if self.hr_spoc_id: data['spoc_user_id'] = self.hr_spoc_id.user_id.id else: data['spoc_user_id'] = None return {'value':data} @api.onchange('opening_date') def onchange_opening_date(self): if self.opening_date: date_object = datetime.strptime(self.opening_date, '%Y-%m-%d') self.closing_date = date_object + timedelta(days=24) def submit_manpower_requisition(self): if not self.no_of_openings or self.no_of_openings < 1: raise ValidationError("Please enter the number of openings for this request !") self.write({'status': 'open'}) return True @api.multi def initiate_manpower_requisition(self): if self.location: location = self.location.id if self.location else None else: location = self.client_location.id if self.client_location else None no_of_openings = self.no_of_openings manpower_req = self.manpower_req designation = self.designation.id if self.designation else None opening_date = self.opening_date hr_manager = self.hr_manager.id if self.hr_manager else None spoc_user_id = self.spoc_user_id.id if self.spoc_user_id else None status = self.status inv_id = self.env['interview.list'].create( { 'location':location, 'no_of_openings':no_of_openings, 'manpower_req':manpower_req, 'designation':designation, 'opening_date':opening_date, 'hr_manager': hr_manager, 'spoc_user_id': spoc_user_id, 'status': status, 'job_posting':self.id }) self.write({'status': 'initiate','initiated':True}) return True @api.multi def complete_job(self): self.status = 'done' return True @api.model def create(self,vals): job_id = super(JobPosting, self).create(vals) # location = vals.get('location') if vals.get('location') else vals.get('client_location') # no_of_openings = vals.get('no_of_openings') # manpower_req = vals.get('manpower_req') # designation = vals.get('designation') # opening_date = vals.get('opening_date') # hr_manager = vals.get('hr_manager') # status = vals.get('status') if not job_id.skills: raise ValidationError(_("Please add skills!!")) # if job_id: # self.env['interview.list'].create({'location':location, # 'no_of_openings':no_of_openings, # 'manpower_req':manpower_req, # 'designation':designation, # 'opening_date':opening_date, # 'job_posting':job_id.id}) return job_id
class OeHealthAppointment(models.Model): _name = 'oeh.medical.appointment' _description = 'Appointment' _inherit = ['mail.thread'] URGENCY_LEVEL = [ ('Normal', 'Normal'), ('Urgent', 'Urgent'), ('Medical Emergency', 'Medical Emergency'), ] PATIENT_STATUS = [ ('Ambulatory', 'Ambulatory'), ('Outpatient', 'Outpatient'), ('Inpatient', 'Inpatient'), ] APPOINTMENT_STATUS = [ ('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Invoiced', 'Invoiced'), ] # Automatically detect logged in physician @api.multi def _get_physician(self): """Return default physician value""" therapist_obj = self.env['oeh.medical.physician'] domain = [('oeh_user_id', '=', self.env.uid)] user_ids = therapist_obj.search(domain, limit=1) if user_ids: return user_ids.id or False else: return False # Calculating Appointment End date @api.multi def _get_appointment_end(self): for apm in self: end_date = False duration = 1 if apm.duration: duration = apm.duration if apm.appointment_date: end_date = datetime.datetime.strptime( apm.appointment_date.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S") + timedelta(hours=duration) apm.appointment_end = end_date return True name = fields.Char(string='Appointment #', size=64, default=lambda *a: '/') patient = fields.Many2one('oeh.medical.patient', string='Patient', help="Patient Name", required=True, readonly=True, states={'Scheduled': [('readonly', False)]}) doctor = fields.Many2one('oeh.medical.physician', string='Physician', help="Current primary care / family doctor", domain=[('is_pharmacist', '=', False)], required=True, readonly=True, states={'Scheduled': [('readonly', False)]}, default=_get_physician) appointment_date = fields.Datetime( string='Appointment Date', required=True, readonly=True, states={'Scheduled': [('readonly', False)]}, default=datetime.datetime.now()) appointment_end = fields.Datetime( compute=_get_appointment_end, string='Appointment End Date', readonly=True, states={'Scheduled': [('readonly', False)]}) duration = fields.Integer(string='Duration (Hours)', readonly=True, states={'Scheduled': [('readonly', False)]}, default=lambda *a: 1) institution = fields.Many2one('oeh.medical.health.center', string='Health Center', help="Medical Center", readonly=True, states={'Scheduled': [('readonly', False)]}) urgency_level = fields.Selection( URGENCY_LEVEL, string='Urgency Level', readonly=True, states={'Scheduled': [('readonly', False)]}, default=lambda *a: 'Normal') comments = fields.Text(string='Comments', readonly=True, states={'Scheduled': [('readonly', False)]}) patient_status = fields.Selection( PATIENT_STATUS, string='Patient Status', readonly=True, states={'Scheduled': [('readonly', False)]}, default=lambda *a: 'Inpatient') state = fields.Selection(APPOINTMENT_STATUS, string='State', readonly=True, default=lambda *a: 'Scheduled') _order = "appointment_date desc" @api.model def create(self, vals): if vals.get('doctor') and vals.get('appointment_date'): self.check_physician_availability(vals.get('doctor'), vals.get('appointment_date')) sequence = self.env['ir.sequence'].next_by_code( 'oeh.medical.appointment') vals['name'] = sequence health_appointment = super(OeHealthAppointment, self).create(vals) return health_appointment @api.multi def check_physician_availability(self, doctor, appointment_date): available = False DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" patient_line_obj = self.env['oeh.medical.physician.line'] need_to_check_availability = False query_doctor_availability = _( "select appointment_type from oeh_medical_physician where id=%s" ) % (doctor) self.env.cr.execute(query_doctor_availability) val = self.env.cr.fetchone() if val and val[0]: if val[0] == "On Weekly Schedule": need_to_check_availability = True #check if doctor is working on selected day of the week if need_to_check_availability: selected_day = datetime.datetime.strptime( appointment_date, DATETIME_FORMAT).strftime('%A') if selected_day: avail_days = patient_line_obj.search( [('name', '=', str(selected_day)), ('physician_id', '=', doctor)], limit=1) if not avail_days: raise UserError( _('Physician is not available on selected day!')) else: #get selected day's start and end time phy_start_time = self.get_time_string( avail_days.start_time).split(':') phy_end_time = self.get_time_string( avail_days.end_time).split(':') user_pool = self.env['res.users'] user = user_pool.browse(self.env.uid) tz = pytz.timezone(user.partner_id.tz) or pytz.utc # get localized dates appointment_date = pytz.utc.localize( datetime.datetime.strptime( appointment_date, DATETIME_FORMAT)).astimezone(tz) t1 = datetime.time(int(phy_start_time[0]), int(phy_start_time[1]), 0) t3 = datetime.time(int(phy_end_time[0]), int(phy_end_time[1]), 0) #get appointment hour and minute t2 = datetime.time(appointment_date.hour, appointment_date.minute, 0) if not (t2 > t1 and t2 < t3): raise UserError( _('Physician is not available on selected time!')) else: available = True return available def get_time_string(self, duration): result = '' currentHours = int(duration // 1) currentMinutes = int(round(duration % 1 * 60)) if (currentHours <= 9): currentHours = "0" + str(currentHours) if (currentMinutes <= 9): currentMinutes = "0" + str(currentMinutes) result = str(currentHours) + ":" + str(currentMinutes) return result @api.multi def _default_account(self): journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1) return journal.default_credit_account_id.id def action_appointment_invoice_create(self): invoice_obj = self.env["account.invoice"] invoice_line_obj = self.env["account.invoice.line"] inv_ids = [] for acc in self: # Create Invoice if acc.patient: curr_invoice = { 'partner_id': acc.patient.partner_id.id, 'account_id': acc.patient.partner_id.property_account_receivable_id.id, 'patient': acc.patient.id, 'state': 'draft', 'type': 'out_invoice', 'date_invoice': acc.appointment_date.strftime('%Y-%m-%d'), 'origin': "Appointment # : " + acc.name, 'sequence_number_next_prefix': False } inv_ids = invoice_obj.create(curr_invoice) inv_id = inv_ids.id if inv_ids: prd_account_id = self._default_account() # Create Invoice line curr_invoice_line = { 'name': "Consultancy invoice for " + acc.name, 'price_unit': acc.doctor.consultancy_price, 'quantity': 1, 'account_id': prd_account_id, 'invoice_id': inv_id, } inv_line_ids = invoice_line_obj.create(curr_invoice_line) self.write({'state': 'Invoiced'}) return { 'domain': "[('id','=', " + str(inv_id) + ")]", 'name': _('Appointment Invoice'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.invoice', 'type': 'ir.actions.act_window' } @api.multi def set_to_completed(self): return self.write({'state': 'Completed'}) @api.multi def unlink(self): for appointment in self.filtered( lambda appointment: appointment.state not in ['Scheduled']): raise UserError( _('You can not delete an appointment which is not in "Scheduled" state !!' )) return super(OeHealthAppointment, self).unlink()
class product_images(models.Model): "Products Image gallery" _name = "product.images" _description = __doc__ _table = "product_images" @api.model def unlink(self): local_media_repository = self.env[ 'res.company'].get_local_media_repository() if local_media_repository: for image in self: path = os.path.join(local_media_repository, image.product_id.default_code, image.name) if os.path.isfile(path): os.remove(path) return super(product_images, self).unlink() @api.model def create(self, vals): if vals.get('name', False) and not vals.get('extention', False): vals['name'], vals['extention'] = os.path.splitext(vals['name']) return super(product_images, self).create(vals) @api.multi def write(self, vals): if vals.get('name', False) and not vals.get('extention', False): vals['name'], vals['extention'] = os.path.splitext(vals['name']) if vals.get('name', False) or vals.get('extention', False): local_media_repository = self.env[ 'res.company'].get_local_media_repository() if local_media_repository: # old_images = self.browse(cr, uid, ids, context=context) res = [] for old_image in self: if vals.get('name', False) and ( old_image.name != vals['name']) or vals.get( 'extention', False) and (old_image.extention != vals['extention']): old_path = os.path.join( local_media_repository, old_image.product_id.default_code, '%s%s' % (old_image.name, old_image.extention)) res.append( super(product_images, self).write(old_image.id, vals)) if 'file' in vals: #a new image have been loaded we should remove the old image #TODO it's look like there is something wrong with function field in openerp indeed the preview is always added in the write :( if os.path.isfile(old_path): os.remove(old_path) else: #we have to rename the image on the file system if os.path.isfile(old_path): os.rename( old_path, os.path.join( local_media_repository, old_image.product_id.default_code, '%s%s' % (old_image.name, old_image.extention))) return res return super(product_images, self).write(vals) @api.multi def get_image(self): product_product_obj = self.env['product.product'] product_template_obj = self.env['product.template'] for rec in self: each = rec.read([ 'link', 'url', 'name', 'file_db_store', 'product_id', 'product_t_id', 'name', 'extention' ])[0] if each['link']: (filename, header) = urllib.request.urlretrieve(each['url']) f = open(filename, 'rb') img = base64.encodestring(f.read()) f.close() rec.file = img else: local_media_repository = self.env[ 'res.company'].get_local_media_repository() if local_media_repository: if each['product_t_id']: product_id = product_template_obj.browse( each['product_t_id'][0]) product_code = product_id.read(['default_code' ])[0]['default_code'] else: product_id = product_product_obj.browse( each['product_id'][0]) product_code = product_id.read(['default_code' ])[0]['default_code'] full_path = os.path.join( local_media_repository, product_code, '%s%s' % (each['name'], each['extention'])) if os.path.exists(full_path): try: f = open(full_path, 'rb') img = base64.encodestring(f.read()) f.close() except Exception as e: return False else: return False else: img = each['file_db_store'] rec.file = img @api.multi def _get_image(self): res = {} for each in self: res[each] = self.get_image() return res @api.multi def _check_filestore(self, image_filestore): '''check if the filestore is created, if not it create it automatically''' # try: if not os.path.isdir(image_filestore): os.makedirs(image_filestore) # except Exception e: # raise osv.except_osv(_('Error'), _('The image filestore can not be created, %s'%e)) return True @api.multi def _save_file(self, path, filename, b64_file): """Save a file encoded in base 64""" full_path = os.path.join(path, filename) self._check_filestore(path) ofile = open(full_path, 'w') try: ofile.write(base64.decodestring(b64_file)) finally: ofile.close() return True @api.multi def _set_image(self, name, value, arg): local_media_repository = self.env[ 'res.company'].get_local_media_repository() if local_media_repository: # image = self.browse(cr, uid, id, context=context) return self._save_file( os.path.join(local_media_repository, self.product_id.default_code), '%s%s' % (self.name, self.extention), value) return self.write({'file_db_store': value}) name = fields.Char('Image Title', size=100, required=True) extention = fields.Char('file extention', size=6) link = fields.Boolean( 'Link?', default=lambda *a: False, help= "Images can be linked from files on your file system or remote (Preferred)" ) file_db_store = fields.Binary('Image stored in database') file = fields.Char(compute=_get_image, inverse=_set_image, type="binary", method=True, filters='*.png,*.jpg,*.gif') url = fields.Char('File Location', size=250) comments = fields.Text('Comments') product_id = fields.Many2one('product.product', 'Product') _sql_constraints = [ ('uniq_name_product_id', 'UNIQUE(product_id, name)', _('A product can have only one image with the same name')) ]
class KeychainAccount(models.Model): """Manage all accounts of external systems in one place.""" _name = 'keychain.account' name = fields.Char(required=True, help="Humain readable label") technical_name = fields.Char( required=True, help="Technical name. Must be unique") namespace = fields.Selection(selection=[], help="Type of account", required=True) environment = fields.Char( required=False, help="'prod', 'dev', etc. or empty (for all)" ) login = fields.Char(help="Login") clear_password = fields.Char( help="Password. Leave empty if no changes", inverse='_inverse_set_password', compute='_compute_password', store=False) password = fields.Char( help="Password is derived from clear_password", readonly=True) data = fields.Text(help="Additionnal data as json") def _compute_password(self): # Only needed in v8 for _description_searchable issues return True def _get_password(self): """Password in clear text.""" try: return self._decode_password(self.password) except UserError as warn: raise UserError(_( "%s \n" "Account: %s %s %s " % ( warn, self.login, self.name, self.technical_name ) )) def get_data(self): """Data in dict form.""" return self._parse_data(self.data) @api.constrains('data') def _check_data(self): """Ensure valid input in data field.""" for account in self: if account.data: parsed = account._parse_data(account.data) if not account._validate_data(parsed): raise ValidationError(_("Data not valid")) def _inverse_set_password(self): """Encode password from clear text.""" # inverse function for rec in self: rec.password = rec._encode_password( rec.clear_password, rec.environment) @api.model def retrieve(self, domain): """Search accounts for a given domain. Environment is added by this function. Use this instead of search() to benefit from environment filtering. Use user.has_group() and suspend_security() before calling this method. """ domain.append(['environment', 'in', self._retrieve_env()]) return self.search(domain) @api.multi def write(self, vals): """At this time there is no namespace set.""" if not vals.get('data') and not self.data: vals['data'] = self._serialize_data(self._init_data()) return super(KeychainAccount, self).write(vals) @implemented_by_keychain def _validate_data(self, data): """Ensure data is valid according to the namespace. How to use: - Create a method prefixed with your namespace - Put your validation logic inside - Return true if data is valid for your usage This method will be called on write(). If false is returned an user error will be raised. Example: def _hereismynamspace_validate_data(): return len(data.get('some_param', '') > 6) @params data dict @returns boolean """ pass def _default_validate_data(self, data): """Default validation. By default says data is always valid. See _validata_data() for more information. """ return True @implemented_by_keychain def _init_data(self): """Initialize data field. How to use: - Create a method prefixed with your namespace - Return a dict with the keys and may be default values your expect. This method will be called on write(). Example: def _hereismynamspace_init_data(): return { 'some_param': 'default_value' } @returns dict """ pass def _default_init_data(self): """Default initialization. See _init_data() for more information. """ return {} @staticmethod def _retrieve_env(): """Return the current environments. You may override this function to fit your needs. returns: a tuple like: ('dev', 'test', False) Which means accounts for dev, test and blank (not set) Order is important: the first one is used for encryption. """ current = config.get('running_env') or False envs = [current] if False not in envs: envs.append(False) return envs @staticmethod def _serialize_data(data): return json.dumps(data) @staticmethod def _parse_data(data): try: return json.loads(data) except ValueError: raise ValidationError(_("Data should be a valid JSON")) @classmethod def _encode_password(cls, data, env): cipher = cls._get_cipher(env) return cipher.encrypt(str((data or '').encode('UTF-8'))) @classmethod def _decode_password(cls, data): cipher = cls._get_cipher() try: return unicode(cipher.decrypt(str(data)), 'UTF-8') except InvalidToken: raise UserError(_( "Password has been encrypted with a different " "key. Unless you can recover the previous key, " "this password is unreadable." )) @classmethod def _get_cipher(cls, force_env=None): """Return a cipher using the keys of environments. force_env = name of the env key. Useful for encoding against one precise env """ def _get_keys(envs): suffixes = [ '_%s' % env if env else '' for env in envs] # ('_dev', '') keys_name = [ 'keychain_key%s' % suf for suf in suffixes] # prefix it keys_str = [ config.get(key) for key in keys_name] # fetch from config return [ Fernet(key) for key in keys_str # build Fernet object if key and len(key) > 0 # remove False values ] if force_env: envs = [force_env] else: envs = cls._retrieve_env() # ex: ('dev', False) keys = _get_keys(envs) if len(keys) == 0: raise UserError(_( "No 'keychain_key_%s' entries found in config file. " "Use a key similar to: %s" % (envs[0], Fernet.generate_key()) )) return MultiFernet(keys)
class TmsExpenseLine(models.Model): _name = 'tms.expense.line' _description = 'Expense Line' loan_id = fields.Many2one('tms.expense.loan', string='Loan') travel_id = fields.Many2one( 'tms.travel', string='Travel') expense_id = fields.Many2one( 'tms.expense', string='Expense', readonly=True) product_qty = fields.Float( string='Qty', default=1.0) unit_price = fields.Float() price_subtotal = fields.Float( compute='_compute_price_subtotal', string='Subtotal',) product_uom_id = fields.Many2one( 'product.uom', string='Unit of Measure') line_type = fields.Selection( [('real_expense', 'Real Expense'), ('made_up_expense', 'Made-up Expense'), ('salary', 'Salary'), ('fuel', 'Fuel'), ('fuel_cash', 'Fuel in Cash'), ('refund', 'Refund'), ('salary_retention', 'Salary Retention'), ('salary_discount', 'Salary Discount'), ('other_income', 'Other Income'), ('tollstations', 'Toll Stations'), ('loan', 'Loan')], compute='_compute_line_type', store=True, readonly=True) name = fields.Char( 'Description', required=True) sequence = fields.Integer( help="Gives the sequence order when displaying a list of " "sales order lines.", default=10) price_total = fields.Float( string='Total', compute='_compute_price_total', ) tax_amount = fields.Float( compute='_compute_tax_amount',) special_tax_amount = fields.Float( string='Special Tax' ) tax_ids = fields.Many2many( 'account.tax', string='Taxes', domain=[('type_tax_use', '=', 'purchase')]) notes = fields.Text() employee_id = fields.Many2one( 'hr.employee', string='Driver') date = fields.Date() state = fields.Char(readonly=True) control = fields.Boolean() automatic = fields.Boolean( help="Check this if you want to create Advances and/or " "Fuel Vouchers for this line automatically") is_invoice = fields.Boolean(string='Is Invoice?') partner_id = fields.Many2one( 'res.partner', string='Supplier', domain=[('company_type', '=', 'company')]) invoice_date = fields.Date('Date') invoice_number = fields.Char() invoice_id = fields.Many2one( 'account.invoice', string='Supplier Invoice') product_id = fields.Many2one( 'product.product', string='Product', required=True,) route_id = fields.Many2one( 'tms.route', related='travel_id.route_id', string='Route', readonly=True) @api.onchange('product_id') def _onchange_product_id(self): if self.line_type not in [ 'salary', 'salary_retention', 'salary_discount']: self.tax_ids = self.product_id.supplier_taxes_id self.line_type = self.product_id.tms_product_category self.product_uom_id = self.product_id.uom_id.id self.name = self.product_id.name @api.depends('product_id') def _compute_line_type(self): for rec in self: rec.line_type = rec.product_id.tms_product_category @api.depends('tax_ids', 'product_qty', 'unit_price') def _compute_tax_amount(self): for rec in self: taxes = rec.tax_ids.compute_all( rec.unit_price, rec.expense_id.currency_id, rec.product_qty, rec.expense_id.employee_id.address_home_id) if taxes['taxes']: for tax in taxes['taxes']: rec.tax_amount += tax['amount'] else: rec.tax_amount = 0.0 @api.depends('product_qty', 'unit_price', 'line_type') def _compute_price_subtotal(self): for rec in self: if rec.line_type in [ 'salary_retention', 'salary_discount', 'loan']: rec.price_subtotal = rec.product_qty * rec.unit_price * -1 elif rec.line_type == 'fuel': rec.price_subtotal = rec.unit_price else: rec.price_subtotal = rec.product_qty * rec.unit_price @api.depends('price_subtotal', 'tax_ids') def _compute_price_total(self): for rec in self: if rec.line_type == 'fuel': rec.price_total = rec.unit_price elif rec.line_type in [ 'salary_retention', 'salary_discount', 'loan']: rec.price_total = rec.price_subtotal - rec.tax_amount else: rec.price_total = rec.price_subtotal + rec.tax_amount @api.model def create(self, values): expense_line = super(TmsExpenseLine, self).create(values) if expense_line.line_type in ( 'salary_discount', 'salary_retention', 'loan'): if expense_line.price_total > 0: raise ValidationError(_('This line type needs a ' 'negative value to continue!')) return expense_line
class SQLRequestMixin(models.AbstractModel): _name = 'sql.request.mixin' _clean_query_enabled = True _check_prohibited_words_enabled = True _check_execution_enabled = True _sql_request_groups_relation = False _sql_request_users_relation = False STATE_SELECTION = [ ('draft', 'Draft'), ('sql_valid', 'SQL Valid'), ] PROHIBITED_WORDS = [ 'delete', 'drop', 'insert', 'alter', 'truncate', 'execute', 'create', 'update', 'ir_config_parameter', ] # Default Section @api.model def _default_group_ids(self): ir_model_obj = self.env['ir.model.data'] return [ ir_model_obj.xmlid_to_res_id( 'sql_request_abstract.group_sql_request_user') ] @api.model def _default_user_ids(self): return [] # Columns Section name = fields.Char('Name', required=True) query = fields.Text( string='Query', required=True, help="You can't use the following words" ": DELETE, DROP, CREATE, INSERT, ALTER, TRUNCATE, EXECUTE, UPDATE.") state = fields.Selection( string='State', selection=STATE_SELECTION, default='draft', help="State of the Request:\n" " * 'Draft': Not tested\n" " * 'SQL Valid': SQL Request has been checked and is valid") group_ids = fields.Many2many(comodel_name='res.groups', string='Allowed Groups', relation=_sql_request_groups_relation, column1='sql_id', column2='group_id', default=_default_group_ids) user_ids = fields.Many2many(comodel_name='res.users', string='Allowed Users', relation=_sql_request_users_relation, column1='sql_id', column2='user_id', default=_default_user_ids) # Action Section @api.multi def button_validate_sql_expression(self): for item in self: if item._clean_query_enabled: item._clean_query() if item._check_prohibited_words_enabled: item._check_prohibited_words() if item._check_execution_enabled: item._check_execution() item.state = 'sql_valid' @api.multi def button_set_draft(self): self.write({'state': 'draft'}) # API Section @api.multi def _execute_sql_request(self, params=None, mode='fetchall', rollback=True, view_name=False, copy_options="CSV HEADER DELIMITER ';'"): """Execute a SQL request on the current database. ??? This function checks before if the user has the right to execute the request. :param params: (dict) of keys / values that will be replaced in the sql query, before executing it. :param mode: (str) result type expected. Available settings : * 'view': create a view with the select query. Extra param required 'view_name'. * 'materialized_view': create a MATERIALIZED VIEW with the select query. Extra parameter required 'view_name'. * 'fetchall': execute the select request, and return the result of 'cr.fetchall()'. * 'fetchone' : execute the select request, and return the result of 'cr.fetchone()' :param rollback: (boolean) mention if a rollback should be played after the execution of the query. Please keep this feature enabled for security reason, except if necessary. (Ignored if @mode in ('view', 'materialized_view')) :param view_name: (str) name of the view. (Ignored if @mode not in ('view', 'materialized_view')) :param copy_options: (str) mentions extra options for "COPY request STDOUT WITH xxx" request. (Ignored if @mode != 'stdout') ..note:: The following exceptions could be raised: psycopg2.ProgrammingError: Error in the SQL Request. odoo.exceptions.UserError: * 'mode' is not implemented. * materialized view is not supported by the Postgresql Server. """ self.ensure_one() res = False # Check if the request is in a valid state if self.state == 'draft': raise UserError( _("It is not allowed to execute a not checked request.")) # Disable rollback if a creation of a view is asked if mode in ('view', 'materialized_view'): rollback = False params = params or {} # pylint: disable=sql-injection query = self.query % params query = query.decode('utf-8') if mode in ('fetchone', 'fetchall'): pass elif mode == 'stdout': query = "COPY (%s) TO STDOUT WITH %s" % (query, copy_options) elif mode in 'view': query = "CREATE VIEW %s AS (%s);" % (query, view_name) elif mode in 'materialized_view': self._check_materialized_view_available() query = "CREATE MATERIALIZED VIEW %s AS (%s);" % (query, view_name) else: raise UserError(_("Unimplemented mode : '%s'" % mode)) if rollback: rollback_name = self._create_savepoint() try: if mode == 'stdout': output = StringIO.StringIO() self.env.cr.copy_expert(query, output) output.getvalue() res = base64.b64encode(output.getvalue()) output.close() else: self.env.cr.execute(query) if mode == 'fetchall': res = self.env.cr.fetchall() elif mode == 'fetchone': res = self.env.cr.fetchone() finally: self._rollback_savepoint(rollback_name) return res # Private Section @api.model def _create_savepoint(self): rollback_name = '%s_%s' % (self._name.replace('.', '_'), uuid.uuid1().hex) # pylint: disable=sql-injection req = "SAVEPOINT %s" % (rollback_name) self.env.cr.execute(req) return rollback_name @api.model def _rollback_savepoint(self, rollback_name): # pylint: disable=sql-injection req = "ROLLBACK TO SAVEPOINT %s" % (rollback_name) self.env.cr.execute(req) @api.model def _check_materialized_view_available(self): self.env.cr.execute("SHOW server_version;") res = self.env.cr.fetchone()[0].split('.') minor_version = float('.'.join(res[:2])) if minor_version < 9.3: raise UserError( _("Materialized View requires PostgreSQL 9.3 or greater but" " PostgreSQL %s is currently installed.") % (minor_version)) @api.multi def _clean_query(self): self.ensure_one() query = self.query.strip() while query[-1] == ';': query = query[:-1] self.query = query @api.multi def _check_prohibited_words(self): """Check if the query contains prohibited words, to avoid maliscious SQL requests""" self.ensure_one() query = self.query.lower() for word in self.PROHIBITED_WORDS: expr = r'\b%s\b' % word is_not_safe = re.search(expr, query) if is_not_safe: raise UserError( _("The query is not allowed because it contains unsafe word" " '%s'") % (word)) @api.multi def _check_execution(self): """Ensure that the query is valid, trying to execute it. A rollback is done after.""" self.ensure_one() query = self._prepare_request_check_execution() rollback_name = self._create_savepoint() res = False try: self.env.cr.execute(query) res = self._hook_executed_request() except ProgrammingError as e: raise UserError( _("The SQL query is not valid:\n\n %s") % e.message) finally: self._rollback_savepoint(rollback_name) return res @api.multi def _prepare_request_check_execution(self): """Overload me to replace some part of the query, if it contains parameters""" self.ensure_one() return self.query def _hook_executed_request(self): """Overload me to insert custom code, when the SQL request has been executed, before the rollback. """ self.ensure_one() return False
class Module(models.Model): _name = "ir.module.module" _rec_name = "shortdesc" _description = "Module" _order = 'sequence,name' @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): res = super(Module, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=False) if view_type == 'form' and res.get('toolbar',False): install_id = self.env.ref('base.action_server_module_immediate_install').id action = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != install_id] res['toolbar'] = {'action': action} return res @classmethod def get_module_info(cls, name): try: return modules.load_information_from_description_file(name) except Exception: _logger.debug('Error when trying to fetch information for module %s', name, exc_info=True) return {} @api.depends('name', 'description') def _get_desc(self): for module in self: path = modules.get_module_resource(module.name, 'static/description/index.html') if path: with tools.file_open(path, 'rb') as desc_file: doc = desc_file.read() html = lxml.html.document_fromstring(doc) for element, attribute, link, pos in html.iterlinks(): if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'): element.set('src', "/%s/static/description/%s" % (module.name, element.get('src'))) module.description_html = tools.html_sanitize(lxml.html.tostring(html)) else: overrides = { 'embed_stylesheet': False, 'doctitle_xform': False, 'output_encoding': 'unicode', 'xml_declaration': False, 'file_insertion_enabled': False, } output = publish_string(source=module.description if not module.application and module.description else '', settings_overrides=overrides, writer=MyWriter()) module.description_html = tools.html_sanitize(output) @api.depends('name') def _get_latest_version(self): default_version = modules.adapt_version('1.0') for module in self: module.installed_version = self.get_module_info(module.name).get('version', default_version) @api.depends('name', 'state') def _get_views(self): IrModelData = self.env['ir.model.data'].with_context(active_test=True) dmodels = ['ir.ui.view', 'ir.actions.report', 'ir.ui.menu'] for module in self: # Skip uninstalled modules below, no data to find anyway. if module.state not in ('installed', 'to upgrade', 'to remove'): module.views_by_module = "" module.reports_by_module = "" module.menus_by_module = "" continue # then, search and group ir.model.data records imd_models = defaultdict(list) imd_domain = [('module', '=', module.name), ('model', 'in', tuple(dmodels))] for data in IrModelData.sudo().search(imd_domain): imd_models[data.model].append(data.res_id) def browse(model): # as this method is called before the module update, some xmlid # may be invalid at this stage; explictly filter records before # reading them return self.env[model].browse(imd_models[model]).exists() def format_view(v): return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type) module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view'))) module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report'))) module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu'))) @api.depends('icon') def _get_icon_image(self): for module in self: module.icon_image = '' if module.icon: path_parts = module.icon.split('/') path = modules.get_module_resource(path_parts[1], *path_parts[2:]) else: path = modules.module.get_module_icon(module.name) if path: with tools.file_open(path, 'rb') as image_file: module.icon_image = base64.b64encode(image_file.read()) name = fields.Char('Technical Name', readonly=True, required=True, index=True) category_id = fields.Many2one('ir.module.category', string='Category', readonly=True, index=True) shortdesc = fields.Char('Module Name', readonly=True, translate=True) summary = fields.Char('Summary', readonly=True, translate=True) description = fields.Text('Description', readonly=True, translate=True) description_html = fields.Html('Description HTML', compute='_get_desc') author = fields.Char("Author", readonly=True) maintainer = fields.Char('Maintainer', readonly=True) contributors = fields.Text('Contributors', readonly=True) website = fields.Char("Website", readonly=True) # attention: Incorrect field names !! # installed_version refers the latest version (the one on disk) # latest_version refers the installed version (the one in database) # published_version refers the version available on the repository installed_version = fields.Char('Latest Version', compute='_get_latest_version') latest_version = fields.Char('Installed Version', readonly=True) published_version = fields.Char('Published Version', readonly=True) url = fields.Char('URL', readonly=True) sequence = fields.Integer('Sequence', default=100) dependencies_id = fields.One2many('ir.module.module.dependency', 'module_id', string='Dependencies', readonly=True) exclusion_ids = fields.One2many('ir.module.module.exclusion', 'module_id', string='Exclusions', readonly=True) auto_install = fields.Boolean('Automatic Installation', help='An auto-installable module is automatically installed by the ' 'system when all its dependencies are satisfied. ' 'If the module has no dependency, it is always installed.') state = fields.Selection(STATES, string='Status', default='uninstallable', readonly=True, index=True) demo = fields.Boolean('Demo Data', default=False, readonly=True) license = fields.Selection([ ('GPL-2', 'GPL Version 2'), ('GPL-2 or any later version', 'GPL-2 or later version'), ('GPL-3', 'GPL Version 3'), ('GPL-3 or any later version', 'GPL-3 or later version'), ('AGPL-3', 'Affero GPL-3'), ('LGPL-3', 'LGPL Version 3'), ('Other OSI approved licence', 'Other OSI Approved Licence'), ('OEEL-1', 'Odoo Enterprise Edition License v1.0'), ('OPL-1', 'Odoo Proprietary License v1.0'), ('Other proprietary', 'Other Proprietary') ], string='License', default='LGPL-3', readonly=True) menus_by_module = fields.Text(string='Menus', compute='_get_views', store=True) reports_by_module = fields.Text(string='Reports', compute='_get_views', store=True) views_by_module = fields.Text(string='Views', compute='_get_views', store=True) application = fields.Boolean('Application', readonly=True) icon = fields.Char('Icon URL') icon_image = fields.Binary(string='Icon', compute='_get_icon_image') to_buy = fields.Boolean('Odoo Enterprise Module', default=False) _sql_constraints = [ ('name_uniq', 'UNIQUE (name)', 'The name of the module must be unique!'), ] @api.multi def unlink(self): if not self: return True for module in self: if module.state in ('installed', 'to upgrade', 'to remove', 'to install'): raise UserError(_('You are trying to remove a module that is installed or will be installed.')) self.clear_caches() return super(Module, self).unlink() @staticmethod def _check_external_dependencies(terp): depends = terp.get('external_dependencies') if not depends: return for pydep in depends.get('python', []): try: importlib.import_module(pydep) except ImportError: raise ImportError('No module named %s' % (pydep,)) for binary in depends.get('bin', []): try: tools.find_in_path(binary) except IOError: raise Exception('Unable to find %r in path' % (binary,)) @classmethod def check_external_dependencies(cls, module_name, newstate='to install'): terp = cls.get_module_info(module_name) try: cls._check_external_dependencies(terp) except Exception as e: if newstate == 'to install': msg = _('Unable to install module "%s" because an external dependency is not met: %s') elif newstate == 'to upgrade': msg = _('Unable to upgrade module "%s" because an external dependency is not met: %s') else: msg = _('Unable to process module "%s" because an external dependency is not met: %s') raise UserError(msg % (module_name, e.args[0])) @api.multi def _state_update(self, newstate, states_to_update, level=100): if level < 1: raise UserError(_('Recursion error in modules dependencies !')) # whether some modules are installed with demo data demo = False for module in self: # determine dependency modules to update/others update_mods, ready_mods = self.browse(), self.browse() for dep in module.dependencies_id: if dep.state == 'unknown': raise UserError(_("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system.") % (module.name, dep.name,)) if dep.depend_id.state == newstate: ready_mods += dep.depend_id else: update_mods += dep.depend_id # update dependency modules that require it, and determine demo for module update_demo = update_mods._state_update(newstate, states_to_update, level=level-1) module_demo = module.demo or update_demo or any(mod.demo for mod in ready_mods) demo = demo or module_demo if module.state in states_to_update: # check dependencies and update module itself self.check_external_dependencies(module.name, newstate) module.write({'state': newstate, 'demo': module_demo}) return demo @assert_log_admin_access @api.multi def button_install(self): # domain to select auto-installable (but not yet installed) modules auto_domain = [('state', '=', 'uninstalled'), ('auto_install', '=', True)] # determine whether an auto-install module must be installed: # - all its dependencies are installed or to be installed, # - at least one dependency is 'to install' install_states = frozenset(('installed', 'to install', 'to upgrade')) def must_install(module): states = set(dep.state for dep in module.dependencies_id) return states <= install_states and 'to install' in states modules = self while modules: # Mark the given modules and their dependencies to be installed. modules._state_update('to install', ['uninstalled']) # Determine which auto-installable modules must be installed. modules = self.search(auto_domain).filtered(must_install) # the modules that are installed/to install/to upgrade install_mods = self.search([('state', 'in', list(install_states))]) # check individual exclusions install_names = {module.name for module in install_mods} for module in install_mods: for exclusion in module.exclusion_ids: if exclusion.name in install_names: msg = _('Modules "%s" and "%s" are incompatible.') raise UserError(msg % (module.shortdesc, exclusion.exclusion_id.shortdesc)) # check category exclusions def closure(module): todo = result = module while todo: result |= todo todo = todo.mapped('dependencies_id.depend_id') return result exclusives = self.env['ir.module.category'].search([('exclusive', '=', True)]) for category in exclusives: # retrieve installed modules in category and sub-categories categories = category.search([('id', 'child_of', category.ids)]) modules = install_mods.filtered(lambda mod: mod.category_id in categories) # the installation is valid if all installed modules in categories # belong to the transitive dependencies of one of them if modules and not any(modules <= closure(module) for module in modules): msg = _('You are trying to install incompatible modules in category "%s":') labels = dict(self.fields_get(['state'])['state']['selection']) raise UserError("\n".join([msg % category.name] + [ "- %s (%s)" % (module.shortdesc, labels[module.state]) for module in modules ])) return dict(ACTION_DICT, name=_('Install')) @assert_log_admin_access @api.multi def button_immediate_install(self): """ Installs the selected module(s) immediately and fully, returns the next res.config action to execute :returns: next res.config item to execute :rtype: dict[str, object] """ _logger.info('User #%d triggered module installation', self.env.uid) return self._button_immediate_function(type(self).button_install) @assert_log_admin_access @api.multi def button_install_cancel(self): self.write({'state': 'uninstalled', 'demo': False}) return True @assert_log_admin_access @api.multi def module_uninstall(self): """ Perform the various steps required to uninstall a module completely including the deletion of all database structures created by the module: tables, columns, constraints, etc. """ modules_to_remove = self.mapped('name') self.env['ir.model.data']._module_data_uninstall(modules_to_remove) # we deactivate prefetching to not try to read a column that has been deleted self.with_context(prefetch_fields=False).write({'state': 'uninstalled', 'latest_version': False}) return True @api.multi def _remove_copied_views(self): """ Remove the copies of the views installed by the modules in `self`. Those copies do not have an external id so they will not be cleaned by `_module_data_uninstall`. This is why we rely on `key` instead. It is important to remove these copies because using them will crash if they rely on data that don't exist anymore if the module is removed. """ domain = expression.OR([[('key', '=like', m.name + '.%')] for m in self]) orphans = self.env['ir.ui.view'].with_context(**{'active_test': False, MODULE_UNINSTALL_FLAG: True}).search(domain) orphans.unlink() @api.multi @api.returns('self') def downstream_dependencies(self, known_deps=None, exclude_states=('uninstalled', 'uninstallable', 'to remove')): """ Return the modules that directly or indirectly depend on the modules in `self`, and that satisfy the `exclude_states` filter. """ if not self: return self known_deps = known_deps or self.browse() query = """ SELECT DISTINCT m.id FROM ir_module_module_dependency d JOIN ir_module_module m ON (d.module_id=m.id) WHERE d.name IN (SELECT name from ir_module_module where id in %s) AND m.state NOT IN %s AND m.id NOT IN %s """ self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids))) new_deps = self.browse([row[0] for row in self._cr.fetchall()]) missing_mods = new_deps - known_deps known_deps |= new_deps if missing_mods: known_deps |= missing_mods.downstream_dependencies(known_deps, exclude_states) return known_deps @api.multi @api.returns('self') def upstream_dependencies(self, known_deps=None, exclude_states=('installed', 'uninstallable', 'to remove')): """ Return the dependency tree of modules of the modules in `self`, and that satisfy the `exclude_states` filter. """ if not self: return self known_deps = known_deps or self.browse() query = """ SELECT DISTINCT m.id FROM ir_module_module_dependency d JOIN ir_module_module m ON (d.module_id=m.id) WHERE m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND m.state NOT IN %s AND m.id NOT IN %s """ self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids))) new_deps = self.browse([row[0] for row in self._cr.fetchall()]) missing_mods = new_deps - known_deps known_deps |= new_deps if missing_mods: known_deps |= missing_mods.upstream_dependencies(known_deps, exclude_states) return known_deps def next(self): """ Return the action linked to an ir.actions.todo is there exists one that should be executed. Otherwise, redirect to /web """ Todos = self.env['ir.actions.todo'] _logger.info('getting next %s', Todos) active_todo = Todos.search([('state', '=', 'open')], limit=1) if active_todo: _logger.info('next action is "%s"', active_todo.name) return active_todo.action_launch() return { 'type': 'ir.actions.act_url', 'target': 'self', 'url': '/web', } @api.multi def _button_immediate_function(self, function): try: # This is done because the installation/uninstallation/upgrade can modify a currently # running cron job and prevent it from finishing, and since the ir_cron table is locked # during execution, the lock won't be released until timeout. self._cr.execute("SELECT * FROM ir_cron FOR UPDATE NOWAIT") except psycopg2.OperationalError: raise UserError(_("The server is busy right now, module operations are not possible at" " this time, please try again later.")) function(self) self._cr.commit() api.Environment.reset() modules.registry.Registry.new(self._cr.dbname, update_module=True) self._cr.commit() env = api.Environment(self._cr, self._uid, self._context) # pylint: disable=next-method-called config = env['ir.module.module'].next() or {} if config.get('type') not in ('ir.actions.act_window_close',): return config # reload the client; open the first available root menu menu = env['ir.ui.menu'].search([('parent_id', '=', False)])[:1] return { 'type': 'ir.actions.client', 'tag': 'reload', 'params': {'menu_id': menu.id}, } @assert_log_admin_access @api.multi def button_immediate_uninstall(self): """ Uninstall the selected module(s) immediately and fully, returns the next res.config action to execute """ _logger.info('User #%d triggered module uninstallation', self.env.uid) return self._button_immediate_function(type(self).button_uninstall) @assert_log_admin_access @api.multi def button_uninstall(self): if 'base' in self.mapped('name'): raise UserError(_("The `base` module cannot be uninstalled")) if not all(state in ('installed', 'to upgrade') for state in self.mapped('state')): raise UserError(_( "One or more of the selected modules have already been uninstalled, if you " "believe this to be an error, you may try again later or contact support." )) deps = self.downstream_dependencies() (self + deps).write({'state': 'to remove'}) return dict(ACTION_DICT, name=_('Uninstall')) @assert_log_admin_access @api.multi def button_uninstall_wizard(self): """ Launch the wizard to uninstall the given module. """ return { 'type': 'ir.actions.act_window', 'target': 'new', 'name': _('Uninstall module'), 'view_mode': 'form', 'res_model': 'base.module.uninstall', 'context': {'default_module_id': self.id}, } @api.multi def button_uninstall_cancel(self): self.write({'state': 'installed'}) return True @assert_log_admin_access @api.multi def button_immediate_upgrade(self): """ Upgrade the selected module(s) immediately and fully, return the next res.config action to execute """ return self._button_immediate_function(type(self).button_upgrade) @assert_log_admin_access @api.multi def button_upgrade(self): Dependency = self.env['ir.module.module.dependency'] self.update_list() todo = list(self) i = 0 while i < len(todo): module = todo[i] i += 1 if module.state not in ('installed', 'to upgrade'): raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (module.name,)) self.check_external_dependencies(module.name, 'to upgrade') for dep in Dependency.search([('name', '=', module.name)]): if dep.module_id.state == 'installed' and dep.module_id not in todo: todo.append(dep.module_id) self.browse(module.id for module in todo).write({'state': 'to upgrade'}) to_install = [] for module in todo: for dep in module.dependencies_id: if dep.state == 'unknown': raise UserError(_('You try to upgrade the module %s that depends on the module: %s.\nBut this module is not available in your system.') % (module.name, dep.name,)) if dep.state == 'uninstalled': to_install += self.search([('name', '=', dep.name)]).ids self.browse(to_install).button_install() return dict(ACTION_DICT, name=_('Apply Schedule Upgrade')) @assert_log_admin_access @api.multi def button_upgrade_cancel(self): self.write({'state': 'installed'}) return True @staticmethod def get_values_from_terp(terp): return { 'description': terp.get('description', ''), 'shortdesc': terp.get('name', ''), 'author': terp.get('author', 'Unknown'), 'maintainer': terp.get('maintainer', False), 'contributors': ', '.join(terp.get('contributors', [])) or False, 'website': terp.get('website', ''), 'license': terp.get('license', 'LGPL-3'), 'sequence': terp.get('sequence', 100), 'application': terp.get('application', False), 'auto_install': terp.get('auto_install', False), 'icon': terp.get('icon', False), 'summary': terp.get('summary', ''), 'url': terp.get('url') or terp.get('live_test_url', ''), 'to_buy': False } @api.model def create(self, vals): new = super(Module, self).create(vals) module_metadata = { 'name': 'module_%s' % vals['name'], 'model': 'ir.module.module', 'module': 'base', 'res_id': new.id, 'noupdate': True, } self.env['ir.model.data'].create(module_metadata) return new # update the list of available packages @assert_log_admin_access @api.model def update_list(self): res = [0, 0] # [update, add] default_version = modules.adapt_version('1.0') known_mods = self.with_context(lang=None).search([]) known_mods_names = {mod.name: mod for mod in known_mods} # iterate through detected modules and update/create them in db for mod_name in modules.get_modules(): mod = known_mods_names.get(mod_name) terp = self.get_module_info(mod_name) values = self.get_values_from_terp(terp) if mod: updated_values = {} for key in values: old = getattr(mod, key) updated = tools.ustr(values[key]) if isinstance(values[key], pycompat.string_types) else values[key] if (old or updated) and updated != old: updated_values[key] = values[key] if terp.get('installable', True) and mod.state == 'uninstallable': updated_values['state'] = 'uninstalled' if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version): res[0] += 1 if updated_values: mod.write(updated_values) else: mod_path = modules.get_module_path(mod_name) if not mod_path or not terp: continue state = "uninstalled" if terp.get('installable', True) else "uninstallable" mod = self.create(dict(name=mod_name, state=state, **values)) res[1] += 1 mod._update_dependencies(terp.get('depends', [])) mod._update_exclusions(terp.get('excludes', [])) mod._update_category(terp.get('category', 'Uncategorized')) return res @assert_log_admin_access @api.multi def download(self, download=True): return [] @assert_log_admin_access @api.model def install_from_urls(self, urls): if not self.env.user.has_group('base.group_system'): raise AccessDenied() # One-click install is opt-in - cfr Issue #15225 ad_dir = tools.config.addons_data_dir if not os.access(ad_dir, os.W_OK): msg = (_("Automatic install of downloaded Apps is currently disabled.") + "\n\n" + _("To enable it, make sure this directory exists and is writable on the server:") + "\n%s" % ad_dir) _logger.warning(msg) raise UserError(msg) apps_server = urls.url_parse(self.get_apps_server()) OPENERP = odoo.release.product_name.lower() tmp = tempfile.mkdtemp() _logger.debug('Install from url: %r', urls) try: # 1. Download & unzip missing modules for module_name, url in urls.items(): if not url: continue # nothing to download, local version is already the last one up = urls.url_parse(url) if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc: raise AccessDenied() try: _logger.info('Downloading module `%s` from OpenERP Apps', module_name) response = requests.get(url) response.raise_for_status() content = response.content except Exception: _logger.exception('Failed to fetch module %s', module_name) raise UserError(_('The `%s` module appears to be unavailable at the moment, please try again later.') % module_name) else: zipfile.ZipFile(io.BytesIO(content)).extractall(tmp) assert os.path.isdir(os.path.join(tmp, module_name)) # 2a. Copy/Replace module source in addons path for module_name, url in urls.items(): if module_name == OPENERP or not url: continue # OPENERP is special case, handled below, and no URL means local module module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) bck = backup(module_path, False) _logger.info('Copy downloaded module `%s` to `%s`', module_name, module_path) shutil.move(os.path.join(tmp, module_name), module_path) if bck: shutil.rmtree(bck) # 2b. Copy/Replace server+base module source if downloaded if urls.get(OPENERP): # special case. it contains the server and the base module. # extract path is not the same base_path = os.path.dirname(modules.get_module_path('base')) # copy all modules in the SERVER/odoo/addons directory to the new "odoo" module (except base itself) for d in os.listdir(base_path): if d != 'base' and os.path.isdir(os.path.join(base_path, d)): destdir = os.path.join(tmp, OPENERP, 'addons', d) # XXX 'odoo' subdirectory ? shutil.copytree(os.path.join(base_path, d), destdir) # then replace the server by the new "base" module server_dir = tools.config['root_path'] # XXX or dirname() bck = backup(server_dir) _logger.info('Copy downloaded module `odoo` to `%s`', server_dir) shutil.move(os.path.join(tmp, OPENERP), server_dir) #if bck: # shutil.rmtree(bck) self.update_list() with_urls = [module_name for module_name, url in urls.items() if url] downloaded = self.search([('name', 'in', with_urls)]) installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')]) to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')]) post_install_action = to_install.button_immediate_install() if installed or to_install: # in this case, force server restart to reload python code... self._cr.commit() odoo.service.server.restart() return { 'type': 'ir.actions.client', 'tag': 'home', 'params': {'wait': True}, } return post_install_action finally: shutil.rmtree(tmp) @api.model def get_apps_server(self): return tools.config.get('apps_server', 'https://apps.odoo.com/apps') def _update_dependencies(self, depends=None): existing = set(dep.name for dep in self.dependencies_id) needed = set(depends or []) for dep in (needed - existing): self._cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (self.id, dep)) for dep in (existing - needed): self._cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (self.id, dep)) self.invalidate_cache(['dependencies_id'], self.ids) def _update_exclusions(self, excludes=None): existing = set(excl.name for excl in self.exclusion_ids) needed = set(excludes or []) for name in (needed - existing): self._cr.execute('INSERT INTO ir_module_module_exclusion (module_id, name) VALUES (%s, %s)', (self.id, name)) for name in (existing - needed): self._cr.execute('DELETE FROM ir_module_module_exclusion WHERE module_id=%s AND name=%s', (self.id, name)) self.invalidate_cache(['exclusion_ids'], self.ids) def _update_category(self, category='Uncategorized'): current_category = self.category_id current_category_path = [] while current_category: current_category_path.insert(0, current_category.name) current_category = current_category.parent_id categs = category.split('/') if categs != current_category_path: cat_id = modules.db.create_categories(self._cr, categs) self.write({'category_id': cat_id}) @api.multi def _update_translations(self, filter_lang=None): if not filter_lang: langs = self.env['res.lang'].search([('translatable', '=', True)]) filter_lang = [lang.code for lang in langs] elif not isinstance(filter_lang, (list, tuple)): filter_lang = [filter_lang] update_mods = self.filtered(lambda r: r.state in ('installed', 'to install', 'to upgrade')) mod_dict = { mod.name: mod.dependencies_id.mapped('name') for mod in update_mods } mod_names = topological_sort(mod_dict) self.env['ir.translation'].load_module_terms(mod_names, filter_lang) @api.multi def _check(self): for module in self: if not module.description_html: _logger.warning('module %s: description is empty !', module.name) @api.model @tools.ormcache() def _installed(self): """ Return the set of installed modules as a dictionary {name: id} """ return { module.name: module.id for module in self.sudo().search([('state', '=', 'installed')]) }
class BackupNotification(models.Model): _name = "odoo_backup_sh.notification" _description = "Backup Notifications" _inherit = ["mail.thread", "mail.activity.mixin"] _order = "date_create desc" _rec_name = "date_create" date_create = fields.Datetime("Date", readonly=True, default=fields.Datetime.now) type = fields.Selection( [ ("insufficient_credits", "Insufficient Credits"), ("forecast_insufficient_credits", "Forecast About Insufficient Credits"), ("other", "Other"), ], string="Notification Type", readonly=True, default="other", ) message = fields.Text("Message", readonly=True) is_read = fields.Boolean("Is Read", readonly=True) def toggle_is_read(self): self.write({"is_read": not self.is_read}) self.activity_ids.unlink() return True def create_mail_activity_record(self): self.env["mail.activity"].create({ "res_id": self.id, "res_model_id": self.env.ref( "odoo_backup_sh.model_odoo_backup_sh_notification").id, "activity_type_id": self.env.ref("odoo_backup_sh.mail_activity_data_notification").id, "summary": "Please read important message.", "date_deadline": datetime.today().strftime(DEFAULT_SERVER_DATE_FORMAT), }) @api.model def fetch_notifications(self): config_params = self.env["ir.config_parameter"] data = { "params": { "user_key": config_params.get_param("odoo_backup_sh.user_key"), "date_last_request": config_params.get_param("odoo_backup_sh.date_last_request", None), } } response = requests.post(BACKUP_SERVICE_ENDPOINT + "/fetch_notifications", json=data).json()["result"] config_params.set_param("odoo_backup_sh.date_last_request", response["date_last_request"]) for n in response["notifications"]: if n.get("type") == "forecast_insufficient_credits": existing_forecast_msg = self.search([ ("type", "=", "forecast_insufficient_credits") ]) if existing_forecast_msg: if existing_forecast_msg.activity_ids: existing_forecast_msg.activity_ids.unlink() existing_forecast_msg.unlink() new_record = self.create(n) new_record.create_mail_activity_record()
class MuskathlonRegistration(models.Model): _name = 'event.registration' _inherit = 'event.registration' lead_id = fields.Many2one('crm.lead', 'Lead') backup_id = fields.Integer(help='Old muskathlon registration id') is_muskathlon = fields.Boolean( related='compassion_event_id.website_muskathlon') sport_discipline_id = fields.Many2one( 'sport.discipline', 'Sport discipline', ) sport_level = fields.Selection([('beginner', 'Beginner'), ('average', 'Average'), ('advanced', 'Advanced')]) sport_level_description = fields.Text('Describe your sport experience') t_shirt_size = fields.Selection( related='partner_id.advocate_details_id.t_shirt_size', store=True) t_shirt_type = fields.Selection( related='partner_id.advocate_details_id.t_shirt_type', store=True) muskathlon_participant_id = fields.Char( related='partner_id.muskathlon_participant_id') muskathlon_event_id = fields.Char( related='compassion_event_id.muskathlon_event_id') reg_id = fields.Char(string='Muskathlon registration ID', size=128) is_in_two_months = fields.Boolean(compute='_compute_is_in_two_months') _sql_constraints = [ ('reg_unique', 'unique(event_id,partner_id)', 'Only one registration per participant/event is allowed!') ] @api.model def create(self, values): # Automatically complete the task sign_child_protection if the charter # has already been signed. partner = self.env['res.partner'].browse(values.get('partner_id')) completed_tasks = values.setdefault('completed_task_ids', []) if partner and partner.has_agreed_child_protection_charter: task = self.env.ref('muskathlon.task_sign_child_protection') completed_tasks.append((4, task.id)) if partner and partner.user_ids and any( partner.mapped('user_ids.login_date')): task = self.env.ref('muskathlon.task_activate_account') completed_tasks.append((4, task.id)) return super(MuskathlonRegistration, self).create(values) @api.multi def _compute_step2_tasks(self): # Consider Muskathlon task for scan passport super(MuskathlonRegistration, self)._compute_step2_tasks() muskathlon_passport = self.env.ref('muskathlon.task_scan_passport') for reg in self: reg.passport_uploaded = muskathlon_passport in \ reg.completed_task_ids def _compute_amount_raised(self): # Use Muskathlon report to compute Muskathlon event donation muskathlon_report = self.env['muskathlon.report'] m_reg = self.filtered('compassion_event_id.website_muskathlon') for registration in m_reg: amount_raised = int( sum(item.amount for item in muskathlon_report.search([ ('user_id', '=', registration.partner_id.id), ('event_id', '=', registration.compassion_event_id.id), ]))) registration.amount_raised = amount_raised super(MuskathlonRegistration, (self - m_reg))._compute_amount_raised() def _compute_is_in_two_months(self): """this function define is the bollean hide or not the survey""" for registration in self: today = datetime.date.today() start_day = fields.Date.from_string(registration.event_begin_date) delta = start_day - today registration.is_in_two_months = delta.days < 60 @api.onchange('event_id') def onchange_event_id(self): return { 'domain': { 'sport_discipline_id': [('id', 'in', self.compassion_event_id.sport_discipline_ids.ids)] } } @api.onchange('sport_discipline_id') def onchange_sport_discipline(self): if self.sport_discipline_id and self.sport_discipline_id not in \ self.compassion_event_id.sport_discipline_ids: self.sport_discipline_id = False return { 'warning': { 'title': _('Invalid sport'), 'message': _('This sport is not in muskathlon') } } @job(default_channel='root.muskathlon') @related_action('related_action_registration') def notify_new_registration(self): """Notify user for registration""" partners = self.mapped('user_id.partner_id') | self.event_id.mapped( 'message_partner_ids') self.message_subscribe(partners.ids) self.message_post( body=_( "The participant registered through the Muskathlon website."), subject=_("%s - New Muskathlon registration") % self.name, message_type='email', subtype="website_event_compassion.mt_registration_create") return True @job(default_channel='root.muskathlon') @related_action('related_action_registration') def muskathlon_medical_survey_done(self): for registration in self: user_input = self.env['survey.user_input'].search([ ('partner_id', '=', registration.partner_id_id), ('survey_id', '=', registration.event_id.medical_survey_id.id) ], limit=1) registration.write({ 'completed_task_ids': [(4, self.env.ref('muskathlon.task_medical').id)], 'medical_survey_id': user_input.id }) # here we need to send a mail to the muskathlon doctor muskathlon_doctor_email = self.env[ 'ir.config_parameter'].get_param('muskathlon.doctor.email') if muskathlon_doctor_email: template = self.env \ .ref("muskathlon.medical_survey_to_doctor_template") \ .with_context(email_to=muskathlon_doctor_email).sudo() template.send_mail( user_input.id, force_send=True, email_values={'email_to': muskathlon_doctor_email}) return True
class Surgery(models.Model): _name = 'medical.surgery' _description = 'Surgery' _inherit = 'res.partner' def surgery_duration(self, name): if (self.surgery_end_date and self.surgery_date): return self.surgery_end_date - self.surgery_date else: return None def patient_age_at_surgery(self, name): if (self.patient.name.dob and self.surgery_date): rdelta = relativedelta (self.surgery_date.date(), self.patient.name.dob) years_months_days = str(rdelta.years) + 'y ' \ + str(rdelta.months) + 'm ' \ + str(rdelta.days) + 'd' return years_months_days else: return None patient = fields.Many2one( comodel_name='medical.patient', string='Patient', required=True ) admission = fields.Many2one( 'medical.appointment', string='Admission' ) operating_room = fields.Many2one( 'medical.center', string='Operating Room' ) code = fields.Char( 'Code', readonly=True, help="Health Center code / sequence" ) procedures = fields.One2many( comodel_name='medical.operation', inverse_name='name', string='Procedures', help="List of the procedures in the surgery. Please enter the first " "one as the main procedure" ) supplies = fields.One2many( comodel_name='medical.surgery_supply', inverse_name='name', string='Supplies', help="List of the supplies required for the surgery" ) pathology = fields.Many2one( comodel_name='medical.pathology', strin='Condition', help="Base Condition / Reason" ) classification = fields.Selection( [ ('o', 'Optional'), ('r', 'Required'), ('u', 'Urgent'), ('e', 'Emergency'), ], string='Urgency', help="Urgency level for this surgery", sort=False ) surgeon = fields.Many2one( 'medical.practitioner', 'Surgeon', help="Surgeon who did the procedure" ) anesthetist = fields.Many2one( 'medical.practitioner', 'Anesthetist', help="Anesthetist in charge" ) surgery_date = fields.Datetime( string='Date', help="Start of the Surgery" ) surgery_end_date = fields.Datetime( 'End', help="Automatically set when the surgery is done." "It is also the estimated end time when confirming the surgery." ) surgery_length = fields.Datetime( 'Duration', states={('invisible', 'state', 'done'), ('state', 'signed')}, help="Length of the surgery", ) state = fields.Selection( [ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('in_progress', 'In Progress'), ('done', 'Done'), ('signed', 'Signed'), ], 'State', readonly=True, sort=False ) signed_by = fields.Many2one( 'medical.practitioner', 'Signed by', readonly=True, states={'invisible', 'state', 'signed'}, help="Health Professional that signed this surgery document" ) # age is deprecated in GNU Health 2.0 age = fields.Char( 'Estimative Age', help="Use this field for historical purposes, \ when no date of surgery is given" ) computed_age = fields.Char( string='Age', help="Computed patient age at the moment of the surgery", ) gender = fields.Selection( [ ('m', 'Male'), ('f', 'Female'), ('f-m','Female -> Male'), ('m-f','Male -> Female'), ], 'Gender', # 'get_patient_gender', searcher='search_patient_gender' ) description = fields.Char(string='Description') preop_mallampati = fields.Selection( [ ('Class 1', 'Class 1: Full visibility of tonsils, uvula and soft palate'), ('Class 2', 'Class 2: Visibility of hard and soft palate, upper portion of tonsils and uvula'), ('Class 3', 'Class 3: Soft and hard palate and base of the uvula are visible'), ('Class 4', 'Class 4: Only Hard Palate visible'), ], 'Mallampati Score', sort=False ) preop_bleeding_risk = fields.Boolean( 'Risk of Massive bleeding', help="Patient has a risk of losing more than 500 " "ml in adults of over 7ml/kg in infants. If so, make sure that " "intravenous access and fluids are available" ) preop_oximeter = fields.Boolean( 'Pulse Oximeter in place', help="Pulse oximeter is in place " "and functioning" ) preop_site_marking = fields.Boolean( 'Surgical Site Marking', help="The surgeon has marked the surgical incision" ) preop_antibiotics = fields.Boolean( 'Antibiotic Prophylaxis', help="Prophylactic antibiotic treatment within the last 60 minutes" ) preop_sterility = fields.Boolean( 'Sterility confirmed', help="Nursing team has confirmed sterility of the devices and room" ) preop_asa = fields.Selection( [ ('ps1', 'PS 1 : Normal healthy patient'), ('ps2', 'PS 2 : Patients with mild systemic disease'), ('ps3', 'PS 3 : Patients with severe systemic disease'), ('ps4', 'PS 4 : Patients with severe systemic disease that is a constant threat to life '), ('ps5', 'PS 5 : Moribund patients who are not expected to survive without the operation'), ('ps6', 'PS 6 : A declared brain-dead patient who organs are being removed for donor purposes'), ], 'ASA PS', help="ASA pre-operative Physical Status", sort=False ) preop_rcri = fields.Many2one( 'medical.rcri', 'RCRI', help='Patient Revised Cardiac Risk Index\n' 'Points 0: Class I Very Low (0.4% complications)\n' 'Points 1: Class II Low (0.9% complications)\n' 'Points 2: Class III Moderate (6.6% complications)\n' 'Points 3 or more : Class IV High (>11% complications)') surgical_wound = fields.Selection( [ ('I', 'Clean . Class I'), ('II', 'Clean-Contaminated . Class II'), ('III', 'Contaminated . Class III'), ('IV', 'Dirty-Infected . Class IV'), ], 'Surgical wound', sort=False ) extra_info = fields.Text(string='Extra Info') anesthesia_report = fields.Text(string='Anesthesia Report') institution = fields.Many2one( 'res.partner', string='Institution' ) report_surgery_date = fields.Date( 'Surgery Date', ) report_surgery_time = fields.Datetime( 'Surgery Time', ) surgery_team = fields.One2many( comodel_name='medical.surgery_team', inverse_name='partner_id', string='Team Members', help="Professionals Involved in the surgery" ) postoperative_dx = fields.Many2one( comodel_name='medical.pathology', string='Post-op dx', states={('invisible', 'state', 'done'), ('state', 'signed')}, help="Post-operative diagnosis" ) def default_institution(): HealthInst = self.env['res.partner'] institution = HealthInst.get_institution() return institution def default_surgery_date(): return Datetime.now() def default_surgeon(): HealthProf= self.env['medical.practitioner'] surgeon = HealthProf.get_practitioner() return surgeon def default_state(): return 'draft' def get_patient_gender(self, name): return self.patient.gender def search_patient_gender(self, name, clause): res = [] value = clause[2] res.append(('patient.name.gender', clause[1], value)) return res # Show the gender and age upon entering the patient # These two are function fields (don't exist at DB level) @api.depends('patient') def on_change_patient(self): gender=None age='' self.gender = self.patient.gender self.computed_age = self.patient.age @api.model def create(self, vals): Sequence = self.env['ir.sequence'] Config = self.env['medical.sequences'] vals = [x.copy() for x in vals] for values in vals: if not values.get('code'): config = Config(1) values['code'] = Sequence.get_id( config.surgery_code_sequence.id) return super(Surgery, self).create(vals) def __setup__(self): super(Surgery, self).__setup__() self._error_messages.update({ 'end_date_before_start': 'End time "%(end_date)s" BEFORE ' 'surgery date "%(surgery_date)s"', 'or_is_not_available': 'Operating Room is not available'}) self._order.insert(0, ('surgery_date', 'DESC')) self._buttons.update({ 'confirmed': { ('invisible', 'state', 'draft'), ('state', 'cancelled'), }, 'cancel': { ('invisible', 'state', 'confirmed'), }, 'start': { ('invisible', 'state', 'confirmed'), }, 'done': { ('invisible', 'state', 'in_progress'), }, 'signsurgery': { ('invisible', 'state', 'done'), }, } ) def validate(self, surgeries): super(Surgery, self).validate(surgeries) for surgery in surgeries: surgery.validate_surgery_period() def validate_surgery_period(self): Lang = self.env['ir.lang'] language, = Lang.search([ ('code', '=', Transaction().language), ]) if (self.surgery_end_date and self.surgery_date): if (self.surgery_end_date < self.surgery_date): self.raise_user_error('end_date_before_start', { 'surgery_date': Lang.strftime(self.surgery_date, language.code, language.date), 'end_date': Lang.strftime(self.surgery_end_date, language.code, language.date), }) def write(self, surgeries, vals): # Don't allow to write the record if the surgery has been signed if surgeries[0].state == 'signed': self.raise_user_error( "This surgery is at state Done and has been signed\n" "You can no longer modify it.") return super(Surgery, self).write(surgeries, vals) ## Method to check for availability and make the Operating Room reservation # for the associated surgery def confirmed(self, surgeries): surgery_id = surgeries[0] Operating_room = self.env['medical.center'] cursor = Transaction().connection.cursor() # Operating Room and end surgery time check if (not surgery_id.operating_room or not surgery_id.surgery_end_date): self.raise_user_error("Operating Room and estimated end time " "are needed in order to confirm the surgery") or_id = surgery_id.operating_room.id cursor.execute("SELECT COUNT(*) \ FROM gnuhealth_surgery \ WHERE (surgery_date::timestamp,surgery_end_date::timestamp) \ OVERLAPS (timestamp %s, timestamp %s) \ AND (state = %s or state = %s) \ AND operating_room = CAST(%s AS INTEGER) ", (surgery_id.surgery_date, surgery_id.surgery_end_date, 'confirmed', 'in_progress', str(or_id))) res = cursor.fetchone() if (surgery_id.surgery_end_date < surgery_id.surgery_date): self.raise_user_error("The Surgery end date must later than the \ Start") if res[0] > 0: self.raise_user_error('or_is_not_available') else: self.write(surgeries, {'state': 'confirmed'}) # Cancel the surgery and set it to draft state # Free the related Operating Room def cancel(self, surgeries): surgery_id = surgeries[0] Operating_room = self.env['medical.center'] self.write(surgeries, {'state': 'cancelled'}) # Start the surgery def start(self, surgeries): surgery_id = surgeries[0] Operating_room = self.env['medical.center'] self.write(surgeries, {'state': 'in_progress', 'surgery_date': Datetime.now(), 'surgery_end_date': Datetime.now()}) Operating_room.write([surgery_id.operating_room], {'state': 'occupied'}) # Finnish the surgery # Free the related Operating Room def done(self, surgeries): surgery_id = surgeries[0] Operating_room = self.env['medical.center'] self.write(surgeries, {'state': 'done', 'surgery_end_date': Datetime.now()}) Operating_room.write([surgery_id.operating_room], {'state': 'free'}) # Sign the surgery document, and the surgical act. def signsurgery(self, surgeries): surgery_id = surgeries[0] # Sign, change the state of the Surgery to "Signed" # and write the name of the signing health professional signing_hp = self.env['medical.practitioner'].get_practitioner() if not signing_hp: self.raise_user_error( "No health professional associated to this user !") self.write(surgeries, { 'state': 'signed', 'signed_by': signing_hp}) def get_report_surgery_date(self, name): Company = self.env('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.surgery_date return Datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).date() def get_report_surgery_time(self, name): Company = self.env['company.company'] timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.surgery_date return Datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).time() def search_rec_name(self, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [ bool_op, ('patient',) + tuple(clause[1:]), ('code',) + tuple(clause[1:]), ]
class HrIqama(models.Model): _name = 'hr.iqama' _order = 'id desc' _inherit = 'mail.thread' _description = 'HR IQAMA' @api.one @api.depends('birthdate') def _get_age(self): """ Calculate age, based on inputed birthdate """ if self.birthdate: try: birthdate = datetime.strptime(self.birthdate, '%Y-%m-%d') age_year = (datetime.now() - birthdate).days / 365 self.age = age_year except: self.age = 0.0 else: self.age = 0.0 name = fields.Char('Name(As in Passport)', size=50, help="Name of the dependent", required=True) arabic_name = fields.Char('Arabic Name', size=50) relation = fields.Selection([('employee', 'Self'), ('child', 'Child'), ('spouse', 'Spouse')], 'Relation') employee_id = fields.Many2one( 'hr.employee', 'Employee', default=lambda self: self.env['hr.employee'].get_employee()) job_id = fields.Many2one('hr.job', readonly=True, string='Job Position') department_id = fields.Many2one('hr.department', readonly=True, string='Department') branch_id = fields.Many2one('hr.branch', readonly=True, string='Office') company_id = fields.Many2one('res.company', readonly=True, string='Company', default=lambda self: self.env.user.company_id) iqama_no = fields.Char('IQAMA Number', size=32, copy=False) issue_date = fields.Date('Date of Issue', copy=False) expiry_date = fields.Date('Date of Expiry', copy=False) profession = fields.Char('Profession', size=64, readonly=False) serial_number = fields.Char('Serial Number', copy=False) place_of_issue = fields.Char('Place of Issue', copy=False) nationality = fields.Many2one('res.country', 'Nationality', readonly=False) religion = fields.Selection([('muslim', 'Muslim'), ('non-muslim', 'Non Muslim')], 'Religion', default="muslim") state = fields.Selection([('draft', 'Draft'), ('confirm', 'Waiting Approval'), ('validate', 'Approved'), ('inprogress', 'In Progress'), ('received', 'Issued'), ('need_renewal', 'To be Renewed'), ('refuse', 'Refused')], 'Status', default='draft') request_type = fields.Selection([('employee', 'Employee'), ('family', 'Family'), ('new_born', 'New Born Baby')], 'Type', required=True, default='employee') approved_date = fields.Datetime('Approved Date', readonly=True, copy=False) approved_by = fields.Many2one('res.users', 'Approved by', readonly=True, copy=False) refused_by = fields.Many2one('res.users', 'Refused by', readonly=True, copy=False) refused_date = fields.Datetime('Refused on', readonly=True, copy=False) age = fields.Float(compute='_get_age', string="Age") birthdate = fields.Date("Date of Birth") is_saudi = fields.Boolean("Is Saudi") description = fields.Text('Description') handled_by_id = fields.Many2one('hr.employee', 'Handled By') hijri_expiry_date = fields.Char('Date of Expiry(Hijri)') iqama_position = fields.Char('IQAMA Position', copy=False) arrival_date = fields.Date('Arrival Date(In KSA)') current_status = fields.Boolean('In Saudi?') @api.onchange('expiry_date', 'issue_date') def onchange_expiry_date(self): """ check expiry date is lower than issue date return: warning """ if self.expiry_date and self.issue_date and self.expiry_date < self.issue_date: raise ValidationError( _('Issue date must be greater then Expiry date.')) @api.onchange('request_type') def onchange_request_type(self): """ check the request_type, Employee, Family or New Born, """ if self.request_type != 'employee' or not self.employee_id: self.birthdate = False self.arabic_name = False self.name = False else: self.birthdate = self.employee_id.sudo().birthday or False self.arabic_name = self.employee_id.arabic_name or False self.name = self.employee_id.name @api.onchange('employee_id') def onchange_employee_id(self): """ auto change the values like, Profession, Job, Department, etc depends on selected Employee. """ self.arabic_name = self.employee_id.arabic_name self.profession = self.employee_id.sudo().job_id.name self.job_id = self.employee_id.sudo().job_id.id self.branch_id = self.employee_id.branch_id.id self.company_id = self.employee_id.company_id.id self.department_id = self.employee_id.department_id.id self.nationality = self.employee_id.sudo().country_id.id self.religion = self.employee_id.religion self.is_saudi = self.employee_id.is_saudi if self.request_type == 'employee': self.birthdate = self.employee_id.sudo().birthday self.name = self.employee_id.name @api.model def create(self, values): """ Create a new record :return: Newly created record ID """ partner = [] if values.get('employee_id', False): employee_id = self.env['hr.employee'].browse(values['employee_id']) values.update({ 'job_id': employee_id.job_id.id, 'department_id': employee_id.department_id.id, 'company_id': employee_id.company_id.id, 'branch_id': employee_id.branch_id.id, }) res = super(HrIqama, self).create(values) if res.employee_id.parent_id.user_id: partner.append(res.employee_id.parent_id.user_id.partner_id.id) if res.employee_id.user_id: partner.append(res.employee_id.user_id.partner_id.id) channel_id = self.env.ref('saudi_hr.manager_channel').id res.message_subscribe(partner_ids=partner, channel_ids=[channel_id]) return res @api.multi def write(self, values): """ Update an existing record. :param values: Current record fields data :return: Current update record ID """ partner = [] if values.get('employee_id', False): employee_id = self.env['hr.employee'].browse(values['employee_id']) values.update({ 'job_id': employee_id.job_id.id, 'department_id': employee_id.department_id.id, 'company_id': employee_id.company_id.id, 'branch_id': employee_id.branch_id.id or False, }) if employee_id.user_id: partner.append(employee_id.user_id.partner_id.id) if employee_id.parent_id.user_id: partner.append(employee_id.parent_id.user_id.partner_id.id) # channel_id=self.env.ref('saudi_hr.manager_channel').id self.message_subscribe(partner_ids=partner) return super(HrIqama, self).write(values) @api.multi def unlink(self): """ Delete/ remove selected record :return: Deleted record ID """ for line in self: if line.state not in ['draft']: raise UserError( _('You cannot remove the IQAMA record which is not in draft state!' )) return super(HrIqama, line).unlink() @api.model def check_iqama_expiry(self): """ Send mail for IQAMA Expiry """ template_id = self.env.ref('saudi_hr_iqama.hr_iqama_expiration_email') today_date = str(fields.Date.today()) next_date = datetime.strptime( str(fields.Date.today()), DEFAULT_SERVER_DATE_FORMAT) + timedelta(days=30) next_date = datetime.strftime(next_date, DEFAULT_SERVER_DATE_FORMAT) for iqama in self.search([('state', '=', 'received'), ('expiry_date', '>=', today_date), ('expiry_date', '<=', next_date)]): diff = datetime.strptime(str(iqama.expiry_date), DEFAULT_SERVER_DATE_FORMAT) - \ datetime.strptime(str(fields.Date.today()), DEFAULT_SERVER_DATE_FORMAT) if diff.days == 10 and template_id: template_id.send_mail(iqama.id, force_send=False, raise_exception=False) if diff.days <= 10: iqama.iqama_need_renewal() @api.multi def iqama_confirm(self): """ sent the status of generating his/her iqama in confirm state """ gr_groups_config_obj = self.env['hr.groups.configuration'] for iqama in self: iqama.state = 'confirm' gr_groups_config_ids = gr_groups_config_obj.search([ ('branch_id', '=', iqama.employee_id.branch_id.id or False), ('gr_ids', '!=', False) ]) user_ids = gr_groups_config_ids and [ employee.user_id.id for employee in gr_groups_config_ids.gr_ids if employee.user_id ] or [] iqama.sudo().message_subscribe_users(user_ids=user_ids) @api.multi def iqama_inprogress(self): """ sent the status of generating his/her iqama in inprogress state """ for iqama in self: iqama.state = 'inprogress' iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA request is In progress')) @api.multi def iqama_refuse(self): """ sent the status of generating his/her iqama in refuse state """ for iqama in self: iqama.write({ 'state': 'refuse', 'refused_by': self.env.uid, 'refused_date': datetime.today() }) iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA request is Refused')) @api.multi def iqama_received(self): """ sent the status of generating his/her iqama in received state by user """ for iqama in self: iqama.state = 'received' iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA request is Received')) @api.multi def iqama_validate(self): """ sent the status of generating his/her iqama in validate state """ for iqama in self: iqama.write({ 'state': 'validate', 'approved_by': self.env.uid, 'approved_date': datetime.today() }) iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA request is Validated')) @api.multi def iqama_set_to_draft(self): """ sent the status of generating his/her iqama in draft state """ for iqama in self: iqama.write({ 'state': 'draft', 'approved_by': False, 'approved_date': False, 'refused_by': False, 'refused_date': False }) iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA request is reset to draft')) @api.multi def iqama_need_renewal(self): """ sent the status of generating his/her iqama in renewal state """ for iqama in self: iqama.state = 'need_renewal' iqama.message_post(message_type="email", subtype='mail.mt_comment', body=_('IQAMA need renewal'))
class AreluxSaleReportLine(models.Model): _name = 'arelux.sale.report.line' _description = 'Arelux Sale Report Line' _order = "position asc" arelux_sale_report_id = fields.Many2one( comodel_name='arelux.sale.report', string='Arelux Sale Report' ) arelux_sale_report_type_id = fields.Many2one( comodel_name='arelux.sale.report.type', string='Arelux Sale Report Type' ) position = fields.Integer( string='Posicion' ) ar_qt_activity_type = fields.Selection( selection=[ ('none','Ninguno'), ('arelux','Arelux'), ('todocesped','Todocesped'), ('evert','Evert') ], string='Tipo de actividad', default='none' ) ar_qt_customer_type = fields.Selection( selection=[ ('none','Ninguno'), ('particular','Particular'), ('profesional','Profesional') ], string='Tipo de cliente', default='none' ) crm_team_id = fields.Many2one( comodel_name='crm.team', string='Equipo de ventas' ) response_type = fields.Text( string='Response type' ) response_result_value = fields.Text( string='Response result value' ) group_by_user = fields.Boolean( default=False, string='Group by user' ) show_in_table_format = fields.Boolean( default=False, string='Show in table format' ) user_line = fields.One2many('arelux.sale.report.line.user', 'arelux_sale_report_line_id', string='User Lines', copy=True) sale_order_line = fields.One2many('arelux.sale.report.line.sale.order', 'arelux_sale_report_line_id', string='Sale Order Lines', copy=True) @api.one def remove_all_user_line(self): arelux_sale_report_line_user_ids = self.env['arelux.sale.report.line.user'].search([('arelux_sale_report_line_id', '=', self.id)]) if len(arelux_sale_report_line_user_ids)>0: for arelux_sale_report_line_user_id in arelux_sale_report_line_user_ids: arelux_sale_report_line_user_id.unlink() @api.one def remove_all_sale_order_line(self): arelux_sale_report_line_sale_order_ids = self.env['arelux.sale.report.line.sale.order'].search([('arelux_sale_report_line_id', '=', self.id)]) if len(arelux_sale_report_line_sale_order_ids)>0: for arelux_sale_report_line_sale_order_id in arelux_sale_report_line_sale_order_ids: arelux_sale_report_line_sale_order_id.unlink() @api.one def _get_line_info_real(self, custom_type): return_values = { 'response_type': '', 'response_result_value': '' } if custom_type=='sale_order_done_amount_untaxed': search_filters = [ ('state', 'in', ('sale', 'done')), ('amount_untaxed', '>', 0), ('claim', '=', False), ('confirmation_date', '>=', self.arelux_sale_report_id.date_from_filter), ('confirmation_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) #sale_team_id if self.crm_team_id.id>0: search_filters.append(('sale_team_id', '=', self.crm_team_id.id)) sale_order_ids = self.env['sale.order'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'sum' amount_untaxed = sum(sale_order_ids.mapped('amount_untaxed')) return_values['response_result_value'] = amount_untaxed return_values['amount_untaxed'] = amount_untaxed else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: #fix if need create user_id = int(sale_order_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'amount_untaxed': 0 } if user_id>0: res_users[user_id]['name'] = sale_order_id.user_id.name #sum res_users[user_id]['amount_untaxed'] += sale_order_id.amount_untaxed #fix response_result_value return_values['response_result_value'] = 'amount_untaxed' return_values['amount_untaxed'] = sum(sale_order_ids.mapped('amount_untaxed')) #remove_all self.remove_all_user_line() #creates if len(sale_order_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'amount_untaxed': res_user['amount_untaxed'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='sale_order_done_count': search_filters = [ ('state', 'in', ('sale', 'done')), ('amount_untaxed', '>', 0), ('claim', '=', False), ('confirmation_date', '>=', self.arelux_sale_report_id.date_from_filter), ('confirmation_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) #sale_team_id if self.crm_team_id.id>0: search_filters.append(('sale_team_id', '=', self.crm_team_id.id)) sale_order_ids = self.env['sale.order'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'count' return_values['response_result_value'] = len(sale_order_ids) return_values['count'] = len(sale_order_ids) else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: #fix if need create user_id = int(sale_order_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'count': 0 } if user_id>0: res_users[user_id]['name'] = sale_order_id.user_id.name #sum res_users[user_id]['count'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' return_values['count'] = len(sale_order_ids) #remove_all self.remove_all_user_line() #creates if len(sale_order_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['count'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='sale_order_ticket_medio': search_filters = [ ('state', 'in', ('sale', 'done')), ('amount_untaxed', '>', 0), ('claim', '=', False), ('confirmation_date', '>=', self.arelux_sale_report_id.date_from_filter), ('confirmation_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) #sale_team_id if self.crm_team_id.id>0: search_filters.append(('sale_team_id', '=', self.crm_team_id.id)) sale_order_ids = self.env['sale.order'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'sum' amount_untaxed = sum(sale_order_ids.mapped('amount_untaxed')) ticket_medio = 0 if len(sale_order_ids)>0: ticket_medio = (amount_untaxed/len(sale_order_ids)) ticket_medio = "{0:.2f}".format(ticket_medio) return_values['response_result_value'] = ticket_medio return_values['ticket_medio'] = ticket_medio else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: #fix if need create user_id = int(sale_order_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'count': 0, 'amount_untaxed': 0 } if user_id>0: res_users[user_id]['name'] = sale_order_id.user_id.name #sum res_users[user_id]['count'] += 1 res_users[user_id]['amount_untaxed'] += sale_order_id.amount_untaxed #fix response_result_value return_values['response_result_value'] = 'amount_untaxed' amount_untaxed = sum(sale_order_ids.mapped('amount_untaxed')) ticket_medio = 0 if len(sale_order_ids)>0: ticket_medio = (amount_untaxed/len(sale_order_ids)) ticket_medio = "{0:.2f}".format(ticket_medio) return_values['ticket_medio'] = ticket_medio #remove_all self.remove_all_user_line() #creates if len(sale_order_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'amount_untaxed': 0 } if res_user['count']>0: arelux_sale_report_line_user_vals['amount_untaxed'] = (res_user['amount_untaxed']/res_user['count']) arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='sale_order_sent_count': search_filters = [ ('date_order_management', '!=', False), ('amount_untaxed', '>', 0), ('opportunity_id', '!=', False), ('claim', '=', False), ('date_order_management', '>=', self.arelux_sale_report_id.date_from_filter), ('date_order_management', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) #sale_team_id if self.crm_team_id.id>0: search_filters.append(('sale_team_id', '=', self.crm_team_id.id)) sale_order_ids = self.env['sale.order'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'count' return_values['response_result_value'] = len(sale_order_ids) return_values['count'] = len(sale_order_ids) else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: #fix if need create user_id = int(sale_order_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'count': 0 } if user_id>0: res_users[user_id]['name'] = sale_order_id.user_id.name #sum res_users[user_id]['count'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' return_values['count'] = len(sale_order_ids) #remove_all self.remove_all_user_line() #creates if len(sale_order_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['count'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='sale_order_done_muestras': search_filters = [ ('state', 'in', ('sale', 'done')), ('amount_untaxed', '=', 0), ('claim', '=', False), ('carrier_id', '!=', False), ('carrier_id.carrier_type', '=', 'nacex'), ('confirmation_date', '>=', self.arelux_sale_report_id.date_from_filter), ('confirmation_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) #sale_team_id if self.crm_team_id.id>0: search_filters.append(('sale_team_id', '=', self.crm_team_id.id)) sale_order_ids = self.env['sale.order'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'count' return_values['response_result_value'] = len(sale_order_ids) return_values['count'] = len(sale_order_ids) else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: #fix if need create user_id = int(sale_order_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'count': 0 } if user_id>0: res_users[user_id]['name'] = sale_order_id.user_id.name #sum res_users[user_id]['count'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' return_values['count'] = len(sale_order_ids) #remove_all self.remove_all_user_line() #creates if len(sale_order_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['count'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='res_partner_potencial_count': return_values['response_type'] = 'list_by_user_id' search_filters = [ ('type', '=', 'contact'), ('active', '=', True), ('create_uid', '!=', 1), ('create_date', '>=', self.arelux_sale_report_id.date_from_filter), ('create_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) res_users = {} res_partner_ids = self.env['res.partner'].search(search_filters) if len(res_partner_ids)>0: for res_partner_id in res_partner_ids: #fix if need create user_id = int(res_partner_id.create_uid.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'total': 0 } if user_id>0: res_users[user_id]['name'] = res_partner_id.create_uid.name #sum res_users[user_id]['total'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' #remove_all self.remove_all_user_line() #creates if len(res_partner_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['total'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='cartera_actual_activa_count': search_filters = [ ('type', '=', 'contact'), ('active', '=', True), ('user_id', '!=', False), ('sale_order_amount_untaxed_year_now', '>', 0), ('create_date', '>=', self.arelux_sale_report_id.date_from_filter), ('create_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) res_partner_ids = self.env['res.partner'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'count' return_values['response_result_value'] = len(res_partner_ids) return_values['count'] = len(res_partner_ids) else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(res_partner_ids)>0: for res_partner_id in res_partner_ids: #fix if need create user_id = int(res_partner_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'count': 0 } if user_id>0: res_users[user_id]['name'] = res_partner_id.user_id.name #sum res_users[user_id]['count'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' return_values['count'] = len(res_partner_ids) #remove_all self.remove_all_user_line() #creates if len(res_partner_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['count'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='cartera_actual_count': search_filters = [ ('type', '=', 'contact'), ('active', '=', True), ('user_id', '!=', False), ('create_date', '>=', self.arelux_sale_report_id.date_from_filter), ('create_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) res_partner_ids = self.env['res.partner'].search(search_filters) if self.group_by_user==False: return_values['response_type'] = 'count' return_values['response_result_value'] = len(res_partner_ids) return_values['count'] = len(res_partner_ids) else: return_values['response_type'] = 'list_by_user_id' res_users = {} if len(res_partner_ids)>0: for res_partner_id in res_partner_ids: #fix if need create user_id = int(res_partner_id.user_id.id) if user_id not in res_users: res_users[user_id] = { 'id': user_id, 'name': 'Sin comercial', 'total': 0 } if user_id>0: res_users[user_id]['name'] = res_partner_id.user_id.name #sum res_users[user_id]['total'] += 1 #fix response_result_value return_values['response_result_value'] = 'count' return_values['count'] = len(res_partner_ids) #remove_all self.remove_all_user_line() #creates if len(res_partner_ids)>0: for res_user_id, res_user in res_users.items(): arelux_sale_report_line_user_vals = { 'arelux_sale_report_line_id': self.id, 'user_id': res_user['id'], 'count': res_user['total'] } arelux_sale_report_line_user_obj = self.env['arelux.sale.report.line.user'].sudo().create(arelux_sale_report_line_user_vals) elif custom_type=='nuevos_clientes_con_ventas': return_values['response_type'] = 'list_sale_orders' #partner_ids with sale_orders < date_from res_partner_ids = [] search_filters = [ ('state', 'in', ('sale', 'done')), ('claim', '=', False), ('amount_untaxed', '>', 0), ('confirmation_date', '<', self.arelux_sale_report_id.date_from_filter), ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) sale_order_ids = self.env['sale.order'].search(search_filters) if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: if sale_order_id.partner_id.id not in res_partner_ids: res_partner_ids.append(sale_order_id.partner_id.id) #sale_orders with filters and partner_id not in search_filters = [ ('state', 'in', ('sale', 'done')), ('claim', '=', False), ('amount_untaxed', '>', 0), ('partner_id', 'not in', res_partner_ids), ('confirmation_date', '>=', self.arelux_sale_report_id.date_from_filter), ('confirmation_date', '<=', self.arelux_sale_report_id.date_to_filter) ] #ar_qt_activity_type if self.ar_qt_activity_type!='none': search_filters.append(('ar_qt_activity_type', '=', self.ar_qt_activity_type)) #ar_qt_customer_type if self.ar_qt_customer_type!='none': search_filters.append(('ar_qt_customer_type', '=', self.ar_qt_customer_type)) sale_order_ids = self.env['sale.order'].search(search_filters) #sort_custom sale_order_ids2 = [] if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: sale_order_ids2.append({ 'id': sale_order_id.id, 'user_name': sale_order_id.user_id.name }) sale_order_ids = sorted(sale_order_ids2, key=operator.itemgetter('user_name')) #remove_all self.remove_all_sale_order_line() #creates if len(sale_order_ids)>0: for sale_order_id in sale_order_ids: arelux_sale_report_line_sale_order_vals = { 'arelux_sale_report_line_id': self.id, 'sale_order_id': sale_order_id['id'] } arelux_sale_report_line_sale_order_obj = self.env['arelux.sale.report.line.sale.order'].sudo().create(arelux_sale_report_line_sale_order_vals) return return_values @api.one def _get_line_info(self): if self.arelux_sale_report_type_id.id>0: if self.arelux_sale_report_type_id.custom_type=='ratio_muestras': if self.group_by_user==False: return_values_sale_order_done_muestras = self._get_line_info_real('sale_order_done_muestras')[0] return_values_sale_order_sent_count = self._get_line_info_real('sale_order_sent_count')[0] self.response_type = 'percent' ratio_muestras = 0 if return_values_sale_order_done_muestras['count']>0 and return_values_sale_order_sent_count['count']>0: ratio_muestras = (float(return_values_sale_order_done_muestras['count'])/float(return_values_sale_order_sent_count['count']))*100 ratio_muestras = "{0:.2f}".format(ratio_muestras) self.response_result_value = ratio_muestras else: self.response_type = 'percent' self.response_result_value = 'percent' elif self.arelux_sale_report_type_id.custom_type=='ratio_calidad': if self.group_by_user==False: self.response_type = 'percent' return_values_sale_order_done_count = self._get_line_info_real('sale_order_done_count')[0] return_values_sale_order_sent_count = self._get_line_info_real('sale_order_sent_count')[0] ratio_calidad = 0 if return_values_sale_order_done_count['count']>0 and return_values_sale_order_sent_count['count']>0: ratio_calidad = (float(return_values_sale_order_done_count['count'])/float(return_values_sale_order_sent_count['count']))*100 ratio_calidad = "{0:.2f}".format(ratio_calidad) self.response_result_value = ratio_calidad else: self.response_type = 'percent' self.response_result_value = 'percent' elif self.arelux_sale_report_type_id.custom_type=='line_break': self.response_type = 'line_break' else: return_values = self._get_line_info_real(self.arelux_sale_report_type_id.custom_type)[0] self.response_type = return_values['response_type'] self.response_result_value = return_values['response_result_value'] #_logger.info(response) #return response
class MuskathlonRegistrationForm(models.AbstractModel): _name = 'cms.form.event.registration.muskathlon' _inherit = ['cms.form.payment', 'cms.form.event.match.partner'] # The form is inside a Muskathlon details page form_buttons_template = 'cms_form_compassion.modal_form_buttons' form_id = 'modal_muskathlon_registration' _form_model = 'event.registration' _form_required_fields = [ 'ambassador_picture_1', 'ambassador_quote', 'sport_level', 'sport_level_description', 'gtc_accept', 't_shirt_size', 't_shirt_type', 'passport_number', 'passport_expiration_date', 'emergency_name', 'emergency_phone', 'birth_name' ] _payment_accept_redirect = '/muskathlon_registration/payment/validate' invoice_id = fields.Many2one('account.invoice') ambassador_picture_1 = fields.Binary('Profile picture') ambassador_quote = fields.Text( 'My motto', default="", help="Write a small quote that will appear on your profile page " "and will be used in thank you letters your donors will " "receive.") t_shirt_size = fields.Selection([('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL')]) t_shirt_type = fields.Selection([ ('singlet', 'Singlet'), ('shirt', 'Shirt'), ('bikeshirt', 'Bikeshirt'), ]) gtc_accept = fields.Boolean("Terms and conditions", required=True) birth_name = fields.Char('Passport name', help='Your name as printed on your passport') emergency_name = fields.Char( help='Please indicate a contact in case of emergency ' 'during the trip.') @property def discipline_ids(self): return self.event_id.sport_discipline_ids.ids @property def _form_fieldsets(self): fieldset = [{ 'id': 'sport', 'title': _('Your sport profile'), 'description': '', 'fields': [ 'ambassador_picture_1', 'sport_discipline_id', 'sport_level', 'sport_level_description', 'ambassador_quote', 't_shirt_size', 't_shirt_type', 'event_id' ] }, { 'id': 'partner', 'title': _('Your coordinates'), 'description': '', 'fields': [ 'partner_title', 'partner_firstname', 'partner_lastname', 'partner_email', 'partner_phone', 'partner_street', 'partner_zip', 'partner_city', 'partner_country_id' ] }, { 'id': 'trip', 'title': _('Information for the trip'), 'fields': [ 'birth_name', 'passport_number', 'passport_expiration_date', 'emergency_name', 'emergency_phone' ] }] if self.event_id.total_price: fieldset.append({ 'id': 'payment', 'title': _('Registration payment'), 'description': _('For validating registrations, we ask a fee of ' 'CHF %s that you can directly pay with your ' 'Postfinance or Credit Card') % str(self.event_id.total_price), 'fields': ['amount', 'currency_id', 'acquirer_ids', 'gtc_accept'] }) else: fieldset.append({'id': 'gtc', 'fields': ['gtc_accept']}) return fieldset @property def form_widgets(self): # Hide fields res = super(MuskathlonRegistrationForm, self).form_widgets res.update({ 'event_id': 'cms_form_compassion.form.widget.hidden', 'amount': 'cms_form_compassion.form.widget.hidden', 'ambassador_picture_1': 'cms_form_compassion.form.widget.simple.image', 'gtc_accept': 'cms_form_compassion.form.widget.terms', }) return res @property def _default_amount(self): return self.event_id.total_price @property def form_title(self): if self.event_id: return _("Registration for ") + self.event_id.name else: return _("New registration") @property def submit_text(self): if self.event_id.total_price: return _("Proceed with payment") else: return _("Register now") @property def gtc(self): html_file = file_open( 'muskathlon/static/src/html/muskathlon_gtc_{}.html'.format( self.env.lang)) text = html_file.read() html_file.close() return text def form_init(self, request, main_object=None, **kw): form = super(MuskathlonRegistrationForm, self).form_init(request, main_object, **kw) # Set default values form.event_id = kw.get('event').sudo().odoo_event_id return form def form_get_request_values(self): """ Save uploaded picture in storage to reload it in case of validation error (to avoid the user to have to re-upload it). """ values = super(MuskathlonRegistrationForm, self).form_get_request_values() fname = 'ambassador_picture_1' image = values.get(fname) form_fields = self.form_fields() image_widget = form_fields[fname]['widget'] if image: image_data = image_widget.form_to_binary(image) # Reset buffer image if hasattr(image, 'seek'): image.seek(0) if image_data: self.request.session[fname] = image_data else: image = self.request.session.get(fname) if image: values[fname] = image return values def _form_load_sport_level_description(self, fname, field, value, **req_values): # Default value for event.registration field return req_values.get('sport_level_description', '') def _form_load_event_id(self, fname, field, value, **req_values): # Default value for event.registration field return int(req_values.get('event_id', self.event_id.id)) def _form_validate_sport_level_description(self, value, **req_values): if not re.match(r"^[\w\s'-]+$", value, re.UNICODE): return 'sport_level_description', _( 'Please avoid any special characters') # No error return 0, 0 def _form_validate_amount(self, value, **req_values): try: amount = float(value) if amount <= 0: raise ValueError except ValueError: return 'amount', _('Please control the amount') except TypeError: # If amount is not defined, the event has no fee. return 0, 0 # No error return 0, 0 def form_before_create_or_update(self, values, extra_values): """ Create invoice for the registration. Create ambassador details. """ super(MuskathlonRegistrationForm, self).form_before_create_or_update(values, extra_values) uid = self.env.ref('muskathlon.user_muskathlon_portal').id partner = self.env['res.partner'].sudo().browse( values.get('partner_id')).exists() invoice_obj = self.env['account.invoice'].sudo(uid) invoice = invoice_obj event = self.event_id.sudo() if event.total_price: fee_template = self.env.ref('muskathlon.product_registration') product = fee_template.sudo(uid).product_variant_ids[:1] invoice = invoice_obj.create({ 'partner_id': partner.id, 'currency_id': extra_values.get('currency_id'), 'origin': 'Muskathlon registration', 'invoice_line_ids': [(0, 0, { 'quantity': 1.0, 'price_unit': event.total_price, 'account_analytic_id': event.compassion_event_id.analytic_id.id, 'account_id': product.property_account_income_id.id, 'name': 'Muskathlon registration fees', 'product_id': product.id })] }) sporty = self.env.ref('partner_compassion.engagement_sport') if partner.advocate_details_id: partner.advocate_details_id.write({ 'engagement_ids': [(4, sporty.id)], 't_shirt_size': values.get('t_shirt_size') }) else: # Creation of ambassador details reloads cache and remove # all field values in the form. # This hacks restores the form state after the creation. # backup = self._backup_fields() self.env['advocate.details'].sudo(uid).create({ 'partner_id': partner.id, 'advocacy_source': 'Online Muskathlon registration', 'engagement_ids': [(4, sporty.id)], 't_shirt_size': values.get('t_shirt_size') }) # self._restore_fields(backup) # Convert the name for event registration values['name'] = values.pop('partner_lastname', '') values['name'] += ' ' + values.pop('partner_firstname') # Force default value instead of setting 0. values.pop('amount_objective') # Parse integer values['event_id'] = int(values['event_id']) values['user_id'] = event.user_id.id values['stage_id'] = self.env.ref( 'muskathlon.stage_down_payment').id # Store invoice and event for after form creation extra_values['invoice_id'] = invoice.id self.event_id = event def _form_create(self, values): uid = self.env.ref('muskathlon.user_muskathlon_portal').id # If notification is sent in same job, the form is reloaded # and all values are lost. main_object = self.form_model.sudo(uid).with_context( tracking_disable=True, registration_force_draft=True).create(values.copy()) main_object.with_delay().notify_new_registration() self.main_object = main_object def form_next_url(self, main_object=None): # Clean storage of picture self.request.session.pop('ambassador_picture_1', False) if self.event_id.total_price: return super(MuskathlonRegistrationForm, self).form_next_url(main_object) else: return '/muskathlon_registration/{}/success'.format( self.main_object.id) def _edit_transaction_values(self, tx_values, form_vals): """ Add registration link and change reference. """ tx_values.update({ 'registration_id': self.main_object.id, 'reference': 'MUSK-REG-' + str(self.main_object.id), 'invoice_id': form_vals['invoice_id'] }) def _backup_fields(self): """ Hack method to save data the can be lost when environment is refreshed. :return: dict of values """ return { 'amount': self.amount, 'currency_id': self.currency_id.id, 'acquirer_id': self.acquirer_id.id, 'partner_id': self.partner_id.id } def _restore_fields(self, backup): """Hack method to restore lost field values after environment refresh. :return: None """ self.amount = backup['amount'] self.currency_id = backup['currency_id'] self.acquirer_id = backup['acquirer_id'] self.partner_id = backup['partner_id']
class MailTracking(models.Model): _name = 'mail.tracking.value' _description = 'Mail Tracking Value' _rec_name = 'field' _order = 'tracking_sequence asc' field = fields.Many2one('ir.model.fields', required=True, readonly=1, ondelete='cascade') field_desc = fields.Char('Field Description', required=True, readonly=1) field_type = fields.Char('Field Type') field_groups = fields.Char(compute='_compute_field_groups') old_value_integer = fields.Integer('Old Value Integer', readonly=1) old_value_float = fields.Float('Old Value Float', readonly=1) old_value_monetary = fields.Float('Old Value Monetary', readonly=1) old_value_char = fields.Char('Old Value Char', readonly=1) old_value_text = fields.Text('Old Value Text', readonly=1) old_value_datetime = fields.Datetime('Old Value DateTime', readonly=1) new_value_integer = fields.Integer('New Value Integer', readonly=1) new_value_float = fields.Float('New Value Float', readonly=1) new_value_monetary = fields.Float('New Value Monetary', readonly=1) new_value_char = fields.Char('New Value Char', readonly=1) new_value_text = fields.Text('New Value Text', readonly=1) new_value_datetime = fields.Datetime('New Value Datetime', readonly=1) currency_id = fields.Many2one( 'res.currency', 'Currency', readonly=True, ondelete='set null', help="Used to display the currency when tracking monetary values") mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, index=True, ondelete='cascade') tracking_sequence = fields.Integer('Tracking field sequence', readonly=1, default=100) def _compute_field_groups(self): for tracking in self: model = self.env[tracking.mail_message_id.model] field = model._fields.get(tracking.field.name) tracking.field_groups = field.groups if field else 'base.group_system' @api.model def create_tracking_values(self, initial_value, new_value, col_name, col_info, tracking_sequence, model_name): tracked = True field = self.env['ir.model.fields']._get(model_name, col_name) if not field: return values = { 'field': field.id, 'field_desc': col_info['string'], 'field_type': col_info['type'], 'tracking_sequence': tracking_sequence } if col_info['type'] in [ 'integer', 'float', 'char', 'text', 'datetime', 'monetary' ]: values.update({ 'old_value_%s' % col_info['type']: initial_value, 'new_value_%s' % col_info['type']: new_value }) elif col_info['type'] == 'date': values.update({ 'old_value_datetime': initial_value and fields.Datetime.to_string( datetime.combine(fields.Date.from_string(initial_value), datetime.min.time())) or False, 'new_value_datetime': new_value and fields.Datetime.to_string( datetime.combine(fields.Date.from_string(new_value), datetime.min.time())) or False, }) elif col_info['type'] == 'boolean': values.update({ 'old_value_integer': initial_value, 'new_value_integer': new_value }) elif col_info['type'] == 'selection': values.update({ 'old_value_char': initial_value and dict(col_info['selection'])[initial_value] or '', 'new_value_char': new_value and dict(col_info['selection'])[new_value] or '' }) elif col_info['type'] == 'many2one': values.update({ 'old_value_integer': initial_value and initial_value.id or 0, 'new_value_integer': new_value and new_value.id or 0, 'old_value_char': initial_value and initial_value.sudo().name_get()[0][1] or '', 'new_value_char': new_value and new_value.sudo().name_get()[0][1] or '' }) else: tracked = False if tracked: return values return {} def get_display_value(self, type): assert type in ('new', 'old') result = [] for record in self: if record.field_type in [ 'integer', 'float', 'char', 'text', 'monetary' ]: result.append( getattr(record, '%s_value_%s' % (type, record.field_type))) elif record.field_type == 'datetime': if record['%s_value_datetime' % type]: new_datetime = getattr(record, '%s_value_datetime' % type) result.append('%sZ' % new_datetime) else: result.append(record['%s_value_datetime' % type]) elif record.field_type == 'date': if record['%s_value_datetime' % type]: new_date = record['%s_value_datetime' % type] result.append(fields.Date.to_string(new_date)) else: result.append(record['%s_value_datetime' % type]) elif record.field_type == 'boolean': result.append(bool(record['%s_value_integer' % type])) else: result.append(record['%s_value_char' % type]) return result def get_old_display_value(self): # grep : # old_value_integer | old_value_datetime | old_value_char return self.get_display_value('old') def get_new_display_value(self): # grep : # new_value_integer | new_value_datetime | new_value_char return self.get_display_value('new')
class OtherMoneyOrder(models.Model): _name = 'other.money.order' _description = u'其他收入/其他支出' _inherit = ['mail.thread'] TYPE_SELECTION = [ ('other_pay', u'其他支出'), ('other_get', u'其他收入'), ] @api.model def create(self, values): # 创建单据时,更新订单类型的不同,生成不同的单据编号 if self.env.context.get('type') == 'other_get': values.update({ 'name': self.env['ir.sequence'].next_by_code('other.get.order') }) if self.env.context.get('type') == 'other_pay' or values.get( 'name', '/') == '/': values.update({ 'name': self.env['ir.sequence'].next_by_code('other.pay.order') }) return super(OtherMoneyOrder, self).create(values) @api.one @api.depends('line_ids.amount', 'line_ids.tax_amount') def _compute_total_amount(self): # 计算应付金额/应收金额 self.total_amount = sum( (line.amount + line.tax_amount) for line in self.line_ids) state = fields.Selection([ ('draft', u'草稿'), ('done', u'已确认'), ('cancel', u'已作废'), ], string=u'状态', readonly=True, default='draft', copy=False, index=True, help=u'其他收支单状态标识,新建时状态为草稿;确认后状态为已确认') partner_id = fields.Many2one('partner', string=u'往来单位', readonly=True, ondelete='restrict', states={'draft': [('readonly', False)]}, help=u'单据对应的业务伙伴,单据确认时会影响他的应收应付余额') date = fields.Date(string=u'单据日期', readonly=True, default=lambda self: fields.Date.context_today(self), states={'draft': [('readonly', False)]}, copy=False, help=u'单据创建日期') name = fields.Char(string=u'单据编号', copy=False, readonly=True, default='/', help=u'单据编号,创建时会根据类型自动生成') total_amount = fields.Float(string=u'金额', compute='_compute_total_amount', store=True, readonly=True, digits=dp.get_precision('Amount'), help=u'本次其他收支的总金额') bank_id = fields.Many2one('bank.account', string=u'结算账户', required=True, ondelete='restrict', readonly=True, states={'draft': [('readonly', False)]}, help=u'本次其他收支的结算账户') line_ids = fields.One2many('other.money.order.line', 'other_money_id', string=u'收支单行', readonly=True, copy=True, states={'draft': [('readonly', False)]}, help=u'其他收支单明细行') type = fields.Selection(TYPE_SELECTION, string=u'类型', readonly=True, default=lambda self: self._context.get('type'), states={'draft': [('readonly', False)]}, help=u'类型:其他收入 或者 其他支出') note = fields.Text(u'备注', help=u'可以为该单据添加一些需要的标识信息') is_init = fields.Boolean(u'初始化应收应付', help=u'此单是否为初始化单') company_id = fields.Many2one( 'res.company', string=u'公司', change_default=True, default=lambda self: self.env['res.company']._company_default_get()) receiver = fields.Char(u'收款人', help=u'收款人') bank_name = fields.Char(u'开户行') bank_num = fields.Char(u'银行账号') voucher_id = fields.Many2one('voucher', u'对应凭证', readonly=True, ondelete='restrict', copy=False, help=u'其他收支单确认时生成的对应凭证') currency_amount = fields.Float(u'外币金额', digits=dp.get_precision('Amount')) @api.onchange('date') def onchange_date(self): if self._context.get('type') == 'other_get': return {'domain': {'partner_id': [('c_category_id', '!=', False)]}} else: return {'domain': {'partner_id': [('s_category_id', '!=', False)]}} @api.onchange('partner_id') def onchange_partner_id(self): """ 更改业务伙伴,自动填入收款人、开户行和银行帐号 """ if self.partner_id: self.receiver = self.partner_id.name self.bank_name = self.partner_id.bank_name self.bank_num = self.partner_id.bank_num @api.multi def other_money_done(self): '''其他收支单的审核按钮''' self.ensure_one() if float_compare(self.total_amount, 0, 3) <= 0: raise UserError(u'金额应该大于0。\n金额:%s' % self.total_amount) if not self.bank_id.account_id: raise UserError(u'请配置%s的会计科目' % (self.bank_id.name)) # 根据单据类型更新账户余额 if self.type == 'other_pay': decimal_amount = self.env.ref('core.decimal_amount') if float_compare(self.bank_id.balance, self.total_amount, decimal_amount.digits) == -1: raise UserError(u'账户余额不足。\n账户余额:%s 本次支出金额:%s' % (self.bank_id.balance, self.total_amount)) self.bank_id.balance -= self.total_amount else: self.bank_id.balance += self.total_amount # 创建凭证并审核非初始化凭证 vouch_obj = self.create_voucher() return self.write({ 'voucher_id': vouch_obj.id, 'state': 'done', }) @api.multi def other_money_draft(self): '''其他收支单的反审核按钮''' self.ensure_one() # 根据单据类型更新账户余额 if self.type == 'other_pay': self.bank_id.balance += self.total_amount else: decimal_amount = self.env.ref('core.decimal_amount') if float_compare(self.bank_id.balance, self.total_amount, decimal_amount.digits) == -1: raise UserError(u'账户余额不足。\n账户余额:%s 本次支出金额:%s' % (self.bank_id.balance, self.total_amount)) self.bank_id.balance -= self.total_amount voucher = self.voucher_id self.write({ 'voucher_id': False, 'state': 'draft', }) # 反审核凭证并删除 if voucher.state == 'done': voucher.voucher_draft() # 始初化单反审核只删除明细行 if self.is_init: vouch_obj = self.env['voucher'].search([('id', '=', voucher.id)]) vouch_obj_lines = self.env['voucher.line'].search([ ('voucher_id', '=', vouch_obj.id), ('account_id', '=', self.bank_id.account_id.id), ('init_obj', '=', 'other_money_order-%s' % (self.id)) ]) for vouch_obj_line in vouch_obj_lines: vouch_obj_line.unlink() else: voucher.unlink() return True @api.multi def create_voucher(self): """创建凭证并审核非初始化凭证""" init_obj = '' # 初始化单的话,先找是否有初始化凭证,没有则新建一个 if self.is_init: vouch_obj = self.env['voucher'].search([('is_init', '=', True)]) if not vouch_obj: vouch_obj = self.env['voucher'].create({ 'date': self.date, 'is_init': True, 'ref': '%s,%s' % (self._name, self.id) }) else: vouch_obj = self.env['voucher'].create({ 'date': self.date, 'ref': '%s,%s' % (self._name, self.id) }) if self.is_init: init_obj = 'other_money_order-%s' % (self.id) if self.type == 'other_get': # 其他收入单 self.other_get_create_voucher_line(vouch_obj, init_obj) else: # 其他支出单 self.other_pay_create_voucher_line(vouch_obj) # 如果非初始化单则审核 if not self.is_init: vouch_obj.voucher_done() return vouch_obj def other_get_create_voucher_line(self, vouch_obj, init_obj): """ 其他收入单生成凭证明细行 :param vouch_obj: 凭证 :return: """ vals = {} for line in self.line_ids: if not line.category_id.account_id: raise UserError(u'请配置%s的会计科目' % (line.category_id.name)) rate_silent = self.env['res.currency'].get_rate_silent( self.date, self.bank_id.currency_id.id) vals.update({ 'vouch_obj_id': vouch_obj.id, 'name': self.name, 'note': line.note or '', 'credit_auxiliary_id': line.auxiliary_id.id, 'amount': abs(line.amount + line.tax_amount), 'credit_account_id': line.category_id.account_id.id, 'debit_account_id': self.bank_id.account_id.id, 'partner_credit': self.partner_id.id, 'partner_debit': '', 'sell_tax_amount': line.tax_amount or 0, 'init_obj': init_obj, 'currency_id': self.bank_id.currency_id.id, 'currency_amount': self.currency_amount, 'rate_silent': rate_silent, }) # 贷方行 if not init_obj: self.env['voucher.line'].create({ 'name': u"%s %s" % (vals.get('name'), vals.get('note')), 'partner_id': vals.get('partner_credit', ''), 'account_id': vals.get('credit_account_id'), 'credit': line.amount, 'voucher_id': vals.get('vouch_obj_id'), 'auxiliary_id': vals.get('credit_auxiliary_id', False), }) # 销项税行 if vals.get('sell_tax_amount'): if not self.env.user.company_id.output_tax_account: raise UserError( u'您还没有配置公司的销项税科目。\n请通过"配置-->高级配置-->公司"菜单来设置销项税科目!') self.env['voucher.line'].create({ 'name': u"%s %s" % (vals.get('name'), vals.get('note')), 'account_id': self.env.user.company_id.output_tax_account.id, 'credit': line.tax_amount or 0, 'voucher_id': vals.get('vouch_obj_id'), }) # 借方行 self.env['voucher.line'].create({ 'name': u"%s" % (vals.get('name')), 'account_id': vals.get('debit_account_id'), 'debit': self.total_amount, # 借方和 'voucher_id': vals.get('vouch_obj_id'), 'partner_id': vals.get('partner_debit', ''), 'auxiliary_id': vals.get('debit_auxiliary_id', False), 'init_obj': vals.get('init_obj', False), 'currency_id': vals.get('currency_id', False), 'currency_amount': vals.get('currency_amount'), 'rate_silent': vals.get('rate_silent'), }) def other_pay_create_voucher_line(self, vouch_obj): """ 其他支出单生成凭证明细行 :param vouch_obj: 凭证 :return: """ vals = {} for line in self.line_ids: if not line.category_id.account_id: raise UserError(u'请配置%s的会计科目' % (line.category_id.name)) rate_silent = self.env['res.currency'].get_rate_silent( self.date, self.bank_id.currency_id.id) vals.update({ 'vouch_obj_id': vouch_obj.id, 'name': self.name, 'note': line.note or '', 'debit_auxiliary_id': line.auxiliary_id.id, 'amount': abs(line.amount + line.tax_amount), 'credit_account_id': self.bank_id.account_id.id, 'debit_account_id': line.category_id.account_id.id, 'partner_credit': '', 'partner_debit': self.partner_id.id, 'buy_tax_amount': line.tax_amount or 0, 'currency_id': self.bank_id.currency_id.id, 'currency_amount': self.currency_amount, 'rate_silent': rate_silent, }) # 借方行 self.env['voucher.line'].create({ 'name': u"%s %s " % (vals.get('name'), vals.get('note')), 'account_id': vals.get('debit_account_id'), 'debit': line.amount, 'voucher_id': vals.get('vouch_obj_id'), 'partner_id': vals.get('partner_debit', ''), 'auxiliary_id': vals.get('debit_auxiliary_id', False), 'init_obj': vals.get('init_obj', False), }) # 进项税行 if vals.get('buy_tax_amount'): if not self.env.user.company_id.import_tax_account: raise UserError(u'请通过"配置-->高级配置-->公司"菜单来设置进项税科目') self.env['voucher.line'].create({ 'name': u"%s %s" % (vals.get('name'), vals.get('note')), 'account_id': self.env.user.company_id.import_tax_account.id, 'debit': line.tax_amount or 0, 'voucher_id': vals.get('vouch_obj_id'), }) # 贷方行 self.env['voucher.line'].create({ 'name': u"%s" % (vals.get('name')), 'partner_id': vals.get('partner_credit', ''), 'account_id': vals.get('credit_account_id'), 'credit': self.total_amount, # 贷方和 'voucher_id': vals.get('vouch_obj_id'), 'auxiliary_id': vals.get('credit_auxiliary_id', False), 'init_obj': vals.get('init_obj', False), 'currency_id': vals.get('currency_id', False), 'currency_amount': vals.get('currency_amount'), 'rate_silent': vals.get('rate_silent'), })
class OeHealthPrescriptions(models.Model): _name = 'oeh.medical.prescription' _description = 'Prescriptions' STATES = [ ('Draft', 'Draft'), ('Invoiced', 'Invoiced'), ('Sent to Pharmacy', 'Sent to Pharmacy'), ] # Automatically detect logged in physician @api.multi def _get_physician(self): """Return default physician value""" therapist_obj = self.env['oeh.medical.physician'] domain = [('oeh_user_id', '=', self.env.uid)] user_ids = therapist_obj.search(domain, limit=1) if user_ids: return user_ids.id or False else: return False name = fields.Char(string='Prescription #', size=64, readonly=True, required=True, default=lambda *a: '/') patient = fields.Many2one('oeh.medical.patient', string='Patient', help="Patient Name", required=True, readonly=True, states={'Draft': [('readonly', False)]}) doctor = fields.Many2one('oeh.medical.physician', string='Physician', domain=[('is_pharmacist', '=', False)], help="Current primary care / family doctor", required=True, readonly=True, states={'Draft': [('readonly', False)]}, default=_get_physician) pharmacy = fields.Many2one('oeh.medical.health.center.pharmacy', 'Pharmacy', readonly=True, states={'Draft': [('readonly', False)]}) date = fields.Datetime(string='Prescription Date', readonly=True, states={'Draft': [('readonly', False)]}, default=datetime.datetime.now()) info = fields.Text(string='Prescription Notes', readonly=True, states={'Draft': [('readonly', False)]}) prescription_line = fields.One2many( 'oeh.medical.prescription.line', 'prescription_id', string='Prescription Lines', readonly=True, states={'Draft': [('readonly', False)]}) state = fields.Selection(STATES, 'State', readonly=True, default=lambda *a: 'Draft') @api.model def create(self, vals): sequence = self.env['ir.sequence'].next_by_code( 'oeh.medical.prescription') vals['name'] = sequence health_prescription = super(OeHealthPrescriptions, self).create(vals) return health_prescription @api.multi def _default_account(self): journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1) return journal.default_credit_account_id.id def action_prescription_invoice_create(self): invoice_obj = self.env["account.invoice"] invoice_line_obj = self.env["account.invoice.line"] inv_ids = [] for pres in self: # Create Invoice if pres.patient: curr_invoice = { 'partner_id': pres.patient.partner_id.id, 'account_id': pres.patient.partner_id.property_account_receivable_id.id, 'patient': pres.patient.id, 'state': 'draft', 'type': 'out_invoice', 'date_invoice': pres.date.strftime('%Y-%m-%d'), 'origin': "Prescription# : " + pres.name, 'sequence_number_next_prefix': False } inv_ids = invoice_obj.create(curr_invoice) inv_id = inv_ids.id if inv_ids: prd_account_id = self._default_account() if pres.prescription_line: for ps in pres.prescription_line: # Create Invoice line curr_invoice_line = { 'name': ps.name.product_id.name, 'product_id': ps.name.product_id.id, 'price_unit': ps.name.product_id.list_price, 'quantity': ps.qty, 'account_id': prd_account_id, 'invoice_id': inv_id, } inv_line_ids = invoice_line_obj.create( curr_invoice_line) self.write({'state': 'Invoiced'}) return { 'domain': "[('id','=', " + str(inv_id) + ")]", 'name': 'Prescription Invoice', 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.invoice', 'type': 'ir.actions.act_window' } @api.multi def unlink(self): for priscription in self.filtered( lambda priscription: priscription.state not in ['Draft']): raise UserError( _('You can not delete a prescription which is not in "Draft" state !!' )) return super(OeHealthPrescriptions, self).unlink() def action_prescription_send_to_pharmacy(self): pharmacy_obj = self.env["oeh.medical.health.center.pharmacy.line"] pharmacy_line_obj = self.env[ "oeh.medical.health.center.pharmacy.prescription.line"] res = {} for pres in self: if not pres.pharmacy: raise UserError(_('No pharmacy selected !!')) else: curr_pres = { 'name': pres.id, 'patient': pres.patient.id, 'doctor': pres.doctor.id, 'pharmacy_id': pres.pharmacy.id, } phy_ids = pharmacy_obj.create(curr_pres) if phy_ids: if pres.prescription_line: for ps in pres.prescription_line: # Create Prescription line curr_pres_line = { 'name': ps.name.id, 'indication': ps.indication.id, 'price_unit': ps.name.product_id.list_price, 'qty': ps.qty, 'actual_qty': ps.qty, 'prescription_id': phy_ids.id, 'state': 'Draft', } phy_line_ids = pharmacy_line_obj.create( curr_pres_line) res = self.write({'state': 'Sent to Pharmacy'}) return True @api.multi def print_patient_prescription(self): return self.env.ref( 'oehealth.action_oeh_medical_report_patient_prescriptions' ).report_action(self)
class HrEmployee(models.Model): _inherit = 'hr.employee' # name = fields.Char(string="Employee Name", store=True, readonly=False, tracking=True) # user_id = fields.Many2one('res.users', 'User', store=True, readonly=False) # active = fields.Boolean('Active', default=True, store=True, readonly=False) # # # #header # employee_type = fields.Selection([('regular','Regular Employee'), # ('contractual_with_agency','Contractual with Agency'), # ('contractual_with_stpi','Contractual with STPI')],string='Employment Type',track_visibility='always', store=True) # # recruitment_type = fields.Selection([ # ('d_recruitment','Direct Recruitment(DR)'), # ('transfer','Transfer(Absorption)'), # ('i_absorption','Immediate Absorption'), # ('deputation','Deputation'), # ('c_appointment','Compassionate Appointment'), # ('promotion','Promotion'), # ],'Recruitment Type',track_visibility='always', store=True) # # salutation = fields.Many2one('res.partner.title',track_visibility='always') # # fax_number = fields.Char('FAX number',track_visibility='always') # # #added by Sangita # pay_level = fields.Many2one('payslip.pay.level', string='Pay Band') #citizenship # citizen_country_id = fields.Many2one('res.country','Country name') # def default_country(self): # return self.env['res.country'].search([('name', '=', 'India')], limit=1) # # country_id = fields.Many2one( # 'res.country', 'Nationality (Country)', groups="hr.group_hr_user", default=default_country) # citizen_number = fields.Char('Citizen Number',track_visibility='always') # citizen_eligibility_date =fields.Date(string='Date of Eligibility',track_visibility='always') # citizen_file_data = fields.Binary('Upload',track_visibility='always') # date_of_eligibility = fields.Date('Date of Eligibility', track_visibility='always') # citizen_file_name = fields.Char('File Name',track_visibility='always') # show_citizen_field = fields.Boolean('Show Field',default=False,copy=False,track_visibility='always') # # #religion # category = fields.Many2one('employee.category',string='Category',track_visibility='always') # religion = fields.Many2one('employee.religion',string='Religion',track_visibility='always') # minority = fields.Boolean('Minority',default=False,track_visibility='always') # # #office work # # gender = fields.Selection(selection_add=[('transgender', 'Transgender')]) # gende = fields.Selection([ # ('male', 'Male'), # ('female', 'Female'), # ('transgender', 'Transgender') # ], string='Gender',track_visibility='always') # recruitment_file_no = fields.Char('Recruitment File No.',track_visibility='always') # office_file_no = fields.Char('Office Order No.',track_visibility='always') # mode_of_recruitment = fields.Char('Mode Of Recruitment',track_visibility='always') # post = fields.Char('Post',track_visibility='always') # date_of_join = fields.Date('Date of Joining',track_visibility='always') # office_order_date = fields.Date('Office Order Date',track_visibility='always') # # #contact # personal_email =fields.Char('Personal Email',track_visibility='always') # phone = fields.Char('Phone (Home)',track_visibility='always') # # #work_infroamtion # ex_serviceman =fields.Selection([('no','No'), # ('yes','Yes')],string='Whether Ex Service Man',track_visibility='always') # # #physical # height = fields.Float('Height (in CMs)',track_visibility='always') # weight = fields.Float('Weight (in KGs)',track_visibility='always') # blood_group = fields.Selection([('a+','A+'), # ('a1+','A1+'), # ('a-','A-'), # ('b+','B+'), # ('b-','B-'), # ('o+', 'O+'), # ('o-', 'O-'), # ('ab+','AB+'), # ('ab-','AB-')],string='Blood Group',track_visibility='always') # differently_abled = fields.Selection([('no','No'), # ('yes','Yes')], default = 'no', string='Differently Abled?',track_visibility='always') # kind_of_disability = fields.Selection([('vh', 'No'), # ('hh', 'Yes'), # ('ph', 'Yes')], string='Kind of Disability', # track_visibility='always') # perc_disability = fields.Char('% of Disability',track_visibility='always') # certificate_upload = fields.Binary('Upload certificate',track_visibility='always') # personal_remark =fields.Char('Personal mark of Identification',track_visibility='always') # # # # #Identification # identify_id = fields.Char(string='Identification No.',copy=False, store=True, track_visibility='always', compute='_compute_identify_no') # pan_no = fields.Char('PAN Card No.',track_visibility='always') # uan_no = fields.Char('UAN No.',track_visibility='always') # pan_upload = fields.Binary('Upload(PAN)',track_visibility='always') # aadhar_no = fields.Char('Aadhar Card No.',track_visibility='always') # aadhar_upload = fields.Binary('Upload(Aadhar)',track_visibility='always') # passport_upload = fields.Binary('Upload(Passport)',track_visibility='always') # bank_name = fields.Char(string='Bank Name') # bank_account_number = fields.Char(string='Bank Account number') # ifsc_code = fields.Char(string='IFSC Code') # # _sql_constraints = [ # ('pan_uniq', 'unique (pan_no)', 'Pan No must be unique!'), # ('aadhar_uniq', 'unique (aadhar_no)', 'Aadhar no must be unique!'), # ('passport_uniq', 'unique (passport_id)', 'Passport no must be unique!'), # ] # # category_ids = fields.Many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Tags', required=False) # @api.constrains('mobile_phone','work_phone','phone') # @api.onchange('mobile_phone','work_phone','phone') # def _check_mobile_phone_num(self): # for rec in self: # if rec.mobile_phone and not rec.mobile_phone.isnumeric(): # raise ValidationError(_("Phone number must be a number")) # if rec.mobile_phone and len(rec.mobile_phone) != 10: # raise ValidationError(_("Please enter correct Mobile number." # "It must be of 10 digits")) # if rec.work_phone and not rec.work_phone.isnumeric(): # raise ValidationError(_("Phone number must be a number")) # if rec.work_phone and len(rec.work_phone) != 10: # raise ValidationError(_("Please enter correct work phone number." # "It must be of 10 digits")) # if rec.phone and not rec.phone.isnumeric(): # raise ValidationError(_("Phone number must be a number")) # if rec.phone and len(rec.phone) != 10: # raise ValidationError(_("Please enter correct phone number." # "It must be of 10 digits")) @api.constrains('personal_email') def check_unique_personal_email(self): for rec in self: count = 0 emp_id = self.env['hr.employee'].sudo().search( [('personal_email', '=', rec.personal_email)]) for e in emp_id: count += 1 if count > 1: raise ValidationError("The Personal Email must be unique") # @api.constrains('work_email') # def check_unique_work_email(self): # for rec in self: # count = 0 # emp_id = self.env['hr.employee'].sudo().search( # [('work_email', '=', rec.work_email)]) # for e in emp_id: # count += 1 # if count > 1: # raise ValidationError("The Work Email must be unique") # # # # @api.constrains('work_email') # def _check_work_mail_val(self): # for employee in self: # regex = '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$' # if not (re.search(regex, employee.work_email)): # raise ValidationError(_('Please enter correct Work Mail Address.')) # @api.onchange('branch_id') @api.constrains('branch_id') def get_partner_from_branch(self): for rec in self: if rec.branch_id.partner_id: rec.address_id = rec.branch_id.partner_id.id # @api.constrains('name') # @api.onchange('name') # def _check_name_validation(self): # for rec in self: # if rec.name: # for e in rec.name: # if not(e.isalpha() or e == ' '): # raise ValidationError(_("Please enter correct Name.")) # # added by sangita def get_document_ids(self): for document in self: document_ids = self.env['hr.employee.document'].sudo().search([('employee_ref', '=', document.id)]) print("?????????????????????????", document_ids) # for doc in document_ids: return document_ids def get_leave_record(self): for leave in self: if leave.id: SQL = """ select hla.create_date as date, hla.number_of_days as days, hla.holiday_status_id as holiday from hr_leave_allocation as hla inner join hr_leave_type as hly on hly.id = hla.holiday_status_id where employee_id = %s and state in ('validate') and holiday_type = 'employee' group by hla.id, hla.employee_id, hla.holiday_status_id """ self.env.cr.execute(SQL, ( leave.id, )) res = self.env.cr.fetchall() # r = [i for i in res] print("??????????????????????casual_leavescasual_leaves", res) return res def find_age(self): age = (date.today() - self.birthday) // timedelta(days=365.2425) # print("?????????????????????????age",age) return age def relative_types(self): for relative in self: relativ_id = self.env['employee.relative'].search([('employee_id', '=', relative.id)]) # print("????????????fffffffffff???????????????",relativ_id) for rel_type in relativ_id: relative_type = rel_type.relative_type # print("relative_typerelative_typerelative_type",relative_type) return relative_type def reltive_details(self): for relative in self: if relative: SQL = """ select er.name, rt.name, ROUND(er.age) as roundage from employee_relative as er inner join hr_employee as he on he.id = er.employee_id inner join relative_type as rt on rt.id = er.relate_type where er.employee_id = %s """ self.env.cr.execute(SQL, ( relative.id, )) res = self.env.cr.fetchall() return res def get_ltc_record(self): for ltc in self: if ltc.id: SQL = """ select he.name as emp, ela.hometown_address, ela.el_encashment from employee_ltc_advance as ela inner join hr_employee as he on he.id = ela.employee_id where ela.employee_id = %s group by he.name, ela.hometown_address, ela.el_encashment """ self.env.cr.execute(SQL, ( ltc.id, )) res = self.env.cr.fetchall() return res def leave_available_balance(self): for leave in self: if leave: SQL = """ select hlr.holiday_status_id as holiday, sum(hlr.number_of_days) as days from hr_leave_report hlr inner join hr_leave_type as hly on hly.id = hlr.holiday_status_id where employee_id = %s and holiday_type = 'employee' and state not in ('refuse') group by hlr.holiday_status_id """ self.env.cr.execute(SQL, ( leave.id, )) res = self.env.cr.fetchall() # r = [i for i in res] # print("??????????????????????casual_leavescasual_leaves",res) return res # @api.model # def create(self, vals): # res = super(HrEmployee, self).create(vals) # if res.employee_type == 'regular': # seq = self.env['ir.sequence'].next_by_code('hr.employee') # res.identify_id = 'STPI' + str(seq) # else: # seq = self.env['ir.sequence'].next_by_code('identify.seqid') # res.identify_id = 'STPITEMP' + str(seq) # return res @api.constrains('date_of_join') def get_joining_emp_code(self): for res in self: if res.employee_type == 'regular': seq = self.env['ir.sequence'].next_by_code('hr.employee') res.identify_id = 'STPI' + str(seq) else: seq = self.env['ir.sequence'].next_by_code('identify.seqid') res.identify_id = 'STPITEMP' + str(seq) # # @api.depends('employee_type') # def _compute_identify_no(self): # for res in self: # if res.identify_id == False: # if res.employee_type == 'regular': # seq = self.env['ir.sequence'].next_by_code('hr.employee') # res.identify_id = 'STPI' + str(seq) # else: # seq = self.env['ir.sequence'].next_by_code('identify.seqid') # res.identify_id = 'STPITEMP' + str(seq) @api.constrains('date_of_join', 'office_order_date') @api.onchange('date_of_join','office_order_date') def _check_office_order_date(self): for record in self: if record.office_order_date and record.date_of_join and (record.office_order_date > record.date_of_join): raise ValidationError("Date of Joining should always be greater then equals to Office Order Date") @api.constrains('bank_account_number') @api.onchange('bank_account_number') def _check_bank_acc_number(self): for rec in self: if rec.bank_account_number: for e in rec.bank_account_number: if not e.isdigit(): raise ValidationError(_("Please enter correct Account number, it must be numeric...")) @api.constrains('aadhar_no') @api.onchange('aadhar_no') def _check_aadhar_number(self): for rec in self: if rec.aadhar_no: for e in rec.aadhar_no: if not e.isdigit(): raise ValidationError(_("Please enter correct Aadhar number, it must be numeric...")) if len(rec.aadhar_no) != 12: raise ValidationError(_("Please enter correct Aadhar number, it must be of 12 digits...")) @api.constrains('pan_no') @api.onchange('pan_no') def _check_pan_number(self): for rec in self: if rec.pan_no and not re.match(r'^[A-Za-z]{5}[0-9]{4}[A-Za-z]$', str(rec.pan_no)): raise ValidationError(_("Please enter correct PAN number...")) @api.constrains('birthday') def _check_birthday_app(self): for employee in self: today = datetime.now().date() if employee.birthday and employee.birthday > today: raise ValidationError(_('Please enter correct date of birth')) @api.constrains('office_order_date') def _check_office_order_date_app(self): for employee in self: today = datetime.now().date() if employee.office_order_date and employee.office_order_date > today: raise ValidationError(_('Please enter correct office order date')) @api.onchange('pan_no') def set_upper(self): if self.pan_no: self.pan_no = str(self.pan_no).upper() # address_ids = fields.One2many('employee.address','employee_id',string='Address',track_visibility='always') #Personal File Detail file_no = fields.Char('File No',track_visibility='always') file_open_date = fields.Date('File Open Date',track_visibility='always') file_close_date = fields.Date('File close Date',track_visibility='always') file_remark = fields.Text('Remark',track_visibility='always') @api.onchange('country_id') def ckech_nationality(self): if self.country_id: if self.country_id.name != 'India': self.show_citizen_field =True else: self.show_citizen_field =False def set_employee_training(self): if self: return { 'name': 'Employee Training', 'view_type': 'form', 'view_mode': 'tree', 'res_model': 'employee.training', 'type': 'ir.actions.act_window', 'target': 'current', 'view_id': self.env.ref('l10n_in_hr_fields.employee_training_tree_view').id, 'domain': [('employee_id', '=', self.id)], 'context':{ 'default_employee_id': self.id} } # def set_last_employer(self): # if self: # return { # 'name': 'Last Employer', # 'view_type': 'form', # 'view_mode': 'tree', # 'res_model': 'employee.last_employer', # 'type': 'ir.actions.act_window', # 'target': 'current', # 'view_id': self.env.ref('l10n_in_hr_fields.employee_last_employer_tree_view').id, # 'domain': [('employee_id', '=', self.id)], # 'context':{ # 'default_employee_id': self.id} # } # def set_employee_transfer(self): # if self: # return { # 'name': 'Hr Employee Transfer', # 'view_type': 'form', # 'view_mode': 'tree,form', # 'res_model': 'hr.employee.transfer', # 'type': 'ir.actions.act_window', # 'target': 'current', # # 'view_id': self.env.ref('l10n_in_hr_fields.employeetransfer_form_view').id, # 'domain': [('employee_id', '=', self.id)], # 'context':{ # 'default_employee_id': self.id} # } # # def get_family_detail(self): # if self: # return { # 'name': 'Family Details', # 'view_type': 'form', # 'view_mode': 'tree', # 'res_model': 'employee.relative', # 'type': 'ir.actions.act_window', # 'target': 'current', # 'view_id': self.env.ref('hr_applicant.view_employee_relative_tree').id, # 'domain': [('employee_id', '=', self.id)], # 'context': { # 'default_employee_id': self.id} # } # # # class EmployeeAddress(models.Model): # _name = 'employee.address' # _description = 'Address' # # def default_country(self): # return self.env['res.country'].search([('name', '=', 'India')], limit=1) # # address_type = fields.Selection([('permanent_add', 'Permanent Add'), # ('present_add', 'Present Add'), # ('office_add', 'Office Add'), # ('hometown_add', 'HomeTown Add'), # ],string='Address Type',required=True) # employee_id = fields.Many2one('hr.employee','Employee Id') # street = fields.Char('Street') # street2 = fields.Char('Street2') # zip = fields.Char('Zip', change_default=True) # is_correspondence_address = fields.Boolean('Is Correspondence Address') # city = fields.Char('City') # state_id = fields.Many2one("res.country.state", string='State') # country_id = fields.Many2one('res.country', string='Country', default = default_country) # count = fields.Integer('Count') # # @api.onchange('street', 'street2','zip', 'country_id','is_correspondence_address', 'city','state_id') # def _onchange_hometown_address(self): # for rec in self: # rec.count = 0 # if rec.address_type == 'hometown_add': # rec.count += 1 # if rec.count >2: # raise ValidationError("You cannot change Homettown address more than 2 times") # # # @api.constrains('address_type','employee_id') # def check_unique_add(self): # for rec in self: # count = 0 # emp_id = self.env['employee.address'].search([('address_type', '=', rec.address_type),('employee_id', '=', rec.employee_id.id)]) # for e in emp_id: # count+=1 # if count >1: # raise ValidationError("The Address Type must be unique")
class OeHealthPrescriptionLines(models.Model): _name = 'oeh.medical.prescription.line' _description = 'Prescription Lines' FREQUENCY_UNIT = [ ('Seconds', 'Seconds'), ('Minutes', 'Minutes'), ('Hours', 'Hours'), ('Days', 'Days'), ('Weeks', 'Weeks'), ('When Required', 'When Required'), ] DURATION_UNIT = [ ('Minutes', 'Minutes'), ('Hours', 'Hours'), ('Days', 'Days'), ('Months', 'Months'), ('Years', 'Years'), ('Indefinite', 'Indefinite'), ] prescription_id = fields.Many2one('oeh.medical.prescription', string='Prescription Reference', required=True, ondelete='cascade', index=True) name = fields.Many2one('oeh.medical.medicines', string='Medicines', help="Prescribed Medicines", domain=[('medicament_type', '=', 'Medicine')], required=True) indication = fields.Many2one( 'oeh.medical.pathology', string='Indication', help= "Choose a disease for this medicament from the disease list. It can be an existing disease of the patient or a prophylactic." ) dose = fields.Integer( string='Dose', help="Amount of medicines (eg, 250 mg ) each time the patient takes it" ) dose_unit = fields.Many2one( 'oeh.medical.dose.unit', string='Dose Unit', help="Unit of measure for the medication to be taken") dose_route = fields.Many2one( 'oeh.medical.drug.route', string='Administration Route', help="HL7 or other standard drug administration route code.") dose_form = fields.Many2one('oeh.medical.drug.form', 'Form', help="Drug form, such as tablet or gel") qty = fields.Integer( string='x', help="Quantity of units (eg, 2 capsules) of the medicament", default=lambda *a: 1.0) common_dosage = fields.Many2one( 'oeh.medical.dosage', string='Frequency', help="Common / standard dosage frequency for this medicines") frequency = fields.Integer('Frequency') frequency_unit = fields.Selection(FREQUENCY_UNIT, 'Unit', index=True) admin_times = fields.Char( string='Admin hours', size=128, help= 'Suggested administration hours. For example, at 08:00, 13:00 and 18:00 can be encoded like 08 13 18' ) duration = fields.Integer(string='Treatment duration') duration_period = fields.Selection( DURATION_UNIT, string='Treatment period', help= "Period that the patient must take the medication. in minutes, hours, days, months, years or indefinately", index=True) start_treatment = fields.Datetime(string='Start of treatment') end_treatment = fields.Datetime('End of treatment') info = fields.Text('Comment') patient = fields.Many2one('oeh.medical.patient', 'Patient', help="Patient Name")
class File(models.Model): _name = 'muk_dms.file' _description = "File" _inherit = [ 'muk_dms.locking', 'muk_dms.access', 'mail.thread', 'mail.activity.mixin' ] #---------------------------------------------------------- # Database #---------------------------------------------------------- name = fields.Char(string="Filename", required=True, index=True) active = fields.Boolean( string="Archived", default=True, help= "If a file is set to archived, it is not displayed, but still exists.") is_top_file = fields.Boolean(string="Top Directory", compute='_compute_is_top_file', search='_search_is_top_file') settings = fields.Many2one(comodel_name='muk_dms.settings', string="Settings", store=True, auto_join=True, ondelete='restrict', compute='_compute_settings') show_tree = fields.Boolean(string="Show Structure", related="settings.show_tree", readonly=True) color = fields.Integer(string="Color") tags = fields.Many2many(comodel_name='muk_dms.tag', relation='muk_dms_file_tag_rel', column1='fid', column2='tid', string='Tags') category = fields.Many2one(string="Category", comodel_name='muk_dms.category', related="directory.category", readonly=True, store=True) content = fields.Binary(string='Content', required=True, compute='_compute_content', inverse='_inverse_content') reference = fields.Reference(selection=[('muk_dms.data', _('Data'))], string="Data Reference", readonly=True) directory = fields.Many2one(comodel_name='muk_dms.directory', string="Directory", domain="[('permission_create', '=', True)]", ondelete='restrict', auto_join=True, required=True) extension = fields.Char(string='Extension', compute='_compute_extension', readonly=True, store=True) mimetype = fields.Char(string='Type', compute='_compute_mimetype', readonly=True, store=True) size = fields.Integer(string='Size', readonly=True) path = fields.Char(string="Path", store=True, index=True, readonly=True, compute='_compute_path') relational_path = fields.Text(string="Path", store=True, readonly=True, compute='_compute_relational_path') custom_thumbnail = fields.Binary(string="Custom Thumbnail") thumbnail = fields.Binary(compute='_compute_thumbnail', string="Thumbnail") #---------------------------------------------------------- # Functions #---------------------------------------------------------- @api.multi def notify_change(self, values, *largs, **kwargs): super(File, self).notify_change(values, *largs, **kwargs) if "save_type" in values: self._update_reference_type() @api.multi def trigger_computation_up(self, fields, *largs, **kwargs): if ("no_trigger_computation_up" not in self.env.context): self.mapped('directory').suspend_security().with_context( is_parent=True).trigger_computation(fields) @api.multi def trigger_computation(self, fields, *largs, **kwargs): super(File, self).trigger_computation(fields, refresh=True, *largs, **kwargs) for record in self: values = {} if "settings" in fields: values.update(record._compute_settings(write=False)) if "path" in fields: values.update(record._compute_path(write=False)) values.update(record._compute_relational_path(write=False)) if "extension" in fields: values.update(record._compute_extension(write=False)) if "mimetype" in fields: values.update(record._compute_mimetype(write=False)) if values: record.write(values) if "settings" in fields: record.notify_change( {'save_type': record.settings.save_type}) @api.model def max_upload_size(self): params = self.env['ir.config_parameter'].sudo() return int(params.get_param('muk_dms.max_upload_size', default=25)) @api.model def forbidden_extensions(self): params = self.env['ir.config_parameter'].sudo() forbidden_extensions = params.get_param('muk_dms.forbidden_extensions', default="") return [x.strip() for x in forbidden_extensions.split(',')] #---------------------------------------------------------- # Search #---------------------------------------------------------- @api.model def _search_permission_create(self, operator, operand): res = super(File, self)._search_permission_create(operator, operand) records = self.browse( res[0][2]).filtered(lambda r: r.directory.check_access('create')) if operator == '=' and operand: return [('id', 'in', records.mapped('id'))] return [('id', 'not in', records.mapped('id'))] @api.model def _search_is_top_file(self, operator, operand): files = self.search( []).filtered(lambda f: not f.directory.check_access('read')) if operator == '=' and operand: return [('id', 'in', files.mapped('id'))] return [('id', 'not in', files.mapped('id'))] #---------------------------------------------------------- # Read, View #---------------------------------------------------------- @api.multi def _compute_settings(self, write=True): if write: for record in self: record.settings = record.directory.settings else: self.ensure_one() return {'settings': self.directory.settings.id} @api.multi def _compute_extension(self, write=True): if write: for record in self: record.extension = os.path.splitext(record.name)[1] else: self.ensure_one() return {'extension': os.path.splitext(self.name)[1]} @api.multi def _compute_mimetype(self, write=True): def get_mimetype(record): mimetype = mimetypes.guess_type(record.name)[0] if (not mimetype or mimetype == 'application/octet-stream') and record.content: mimetype = guess_mimetype(base64.b64decode(record.content)) return mimetype or 'application/octet-stream' if write: for record in self: record.mimetype = get_mimetype(record) else: self.ensure_one() return {'mimetype': get_mimetype(self)} @api.multi def _compute_path(self, write=True): if write: for record in self: record.path = "%s%s" % (record.directory.path, record.name) else: self.ensure_one() return {'path': "%s%s" % (self.directory.path, self.name)} @api.multi def _compute_relational_path(self, write=True): def get_relational_path(record): path = json.loads(record.directory.relational_path) path.append({ 'model': record._name, 'id': record.id, 'name': record.name }) return json.dumps(path) if write: for record in self: record.relational_path = get_relational_path(record) else: self.ensure_one() return {'relational_path': get_relational_path(self)} @api.multi def _compute_content(self): for record in self: record.content = record._get_content() @api.multi def _compute_permissions(self): super(File, self)._compute_permissions() for record in self: record.permission_create = record.permission_create and record.directory.check_access( 'create') @api.depends('directory') def _compute_is_top_file(self): for record in self: record.is_top_file = record.directory.check_access('read') @api.depends('custom_thumbnail') def _compute_thumbnail(self): for record in self: if record.custom_thumbnail: record.thumbnail = record.with_context({}).custom_thumbnail else: extension = record.extension and record.extension.strip( ".") or "" path = os.path.join(_img_path, "file_%s.png" % extension) if not os.path.isfile(path): path = os.path.join(_img_path, "file_unkown.png") with open(path, "rb") as image_file: record.thumbnail = base64.b64encode(image_file.read()) #---------------------------------------------------------- # Create, Update, Delete #---------------------------------------------------------- @api.constrains('name') def _check_name(self): if not self.check_name(self.name): raise ValidationError(_("The file name is invalid.")) childs = self.sudo().directory.files.mapped( lambda rec: [rec.id, rec.name]) duplicates = [ rec for rec in childs if rec[1] == self.name and rec[0] != self.id ] if duplicates: raise ValidationError( _("A file with the same name already exists.")) @api.constrains('name') def _check_extension(self): forbidden_extensions = self.forbidden_extensions() file_extension = self._compute_extension(write=False)['extension'] if file_extension and file_extension in forbidden_extensions: raise ValidationError( _("The file has a forbidden file extension.")) @api.constrains('content') def _check_size(self): max_upload_size = self.max_upload_size() if (max_upload_size * 1024 * 1024) < len(base64.b64decode( self.content)): raise ValidationError( _("The maximum upload size is %s MB).") % max_upload_size) @api.constrains('directory') def _check_directory_access(self): for record in self: record.directory.check_access('create', raise_exception=True) @api.multi def _inverse_content(self): for record in self: if record.content: content = record.content size = len(base64.b64decode(content)) reference = record.reference if reference: record._update_reference_values({'content': content}) record.write({'size': size}) else: directory = record.directory settings = record.settings if record.settings else directory.settings reference = record._create_reference( settings, directory.path, record.name, content) reference = "%s,%s" % (reference._name, reference.id) record.write({'reference': reference, 'size': size}) else: record._unlink_reference() record.reference = None @api.multi def _before_unlink(self, *largs, **kwargs): info = super(File, self)._before_unlink(*largs, **kwargs) references = [ list((k, list(map(lambda rec: rec.reference.id, v)))) for k, v in itertools.groupby( self.sorted(key=lambda rec: rec.reference._name), lambda rec: rec.reference._name) ] info['references'] = references return info @api.multi def _after_unlink(self, result, info, infos, *largs, **kwargs): super(File, self)._after_unlink(result, info, infos, *largs, **kwargs) if 'references' in info and info['references']: for tuple in info['references']: self.env[tuple[0]].sudo().browse(list(filter( None, tuple[1]))).unlink() @api.returns('self', lambda value: value.id) def copy(self, default=None): self.ensure_one() default = dict(default or []) names = [] if 'directory' in default: directory = self.env['muk_dms.directory'].sudo().browse( default['directory']) names = directory.files.mapped('name') else: names = self.sudo().directory.files.mapped('name') default.update( {'name': self.unique_name(self.name, names, self.extension)}) vals = self.copy_data(default)[0] if 'reference' in vals: del vals['reference'] if not 'content' in vals: vals.update({'content': self.content}) new = self.with_context(lang=None).create(vals) self.copy_translations(new) return new @api.multi def _check_recomputation(self, vals, olds, *largs, **kwargs): super(File, self)._check_recomputation(vals, olds, *largs, **kwargs) fields = [] if 'name' in vals: fields.extend(['extension', 'mimetype', 'path']) if 'directory' in vals: fields.extend(['settings', 'path']) if 'content' in vals: fields.extend(['index_content']) if fields: self.trigger_computation(fields) self._check_reference_values(vals) #---------------------------------------------------------- # Reference #---------------------------------------------------------- @api.multi def _create_reference(self, settings, path, filename, content): self.ensure_one() self.check_access('create', raise_exception=True) if settings.save_type == 'database': return self.env['muk_dms.data_database'].sudo().create( {'data': content}) return None @api.multi def _update_reference_values(self, values): self.check_access('write', raise_exception=True) references = self.sudo().mapped('reference') if references: references.sudo().update(values) @api.multi def _update_reference_type(self): self.check_access('write', raise_exception=True) for record in self: if record.reference and record.settings.save_type != record.reference.type( ): reference = record._create_reference(record.settings, record.directory.path, record.name, record.content) record._unlink_reference() record.reference = "%s,%s" % (reference._name, reference.id) @api.multi def _check_reference_values(self, values): self.check_access('write', raise_exception=True) if 'content' in values: self._update_reference_values({'content': values['content']}) if 'settings' in values: self._update_reference_type() @api.multi def _get_content(self): self.ensure_one() self.check_access('read', raise_exception=True) return self.reference.sudo().content() if self.reference else None @api.multi def _unlink_reference(self): self.check_access('unlink', raise_exception=True) for tuple in [ list((k, list(map(lambda rec: rec.reference.id, v)))) for k, v in itertools.groupby( self.sorted(key=lambda rec: rec.reference._name), lambda rec: rec.reference._name) ]: self.env[tuple[0]].sudo().browse(list(filter(None, tuple[1]))).unlink()
class KsGlobalDiscountInvoice(models.Model): # _inherit = "account.invoice" """ changing the model to account.move """ _inherit = "account.move" ks_global_discount_type = fields.Selection( [('percent', 'Percentage'), ('amount', 'Amount')], string='Universal Discount Type', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }, default='percent') ks_global_discount_rate = fields.Float('Universal Discount', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }) ks_amount_discount = fields.Monetary(string='Universal Discount', readonly=True, compute='_compute_amount', store=True, track_visibility='always') ks_enable_discount = fields.Boolean(compute='ks_verify_discount') ks_sales_discount_account = fields.Text(compute='ks_verify_discount') ks_purchase_discount_account = fields.Text(compute='ks_verify_discount') inv_num = fields.Char("Invoice#", default="010101") cheque_no = fields.Char(string="Cheque No") chq_date = fields.Date(string="Cheque Date") purpose = fields.Char(string="Purpose") is_petty = fields.Boolean(string="Is petty") @api.constrains('name', 'journal_id', 'state') def _check_unique_sequence_number(self): moves = self.filtered(lambda move: move.state == 'posted') if not moves: return self.flush() # /!\ Computed stored fields are not yet inside the database. self._cr.execute( ''' SELECT move2.id FROM account_move move INNER JOIN account_move move2 ON move2.name = move.name AND move2.journal_id = move.journal_id AND move2.type = move.type AND move2.id != move.id WHERE move.id IN %s AND move2.state = 'posted' ''', [tuple(moves.ids)]) res = self._cr.fetchone() @api.model def _get_default_journal(self): ''' Get the default journal. It could either be passed through the context using the 'default_journal_id' key containing its id, either be determined by the default type. ''' move_type = self._context.get('default_type', 'entry') journal_type = 'general' is_pty = self._context.get('default_is_petty', 0) if move_type in self.get_sale_types(include_receipts=True): journal_type = 'sale' elif move_type in self.get_purchase_types(include_receipts=True): journal_type = 'purchase' if self._context.get('default_journal_id'): journal = self.env['account.journal'].browse( self._context['default_journal_id']) if move_type != 'entry' and journal.type != journal_type: raise UserError( _("Cannot create an invoice of type %s with a journal having %s as type." ) % (move_type, journal_type)) elif is_pty == 1: company_id = self._context.get('default_company_id', self.env.company.id) domain = [('company_id', '=', company_id), ('type', '=', journal_type), ('name', '=', 'Patty Cash')] journal = self.env['account.journal'].search(domain) if not journal: journal = self.env['account.journal'].create({ 'company_id': company_id, 'type': journal_type, 'name': 'Patty Cash', 'code': 'ptch' }) else: company_id = self._context.get('default_company_id', self.env.company.id) domain = [('company_id', '=', company_id), ('type', '=', journal_type)] journal = None if self._context.get('default_currency_id'): currency_domain = domain + [ ('currency_id', '=', self._context['default_currency_id']) ] journal = self.env['account.journal'].search(currency_domain, limit=1) if not journal: journal = self.env['account.journal'].search(domain, limit=1) if not journal: error_msg = _( 'Please define an accounting miscellaneous journal in your company' ) if journal_type == 'sale': error_msg = _( 'Please define an accounting sale journal in your company' ) elif journal_type == 'purchase': error_msg = _( 'Please define an accounting purchase journal in your company' ) raise UserError(error_msg) return journal # @api.multi @api.depends('name') def ks_verify_discount(self): for rec in self: rec.ks_enable_discount = rec.env['ir.config_parameter'].sudo( ).get_param('ks_enable_discount') rec.ks_sales_discount_account = rec.env[ 'ir.config_parameter'].sudo().get_param( 'ks_sales_discount_account') rec.ks_purchase_discount_account = rec.env[ 'ir.config_parameter'].sudo().get_param( 'ks_purchase_discount_account') # @api.multi # 1. tax_line_ids is replaced with tax_line_id. 2. api.mulit is also removed. # @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'tax_line_ids.amount_rounding', # 'currency_id', 'company_id', 'date_invoice', 'type', 'ks_global_discount_type', # 'ks_global_discount_rate') @api.depends('line_ids.debit', 'line_ids.credit', 'line_ids.currency_id', 'line_ids.amount_currency', 'line_ids.amount_residual', 'line_ids.amount_residual_currency', 'line_ids.payment_id.state', 'ks_global_discount_type', 'ks_global_discount_rate') def _compute_amount(self): for rec in self: res = super(KsGlobalDiscountInvoice, rec)._compute_amount() if not ('ks_global_tax_rate' in rec): rec.ks_calculate_discount() sign = rec.type in ['in_refund', 'out_refund'] and -1 or 1 rec.amount_total_company_signed = rec.amount_total * sign rec.amount_total_signed = rec.amount_total * sign return res # @api.multi def ks_calculate_discount(self): for rec in self: if rec.ks_global_discount_type == "amount": rec.ks_amount_discount = rec.ks_global_discount_rate if rec.amount_untaxed > 0 else 0 elif rec.ks_global_discount_type == "percent": if rec.ks_global_discount_rate != 0.0: rec.ks_amount_discount = ( rec.amount_untaxed + rec.amount_tax) * rec.ks_global_discount_rate / 100 else: rec.ks_amount_discount = 0 elif not rec.ks_global_discount_type: rec.ks_global_discount_rate = 0 rec.ks_amount_discount = 0 rec.amount_total = rec.amount_tax + rec.amount_untaxed - rec.ks_amount_discount rec.ks_update_universal_discount() @api.constrains('ks_global_discount_rate') def ks_check_discount_value(self): if self.ks_global_discount_type == "percent": if self.ks_global_discount_rate > 100 or self.ks_global_discount_rate < 0: raise ValidationError( 'You cannot enter percentage value greater than 100.') else: if self.ks_global_discount_rate < 0 or self.amount_untaxed < 0: raise ValidationError( 'You cannot enter discount amount greater than actual cost or value lower than 0.' ) # @api.onchange('purchase_id') # def ks_get_purchase_order_discount(self): # self.ks_global_discount_rate = self.purchase_id.ks_global_discount_rate # self.ks_global_discount_type = self.purchase_id.ks_global_discount_type # @api.model # def invoice_line_move_line_get(self): # ks_res = super(KsGlobalDiscountInvoice, self).invoice_line_move_line_get() # if self.ks_amount_discount > 0: # ks_name = "Universal Discount" # if self.ks_global_discount_type == "percent": # ks_name = ks_name + " (" + str(self.ks_global_discount_rate) + "%)" # ks_name = ks_name + " for " + (self.origin if self.origin else ("Invoice No " + str(self.id))) # if self.ks_sales_discount_account and (self.type == "out_invoice" or self.type == "out_refund"): # # dict = { # 'invl_id': self.number, # 'type': 'src', # 'name': ks_name, # 'price_unit': self.move_id.ks_amount_discount, # 'quantity': 1, # 'amount': -self.move_id.ks_amount_discount, # 'account_id': int(self.move_id.ks_sales_discount_account), # 'move_id': self.id, # 'date': self.date, # 'user_id': self.move_id.invoice_user_id.id or self._uid, # 'company_id': self.move_id.account_id.company_id.id or self.env.company.id, # } # ks_res.append(dict) # # elif self.ks_purchase_discount_account and (self.type == "in_invoice" or self.type == "in_refund"): # dict = { # 'invl_id': self.number, # 'type': 'src', # 'name': ks_name, # 'price_unit': self.ks_amount_discount, # 'quantity': 1, # 'price': -self.ks_amount_discount, # 'account_id': int(self.ks_purchase_discount_account), # # 'invoice_id': self.id, # } # ks_res.append(dict) # # return ks_res @api.model def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None): ks_res = super(KsGlobalDiscountInvoice, self)._prepare_refund(invoice, date_invoice=None, date=None, description=None, journal_id=None) ks_res['ks_global_discount_rate'] = self.ks_global_discount_rate ks_res['ks_global_discount_type'] = self.ks_global_discount_type return ks_res def ks_update_universal_discount(self): """This Function Updates the Universal Discount through Sale Order""" for rec in self: already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find('Universal Discount' ) == 0) terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) if already_exists: amount = rec.ks_amount_discount if rec.ks_sales_discount_account \ and (rec.type == "out_invoice" or rec.type == "out_refund")\ and amount > 0: if rec.type == "out_invoice": already_exists.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: already_exists.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if rec.ks_purchase_discount_account \ and (rec.type == "in_invoice" or rec.type == "in_refund")\ and amount > 0: if rec.type == "in_invoice": already_exists.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: already_exists.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, }) if not already_exists and rec.ks_global_discount_rate > 0: in_draft_mode = self != self._origin if not in_draft_mode and rec.type == 'out_invoice': rec._recompute_universal_discount_lines() print() @api.onchange('ks_global_discount_rate', 'ks_global_discount_type', 'line_ids') def _recompute_universal_discount_lines(self): """This Function Create The General Entries for Universal Discount""" for rec in self: type_list = [ 'out_invoice', 'out_refund', 'in_invoice', 'in_refund' ] if rec.ks_global_discount_rate > 0 and rec.type in type_list: if rec.is_invoice(include_receipts=True): in_draft_mode = self != self._origin ks_name = "Universal Discount " if rec.ks_global_discount_type == "amount": ks_value = "of amount #" + str( self.ks_global_discount_rate) elif rec.ks_global_discount_type == "percent": ks_value = " @" + str( self.ks_global_discount_rate) + "%" else: ks_value = '' ks_name = ks_name + ks_value # ("Invoice No: " + str(self.ids) # if self._origin.id # else (self.display_name)) terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) if already_exists: amount = self.ks_amount_discount if self.ks_sales_discount_account \ and (self.type == "out_invoice" or self.type == "out_refund"): if self.type == "out_invoice": already_exists.update({ 'name': ks_name, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: already_exists.update({ 'name': ks_name, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if self.ks_purchase_discount_account\ and (self.type == "in_invoice" or self.type == "in_refund"): if self.type == "in_invoice": already_exists.update({ 'name': ks_name, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: already_exists.update({ 'name': ks_name, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: new_tax_line = self.env['account.move.line'] create_method = in_draft_mode and \ self.env['account.move.line'].new or\ self.env['account.move.line'].create if self.ks_sales_discount_account \ and (self.type == "out_invoice" or self.type == "out_refund"): amount = self.ks_amount_discount dict = { 'move_name': self.name, 'name': ks_name, 'price_unit': self.ks_amount_discount, 'quantity': 1, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, 'account_id': int(self.ks_sales_discount_account), 'move_id': self._origin, 'date': self.date, 'exclude_from_invoice_tab': True, 'partner_id': terms_lines.partner_id.id, 'company_id': terms_lines.company_id.id, 'company_currency_id': terms_lines.company_currency_id.id, } if self.type == "out_invoice": dict.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: dict.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if in_draft_mode: self.line_ids += create_method(dict) # Updation of Invoice Line Id duplicate_id = self.invoice_line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) self.invoice_line_ids = self.invoice_line_ids - duplicate_id else: dict.update({ 'price_unit': 0.0, 'debit': 0.0, 'credit': 0.0, }) self.line_ids = [(0, 0, dict)] if self.ks_purchase_discount_account\ and (self.type == "in_invoice" or self.type == "in_refund"): amount = self.ks_amount_discount dict = { 'move_name': self.name, 'name': ks_name, 'price_unit': self.ks_amount_discount, 'quantity': 1, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, 'account_id': int(self.ks_purchase_discount_account), 'move_id': self.id, 'date': self.date, 'exclude_from_invoice_tab': True, 'partner_id': terms_lines.partner_id.id, 'company_id': terms_lines.company_id.id, 'company_currency_id': terms_lines.company_currency_id.id, } if self.type == "in_invoice": dict.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: dict.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) self.line_ids += create_method(dict) # updation of invoice line id duplicate_id = self.invoice_line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) self.invoice_line_ids = self.invoice_line_ids - duplicate_id if in_draft_mode: # Update the payement account amount terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, }) else: terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) total_balance = sum( other_lines.mapped('balance')) + amount total_amount_currency = sum( other_lines.mapped('amount_currency')) dict1 = { 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, } dict2 = { 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, } self.line_ids = [(1, already_exists.id, dict1), (1, terms_lines.id, dict2)] print() elif self.ks_global_discount_rate <= 0: already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) if already_exists: self.line_ids -= already_exists terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, }) def print_credit_report(self): return self.env.ref('cost_sheet_quotations.action_credit_note_print' ).report_action(self)
class AccountAssetAsset(models.Model): _name = 'account.asset.asset' _description = 'Asset/Revenue Recognition' _inherit = ['mail.thread', 'ir.needaction_mixin'] entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries') name = fields.Char(string='Asset Name', required=True, readonly=True, states={'draft': [('readonly', False)]}) code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}) value = fields.Float(string='Gross Value', required=True, readonly=True, digits=0, states={'draft': [('readonly', False)]}, oldname='purchase_value') currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id.id) company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['res.company']._company_default_get('account.asset.asset')) note = fields.Text() category_id = fields.Many2one('account.asset.category', string='Category', required=True, change_default=True, readonly=True, states={'draft': [('readonly', False)]}) date = fields.Date(string='Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, oldname="purchase_date") state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], 'Status', required=True, copy=False, default='draft', help="When an asset is created, the status is 'Draft'.\n" "If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n" "You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.") active = fields.Boolean(default=True) partner_id = fields.Many2one('res.partner', string='Partner', readonly=True, states={'draft': [('readonly', False)]}) method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='linear', help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" " * Degressive: Calculated on basis of: Residual Value * Degressive Factor") method_number = fields.Integer(string='Number of Depreciations', readonly=True, states={'draft': [('readonly', False)]}, default=5, help="The number of depreciations needed to depreciate your asset") method_period = fields.Integer(string='Number of Months in a Period', required=True, readonly=True, default=12, states={'draft': [('readonly', False)]}, help="The amount of time between two depreciations, in months") method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]}) method_progress_factor = fields.Float(string='Degressive Factor', readonly=True, default=0.3, states={'draft': [('readonly', False)]}) value_residual = fields.Float(compute='_amount_residual', method=True, digits=0, string='Residual Value') method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True, readonly=True, default='number', states={'draft': [('readonly', False)]}, help="Choose the method to use to compute the dates and number of depreciation lines.\n" " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]}, help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January / Start date of fiscal year') depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id', string='Depreciation Lines', readonly=True, states={'draft': [('readonly', False)], 'open': [('readonly', False)]}) salvage_value = fields.Float(string='Salvage Value', digits=0, readonly=True, states={'draft': [('readonly', False)]}, help="It is the amount you plan to have that you cannot depreciate.") invoice_id = fields.Many2one('account.invoice', string='Invoice', states={'draft': [('readonly', False)]}, copy=False) type = fields.Selection(related="category_id.type", string='Type', required=True) @api.multi def unlink(self): for asset in self: if asset.state in ['open', 'close']: raise UserError(_('You cannot delete a document is in %s state.') % (asset.state,)) for depreciation_line in asset.depreciation_line_ids: if depreciation_line.move_id: raise UserError(_('You cannot delete a document that contains posted entries.')) return super(AccountAssetAsset, self).unlink() @api.multi def _get_last_depreciation_date(self): """ @param id: ids of a account.asset.asset objects @return: Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids. If there isn't any, return the purchase date of this asset """ self.env.cr.execute(""" SELECT a.id as id, COALESCE(MAX(m.date),a.date) AS date FROM account_asset_asset a LEFT JOIN account_asset_depreciation_line rel ON (rel.asset_id = a.id) LEFT JOIN account_move m ON (rel.move_id = m.id) WHERE a.id IN %s GROUP BY a.id, m.date """, (tuple(self.ids),)) result = dict(self.env.cr.fetchall()) return result @api.model def _cron_generate_entries(self): self.compute_generated_entries(datetime.today()) @api.model def compute_generated_entries(self, date, asset_type=None): # Entries generated : one by grouped category and one by asset from ungrouped category created_move_ids = [] type_domain = [] if asset_type: type_domain = [('type', '=', asset_type)] ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)]) created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False) for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]): assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)]) created_move_ids += assets._compute_entries(date, group_entries=True) return created_move_ids def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date): amount = 0 if sequence == undone_dotation_number: amount = residual_amount else: if self.method == 'linear': amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids)) if self.prorata and self.category_id.type == 'purchase': amount = amount_to_depr / self.method_number if sequence == 1: days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 amount = (amount_to_depr / self.method_number) / total_days * days elif self.method == 'degressive': amount = residual_amount * self.method_progress_factor if self.prorata: if sequence == 1: days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 amount = (residual_amount * self.method_progress_factor) / total_days * days return amount def _compute_board_undone_dotation_nb(self, depreciation_date, total_days): undone_dotation_number = self.method_number if self.method_time == 'end': end_date = datetime.strptime(self.method_end, DF).date() undone_dotation_number = 0 while depreciation_date <= end_date: depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period) undone_dotation_number += 1 if self.prorata and self.category_id.type == 'purchase': undone_dotation_number += 1 return undone_dotation_number @api.multi def compute_depreciation_board(self): self.ensure_one() posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date) unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check) # Remove old unposted depreciation lines. We cannot use unlink() with One2many field commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] if self.value_residual != 0.0: amount_to_depr = residual_amount = self.value_residual if self.prorata: depreciation_date = datetime.strptime(self._get_last_depreciation_date()[self.id], DF).date() else: # depreciation_date = 1st of January of purchase year if annual valuation, 1st of # purchase month in other cases if self.method_period >= 12: asset_date = datetime.strptime(self.date[:4] + '-01-01', DF).date() else: asset_date = datetime.strptime(self.date[:7] + '-01', DF).date() # if we already have some previous validated entries, starting date isn't 1st January but last entry + method period if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date: last_depreciation_date = datetime.strptime(posted_depreciation_line_ids[-1].depreciation_date, DF).date() depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period) else: depreciation_date = asset_date day = depreciation_date.day month = depreciation_date.month year = depreciation_date.year total_days = (year % 4) and 365 or 366 undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days) for x in range(len(posted_depreciation_line_ids), undone_dotation_number): sequence = x + 1 amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date) amount = self.currency_id.round(amount) residual_amount -= amount vals = { 'amount': amount, 'asset_id': self.id, 'sequence': sequence, 'name': (self.code or '') + '/' + str(sequence), 'remaining_value': residual_amount, 'depreciated_value': self.value - (self.salvage_value + residual_amount), 'depreciation_date': depreciation_date.strftime(DF), } commands.append((0, False, vals)) # Considering Depr. Period as months depreciation_date = date(year, month, day) + relativedelta(months=+self.method_period) day = depreciation_date.day month = depreciation_date.month year = depreciation_date.year self.write({'depreciation_line_ids': commands}) return True @api.multi def validate(self): self.write({'state': 'open'}) fields = [ 'method', 'method_number', 'method_period', 'method_end', 'method_progress_factor', 'method_time', 'salvage_value', 'invoice_id', ] ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields) for asset in self: tracked_fields = ref_tracked_fields.copy() if asset.method == 'linear': del(tracked_fields['method_progress_factor']) if asset.method_time != 'end': del(tracked_fields['method_end']) else: del(tracked_fields['method_number']) dummy, tracking_value_ids = asset._message_track(tracked_fields, dict.fromkeys(fields)) asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids) @api.multi def set_to_close(self): move_ids = [] for asset in self: unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check) if unposted_depreciation_line_ids: old_values = { 'method_end': asset.method_end, 'method_number': asset.method_number, } # Remove all unposted depr. lines commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] # Create a new depr. line with the residual amount and post it sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1 today = datetime.today().strftime(DF) vals = { 'amount': asset.value_residual, 'asset_id': asset.id, 'sequence': sequence, 'name': (asset.code or '') + '/' + str(sequence), 'remaining_value': 0, 'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated 'depreciation_date': today, } commands.append((0, False, vals)) asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence}) tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end']) changes, tracking_value_ids = asset._message_track(tracked_fields, old_values) if changes: asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids) move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False) if move_ids: name = _('Disposal Move') view_mode = 'form' if len(move_ids) > 1: name = _('Disposal Moves') view_mode = 'tree,form' return { 'name': name, 'view_type': 'form', 'view_mode': view_mode, 'res_model': 'account.move', 'type': 'ir.actions.act_window', 'target': 'current', 'res_id': move_ids[0], } @api.multi def set_to_draft(self): self.write({'state': 'draft'}) @api.one @api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check', 'depreciation_line_ids.amount') def _amount_residual(self): total_amount = 0.0 for line in self.depreciation_line_ids: if line.move_check: total_amount += line.amount self.value_residual = self.value - total_amount - self.salvage_value @api.onchange('company_id') def onchange_company_id(self): self.currency_id = self.company_id.currency_id.id @api.multi @api.depends('depreciation_line_ids.move_id') def _entry_count(self): for asset in self: res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)]) asset.entry_count = res or 0 @api.one @api.constrains('prorata', 'method_time') def _check_prorata(self): if self.prorata and self.method_time != 'number': raise ValidationError(_('Prorata temporis can be applied only for time method "number of depreciations".')) @api.onchange('category_id') def onchange_category_id(self): vals = self.onchange_category_id_values(self.category_id.id) # We cannot use 'write' on an object that doesn't exist yet if vals: for k, v in vals['value'].iteritems(): setattr(self, k, v) def onchange_category_id_values(self, category_id): if category_id: category = self.env['account.asset.category'].browse(category_id) return { 'value': { 'method': category.method, 'method_number': category.method_number, 'method_time': category.method_time, 'method_period': category.method_period, 'method_progress_factor': category.method_progress_factor, 'method_end': category.method_end, 'prorata': category.prorata, } } @api.onchange('method_time') def onchange_method_time(self): if self.method_time != 'number': self.prorata = False @api.multi def copy_data(self, default=None): if default is None: default = {} default['name'] = self.name + _(' (copy)') return super(AccountAssetAsset, self).copy_data(default) @api.multi def _compute_entries(self, date, group_entries=False): depreciation_ids = self.env['account.asset.depreciation.line'].with_context(depreciation_date=date).search([ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), ('move_check', '=', False)]) if group_entries: return depreciation_ids.create_grouped_move() return depreciation_ids.create_move() @api.model def create(self, vals): asset = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals) asset.compute_depreciation_board() return asset @api.multi def write(self, vals): res = super(AccountAssetAsset, self).write(vals) if 'depreciation_line_ids' not in vals and 'state' not in vals: for rec in self: rec.compute_depreciation_board() return res @api.multi def open_entries(self): move_ids = [] for asset in self: for depreciation_line in asset.depreciation_line_ids: if depreciation_line.move_id: move_ids.append(depreciation_line.move_id.id) return { 'name': _('Journal Entries'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', 'domain': [('id', 'in', move_ids)] }
class SaleOrder(models.Model): _inherit = "sale.order" _description = "Sales Order" purchase_order_id = fields.Many2one(comodel_name="purchase.order", string="PO#", copy=False) vendor_id = fields.Many2one(comodel_name='res.partner', string="Vendor") # Instructions colour_instructions = fields.Text(string="Colour Instructions") packing = fields.Text(string="Packing") face_stamp = fields.Html(string="Face Stamp on Paper and Booklet File") selvedge = fields.Html(string="Selvedge") shipping_mark = fields.Html(string="Shipping Mark") shipping_sample_book = fields.Text(string="Shippment Sample") notes = fields.Text(string="Notes") # Other details shipment_date = fields.Date(string="Shipment Date") insurance_id = fields.Many2one(comodel_name='res.insurance', string="Insurance") destination_id = fields.Many2one(comodel_name='res.destination', string='Destination') marks = fields.Char(string="Marks") attachment_ids = fields.One2many('ir.attachment', 'sale_id', string='Attachment') attachment_count = fields.Integer(compute='_compute_attachment_count') actual_grand_total = fields.Float(string="Actual Grand Total", compute='_compute_grand_total') @api.depends('order_line') def _compute_grand_total(self): grand_total = 0 for record in self: for line in self.order_line: grand_total = grand_total + line.actual_net_amount record.actual_grand_total = grand_total @api.onchange('colour_instructions') def _onchange_colour_instructions(self): if self.purchase_order_id: self.purchase_order_id.colour_instructions = self.colour_instructions @api.onchange('packing') def _onchange_packing(self): if self.purchase_order_id: self.purchase_order_id.packing = self.packing @api.onchange('face_stamp') def _onchange_face_stamp(self): if self.purchase_order_id: self.purchase_order_id.face_stamp = self.face_stamp @api.onchange('selvedge') def _onchange_selvedge(self): if self.purchase_order_id: self.purchase_order_id.selvedge = self.selvedge @api.onchange('shipping_mark') def _onchange_shipping_mark(self): if self.purchase_order_id: self.purchase_order_id.shipping_mark = self.shipping_mark @api.onchange('shipping_sample_book') def _onchange_shipping_sample_book(self): if self.purchase_order_id: self.purchase_order_id.shipping_sample_book = self.shipping_sample_book @api.onchange('notes') def _onchange_notes(self): if self.purchase_order_id: self.purchase_order_id.notes = self.notes @api.onchange('shipment_date') def _onchange_shipment_date(self): if self.purchase_order_id: self.purchase_order_id.shipment_date = self.shipment_date @api.onchange('destination_id') def _onchange_destination_id(self): if self.purchase_order_id and self.destination_id: self.purchase_order_id.destination_id = self.destination_id.id @api.onchange('marks') def _onchange_marks(self): if self.purchase_order_id: self.purchase_order_id.marks = self.marks @api.onchange('attachment_ids') def _onchange_attachment_ids(self): if self.purchase_order_id: for attachment in self.attachment_ids: attachment.purchase_id = self.purchase_order_id.id def photos(self): return { 'name': 'Photos', 'view_type': 'form', 'view_mode': 'kanban,tree,form', 'res_model': 'ir.attachment', 'view_id': False, 'type': 'ir.actions.act_window', 'context': {'default_sale_id': self.id, 'default_purchase_id': self.purchase_order_id.id if self.purchase_order_id else ''}, 'domain': [('sale_id', '=', self.id)] } @api.depends('attachment_ids') def _compute_attachment_count(self): for order in self: order.attachment_count = len(order.attachment_ids) def action_confirm(self): """ inherited to create sale order, first check for an existing sale order for the corresponding SO if does not exist, create a new purchase order""" for record in self: res = super(SaleOrder, self).action_confirm() if not record.purchase_order_id and record.vendor_id: purchase_order_lines_obj = self.env['purchase.order.line'] attachment_ids = [] purchase_order_obj = self.env['purchase.order'] for attchment in record.attachment_ids: attachment_ids.append((0, 0, { 'name': attchment.name, 'datas': attchment.datas, "description": attchment.description, "mimetype": attchment.mimetype, 'index_content': attchment.index_content, "create_uid": attchment.create_uid.id, })) vals = { "partner_id": record.vendor_id.id, "sale_order_id": record.id, "customer_id": record.partner_id.id, "attachment_ids": attachment_ids, "colour_instructions": record.colour_instructions, "packing": record.packing, "face_stamp": record.face_stamp, "name": record.name, "selvedge": record.selvedge, "shipping_mark": record.shipping_mark, "shipping_sample_book": record.shipping_sample_book, "notes": record.notes, "marks": record.marks, "shipment_date": record.shipment_date, "destination_id": record.destination_id.id, "currency_id": record.currency_id.id, } purchase = purchase_order_obj.create(vals) record.purchase_order_id = purchase.id for line in record.order_line: taxes = line.product_id.supplier_taxes_id fpos = record.fiscal_position_id taxes_id = fpos.map_tax(taxes, line.product_id, record.vendor_id) if fpos else taxes if taxes_id: taxes_id = taxes_id.filtered(lambda x: x.company_id.id == record.company_id.id) purchase_order_line = purchase_order_lines_obj.create({'product_id': line.product_id.id, 'name': line.name, 'product_qty': line.product_uom_qty, "date_planned": datetime.today(), "product_uom": line.product_uom.id, 'price_unit': line.price_unit, "order_id": purchase.id, "actual_qty": line.actual_qty, "sale_order_line_id": line.id, # "discount": line.discount, 'taxes_id': [(6, 0, taxes_id.ids)], }) line.purchase_order_line_id = purchase_order_line.id return res @api.model def create(self, values): """ adding values to the name from sequence :param values: :return: new record id """ if values.get('name', _('New')) == _('New'): # values['name'] = self.env['ir.sequence'].next_by_code('sale.delivery') values['name'] = self.env['ir.sequence'].next_by_code('order.reference', None) or _('New') # values['marks'] = values['name'] customer_code = '' if values.get('partner_id'): customer = self.env['res.partner'].browse(values.get('partner_id')) customer_code = customer.customer_code if values.get('marks'): marks_field = values.get('marks') else: marks_field = ' ' values['marks'] = '%s %s %s' % (customer_code, values['name'], marks_field) return super(SaleOrder, self).create(values) def name_get(self): """adding SO to the name""" result = [] for r in self: result.append((r.id, u"%s %s" % ('SO', r.name))) return result def _prepare_invoice(self): res = super(SaleOrder, self)._prepare_invoice() res['reference'] = self.name res['sale_id'] = self.id res['ref'] = self.name res['is_order_to_invoice'] = True return res def create_invoices(self): self._create_invoices(self) return self.action_view_invoice() def _create_invoices(self, grouped=False, final=False): res = super(SaleOrder, self)._create_invoices() return res
class DeliveryCarrier(models.Model): _inherit = "delivery.carrier" tracking_url = fields.Text(string="Tracking URL")
class MedicalDocumentReference(models.Model): # FHIR Entity: Document Reference # (https://www.hl7.org/fhir/documentreference.html) _name = "medical.document.reference" _description = "Medical Document Reference" _inherit = ["medical.request", "medical.document.language"] internal_identifier = fields.Char(string="Document reference") state = fields.Selection( [ ("draft", "Draft"), ("current", "Current"), ("superseded", "Superseded"), ], required=True, track_visibility=True, default="draft", ) document_type_id = fields.Many2one( "medical.document.type", required=True, readonly=True, states={"draft": [("readonly", False)]}, ondelete="restrict", ) document_type = fields.Selection(related="document_type_id.document_type", readonly=True) document_template_id = fields.Many2one( "medical.document.template", readonly=True, copy=False, ondelete="restrict", ) is_editable = fields.Boolean(compute="_compute_is_editable") text = fields.Text(string="Document text", readonly=True, copy=False, sanitize=True) lang = fields.Selection( required=False, readonly=True, copy=False, states={"draft": [("readonly", False)]}, ) def _get_language(self): return self.lang or self.patient_id.lang def check_is_billable(self): return self.is_billable def _get_internal_identifier(self, vals): return ( self.env["ir.sequence"].next_by_code("medical.document.reference") or "/") @api.multi @api.depends("state") def _compute_is_editable(self): for rec in self: rec.is_editable = bool(rec.state == "draft") def action_view_request_parameters(self): return { "view": "medical_document.medical_document_reference_action", "view_form": "medical.document.reference.view.form", } def _get_parent_field_name(self): return "document_reference_id" @api.multi def print(self): return self._print(self.print_action) @api.multi def view(self): return self._print(self.view_action) @api.multi def render(self): return self._print(self.render_report) def _print(self, action): self.ensure_one() if self.state == "draft": return self._draft2current(action) return action() def _render(self): return self.with_context( lang=self.lang).document_type_id.report_action_id.render(self.id) def render_report(self): return base64.b64encode(self._render()[0]) def view_action(self): if self.document_type == "action": return self.document_type_id.report_action_id.report_action(self) raise UserError(_("Function must be defined")) def _get_printer_usage(self): return "standard" def print_action(self): content, mime = self._render() behaviour = self.remote.with_context( printer_usage=self._get_printer_usage()).get_printer_behaviour() if "printer" not in behaviour: return False printer = behaviour.pop("printer") return printer.with_context( print_report_name="doc_" + self.internal_identifier).print_document( report=self.document_type_id.report_action_id, content=content, doc_format=mime, ) @api.multi def draft2current(self): return self._draft2current(self.print_action) @api.multi def cancel(self): pass def draft2current_values(self): template_id = self.document_type_id.current_template_id.id return { "lang": self._get_language(), "document_template_id": template_id, "text": self.with_context( template_id=template_id, render_language=self._get_language()).render_text(), } @api.multi def change_lang(self, lang): text = self.with_context(template_id=self.document_template_id.id, render_language=lang).render_text() return self.write({"lang": lang, "text": text}) def _draft2current(self, action): self.ensure_one() if self.state != "draft": raise ValidationError(_("State must be draft")) self.write(self.draft2current_values()) res = action() if res: self.write({"state": "current"}) return res def render_text(self): if self.document_type == "action": template = self.document_template_id or self.env[ "medical.document.template"].browse( self._context.get("template_id", False)) return template.render_template(self._name, self.id) raise UserError(_("Function must be defined")) def current2superseded_values(self): return {"state": "superseded"} @api.multi def current2superseded(self): if self.filtered(lambda r: r.state != "current"): raise ValidationError(_("State must be Current")) self.write(self.current2superseded_values())
class HospitalPatient(models.Model): _name = 'hospital.patient' _inherit = ['mail.thread', 'mail.activity.mixin'] _description = 'Patient Record' _rec_name = 'patient_name' # rec.xml_id = res.get(rec.id) # name = fields.Char(string="Contact Number") name_seq = fields.Char(string='Patient ID', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) gender = fields.Selection([ ('male', 'Male'), ('fe_male', 'Female'), ], default='male', string="Gender") age_group = fields.Selection([ ('major', 'Major'), ('minor', 'Minor'), ], string="Age Group", compute='set_age_group', store=True) patient_name = fields.Char(string='Name', required=True, track_visibility="always") # add track visibility (already done in my copy/paste ! patient_age = fields.Integer('Age', track_visibility="always", group_operator=False) patient_age2 = fields.Float(string="Age2", required=False) notes = fields.Text(string="Registration Note") image = fields.Binary(string="Image", attachment=True) appointment_count = fields.Integer(string='Appointment', compute='get_appointment_count') active = fields.Boolean("Active", default=True) doctor_id = fields.Many2one('hospital.doctor', string="Doctor") email_id = fields.Char(string="Email") user_id = fields.Many2one('res.users', string="PRO") doctor_gender = fields.Selection([ ('male', 'Male'), ('fe_male', 'Female'), ], string="Doctor Gender") patient_name_upper = fields.Char(compute='_compute_upper_name', inverse='_inverse_upper_name') # Overriding the create method to assign sequence for the record # https://www.youtube.com/watch?v=ZfKzmfiqeg0&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=8 @api.model def create(self, vals): if vals.get('name_seq', _('New')) == _('New'): vals['name_seq'] = self.env['ir.sequence'].next_by_code( 'hospital.patient.sequence') or _('New') result = super(HospitalPatient, self).create(vals) return result # compute function in Odoo # https://www.youtube.com/watch?v=Mg80GxrKDOc&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=11 @api.depends('patient_name') def _compute_upper_name(self): for rec in self: rec.patient_name_upper = rec.patient_name.upper( ) if rec.patient_name else False # Making compute field editable using inverse function # https://www.youtube.com/watch?v=NEr6hUTrn84&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=47 def _inverse_upper_name(self): for rec in self: rec.patient_name = rec.patient_name_upper.lower( ) if rec.patient_name_upper else False # Action For Smart Button # https://www.youtube.com/watch?v=I93Lr-bprIc&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=19 def open_patient_appointments(self): return { 'name': _('Appointments'), 'domain': [('patient_id', '=', self.id)], # 'view_type': 'form', 'res_model': 'hospital.appointment', 'view_id': False, 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', } def get_appointment_count(self): count = self.env['hospital.appointment'].search_count([('patient_id', '=', self.id)]) self.appointment_count = count @api.depends('patient_age') def set_age_group(self): for rec in self: if rec.patient_age: if rec.patient_age < 18: rec.age_group = 'minor' else: rec.age_group = 'major' # Add Constrains For a Field # https://www.youtube.com/watch?v=ijS-N1CdiWU&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=14 @api.constrains('patient_age') def check_age(self): for rec in self: if rec.patient_age < 5: raise ValidationError(_('The Age Must be Greater Than 5..!')) # How to Write Onchange Functions # https://www.youtube.com/watch?v=qyRhjyp1MeE&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=39 @api.onchange('doctor_id') def set_doctor_gender(self): for rec in self: if rec.doctor_id: rec.doctor_gender = rec.doctor_id.gender # https://www.youtube.com/watch?v=-1r3WSwtqxQ def name_get(self): # name get function for the model executes automatically res = [] for rec in self: res.append((rec.id, '%s - %s' % (rec.name_seq, rec.patient_name))) return res # Sending Email in Button Click # https://www.youtube.com/watch?v=CZVRmtv6re0&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=44 def action_send_card(self): # sending the patient report to patient via email template_id = self.env.ref( 'jm_hospital.patient_card_email_template').id template = self.env['mail.template'].browse(template_id) template.send_mail(self.id, force_send=True) # Function which is executed using the Cron Job/ Scheduled Action # https://www.youtube.com/watch?v=_P_AVSNr6uU&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=52 @api.model def test_cron_job(self): print("Abcd") # print will get printed in the log of pycharm #code accordingly to execute the cron # Print PDF Report From Button Click in Form # https://www.youtube.com/watch?v=Dc8GDj7ygsI&list=PLqRRLx0cl0hoJhjFWkFYowveq2Zn55dhM&index=67 def print_report(self): return self.env.ref('jm_hospital.report_patient_card').report_action( self) # Server action def action_patients(self): print("Odoo Mates..............") return { 'name': _('Patients Server Action'), 'domain': [], 'view_type': 'form', 'res_model': 'hospital.patient', 'view_id': False, 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', }