class res_company(osv.Model): _name = "res.company" _inherit = "res.company" _columns = { "gengo_private_key": fields.text("Gengo Private Key", copy=False, groups="base.group_system"), "gengo_public_key": fields.text("Gengo Public Key", copy=False, groups="base.group_user"), "gengo_comment": fields.text( "Comments", help= "This comment will be automatically be enclosed in each an every request sent to Gengo", groups="base.group_user"), "gengo_auto_approve": fields.boolean("Auto Approve Translation ?", help="Jobs are Automatically Approved by Gengo.", groups="base.group_user"), "gengo_sandbox": fields.boolean( "Sandbox Mode", help= "Check this box if you're using the sandbox mode of Gengo, mainly used for testing purpose." ), } _defaults = { "gengo_auto_approve": True, }
class ir_model_fields_anonymization_history(osv.osv): _name = 'ir.model.fields.anonymization.history' _order = "date desc" _columns = { 'date': fields.datetime('Date', required=True, readonly=True), 'field_ids': fields.many2many('ir.model.fields.anonymization', 'anonymized_field_to_history_rel', 'field_id', 'history_id', 'Fields', readonly=True), 'state': fields.selection(selection=ANONYMIZATION_HISTORY_STATE, string='Status', required=True, readonly=True), 'direction': fields.selection(selection=ANONYMIZATION_DIRECTION, string='Direction', size=20, required=True, readonly=True), 'msg': fields.text('Message', readonly=True), 'filepath': fields.char(string='File path', readonly=True), }
class ir_logging(osv.Model): _name = 'ir.logging' _order = 'id DESC' EXCEPTIONS_TYPE = [('client', 'Client'), ('server', 'Server')] _columns = { 'create_date': fields.datetime('Create Date', readonly=True), 'create_uid': fields.integer('Uid', readonly=True), # Integer not m2o is intentionnal 'name': fields.char('Name', required=True), 'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=True), 'dbname': fields.char('Database Name', select=True), 'level': fields.char('Level', select=True), 'message': fields.text('Message', required=True), 'path': fields.char('Path', required=True), 'func': fields.char('Function', required=True), 'line': fields.char('Line', required=True), }
class wizard_price(osv.osv): _name = "wizard.price" _description = "Compute price wizard" _columns = { 'info_field': fields.text('Info', readonly=True), 'real_time_accounting': fields.boolean("Generate accounting entries when real-time"), 'recursive': fields.boolean("Change prices of child BoMs too"), } def default_get(self, cr, uid, fields, context=None): res = super(wizard_price, self).default_get(cr, uid, fields, context=context) product_pool = self.pool.get('product.template') product_obj = product_pool.browse(cr, uid, context.get('active_id', False)) if context is None: context = {} rec_id = context and context.get('active_id', False) assert rec_id, _('Active ID is not set in Context.') computed_price = product_pool.compute_price( cr, uid, [], template_ids=[product_obj.id], test=True, context=context) if product_obj.id in computed_price: res['info_field'] = "%s: %s" % (product_obj.name, computed_price[product_obj.id]) else: res['info_field'] = "" return res def compute_from_bom(self, cr, uid, ids, context=None): assert len(ids) == 1 if context is None: context = {} model = context.get('active_model') if model != 'product.template': raise UserError( _('This wizard is build for product templates, while you are currently running it from a product variant.' )) rec_id = context and context.get('active_id', False) assert rec_id, _('Active ID is not set in Context.') prod_obj = self.pool.get('product.template') res = self.browse(cr, uid, ids, context=context) prod = prod_obj.browse(cr, uid, rec_id, context=context) prod_obj.compute_price( cr, uid, [], template_ids=[prod.id], real_time_accounting=res[0].real_time_accounting, recursive=res[0].recursive, test=False, context=context)
class website_seo_metadata(osv.Model): _name = 'website.seo.metadata' _description = 'SEO metadata' _columns = { 'website_meta_title': fields.char("Website meta title", translate=True), 'website_meta_description': fields.text("Website meta description", translate=True), 'website_meta_keywords': fields.char("Website meta keywords", translate=True), }
class WebsiteResPartner(osv.Model): _name = 'res.partner' _inherit = [ 'res.partner', 'website.seo.metadata', 'website.published.mixin' ] def _get_ids(self, cr, uid, ids, flds, args, context=None): return {i: i for i in ids} def _set_private(self, cr, uid, ids, field_name, value, arg, context=None): return self.write(cr, uid, ids, {'website_published': not value}, context=context) def _get_private(self, cr, uid, ids, field_name, arg, context=None): return dict((rec.id, not rec.website_published) for rec in self.browse(cr, uid, ids, context=context)) def _search_private(self, cr, uid, obj, name, args, context=None): return [('website_published', '=', not args[0][2])] _columns = { 'website_private': fields.function(_get_private, fnct_inv=_set_private, fnct_search=_search_private, type='boolean', string='Private Profile'), 'website_description': fields.html('Website Partner Full Description', strip_style=True), 'website_short_description': fields.text('Website Partner Short Description'), # hack to allow using plain browse record in qweb views 'self': fields.function(_get_ids, type='many2one', relation=_name), } def _website_url(self, cr, uid, ids, field_name, arg, context=None): res = super(WebsiteResPartner, self)._website_url(cr, uid, ids, field_name, arg, context=context) for partner in self.browse(cr, uid, ids, context=context): res[partner.id] = "/partners/%s" % slug(partner) return res _defaults = { 'website_private': True, }
class module_category(osv.osv): _name = "ir.module.category" _description = "Application" def _module_nbr(self, cr, uid, ids, prop, unknow_none, context): cr.execute( 'SELECT category_id, COUNT(*) \ FROM ir_module_module \ WHERE category_id IN %(ids)s \ OR category_id IN (SELECT id \ FROM ir_module_category \ WHERE parent_id IN %(ids)s) \ GROUP BY category_id', {'ids': tuple(ids)}) result = dict(cr.fetchall()) for id in ids: cr.execute('select id from ir_module_category where parent_id=%s', (id, )) result[id] = sum([result.get(c, 0) for (c, ) in cr.fetchall()], result.get(id, 0)) return result _columns = { 'name': fields.char("Name", required=True, translate=True, select=True), 'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True), 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'), 'module_nr': fields.function(_module_nbr, string='Number of Apps', type='integer'), 'module_ids': fields.one2many('ir.module.module', 'category_id', 'Modules'), 'description': fields.text("Description", translate=True), 'sequence': fields.integer('Sequence'), 'visible': fields.boolean('Visible'), 'xml_id': fields.function(osv.osv.get_external_id, type='char', string="External ID"), } _order = 'name' _defaults = { 'visible': 1, }
class WebsiteTwitterTweet(osv.osv): _name = "website.twitter.tweet" _description = "Twitter Tweets" _columns = { 'website_id': fields.many2one('website', string="Website"), 'screen_name': fields.char("Screen Name"), 'tweet': fields.text('Tweets'), # Twitter IDs are 64-bit unsigned ints, so we need to store them in # unlimited precision NUMERIC columns, which can be done with a # float field. Used digits=(0,0) to indicate unlimited. # Using VARCHAR would work too but would have sorting problems. 'tweet_id': fields.float("Tweet ID", digits=(0, 0)), # Twitter }
class Country(osv.osv): _name = 'res.country' _description = 'Country' _columns = { 'name': fields.char('Country Name', help='The full name of the country.', required=True, translate=True), 'code': fields.char('Country Code', size=2, help='The ISO country code in two chars.\n' 'You can use this field for quick search.'), 'address_format': fields.text('Address Format', help="""You can state here the usual format to use for the \ addresses belonging to this country.\n\nYou can use the python-style string patern with all the field of the address \ (for example, use '%(street)s' to display the field 'street') plus \n%(state_name)s: the name of the state \n%(state_code)s: the code of the state \n%(country_name)s: the name of the country \n%(country_code)s: the code of the country"""), 'currency_id': fields.many2one('res.currency', 'Currency'), 'image': fields.binary("Image", attachment=True), 'phone_code': fields.integer('Country Calling Code'), 'country_group_ids': fields.many2many('res.country.group', 'res_country_res_country_group_rel', 'res_country_id', 'res_country_group_id', string='Country Groups'), 'state_ids': fields.one2many('res.country.state', 'country_id', string='States'), } _sql_constraints = [ ('name_uniq', 'unique (name)', 'The name of the country must be unique !'), ('code_uniq', 'unique (code)', 'The code of the country must be unique !') ] _defaults = { 'address_format': "%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s", } _order = 'name' name_search = location_name_search def create(self, cursor, user, vals, context=None): if vals.get('code'): vals['code'] = vals['code'].upper() return super(Country, self).create(cursor, user, vals, context=context) def write(self, cursor, user, ids, vals, context=None): if vals.get('code'): vals['code'] = vals['code'].upper() return super(Country, self).write(cursor, user, ids, vals, context=context) def get_address_fields(self, cr, uid, ids, context=None): res = {} for country in self.browse(cr, uid, ids, context=context): res[country.id] = re.findall('\((.+?)\)', country.address_format) return res
class crm_lead_forward_to_partner(osv.TransientModel): """ Forward info history to partners. """ _name = 'crm.lead.channel.interested' _columns = { 'interested': fields.boolean('Interested by this lead'), 'contacted': fields.boolean('Did you contact the lead?', help="The lead has been contacted"), 'comment': fields.text('Comment', help="What are the elements that have led to this decision?", required=True), } _defaults = { 'interested': lambda self, cr, uid, c: c.get('interested', True), 'contacted': False, } def action_confirm(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context=context) if wizard.interested and not wizard.contacted: raise UserError(_("You must contact the lead before saying that you are interested")) lead_obj = self.pool.get('crm.lead') lead_obj.check_access_rights(cr, uid, 'write') if wizard.interested: message = _('<p>I am interested by this lead.</p>') values = {} else: stage = 'stage_portal_lead_recycle' if wizard.contacted: message = _('<p>I am not interested by this lead. I contacted the lead.</p>') else: message = _('<p>I am not interested by this lead. I have not contacted the lead.</p>') values = {'partner_assigned_id': False} user = self.pool.get('res.users').browse(cr, uid, uid, context=context) partner_ids = self.pool.get('res.partner').search(cr, SUPERUSER_ID, [('id', 'child_of', user.partner_id.commercial_partner_id.id)], context=context) lead_obj.message_unsubscribe(cr, SUPERUSER_ID, context.get('active_ids', []), partner_ids, context=None) try: stage_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', stage)[1] except ValueError: stage_id = False if stage_id: values.update({'stage_id': stage_id}) if wizard.comment: message += '<p>%s</p>' % wizard.comment for active_id in context.get('active_ids', []): lead_obj.message_post(cr, uid, active_id, body=message, subtype="mail.mt_comment", context=context) if values: lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids', []), values) if wizard.interested: for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context): lead_obj.convert_opportunity(cr, SUPERUSER_ID, [lead.id], lead.partner_id and lead.partner_id.id or None, context=None) return { 'type': 'ir.actions.act_window_close', }
class wizard(osv.osv_memory): """ A wizard to manage the creation/removal of portal users. """ _name = 'portal.wizard' _description = 'Portal Access Management' _columns = { 'portal_id': fields.many2one('res.groups', domain=[('is_portal', '=', True)], required=True, string='Portal', help="The portal that users can be added in or removed from."), 'user_ids': fields.one2many('portal.wizard.user', 'wizard_id', string='Users'), 'welcome_message': fields.text(string='Invitation Message', help="This text is included in the email sent to new users of the portal."), } def _default_portal(self, cr, uid, context): portal_ids = self.pool.get('res.groups').search(cr, uid, [('is_portal', '=', True)]) return portal_ids and portal_ids[0] or False _defaults = { 'portal_id': _default_portal, } def onchange_portal_id(self, cr, uid, ids, portal_id, context=None): # for each partner, determine corresponding portal.wizard.user records res_partner = self.pool.get('res.partner') partner_ids = context and context.get('active_ids') or [] contact_ids = set() user_changes = [] for partner in res_partner.browse(cr, SUPERUSER_ID, partner_ids, context): for contact in (partner.child_ids or [partner]): # make sure that each contact appears at most once in the list if contact.id not in contact_ids: contact_ids.add(contact.id) in_portal = False if contact.user_ids: in_portal = portal_id in [g.id for g in contact.user_ids[0].groups_id] user_changes.append((0, 0, { 'partner_id': contact.id, 'email': contact.email, 'in_portal': in_portal, })) return {'value': {'user_ids': user_changes}} def action_apply(self, cr, uid, ids, context=None): wizard = self.browse(cr, uid, ids[0], context) portal_user_ids = [user.id for user in wizard.user_ids] self.pool.get('portal.wizard.user').action_apply(cr, uid, portal_user_ids, context) return {'type': 'ir.actions.act_window_close'}
class test_converter(orm.Model): _name = 'web_editor.converter.test' # disable translation export for those brilliant field labels and values _translate = False _columns = { 'char': fields.char(), 'integer': fields.integer(), 'float': fields.float(), 'numeric': fields.float(digits=(16, 2)), 'many2one': fields.many2one('web_editor.converter.test.sub'), 'binary': fields.binary(), 'date': fields.date(), 'datetime': fields.datetime(), 'selection': fields.selection([ (1, "réponse A"), (2, "réponse B"), (3, "réponse C"), (4, "réponse D"), ]), 'selection_str': fields.selection( [ ('A', "Qu'il n'est pas arrivé à Toronto"), ('B', "Qu'il était supposé arriver à Toronto"), ('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"), ('D', "La réponse D"), ], string= u"Lorsqu'un pancake prend l'avion à destination de Toronto et " u"qu'il fait une escale technique à St Claude, on dit:"), 'html': fields.html(), 'text': fields.text(), }
class gamification_badge_user(osv.Model): """User having received a badge""" _name = 'gamification.badge.user' _description = 'Gamification user badge' _order = "create_date desc" _rec_name = "badge_name" _columns = { 'user_id': fields.many2one('res.users', string="User", required=True, ondelete="cascade"), 'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"), 'badge_id': fields.many2one('gamification.badge', string='Badge', required=True, ondelete="cascade"), 'challenge_id': fields.many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge"), 'comment': fields.text('Comment'), 'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"), 'create_date': fields.datetime('Created', readonly=True), 'create_uid': fields.many2one('res.users', string='Creator', readonly=True), } def _send_badge(self, cr, uid, ids, context=None): """Send a notification to a user for receiving a badge Does not verify constrains on badge granting. The users are added to the owner_ids (create badge_user if needed) The stats counters are incremented :param ids: list(int) of badge users that will receive the badge """ res = True temp_obj = self.pool.get('mail.template') user_obj = self.pool.get('res.users') template_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'gamification', 'email_template_badge_received')[1] for badge_user in self.browse(cr, uid, ids, context=context): template = temp_obj.get_email_template(cr, uid, template_id, badge_user.id, context=context) body_html = temp_obj.render_template(cr, uid, template.body_html, 'gamification.badge.user', badge_user.id, context=template._context) res = user_obj.message_post( cr, uid, badge_user.user_id.id, body=body_html, subtype='gamification.mt_badge_granted', partner_ids=[badge_user.user_id.partner_id.id], context=context) return res def create(self, cr, uid, vals, context=None): self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context) return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
class ir_model_fields_anonymization_migration_fix(osv.osv): _name = 'ir.model.fields.anonymization.migration.fix' _order = "sequence" _columns = { 'target_version': fields.char('Target Version'), 'model_name': fields.char('Model'), 'field_name': fields.char('Field'), 'query': fields.text('Query'), 'query_type': fields.selection(string='Query', selection=[('sql', 'sql'), ('python', 'python')]), 'sequence': fields.integer('Sequence'), }
class sale_quote_template(osv.osv): _name = "sale.quote.template" _description = "Sale Quotation Template" _columns = { 'name': fields.char('Quotation Template', required=True), 'website_description': fields.html('Description', translate=True), 'quote_line': fields.one2many('sale.quote.line', 'quote_id', 'Quotation Template Lines', copy=True), 'note': fields.text('Terms and conditions'), 'options': fields.one2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True), 'number_of_days': fields.integer( 'Quotation Duration', help= 'Number of days for the validity date computation of the quotation' ), 'require_payment': fields.selection( [(0, 'Not mandatory on website quote validation'), (1, 'Immediate after website order validation')], 'Payment', help= "Require immediate payment by the customer when validating the order from the website quote" ), } def open_template(self, cr, uid, quote_id, context=None): return { 'type': 'ir.actions.act_url', 'target': 'self', 'url': '/quote/template/%d' % quote_id[0] }
class grant_badge_wizard(osv.TransientModel): """ Wizard allowing to grant a badge to a user""" _name = 'gamification.badge.user.wizard' _columns = { 'user_id': fields.many2one("res.users", string='User', required=True), 'badge_id': fields.many2one("gamification.badge", string='Badge', required=True), 'comment': fields.text('Comment'), } def action_grant_badge(self, cr, uid, ids, context=None): """Wizard action for sending a badge to a chosen user""" badge_user_obj = self.pool.get('gamification.badge.user') for wiz in self.browse(cr, uid, ids, context=context): if uid == wiz.user_id.id: raise UserError(_('You can not grant a badge to yourself')) #create the badge values = { 'user_id': wiz.user_id.id, 'sender_id': uid, 'badge_id': wiz.badge_id.id, 'comment': wiz.comment, } badge_user = badge_user_obj.create(cr, uid, values, context=context) result = badge_user_obj._send_badge(cr, uid, badge_user, context=context) return result
class hr_job(osv.Model): def _get_nbr_employees(self, cr, uid, ids, name, args, context=None): res = {} for job in self.browse(cr, uid, ids, context=context): nb_employees = len(job.employee_ids or []) res[job.id] = { 'no_of_employee': nb_employees, 'expected_employees': nb_employees + job.no_of_recruitment, } return res def _get_job_position(self, cr, uid, ids, context=None): res = [] for employee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context): if employee.job_id: res.append(employee.job_id.id) return res _name = "hr.job" _description = "Job Position" _inherit = ['mail.thread'] _columns = { 'name': fields.char('Job Name', required=True, select=True, translate=True), 'expected_employees': fields.function( _get_nbr_employees, string='Total Forecasted Employees', help= 'Expected number of employees for this job position after new recruitment.', store={ 'hr.job': (lambda self, cr, uid, ids, c=None: ids, ['no_of_recruitment'], 10), 'hr.employee': (_get_job_position, ['job_id'], 10), }, type='integer', multi='_get_nbr_employees'), 'no_of_employee': fields.function( _get_nbr_employees, string="Current Number of Employees", help='Number of employees currently occupying this job position.', store={ 'hr.employee': (_get_job_position, ['job_id'], 10), }, type='integer', multi='_get_nbr_employees'), 'no_of_recruitment': fields.integer('Expected New Employees', copy=False, help='Number of new employees you expect to recruit.'), 'no_of_hired_employee': fields.integer( 'Hired Employees', copy=False, help= 'Number of hired employees for this job position during recruitment phase.' ), 'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'), 'description': fields.text('Job Description'), 'requirements': fields.text('Requirements'), 'department_id': fields.many2one('hr.department', 'Department'), 'company_id': fields.many2one('res.company', 'Company'), 'state': fields.selection( [('recruit', 'Recruitment in Progress'), ('open', 'Recruitment Closed')], string='Status', readonly=True, required=True, track_visibility='always', copy=False, help= "Set whether the recruitment process is open or closed for this job position." ), 'write_date': fields.datetime('Update Date', readonly=True), } _defaults = { 'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company'). _company_default_get(cr, uid, 'hr.job', context=ctx), 'state': 'recruit', 'no_of_recruitment': 1, } _sql_constraints = [ ('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!' ), ] def set_recruit(self, cr, uid, ids, context=None): for job in self.browse(cr, uid, ids, context=context): no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment self.write(cr, uid, [job.id], { 'state': 'recruit', 'no_of_recruitment': no_of_recruitment }, context=context) return True def set_open(self, cr, uid, ids, context=None): self.write(cr, uid, ids, { 'state': 'open', 'no_of_recruitment': 0, 'no_of_hired_employee': 0 }, context=context) return True # TDE note: done in new api, because called with new api -> context is a # frozendict -> error when tryign to manipulate it @api.model def create(self, values): return super( hr_job, self.with_context(mail_create_nosubscribe=True)).create(values) def copy(self, cr, uid, id, default=None, context=None): if default is None: default = {} if 'name' not in default: job = self.browse(cr, uid, id, context=context) default['name'] = _("%s (copy)") % (job.name) return super(hr_job, self).copy(cr, uid, id, default=default, context=context) # ---------------------------------------- # Compatibility methods # ---------------------------------------- _no_of_employee = _get_nbr_employees # v7 compatibility job_open = set_open # v7 compatibility job_recruitment = set_recruit # v7 compatibility
class hr_employee(osv.osv): _name = "hr.employee" _description = "Employee" _order = 'name_related' _inherits = {'resource.resource': "resource_id"} _inherit = ['mail.thread'] _mail_post_access = 'read' _columns = { #we need a related field in order to be able to sort the employee by name 'name_related': fields.related('resource_id', 'name', type='char', string='Name', readonly=True, store=True), 'country_id': fields.many2one('res.country', 'Nationality (Country)'), 'birthday': fields.date("Date of Birth"), 'ssnid': fields.char('SSN No', help='Social Security Number'), 'sinid': fields.char('SIN No', help="Social Insurance Number"), 'identification_id': fields.char('Identification No'), 'gender': fields.selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')], 'Gender'), 'marital': fields.selection([('single', 'Single'), ('married', 'Married'), ('widower', 'Widower'), ('divorced', 'Divorced')], 'Marital Status'), 'department_id': fields.many2one('hr.department', 'Department'), 'address_id': fields.many2one('res.partner', 'Working Address'), 'address_home_id': fields.many2one('res.partner', 'Home Address'), 'bank_account_id': fields.many2one('res.partner.bank', 'Bank Account Number', domain="[('partner_id','=',address_home_id)]", help="Employee bank salary account"), 'work_phone': fields.char('Work Phone', readonly=False), 'mobile_phone': fields.char('Work Mobile', readonly=False), 'work_email': fields.char('Work Email', size=240), 'work_location': fields.char('Work Location'), 'notes': fields.text('Notes'), 'parent_id': fields.many2one('hr.employee', 'Manager'), 'category_ids': fields.many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Tags'), 'child_ids': fields.one2many('hr.employee', 'parent_id', 'Subordinates'), 'resource_id': fields.many2one('resource.resource', 'Resource', ondelete='cascade', required=True, auto_join=True), 'coach_id': fields.many2one('hr.employee', 'Coach'), 'job_id': fields.many2one('hr.job', 'Job Title'), 'passport_id': fields.char('Passport No'), 'color': fields.integer('Color Index'), 'city': fields.related('address_id', 'city', type='char', string='City'), 'login': fields.related('user_id', 'login', type='char', string='Login', readonly=1), 'last_login': fields.related('user_id', 'date', type='datetime', string='Latest Connection', readonly=1), } # image: all image fields are base64 encoded and PIL-supported image = ecore.fields.Binary( "Photo", attachment=True, help= "This field holds the image used as photo for the employee, limited to 1024x1024px." ) image_medium = ecore.fields.Binary("Medium-sized photo", compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, help="Medium-sized photo of the employee. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") image_small = ecore.fields.Binary("Small-sized photo", compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, help="Small-sized photo of the employee. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") @api.depends('image') def _compute_images(self): for rec in self: rec.image_medium = tools.image_resize_image_medium(rec.image) rec.image_small = tools.image_resize_image_small(rec.image) def _inverse_image_medium(self): for rec in self: rec.image = tools.image_resize_image_big(rec.image_medium) def _inverse_image_small(self): for rec in self: rec.image = tools.image_resize_image_big(rec.image_small) def _get_default_image(self, cr, uid, context=None): image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') return tools.image_resize_image_big( open(image_path, 'rb').read().encode('base64')) defaults = { 'active': 1, 'image': _get_default_image, 'color': 0, } def unlink(self, cr, uid, ids, context=None): resource_ids = [] for employee in self.browse(cr, uid, ids, context=context): resource_ids.append(employee.resource_id.id) super(hr_employee, self).unlink(cr, uid, ids, context=context) return self.pool.get('resource.resource').unlink(cr, uid, resource_ids, context=context) def onchange_address_id(self, cr, uid, ids, address, context=None): if address: address = self.pool.get('res.partner').browse(cr, uid, address, context=context) return { 'value': { 'work_phone': address.phone, 'mobile_phone': address.mobile } } return {'value': {}} def onchange_company(self, cr, uid, ids, company, context=None): address_id = False if company: company_id = self.pool.get('res.company').browse(cr, uid, company, context=context) address = self.pool.get('res.partner').address_get( cr, uid, [company_id.partner_id.id], ['contact']) address_id = address and address['contact'] or False return {'value': {'address_id': address_id}} def onchange_department_id(self, cr, uid, ids, department_id, context=None): value = {'parent_id': False} if department_id: department = self.pool.get('hr.department').browse( cr, uid, department_id) value['parent_id'] = department.manager_id.id return {'value': value} def onchange_user(self, cr, uid, ids, name, image, user_id, context=None): if user_id: user = self.pool['res.users'].browse(cr, uid, user_id, context=context) values = { 'name': name or user.name, 'work_email': user.email, 'image': image or user.image, } return {'value': values} def action_follow(self, cr, uid, ids, context=None): """ Wrapper because message_subscribe_users take a user_ids=None that receive the context without the wrapper. """ return self.message_subscribe_users(cr, uid, ids, context=context) def action_unfollow(self, cr, uid, ids, context=None): """ Wrapper because message_unsubscribe_users take a user_ids=None that receive the context without the wrapper. """ return self.message_unsubscribe_users(cr, uid, ids, context=context) def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=None, context=None): """ Overwrite of the original method to always follow user_id field, even when not track_visibility so that a user will follow it's employee """ if auto_follow_fields is None: auto_follow_fields = ['user_id'] user_field_lst = [] for name, field in self._fields.items(): if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users': user_field_lst.append(name) return user_field_lst _constraints = [ (osv.osv._check_recursion, _('Error! You cannot create recursive hierarchy of Employee(s).'), ['parent_id']), ]
class product_template(osv.osv): _name = 'product.template' _inherit = 'product.template' def _product_available(self, cr, uid, ids, name, arg, context=None): prod_available = {} product_ids = self.browse(cr, uid, ids, context=context) var_ids = [] for product in product_ids: var_ids += [p.id for p in product.product_variant_ids] variant_available = self.pool['product.product']._product_available( cr, uid, var_ids, context=context) for product in product_ids: qty_available = 0 virtual_available = 0 incoming_qty = 0 outgoing_qty = 0 for p in product.product_variant_ids: qty_available += variant_available[p.id]["qty_available"] virtual_available += variant_available[ p.id]["virtual_available"] incoming_qty += variant_available[p.id]["incoming_qty"] outgoing_qty += variant_available[p.id]["outgoing_qty"] prod_available[product.id] = { "qty_available": qty_available, "virtual_available": virtual_available, "incoming_qty": incoming_qty, "outgoing_qty": outgoing_qty, } return prod_available def _search_product_quantity(self, cr, uid, obj, name, domain, context): prod = self.pool.get("product.product") product_variant_ids = prod.search(cr, uid, domain, context=context) return [('product_variant_ids', 'in', product_variant_ids)] def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None): res = {} for product in self.browse(cr, uid, ids, context=context): res[product.id] = str(product.qty_available) + _(" On Hand") return res def _compute_nbr_reordering_rules(self, cr, uid, ids, field_names=None, arg=None, context=None): res = dict.fromkeys( ids, { 'nbr_reordering_rules': 0, 'reordering_min_qty': 0, 'reordering_max_qty': 0 }) product_data = self.pool['stock.warehouse.orderpoint'].read_group( cr, uid, [('product_id.product_tmpl_id', 'in', ids)], ['product_id', 'product_min_qty', 'product_max_qty'], ['product_id'], context=context) for data in product_data: product_tmpl_id = data['__domain'][1][2][0] res[product_tmpl_id][ 'nbr_reordering_rules'] = res[product_tmpl_id].get( 'nbr_reordering_rules', 0) + int(data['product_id_count']) res[product_tmpl_id]['reordering_min_qty'] = data[ 'product_min_qty'] res[product_tmpl_id]['reordering_max_qty'] = data[ 'product_max_qty'] return res def _get_product_template_type(self, cr, uid, context=None): res = super(product_template, self)._get_product_template_type(cr, uid, context=context) if 'product' not in [item[0] for item in res]: res.append(('product', 'Producto Almacenable')) return res _columns = { 'property_stock_procurement': fields.property( type='many2one', relation='stock.location', string="Procurement Location", domain=[('usage', 'like', 'procurement')], help= "This stock location will be used, instead of the default one, as the source location for stock moves generated by procurements." ), 'property_stock_production': fields.property( type='many2one', relation='stock.location', string="Production Location", domain=[('usage', 'like', 'production')], help= "This stock location will be used, instead of the default one, as the source location for stock moves generated by manufacturing orders." ), 'property_stock_inventory': fields.property( type='many2one', relation='stock.location', string="Inventory Location", domain=[('usage', 'like', 'inventory')], help= "This stock location will be used, instead of the default one, as the source location for stock moves generated when you do an inventory." ), 'sale_delay': fields.float( 'Customer Lead Time', help= "The average delay in days between the confirmation of the customer order and the delivery of the finished products. It's the time you promise to your customers." ), 'tracking': fields.selection(selection=[('serial', 'By Unique Serial Number'), ('lot', 'By Lots'), ('none', 'No Tracking')], string="Tracking", required=True), 'description_picking': fields.text('Description on Picking', translate=True), # sum of product variant qty # 'reception_count': fields.function(_product_available, multi='qty_available', # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), # 'delivery_count': fields.function(_product_available, multi='qty_available', # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), 'qty_available': fields.function( _product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), 'virtual_available': fields.function( _product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Forecasted Quantity'), 'incoming_qty': fields.function( _product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Incoming'), 'outgoing_qty': fields.function( _product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'), fnct_search=_search_product_quantity, type='float', string='Outgoing'), 'location_id': fields.dummy(string='Location', relation='stock.location', type='many2one'), 'warehouse_id': fields.dummy(string='Warehouse', relation='stock.warehouse', type='many2one'), 'route_ids': fields.many2many( 'stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]", help= "Depending on the modules installed, this will allow you to define the route of the product: whether it will be bought, manufactured, MTO/MTS,..." ), 'nbr_reordering_rules': fields.function(_compute_nbr_reordering_rules, string='Reordering Rules', type='integer', multi=True), 'reordering_min_qty': fields.function(_compute_nbr_reordering_rules, type='float', multi=True), 'reordering_max_qty': fields.function(_compute_nbr_reordering_rules, type='float', multi=True), 'route_from_categ_ids': fields.related('categ_id', 'total_route_ids', type="many2many", relation="stock.location.route", string="Category Routes"), } _defaults = { 'sale_delay': 7, 'tracking': 'none', } def action_view_routes(self, cr, uid, ids, context=None): route_obj = self.pool.get("stock.location.route") act_obj = self.pool.get('ir.actions.act_window') mod_obj = self.pool.get('ir.model.data') product_route_ids = set() for product in self.browse(cr, uid, ids, context=context): product_route_ids |= set([r.id for r in product.route_ids]) product_route_ids |= set( [r.id for r in product.categ_id.total_route_ids]) route_ids = route_obj.search(cr, uid, [ '|', ('id', 'in', list(product_route_ids)), ('warehouse_selectable', '=', True) ], context=context) result = mod_obj.xmlid_to_res_id(cr, uid, 'stock.action_routes_form', raise_if_not_found=True) result = act_obj.read(cr, uid, [result], context=context)[0] result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]" return result def onchange_tracking(self, cr, uid, ids, tracking, context=None): if not tracking: return {} product_product = self.pool['product.product'] variant_ids = product_product.search(cr, uid, [('product_tmpl_id', 'in', ids)], context=context) return product_product.onchange_tracking(cr, uid, variant_ids, tracking, context=context) def _get_products(self, cr, uid, ids, context=None): products = [] for prodtmpl in self.browse(cr, uid, ids, context=None): products += [x.id for x in prodtmpl.product_variant_ids] return products def _get_act_window_dict(self, cr, uid, name, context=None): mod_obj = self.pool.get('ir.model.data') act_obj = self.pool.get('ir.actions.act_window') result = mod_obj.xmlid_to_res_id(cr, uid, name, raise_if_not_found=True) result = act_obj.read(cr, uid, [result], context=context)[0] return result def action_open_quants(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) result = self._get_act_window_dict(cr, uid, 'stock.product_open_quants', context=context) result['domain'] = "[('product_id','in',[" + ','.join( map(str, products)) + "])]" result[ 'context'] = "{'search_default_locationgroup': 1, 'search_default_internal_loc': 1}" return result def action_view_orderpoints(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) result = self._get_act_window_dict(cr, uid, 'stock.product_open_orderpoint', context=context) if len(ids) == 1 and len(products) == 1: result['context'] = "{'default_product_id': " + str( products[0]) + ", 'search_default_product_id': " + str( products[0]) + "}" else: result['domain'] = "[('product_id','in',[" + ','.join( map(str, products)) + "])]" result['context'] = "{}" return result def action_view_stock_moves(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) result = self._get_act_window_dict(cr, uid, 'stock.act_product_stock_move_open', context=context) if products: result['context'] = "{'default_product_id': %d}" % products[0] result['domain'] = "[('product_id.product_tmpl_id','in',[" + ','.join( map(str, ids)) + "])]" return result def write(self, cr, uid, ids, vals, context=None): if 'uom_id' in vals: new_uom = self.pool.get('product.uom').browse(cr, uid, vals['uom_id'], context=context) for product in self.browse(cr, uid, ids, context=context): old_uom = product.uom_id if old_uom != new_uom: if self.pool.get('stock.move').search( cr, uid, [('product_id', 'in', [x.id for x in product.product_variant_ids]), ('state', '=', 'done')], limit=1, context=context): raise UserError( _("You can not change the unit of measure of a product that has already been used in a done stock move. If you need to change the unit of measure, you may deactivate this product." )) return super(product_template, self).write(cr, uid, ids, vals, context=context)
class ir_translation(osv.osv): _name = "ir.translation" _log_access = False def _get_language(self, cr, uid, context): lang_model = self.pool.get('res.lang') lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context) lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context) return [(d['code'], d['name']) for d in lang_data] def _get_src(self, cr, uid, ids, name, arg, context=None): ''' Get source name for the translation. If object type is model then return the value store in db. Otherwise return value store in src field ''' if context is None: context = {} res = dict.fromkeys(ids, False) for record in self.browse(cr, uid, ids, context=context): res[record.id] = record.src if record.type == 'model': model_name, field_name = record.name.split(',') model = self.pool.get(model_name) if model is None: continue field = model._fields.get(field_name) if field is None: continue if not callable(field.translate): # Pass context without lang, need to read real stored field, not translation context_no_lang = dict(context, lang=None) result = model.read(cr, uid, [record.res_id], [field_name], context=context_no_lang) res[record.id] = result[0][field_name] if result else False return res def _set_src(self, cr, uid, id, name, value, args, context=None): ''' When changing source term of a translation, change its value in db for the associated object, and the src field ''' if context is None: context = {} record = self.browse(cr, uid, id, context=context) if record.type == 'model': model_name, field_name = record.name.split(',') model = self.pool.get(model_name) field = model._fields[field_name] if not callable(field.translate): # Make a context without language information, because we want # to write on the value stored in db and not on the one # associated with the current language. Also not removing lang # from context trigger an error when lang is different. context_wo_lang = context.copy() context_wo_lang.pop('lang', None) model.write(cr, uid, [record.res_id], {field_name: value}, context=context_wo_lang) return self.write(cr, uid, id, {'src': value}, context=context) def _search_src(self, cr, uid, obj, name, args, context): ''' the source term is stored on 'src' field ''' res = [] for field, operator, value in args: res.append(('src', operator, value)) return res _columns = { 'name': fields.char('Translated field', required=True), 'res_id': fields.integer('Record ID', select=True), 'lang': fields.selection(_get_language, string='Language'), 'type': fields.selection(TRANSLATION_TYPE, string='Type', select=True), 'src': fields.text('Internal Source' ), # stored in database, kept for backward compatibility 'source': fields.function(_get_src, fnct_inv=_set_src, fnct_search=_search_src, type='text', string='Source term'), 'value': fields.text('Translation Value'), 'module': fields.char('Module', help="Module this term belongs to", select=True), 'state': fields.selection( [('to_translate', 'To Translate'), ('inprogress', 'Translation in Progress'), ('translated', 'Translated')], string="Status", help= "Automatically set to let administators find new terms that might need to be translated" ), # aka gettext extracted-comments - we use them to flag ecore-web translation # cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html 'comments': fields.text('Translation comments', select=True), } _defaults = { 'state': 'to_translate', } _sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)', 'Language code of translation item must be among known languages'), ] def _auto_init(self, cr, context=None): super(ir_translation, self)._auto_init(cr, context) cr.execute( "SELECT indexname FROM pg_indexes WHERE indexname LIKE 'ir_translation_%'" ) indexes = [row[0] for row in cr.fetchall()] # Removed because there is a size limit on btree indexed values (problem with column src): # cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)') # cr.execute('CREATE INDEX ir_translation_lts ON ir_translation (lang, type, src)') # # Removed because hash indexes are not compatible with postgres streaming replication: # cr.execute('CREATE INDEX ir_translation_src_hash_idx ON ir_translation USING hash (src)') if set(indexes) & set([ 'ir_translation_ltns', 'ir_translation_lts', 'ir_translation_src_hash_idx' ]): cr.execute( 'DROP INDEX IF EXISTS ir_translation_ltns, ir_translation_lts, ir_translation_src_hash_idx' ) cr.commit() # Add separate md5 index on src (no size limit on values, and good performance). if 'ir_translation_src_md5' not in indexes: cr.execute( 'CREATE INDEX ir_translation_src_md5 ON ir_translation (md5(src))' ) cr.commit() if 'ir_translation_ltn' not in indexes: cr.execute( 'CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)' ) cr.commit() def _check_selection_field_value(self, cr, uid, field, value, context=None): if field == 'lang': return return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context) def _get_ids(self, cr, uid, name, tt, lang, ids): translations = dict.fromkeys(ids, False) if ids: cr.execute( 'select res_id,value ' 'from ir_translation ' 'where lang=%s ' 'and type=%s ' 'and name=%s ' 'and res_id IN %s', (lang, tt, name, tuple(ids))) for res_id, value in cr.fetchall(): translations[res_id] = value return translations def _set_ids(self, cr, uid, name, tt, lang, ids, value, src=None): self.clear_caches() cr.execute( 'update ir_translation ' 'set value=%s ' ' , src=%s ' ' , state=%s ' 'where lang=%s ' 'and type=%s ' 'and name=%s ' 'and res_id IN %s ' 'returning res_id', ( value, src, 'translated', lang, tt, name, tuple(ids), )) existing_ids = [x[0] for x in cr.fetchall()] for id in list(set(ids) - set(existing_ids)): self.create( cr, uid, { 'lang': lang, 'type': tt, 'name': name, 'res_id': id, 'value': value, 'src': src, 'state': 'translated' }) return len(ids) def _get_source_query(self, cr, uid, name, types, lang, source, res_id): if source: # Note: the extra test on md5(src) is a hint for postgres to use the # index ir_translation_src_md5 query = """SELECT value FROM ir_translation WHERE lang=%s AND type in %s AND src=%s AND md5(src)=md5(%s)""" source = tools.ustr(source) params = (lang or '', types, source, source) if res_id: query += " AND res_id in %s" params += (res_id, ) if name: query += " AND name=%s" params += (tools.ustr(name), ) else: query = """SELECT value FROM ir_translation WHERE lang=%s AND type in %s AND name=%s""" params = (lang or '', types, tools.ustr(name)) return (query, params) @tools.ormcache('name', 'types', 'lang', 'source', 'res_id') def __get_source(self, cr, uid, name, types, lang, source, res_id): # res_id is a tuple or None, otherwise ormcache cannot cache it! query, params = self._get_source_query(cr, uid, name, types, lang, source, res_id) cr.execute(query, params) res = cr.fetchone() trad = res and res[0] or u'' if source and not trad: return tools.ustr(source) return trad def _get_source(self, cr, uid, name, types, lang, source=None, res_id=None): """ Returns the translation for the given combination of name, type, language and source. All values passed to this method should be unicode (not byte strings), especially ``source``. :param name: identification of the term to translate, such as field name (optional if source is passed) :param types: single string defining type of term to translate (see ``type`` field on ir.translation), or sequence of allowed types (strings) :param lang: language code of the desired translation :param source: optional source term to translate (should be unicode) :param res_id: optional resource id or a list of ids to translate (if used, ``source`` should be set) :rtype: unicode :return: the request translation, or an empty unicode string if no translation was found and `source` was not passed """ # FIXME: should assert that `source` is unicode and fix all callers to always pass unicode # so we can remove the string encoding/decoding. if not lang: return tools.ustr(source or '') if isinstance(types, basestring): types = (types, ) if res_id: if isinstance(res_id, (int, long)): res_id = (res_id, ) else: res_id = tuple(res_id) return self.__get_source(cr, uid, name, types, lang, source, res_id) @api.model def _get_terms_query(self, field, records): """ Utility function that makes the query for field terms. """ query = """ SELECT * FROM ir_translation WHERE lang=%s AND type=%s AND name=%s AND res_id IN %s """ name = "%s,%s" % (field.model_name, field.name) params = (records.env.lang, 'model', name, tuple(records.ids)) return query, params @api.model def _get_terms_mapping(self, field, records): """ Return a function mapping a ir_translation row (dict) to a value. This method is called before querying the database for translations. """ return lambda data: data['value'] @api.model def _get_terms_translations(self, field, records): """ Return the terms and translations of a given `field` on `records`. :return: {record_id: {source: value}} """ result = {rid: {} for rid in records.ids} if records: map_trans = self._get_terms_mapping(field, records) query, params = self._get_terms_query(field, records) self._cr.execute(query, params) for data in self._cr.dictfetchall(): result[data['res_id']][data['src']] = map_trans(data) return result @api.model def _sync_terms_translations(self, field, records): """ Synchronize the translations to the terms to translate, after the English value of a field is modified. The algorithm tries to match existing translations to the terms to translate, provided the distance between modified strings is not too large. It allows to not retranslate data where a typo has been fixed in the English value. """ if not callable(getattr(field, 'translate', None)): return trans = self.env['ir.translation'] outdated = trans discarded = trans for record in records: # get field value and terms to translate value = record[field.name] terms = set(field.get_trans_terms(value)) record_trans = trans.search([ ('type', '=', 'model'), ('name', '=', "%s,%s" % (field.model_name, field.name)), ('res_id', '=', record.id), ]) if not terms: # discard all translations for that field discarded += record_trans continue # remap existing translations on terms when possible for trans in record_trans: if trans.src == trans.value: discarded += trans elif trans.src not in terms: matches = get_close_matches(trans.src, terms, 1, 0.9) if matches: trans.write({'src': matches[0], 'state': trans.state}) else: outdated += trans # process outdated and discarded translations outdated.write({'state': 'to_translate'}) discarded.unlink() @api.model @tools.ormcache_context('model_name', keys=('lang', )) def get_field_string(self, model_name): """ Return the translation of fields strings in the context's language. Note that the result contains the available translations only. :param model_name: the name of a model :return: the model's fields' strings as a dictionary `{field_name: field_string}` """ fields = self.env['ir.model.fields'].search([('model', '=', model_name) ]) return {field.name: field.field_description for field in fields} @api.model @tools.ormcache_context('model_name', keys=('lang', )) def get_field_help(self, model_name): """ Return the translation of fields help in the context's language. Note that the result contains the available translations only. :param model_name: the name of a model :return: the model's fields' help as a dictionary `{field_name: field_help}` """ fields = self.env['ir.model.fields'].search([('model', '=', model_name) ]) return {field.name: field.help for field in fields} @api.multi def check(self, mode): """ Check access rights of operation ``mode`` on ``self`` for the current user. Raise an AccessError in case conditions are not met. """ if self.env.user._is_admin(): return # collect translated field records (model_ids) and other translations trans_ids = [] model_ids = defaultdict(list) model_fields = defaultdict(list) for trans in self: if trans.type == 'model': mname, fname = trans.name.split(',') model_ids[mname].append(trans.res_id) model_fields[mname].append(fname) else: trans_ids.append(trans.id) # check for regular access rights on other translations if trans_ids: records = self.browse(trans_ids) records.check_access_rights(mode) records.check_access_rule(mode) # check for read/write access on translated field records fmode = 'read' if mode == 'read' else 'write' for mname, ids in model_ids.iteritems(): records = self.env[mname].browse(ids) records.check_access_rights(fmode) records.check_field_access_rights(fmode, model_fields[mname]) records.check_access_rule(fmode) @api.model def create(self, vals): if vals.get('type') == 'model' and vals.get('value'): # check and sanitize value mname, fname = vals['name'].split(',') field = self.env[mname]._fields[fname] vals['value'] = field.check_trans_value(vals['value']) record = super(ir_translation, self.sudo()).create(vals).with_env(self.env) record.check('create') self.clear_caches() return record @api.multi def write(self, vals): if vals.get('value'): vals.setdefault('state', 'translated') ttype = vals.get('type') or self[:1].type if ttype == 'model': # check and sanitize value name = vals.get('name') or self[:1].name mname, fname = name.split(',') field = self.env[mname]._fields[fname] vals['value'] = field.check_trans_value(vals['value']) elif vals.get('src') or not vals.get('value', True): vals.setdefault('state', 'to_translate') self.check('write') result = super(ir_translation, self.sudo()).write(vals) self.check('write') self.clear_caches() return result @api.multi def unlink(self): self.check('unlink') self.clear_caches() return super(ir_translation, self.sudo()).unlink() @api.model def insert_missing(self, field, records): """ Insert missing translations for `field` on `records`. """ records = records.with_context(lang=None) external_ids = records.get_external_id() # if no xml_id, empty string if callable(field.translate): # insert missing translations for each term in src query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value, module) SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s, %(module)s FROM res_lang l WHERE NOT EXISTS ( SELECT 1 FROM ir_translation WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s AND src=%(src)s AND module=%(module)s ); """ for record in records: module = external_ids[record.id].split('.')[0] src = record[field.name] or None for term in set(field.get_trans_terms(src)): self._cr.execute( query, { 'name': "%s,%s" % (field.model_name, field.name), 'res_id': record.id, 'src': term, 'module': module }) else: # insert missing translations for src query = """ INSERT INTO ir_translation (lang, type, name, res_id, src, value, module) SELECT l.code, 'model', %(name)s, %(res_id)s, %(src)s, %(src)s, %(module)s FROM res_lang l WHERE l.code != 'en_US' AND NOT EXISTS ( SELECT 1 FROM ir_translation WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s AND module=%(module)s ); UPDATE ir_translation SET src=%(src)s WHERE type='model' AND name=%(name)s AND res_id=%(res_id)s AND module=%(module)s; """ for record in records: module = external_ids[record.id].split('.')[0] self._cr.execute( query, { 'name': "%s,%s" % (field.model_name, field.name), 'res_id': record.id, 'src': record[field.name] or None, 'module': module }) self.clear_caches() @api.model def translate_fields(self, model, id, field=None): """ Open a view for translating the field(s) of the record (model, id). """ main_lang = 'en_US' if not self.env['res.lang'].search_count([('code', '!=', main_lang)]): raise UserError( _("Translation features are unavailable until you install an extra translation." )) # determine domain for selecting translations record = self.env[model].with_context(lang=main_lang).browse(id) domain = ['&', ('res_id', '=', id), ('name', '=like', model + ',%')] def make_domain(fld, rec): name = "%s,%s" % (fld.model_name, fld.name) return ['&', ('res_id', '=', rec.id), ('name', '=', name)] # insert missing translations, and extend domain for related fields for name, fld in record._fields.items(): if not getattr(fld, 'translate', False): continue rec = record if fld.related: try: # traverse related fields up to their data source while fld.related: rec, fld = fld.traverse_related(rec) if rec: domain = ['|'] + domain + make_domain(fld, rec) except AccessError: continue assert fld.translate and rec._name == fld.model_name self.insert_missing(fld, rec) action = { 'name': 'Translate', 'res_model': 'ir.translation', 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'tree,form', 'domain': domain, } if field: fld = record._fields[field] if not fld.related: action['context'] = { 'search_default_name': "%s,%s" % (fld.model_name, fld.name), } return action def _get_import_cursor(self, cr, uid, context=None): """ Return a cursor-like object for fast inserting translations """ return ir_translation_import_cursor(cr, uid, self, context=context) def load_module_terms(self, cr, modules, langs, context=None): context = dict(context or {}) # local copy for module_name in modules: modpath = ecore.modules.get_module_path(module_name) if not modpath: continue for lang in langs: lang_code = tools.get_iso_codes(lang) base_lang_code = None if '_' in lang_code: base_lang_code = lang_code.split('_')[0] # Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po) if base_lang_code: base_trans_file = ecore.modules.get_module_resource( module_name, 'i18n', base_lang_code + '.po') if base_trans_file: _logger.info( 'module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang) tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context) context[ 'overwrite'] = True # make sure the requested translation will override the base terms later # i18n_extra folder is for additional translations handle manually (eg: for l10n_be) base_trans_extra_file = ecore.modules.get_module_resource( module_name, 'i18n_extra', base_lang_code + '.po') if base_trans_extra_file: _logger.info( 'module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang) tools.trans_load(cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context) context[ 'overwrite'] = True # make sure the requested translation will override the base terms later # Step 2: then load the main translation file, possibly overriding the terms coming from the base language trans_file = ecore.modules.get_module_resource( module_name, 'i18n', lang_code + '.po') if trans_file: _logger.info( 'module %s: loading translation file (%s) for language %s', module_name, lang_code, lang) tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context) elif lang_code != 'en_US': _logger.info('module %s: no translation for language %s', module_name, lang_code) trans_extra_file = ecore.modules.get_module_resource( module_name, 'i18n_extra', lang_code + '.po') if trans_extra_file: _logger.info( 'module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang) tools.trans_load(cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context) return True
class PaymentTransaction(osv.Model): """ Transaction Model. Each specific acquirer can extend the model by adding its own fields. Methods that can be added in an acquirer-specific implementation: - ``<name>_create``: method receiving values used when creating a new transaction and that returns a dictionary that will update those values. This method can be used to tweak some transaction values. Methods defined for convention, depending on your controllers: - ``<name>_form_feedback(self, cr, uid, data, context=None)``: method that handles the data coming from the acquirer after the transaction. It will generally receives data posted by the acquirer after the transaction. """ _name = 'payment.transaction' _description = 'Payment Transaction' _order = 'id desc' _rec_name = 'reference' def _lang_get(self, cr, uid, context=None): lang_ids = self.pool['res.lang'].search(cr, uid, [], context=context) languages = self.pool['res.lang'].browse(cr, uid, lang_ids, context=context) return [(language.code, language.name) for language in languages] def _default_partner_country_id(self, cr, uid, context=None): comp = self.pool['res.company'].browse(cr, uid, context.get('company_id', 1), context=context) return comp.country_id.id _columns = { 'create_date': fields.datetime('Creation Date', readonly=True), 'date_validate': fields.datetime('Validation Date'), 'acquirer_id': fields.many2one( 'payment.acquirer', 'Acquirer', required=True, ), 'type': fields.selection( [('server2server', 'Server To Server'), ('form', 'Form'), ('form_save', 'Form with credentials storage')], string='Type', required=True), 'state': fields.selection( [('draft', 'Draft'), ('pending', 'Pending'), ('done', 'Done'), ('error', 'Error'), ('cancel', 'Canceled') ], 'Status', required=True, track_visibility='onchange', copy=False), 'state_message': fields.text('Message', help='Field used to store error and/or validation messages for information'), # payment 'amount': fields.float('Amount', required=True, digits=(16, 2), track_visibility='always', help='Amount'), 'fees': fields.float('Fees', digits=(16, 2), track_visibility='always', help='Fees amount; set by the system because depends on the acquirer'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), 'reference': fields.char('Reference', required=True, help='Internal reference of the TX'), 'acquirer_reference': fields.char('Acquirer Reference', help='Reference of the TX as stored in the acquirer database'), # duplicate partner / transaction data to store the values at transaction time 'partner_id': fields.many2one('res.partner', 'Partner', track_visibility='onchange',), 'partner_name': fields.char('Partner Name'), 'partner_lang': fields.selection(_lang_get, 'Language'), 'partner_email': fields.char('Email'), 'partner_zip': fields.char('Zip'), 'partner_address': fields.char('Address'), 'partner_city': fields.char('City'), 'partner_country_id': fields.many2one('res.country', 'Country', required=True), 'partner_phone': fields.char('Phone'), 'html_3ds': fields.char('3D Secure HTML'), 'callback_eval': fields.char('S2S Callback', help="""\ Will be safe_eval with `self` being the current transaction. i.e.: self.env['my.model'].payment_validated(self)""", oldname="s2s_cb_eval"), 'payment_method_id': fields.many2one('payment.method', 'Payment Method', domain="[('acquirer_id', '=', acquirer_id)]"), } def _check_reference(self, cr, uid, ids, context=None): transaction = self.browse(cr, uid, ids[0], context=context) if transaction.state not in ['cancel', 'error']: if self.search(cr, uid, [('reference', '=', transaction.reference), ('id', '!=', transaction.id)], context=context, count=True): return False return True _constraints = [ (_check_reference, 'The payment transaction reference must be unique!', ['reference', 'state']), ] _defaults = { 'type': 'form', 'state': 'draft', 'partner_lang': 'en_US', 'partner_country_id': _default_partner_country_id, 'reference': lambda s, c, u, ctx=None: s.pool['ir.sequence'].next_by_code(c, u, 'payment.transaction', context=ctx), } def create(self, cr, uid, values, context=None): Acquirer = self.pool['payment.acquirer'] if values.get('partner_id'): # @TDENOTE: not sure values.update(self.on_change_partner_id(cr, uid, None, values.get('partner_id'), context=context)['value']) # call custom create method if defined (i.e. ogone_create for ogone) if values.get('acquirer_id'): acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context) # compute fees custom_method_name = '%s_compute_fees' % acquirer.provider if hasattr(Acquirer, custom_method_name): fees = getattr(Acquirer, custom_method_name)( cr, uid, acquirer.id, values.get('amount', 0.0), values.get('currency_id'), values.get('partner_country_id'), context=None) values['fees'] = float_round(fees, 2) # custom create custom_method_name = '%s_create' % acquirer.provider if hasattr(self, custom_method_name): values.update(getattr(self, custom_method_name)(cr, uid, values, context=context)) # Default value of reference is tx_id = super(PaymentTransaction, self).create(cr, uid, values, context=context) if not values.get('reference'): self.write(cr, uid, [tx_id], {'reference': str(tx_id)}, context=context) return tx_id def write(self, cr, uid, ids, values, context=None): Acquirer = self.pool['payment.acquirer'] if ('acquirer_id' in values or 'amount' in values) and 'fees' not in values: # The acquirer or the amount has changed, and the fees are not explicitely forced. Fees must be recomputed. if isinstance(ids, (int, long)): ids = [ids] for txn_id in ids: vals = dict(values) vals['fees'] = 0.0 transaction = self.browse(cr, uid, txn_id, context=context) if 'acquirer_id' in values: acquirer = Acquirer.browse(cr, uid, values['acquirer_id'], context=context) if values['acquirer_id'] else None else: acquirer = transaction.acquirer_id if acquirer: custom_method_name = '%s_compute_fees' % acquirer.provider if hasattr(Acquirer, custom_method_name): amount = (values['amount'] if 'amount' in values else transaction.amount) or 0.0 currency_id = values.get('currency_id') or transaction.currency_id.id country_id = values.get('partner_country_id') or transaction.partner_country_id.id fees = getattr(Acquirer, custom_method_name)(cr, uid, acquirer.id, amount, currency_id, country_id, context=None) vals['fees'] = float_round(fees, 2) res = super(PaymentTransaction, self).write(cr, uid, txn_id, vals, context=context) return res return super(PaymentTransaction, self).write(cr, uid, ids, values, context=context) def on_change_partner_id(self, cr, uid, ids, partner_id, context=None): partner = None if partner_id: partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) return {'value': { 'partner_name': partner and partner.name or False, 'partner_lang': partner and partner.lang or 'en_US', 'partner_email': partner and partner.email or False, 'partner_zip': partner and partner.zip or False, 'partner_address': _partner_format_address(partner and partner.street or '', partner and partner.street2 or ''), 'partner_city': partner and partner.city or False, 'partner_country_id': partner and partner.country_id.id or self._default_partner_country_id(cr, uid, context=context), 'partner_phone': partner and partner.phone or False, }} return {} def get_next_reference(self, cr, uid, reference, context=None): ref_suffix = 1 init_ref = reference while self.pool['payment.transaction'].search_count(cr, uid, [('reference', '=', reference)], context=context): reference = init_ref + '-' + str(ref_suffix) ref_suffix += 1 return reference # -------------------------------------------------- # FORM RELATED METHODS # -------------------------------------------------- def render(self, cr, uid, id, context=None): tx = self.browse(cr, uid, id, context=context) values = { 'reference': tx.reference, 'amount': tx.amount, 'currency_id': tx.currency_id.id, 'currency': tx.currency_id, 'partner': tx.partner_id, 'partner_name': tx.partner_name, 'partner_lang': tx.partner_lang, 'partner_email': tx.partner_email, 'partner_zip': tx.partner_zip, 'partner_address': tx.partner_address, 'partner_city': tx.partner_city, 'partner_country_id': tx.partner_country_id.id, 'partner_country': tx.partner_country_id, 'partner_phone': tx.partner_phone, 'partner_state': None, } return tx.acquirer_id.render(None, None, None, values=values) def form_feedback(self, cr, uid, data, acquirer_name, context=None): invalid_parameters, tx = None, None tx_find_method_name = '_%s_form_get_tx_from_data' % acquirer_name if hasattr(self, tx_find_method_name): tx = getattr(self, tx_find_method_name)(cr, uid, data, context=context) invalid_param_method_name = '_%s_form_get_invalid_parameters' % acquirer_name if hasattr(self, invalid_param_method_name): invalid_parameters = getattr(self, invalid_param_method_name)(cr, uid, tx, data, context=context) if invalid_parameters: _error_message = '%s: incorrect tx data:\n' % (acquirer_name) for item in invalid_parameters: _error_message += '\t%s: received %s instead of %s\n' % (item[0], item[1], item[2]) _logger.error(_error_message) return False feedback_method_name = '_%s_form_validate' % acquirer_name if hasattr(self, feedback_method_name): return getattr(self, feedback_method_name)(cr, uid, tx, data, context=context) return True # -------------------------------------------------- # SERVER2SERVER RELATED METHODS # -------------------------------------------------- def s2s_create(self, cr, uid, values, cc_values, context=None): tx_id, tx_result = self.s2s_send(cr, uid, values, cc_values, context=context) self.s2s_feedback(cr, uid, tx_id, tx_result, context=context) return tx_id def s2s_do_transaction(self, cr, uid, id, context=None, **kwargs): tx = self.browse(cr, uid, id, context=context) custom_method_name = '%s_s2s_do_transaction' % tx.acquirer_id.provider if hasattr(self, custom_method_name): return getattr(self, custom_method_name)(cr, uid, id, context=context, **kwargs) def s2s_get_tx_status(self, cr, uid, tx_id, context=None): """ Get the tx status. """ tx = self.browse(cr, uid, tx_id, context=context) invalid_param_method_name = '_%s_s2s_get_tx_status' % tx.acquirer_id.provider if hasattr(self, invalid_param_method_name): return getattr(self, invalid_param_method_name)(cr, uid, tx, context=context) return True
class project_issue(osv.Model): _name = "project.issue" _description = "Project Issue" _order = "priority desc, create_date desc" _inherit = ['mail.thread', 'ir.needaction_mixin'] _mail_post_access = 'read' def _get_default_partner(self, cr, uid, context=None): if context is None: context = {} if 'default_project_id' in context: project = self.pool.get('project.project').browse( cr, uid, context['default_project_id'], context=context) if project and project.partner_id: return project.partner_id.id return False def _get_default_stage_id(self, cr, uid, context=None): """ Gives default stage_id """ if context is None: context = {} return self.stage_find(cr, uid, [], context.get('default_project_id'), [('fold', '=', False)], context=context) def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): if context is None: context = {} access_rights_uid = access_rights_uid or uid stage_obj = self.pool.get('project.task.type') order = stage_obj._order # lame hack to allow reverting search, should just work in the trivial case if read_group_order == 'stage_id desc': order = "%s desc" % order # retrieve team_id from the context, add them to already fetched columns (ids) if 'default_project_id' in context: search_domain = [ '|', ('project_ids', '=', context['default_project_id']), ('id', 'in', ids) ] else: search_domain = [('id', 'in', ids)] # perform search stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) # restore order of the search result.sort( lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) fold = {} for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context): fold[stage.id] = stage.fold or False return result, fold def _compute_day(self, cr, uid, ids, fields, args, context=None): """ @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, @param ids: List of Openday’s IDs @return: difference between current date and log date @param context: A standard dictionary for contextual values """ Calendar = self.pool['resource.calendar'] res = dict((res_id, {}) for res_id in ids) for issue in self.browse(cr, uid, ids, context=context): values = { 'day_open': 0.0, 'day_close': 0.0, 'working_hours_open': 0.0, 'working_hours_close': 0.0, 'days_since_creation': 0.0, 'inactivity_days': 0.0, } # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None calendar_id = None if issue.project_id and issue.project_id.resource_calendar_id: calendar_id = issue.project_id.resource_calendar_id.id dt_create_date = datetime.strptime(issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT) if issue.date_open: dt_date_open = datetime.strptime( issue.date_open, DEFAULT_SERVER_DATETIME_FORMAT) values['day_open'] = (dt_date_open - dt_create_date ).total_seconds() / (24.0 * 3600) values['working_hours_open'] = Calendar._interval_hours_get( cr, uid, calendar_id, dt_create_date, dt_date_open, timezone_from_uid=issue.user_id.id or uid, exclude_leaves=False, context=context) if issue.date_closed: dt_date_closed = datetime.strptime( issue.date_closed, DEFAULT_SERVER_DATETIME_FORMAT) values['day_close'] = (dt_date_closed - dt_create_date ).total_seconds() / (24.0 * 3600) values['working_hours_close'] = Calendar._interval_hours_get( cr, uid, calendar_id, dt_create_date, dt_date_closed, timezone_from_uid=issue.user_id.id or uid, exclude_leaves=False, context=context) days_since_creation = datetime.today() - dt_create_date values['days_since_creation'] = days_since_creation.days if issue.date_action_last: inactive_days = datetime.today() - datetime.strptime( issue.date_action_last, DEFAULT_SERVER_DATETIME_FORMAT) elif issue.date_last_stage_update: inactive_days = datetime.today() - datetime.strptime( issue.date_last_stage_update, DEFAULT_SERVER_DATETIME_FORMAT) else: inactive_days = datetime.today() - datetime.strptime( issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT) values['inactivity_days'] = inactive_days.days # filter only required values for field in fields: res[issue.id][field] = values[field] return res def on_change_project(self, cr, uid, ids, project_id, context=None): if project_id: project = self.pool.get('project.project').browse(cr, uid, project_id, context=context) if project and project.partner_id: return {'value': {'partner_id': project.partner_id.id}} return {'value': {'partner_id': False}} _columns = { 'id': fields.integer('ID', readonly=True), 'name': fields.char('Issue', required=True), 'active': fields.boolean('Active', required=False), 'create_date': fields.datetime('Creation Date', readonly=True, select=True), 'write_date': fields.datetime('Update Date', readonly=True), 'days_since_creation': fields.function(_compute_day, string='Days since creation date', \ multi='compute_day', type="integer", help="Difference in days between creation date and current date"), 'date_deadline': fields.date('Deadline'), 'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id',\ select=True, help='Sales team to which Case belongs to.\ Define Responsible user and Email account for mail gateway.' ), 'partner_id': fields.many2one('res.partner', 'Contact', select=1), 'company_id': fields.many2one('res.company', 'Company'), 'description': fields.text('Private Note'), 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', track_visibility='onchange', help="A Issue's kanban state indicates special situations affecting it:\n" " * Normal is the default situation\n" " * Blocked indicates something is preventing the progress of this issue\n" " * Ready for next stage indicates the issue is ready to be pulled to the next stage", required=True), 'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1), 'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), 'date_open': fields.datetime('Assigned', readonly=True, select=True), # Project Issue fields 'date_closed': fields.datetime('Closed', readonly=True, select=True), 'date': fields.datetime('Date'), 'date_last_stage_update': fields.datetime('Last Stage Update', select=True), 'channel': fields.char('Channel', help="Communication channel."), 'tag_ids': fields.many2many('project.tags', string='Tags'), 'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority', select=True), 'stage_id': fields.many2one ('project.task.type', 'Stage', track_visibility='onchange', select=True, domain="[('project_ids', '=', project_id)]", copy=False), 'project_id': fields.many2one('project.project', 'Project', track_visibility='onchange', select=True), 'duration': fields.float('Duration'), 'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]", help="You can link this issue to an existing task or directly create a new one from here"), 'day_open': fields.function(_compute_day, string='Days to Assign', multi='compute_day', type="float", store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}), 'day_close': fields.function(_compute_day, string='Days to Close', multi='compute_day', type="float", store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}), 'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1, track_visibility='onchange'), 'working_hours_open': fields.function(_compute_day, string='Working Hours to assign the Issue', multi='compute_day', type="float", store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}), 'working_hours_close': fields.function(_compute_day, string='Working Hours to close the Issue', multi='compute_day', type="float", store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}), 'inactivity_days': fields.function(_compute_day, string='Days since last action', multi='compute_day', type="integer", help="Difference in days between last action and current date"), 'color': fields.integer('Color Index'), 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True), 'date_action_last': fields.datetime('Last Action', readonly=1), 'date_action_next': fields.datetime('Next Action', readonly=1), 'legend_blocked': fields.related("stage_id", "legend_blocked", type="char", string='Kanban Blocked Explanation'), 'legend_done': fields.related("stage_id", "legend_done", type="char", string='Kanban Valid Explanation'), 'legend_normal': fields.related("stage_id", "legend_normal", type="char", string='Kanban Ongoing Explanation'), } _defaults = { 'active': 1, 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id( cr, uid, context=c), 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), 'company_id': lambda s, cr, uid, c: s.pool['res.users']._get_company( cr, uid, context=c), 'priority': '0', 'kanban_state': 'normal', 'date_last_stage_update': fields.datetime.now, 'user_id': lambda obj, cr, uid, context: uid, } _group_by_full = {'stage_id': _read_group_stage_ids} def copy(self, cr, uid, id, default=None, context=None): issue = self.read(cr, uid, [id], ['name'], context=context)[0] if not default: default = {} default = default.copy() default.update(name=_('%s (copy)') % (issue['name'])) return super(project_issue, self).copy(cr, uid, id, default=default, context=context) def create(self, cr, uid, vals, context=None): context = dict(context or {}) if vals.get('project_id') and not context.get('default_project_id'): context['default_project_id'] = vals.get('project_id') if vals.get('user_id') and not vals.get('date_open'): vals['date_open'] = fields.datetime.now() if 'stage_id' in vals: vals.update( self.onchange_stage_id(cr, uid, None, vals.get('stage_id'), context=context)['value']) # context: no_log, because subtype already handle this create_context = dict(context, mail_create_nolog=True) return super(project_issue, self).create(cr, uid, vals, context=create_context) def write(self, cr, uid, ids, vals, context=None): # stage change: update date_last_stage_update if 'stage_id' in vals: vals.update( self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']) vals['date_last_stage_update'] = fields.datetime.now() if 'kanban_state' not in vals: vals['kanban_state'] = 'normal' # user_id change: update date_open if vals.get('user_id') and 'date_open' not in vals: vals['date_open'] = fields.datetime.now() return super(project_issue, self).write(cr, uid, ids, vals, context) def onchange_task_id(self, cr, uid, ids, task_id, context=None): if not task_id: return {'value': {}} task = self.pool.get('project.task').browse(cr, uid, task_id, context=context) return { 'value': { 'user_id': task.user_id.id, } } def onchange_partner_id(self, cr, uid, ids, partner_id, context=None): """ This function returns value of partner email address based on partner :param part: Partner's id """ if partner_id: partner = self.pool['res.partner'].browse(cr, uid, partner_id, context) return {'value': {'email_from': partner.email}} return {'value': {'email_from': False}} def get_empty_list_help(self, cr, uid, help, context=None): context = dict(context or {}) context['empty_list_help_model'] = 'project.project' context['empty_list_help_id'] = context.get('default_project_id') context['empty_list_help_document_name'] = _("issues") return super(project_issue, self).get_empty_list_help(cr, uid, help, context=context) # ------------------------------------------------------- # Stage management # ------------------------------------------------------- def onchange_stage_id(self, cr, uid, ids, stage_id, context=None): if not stage_id: return {'value': {}} stage = self.pool['project.task.type'].browse(cr, uid, stage_id, context=context) if stage.fold: return {'value': {'date_closed': fields.datetime.now()}} return {'value': {'date_closed': False}} def stage_find(self, cr, uid, cases, team_id, domain=[], order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the issue: - type: stage type must be the same or 'both' - team_id: if set, stages must belong to this team or be a default case """ if isinstance(cases, (int, long)): cases = self.browse(cr, uid, cases, context=context) # collect all team_ids team_ids = [] if team_id: team_ids.append(team_id) for task in cases: if task.project_id: team_ids.append(task.project_id.id) # OR all team_ids and OR with case_default search_domain = [] if team_ids: search_domain += [('|')] * (len(team_ids) - 1) for team_id in team_ids: search_domain.append(('project_ids', '=', team_id)) search_domain += list(domain) # perform search, return the first found stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context) if stage_ids: return stage_ids[0] return False # ------------------------------------------------------- # Mail gateway # ------------------------------------------------------- def _track_subtype(self, cr, uid, ids, init_values, context=None): record = self.browse(cr, uid, ids[0], context=context) if 'kanban_state' in init_values and record.kanban_state == 'blocked': return 'project_issue.mt_issue_blocked' elif 'kanban_state' in init_values and record.kanban_state == 'done': return 'project_issue.mt_issue_ready' elif 'user_id' in init_values and record.user_id: # assigned -> new return 'project_issue.mt_issue_new' elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: # start stage -> new return 'project_issue.mt_issue_new' elif 'stage_id' in init_values: return 'project_issue.mt_issue_stage' return super(project_issue, self)._track_subtype(cr, uid, ids, init_values, context=context) def _notification_group_recipients(self, cr, uid, ids, message, recipients, done_ids, group_data, context=None): """ Override the mail.thread method to handle project users and officers recipients. Indeed those will have specific action in their notification emails: creating tasks, assigning it. """ group_project_user = self.pool['ir.model.data'].xmlid_to_res_id( cr, uid, 'project.group_project_user') for recipient in recipients: if recipient.id in done_ids: continue if recipient.user_ids and group_project_user in recipient.user_ids[ 0].groups_id.ids: group_data['group_project_user'] |= recipient done_ids.add(recipient.id) return super(project_issue, self)._notification_group_recipients(cr, uid, ids, message, recipients, done_ids, group_data, context=context) def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None): res = super(project_issue, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context) new_action_id = self.pool['ir.model.data'].xmlid_to_res_id( cr, uid, 'project_issue.project_issue_categ_act0') take_action = self._notification_link_helper(cr, uid, ids, 'assign', context=context) new_action = self._notification_link_helper(cr, uid, ids, 'new', context=context, action_id=new_action_id) task_record = self.browse(cr, uid, ids[0], context=context) actions = [] if not task_record.user_id: actions.append({'url': take_action, 'title': _('I take it')}) else: actions.append({'url': new_action, 'title': _('New Issue')}) res['group_project_user'] = {'actions': actions} return res @api.cr_uid_context def message_get_reply_to(self, cr, uid, ids, default=None, context=None): """ Override to get the reply_to of the parent project. """ issues = self.browse(cr, SUPERUSER_ID, ids, context=context) project_ids = set( [issue.project_id.id for issue in issues if issue.project_id]) aliases = self.pool['project.project'].message_get_reply_to( cr, uid, list(project_ids), default=default, context=context) return dict( (issue.id, aliases.get(issue.project_id and issue.project_id.id or 0, False)) for issue in issues) def message_get_suggested_recipients(self, cr, uid, ids, context=None): recipients = super(project_issue, self).message_get_suggested_recipients( cr, uid, ids, context=context) try: for issue in self.browse(cr, uid, ids, context=context): if issue.partner_id: issue._message_add_suggested_recipient( recipients, partner=issue.partner_id, reason=_('Customer')) elif issue.email_from: issue._message_add_suggested_recipient( recipients, email=issue.email_from, reason=_('Customer Email')) except AccessError: # no read access rights -> just ignore suggested recipients because this imply modifying followers pass return recipients def email_split(self, cr, uid, ids, msg, context=None): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias issue_ids = self.browse(cr, uid, ids, context=context) aliases = [ issue.project_id.alias_name for issue in issue_ids if issue.project_id ] return filter(lambda x: x.split('@')[0] not in aliases, email_list) def message_new(self, cr, uid, msg, custom_values=None, context=None): """ Overrides mail_thread message_new that is called by the mailgateway through message_process. This override updates the document according to the email. """ if custom_values is None: custom_values = {} context = dict(context or {}, state_to='draft') defaults = { 'name': msg.get('subject') or _("No Subject"), 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'partner_id': msg.get('author_id', False), 'user_id': False, } defaults.update(custom_values) res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=defaults, context=context) email_list = self.email_split(cr, uid, [res_id], msg, context=context) partner_ids = self._find_partner_from_emails(cr, uid, [res_id], email_list, force_create=True, context=context) self.message_subscribe(cr, uid, [res_id], partner_ids, context=context) return res_id def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): """ Override to update the issue according to the email. """ email_list = self.email_split(cr, uid, ids, msg, context=context) partner_ids = self._find_partner_from_emails(cr, uid, ids, email_list, force_create=True, context=context) self.message_subscribe(cr, uid, ids, partner_ids, context=context) return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) @api.cr_uid_ids_context @api.returns('mail.message', lambda value: value.id) def message_post(self, cr, uid, thread_id, subtype=None, context=None, **kwargs): """ Overrides mail_thread message_post so that we can set the date of last action field when a new message is posted on the issue. """ if context is None: context = {} res = super(project_issue, self).message_post(cr, uid, thread_id, subtype=subtype, context=context, **kwargs) if thread_id and subtype: self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': fields.datetime.now()}, context=context) return res
class procurement_order(osv.osv): """ Procurement Orders """ _name = "procurement.order" _description = "Procurement" _order = 'priority desc, date_planned, id asc' _inherit = ['mail.thread', 'ir.needaction_mixin'] _log_create = False _columns = { 'name': fields.text('Description', required=True), 'origin': fields.char( 'Source Document', help="Reference of the document that created this Procurement.\n" "This is automatically completed by eCore."), 'company_id': fields.many2one('res.company', 'Company', required=True), # These two fields are used for shceduling 'priority': fields.selection(PROCUREMENT_PRIORITIES, 'Priority', required=True, select=True, track_visibility='onchange'), 'date_planned': fields.datetime('Scheduled Date', required=True, select=True, track_visibility='onchange'), 'group_id': fields.many2one('procurement.group', 'Procurement Group'), 'rule_id': fields.many2one( 'procurement.rule', 'Rule', track_visibility='onchange', help= "Chosen rule for the procurement resolution. Usually chosen by the system but can be manually set by the procurement manager to force an unusual behavior." ), 'product_id': fields.many2one('product.product', 'Product', required=True, states={'confirmed': [('readonly', False)]}, readonly=True), 'product_qty': fields.float( 'Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'confirmed': [('readonly', False)]}, readonly=True), 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'confirmed': [('readonly', False)]}, readonly=True), 'state': fields.selection([('cancel', 'Cancelled'), ('confirmed', 'Confirmed'), ('exception', 'Exception'), ('running', 'Running'), ('done', 'Done')], 'Status', required=True, track_visibility='onchange', copy=False), } _defaults = { 'state': 'confirmed', 'priority': '1', 'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'company_id': lambda self, cr, uid, c: self.pool.get('res.company'). _company_default_get(cr, uid, 'procurement.order', context=c) } def _needaction_domain_get(self, cr, uid, context=None): return [('state', '=', 'exception')] def unlink(self, cr, uid, ids, context=None): procurements = self.read(cr, uid, ids, ['state'], context=context) unlink_ids = [] for s in procurements: if s['state'] == 'cancel': unlink_ids.append(s['id']) else: raise UserError( _('Cannot delete Procurement Order(s) which are in %s state.' ) % s['state']) return osv.osv.unlink(self, cr, uid, unlink_ids, context=context) def create(self, cr, uid, vals, context=None): context = context or {} procurement_id = super(procurement_order, self).create(cr, uid, vals, context=context) if not context.get('procurement_autorun_defer'): self.run(cr, uid, [procurement_id], context=context) return procurement_id def do_view_procurements(self, cr, uid, ids, context=None): ''' This function returns an action that display existing procurement orders of same procurement group of given ids. ''' act_obj = self.pool.get('ir.actions.act_window') action_id = self.pool.get('ir.model.data').xmlid_to_res_id( cr, uid, 'procurement.do_view_procurements', raise_if_not_found=True) result = act_obj.read(cr, uid, [action_id], context=context)[0] group_ids = set([ proc.group_id.id for proc in self.browse(cr, uid, ids, context=context) if proc.group_id ]) result['domain'] = "[('group_id','in',[" + ','.join( map(str, list(group_ids))) + "])]" return result def onchange_product_id(self, cr, uid, ids, product_id, context=None): """ Finds UoM of changed product. @param product_id: Changed id of product. @return: Dictionary of values. """ if product_id: w = self.pool.get('product.product').browse(cr, uid, product_id, context=context) v = { 'product_uom': w.uom_id.id, } return {'value': v} return {} def get_cancel_ids(self, cr, uid, ids, context=None): return [ proc.id for proc in self.browse(cr, uid, ids, context=context) if proc.state != 'done' ] def cancel(self, cr, uid, ids, context=None): #cancel only the procurements that aren't done already to_cancel_ids = self.get_cancel_ids(cr, uid, ids, context=context) if to_cancel_ids: return self.write(cr, uid, to_cancel_ids, {'state': 'cancel'}, context=context) def reset_to_confirmed(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'confirmed'}, context=context) @api.v8 def run(self, autocommit=False): return self._model.run(self._cr, self._uid, self.ids, autocommit=False, context=self._context) @api.v7 def run(self, cr, uid, ids, autocommit=False, context=None): for procurement_id in ids: #we intentionnaly do the browse under the for loop to avoid caching all ids which would be resource greedy #and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration #will fetch all the ids again) procurement = self.browse(cr, uid, procurement_id, context=context) if procurement.state not in ("running", "done"): try: if self._assign(cr, uid, procurement, context=context): res = self._run(cr, uid, procurement, context=context or {}) if res: self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context) else: self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context) else: self.message_post( cr, uid, [procurement.id], body=_('No rule matching this procurement'), context=context) self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context) if autocommit: cr.commit() except OperationalError: if autocommit: cr.rollback() continue else: raise return True def check(self, cr, uid, ids, autocommit=False, context=None): done_ids = [] for procurement in self.browse(cr, uid, ids, context=context): try: result = self._check(cr, uid, procurement, context=context) if result: done_ids.append(procurement.id) if autocommit: cr.commit() except OperationalError: if autocommit: cr.rollback() continue else: raise if done_ids: self.write(cr, uid, done_ids, {'state': 'done'}, context=context) return done_ids # # Method to overwrite in different procurement modules # def _find_suitable_rule(self, cr, uid, procurement, context=None): '''This method returns a procurement.rule that depicts what to do with the given procurement in order to complete its needs. It returns False if no suiting rule is found. :param procurement: browse record :rtype: int or False ''' return False def _assign(self, cr, uid, procurement, context=None): '''This method check what to do with the given procurement in order to complete its needs. It returns False if no solution is found, otherwise it stores the matching rule (if any) and returns True. :param procurement: browse record :rtype: boolean ''' #if the procurement already has a rule assigned, we keep it (it has a higher priority as it may have been chosen manually) if procurement.rule_id: return True elif procurement.product_id.type not in ('service', 'digital'): rule_id = self._find_suitable_rule(cr, uid, procurement, context=context) if rule_id: self.write(cr, uid, [procurement.id], {'rule_id': rule_id}, context=context) return True return False def _run(self, cr, uid, procurement, context=None): '''This method implements the resolution of the given procurement :param procurement: browse record :returns: True if the resolution of the procurement was a success, False otherwise to set it in exception ''' return True def _check(self, cr, uid, procurement, context=None): '''Returns True if the given procurement is fulfilled, False otherwise :param procurement: browse record :rtype: boolean ''' return False # # Scheduler # def run_scheduler(self, cr, uid, use_new_cursor=False, company_id=False, context=None): ''' Call the scheduler to check the procurement order. This is intented to be done for all existing companies at the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues. @param self: The object pointer @param cr: The current row, from the database cursor, @param uid: The current user ID for security checks @param ids: List of selected IDs @param use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement. This is appropriate for batch jobs only. @param context: A standard dictionary for contextual values @return: Dictionary of values ''' if context is None: context = {} try: if use_new_cursor: cr = ecore.registry(cr.dbname).cursor() # Run confirmed procurements dom = [('state', '=', 'confirmed')] if company_id: dom += [('company_id', '=', company_id)] prev_ids = [] while True: ids = self.search(cr, SUPERUSER_ID, dom, context=context) if not ids or prev_ids == ids: break else: prev_ids = ids self.run(cr, SUPERUSER_ID, ids, autocommit=use_new_cursor, context=context) if use_new_cursor: cr.commit() # Check if running procurements are done offset = 0 dom = [('state', '=', 'running')] if company_id: dom += [('company_id', '=', company_id)] prev_ids = [] while True: ids = self.search(cr, SUPERUSER_ID, dom, offset=offset, context=context) if not ids or prev_ids == ids: break else: prev_ids = ids self.check(cr, SUPERUSER_ID, ids, autocommit=use_new_cursor, context=context) if use_new_cursor: cr.commit() finally: if use_new_cursor: try: cr.close() except Exception: pass return {}
class hr_department(osv.osv): _name = "hr.department" _description = "HR Department" _inherit = ['mail.thread', 'ir.needaction_mixin'] def _dept_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None): res = self.name_get(cr, uid, ids, context=context) return dict(res) _columns = { 'name': fields.char('Department Name', required=True), 'complete_name': fields.function(_dept_name_get_fnc, type="char", string='Name'), 'company_id': fields.many2one('res.company', 'Company', select=True, required=False), 'parent_id': fields.many2one('hr.department', 'Parent Department', select=True), 'child_ids': fields.one2many('hr.department', 'parent_id', 'Child Departments'), 'manager_id': fields.many2one('hr.employee', 'Manager', track_visibility='onchange'), 'member_ids': fields.one2many('hr.employee', 'department_id', 'Members', readonly=True), 'jobs_ids': fields.one2many('hr.job', 'department_id', 'Jobs'), 'note': fields.text('Note'), 'color': fields.integer('Color Index'), } _defaults = { 'company_id': lambda self, cr, uid, c: self.pool.get('res.company'). _company_default_get(cr, uid, 'hr.department', context=c), } _constraints = [(osv.osv._check_recursion, _('Error! You cannot create recursive departments.'), ['parent_id'])] def name_get(self, cr, uid, ids, context=None): if not ids: return [] if isinstance(ids, (int, long)): ids = [ids] if context is None: context = {} reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context) res = [] for record in reads: name = record['name'] if record['parent_id']: name = record['parent_id'][1] + ' / ' + name res.append((record['id'], name)) return res def create(self, cr, uid, vals, context=None): if context is None: context = {} context['mail_create_nosubscribe'] = True # TDE note: auto-subscription of manager done by hand, because currently # the tracking allows to track+subscribe fields linked to a res.user record # An update of the limited behavior should come, but not currently done. manager_id = vals.get("manager_id") new_id = super(hr_department, self).create(cr, uid, vals, context=context) if manager_id: employee = self.pool.get('hr.employee').browse(cr, uid, manager_id, context=context) if employee.user_id: self.message_subscribe_users(cr, uid, [new_id], user_ids=[employee.user_id.id], context=context) return new_id def write(self, cr, uid, ids, vals, context=None): # TDE note: auto-subscription of manager done by hand, because currently # the tracking allows to track+subscribe fields linked to a res.user record # An update of the limited behavior should come, but not currently done. if isinstance(ids, (int, long)): ids = [ids] employee_ids = [] if 'manager_id' in vals: manager_id = vals.get("manager_id") if manager_id: employee = self.pool['hr.employee'].browse(cr, uid, manager_id, context=context) if employee.user_id: self.message_subscribe_users( cr, uid, ids, user_ids=[employee.user_id.id], context=context) for department in self.browse(cr, uid, ids, context=context): employee_ids += self.pool['hr.employee'].search( cr, uid, [('id', '!=', manager_id), ('department_id', '=', department.id), ('parent_id', '=', department.manager_id.id)], context=context) self.pool['hr.employee'].write(cr, uid, employee_ids, {'parent_id': manager_id}, context=context) return super(hr_department, self).write(cr, uid, ids, vals, context=context)
def function_fn_write(model, cr, uid, id, field_name, field_value, fnct_inv_arg, context): """ just so CreatorCase.export can be used """ pass models = [ ('boolean', fields.boolean()), ('integer', fields.integer()), ('float', fields.float()), ('decimal', fields.float(digits=(16, 3))), ('string.bounded', fields.char('unknown', size=16)), ('string.required', fields.char('unknown', size=None, required=True)), ('string', fields.char('unknown', size=None)), ('date', fields.date()), ('datetime', fields.datetime()), ('text', fields.text()), ('selection', fields.selection([(1, "Foo"), (2, "Bar"), (3, "Qux"), (4, '')])), # here use size=-1 to store the values as integers instead of strings ('selection.function', fields.selection(selection_fn, size=-1)), # just relate to an integer ('many2one', fields.many2one('export.integer')), ('one2many', fields.one2many('export.one2many.child', 'parent_id')), ('many2many', fields.many2many('export.many2many.other')), ('function', fields.function(function_fn, fnct_inv=function_fn_write, type="integer")), # related: specialization of fields.function, should work the same way # TODO: reference ] for name, field in models: class NewModel(orm.Model): _name = 'export.%s' % name
class ir_filters(osv.osv): _name = 'ir.filters' _description = 'Filters' def _list_all_models(self, cr, uid, context=None): cr.execute("SELECT model, name FROM ir_model ORDER BY name") return cr.fetchall() def copy(self, cr, uid, id, default=None, context=None): name = self.read(cr, uid, [id], ['name'])[0]['name'] default.update({'name':_('%s (copy)') % name}) return super(ir_filters, self).copy(cr, uid, id, default, context) def _get_action_domain(self, cr, uid, action_id=None): """Return a domain component for matching filters that are visible in the same context (menu/view) as the given action.""" if action_id: # filters specific to this menu + global ones return [('action_id', 'in' , [action_id, False])] # only global ones return [('action_id', '=', False)] def get_filters(self, cr, uid, model, action_id=None, context=None): """Obtain the list of filters available for the user on the given model. :param action_id: optional ID of action to restrict filters to this action plus global filters. If missing only global filters are returned. The action does not have to correspond to the model, it may only be a contextual action. :return: list of :meth:`~osv.read`-like dicts containing the ``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple), ``action_id`` (m2o tuple) and ``context`` of the matching ``ir.filters``. """ # available filters: private filters (user_id=uid) and public filters (uid=NULL), # and filters for the action (action_id=action_id) or global (action_id=NULL) context = self.pool['res.users'].context_get(cr, uid) action_domain = self._get_action_domain(cr, uid, action_id) filter_ids = self.search(cr, uid, action_domain + [('model_id','=',model),('user_id','in',[uid, False])]) my_filters = self.read(cr, uid, filter_ids, ['name', 'is_default', 'domain', 'context', 'user_id', 'sort'], context=context) return my_filters def _check_global_default(self, cr, uid, vals, matching_filters, context=None): """ _check_global_default(cursor, UID, dict, list(dict), dict) -> None Checks if there is a global default for the model_id requested. If there is, and the default is different than the record being written (-> we're not updating the current global default), raise an error to avoid users unknowingly overwriting existing global defaults (they have to explicitly remove the current default before setting a new one) This method should only be called if ``vals`` is trying to set ``is_default`` :raises ecore.exceptions.Warning: if there is an existing default and we're not updating it """ action_domain = self._get_action_domain(cr, uid, vals.get('action_id')) existing_default = self.search(cr, uid, action_domain + [ ('model_id', '=', vals['model_id']), ('user_id', '=', False), ('is_default', '=', True)], context=context) if not existing_default: return if matching_filters and \ (matching_filters[0]['id'] == existing_default[0]): return raise exceptions.Warning( _("There is already a shared filter set as default for %(model)s, delete or change it before setting a new default") % { 'model': vals['model_id'] }) def create_or_replace(self, cr, uid, vals, context=None): lower_name = vals['name'].lower() action_id = vals.get('action_id') current_filters = self.get_filters(cr, uid, vals['model_id'], action_id) matching_filters = [f for f in current_filters if f['name'].lower() == lower_name # next line looks for matching user_ids (specific or global), i.e. # f.user_id is False and vals.user_id is False or missing, # or f.user_id.id == vals.user_id if (f['user_id'] and f['user_id'][0]) == vals.get('user_id', False)] if vals.get('is_default'): if vals.get('user_id'): # Setting new default: any other default that belongs to the user # should be turned off action_domain = self._get_action_domain(cr, uid, action_id) act_ids = self.search(cr, uid, action_domain + [ ('model_id', '=', vals['model_id']), ('user_id', '=', vals['user_id']), ('is_default', '=', True), ], context=context) if act_ids: self.write(cr, uid, act_ids, {'is_default': False}, context=context) else: self._check_global_default( cr, uid, vals, matching_filters, context=None) # When a filter exists for the same (name, model, user) triple, we simply # replace its definition (considering action_id irrelevant here) if matching_filters: self.write(cr, uid, matching_filters[0]['id'], vals, context) return matching_filters[0]['id'] return self.create(cr, uid, vals, context) _sql_constraints = [ # Partial constraint, complemented by unique index (see below) # Still useful to keep because it provides a proper error message when a violation # occurs, as it shares the same prefix as the unique index. ('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'), ] def _auto_init(self, cr, context=None): result = super(ir_filters, self)._auto_init(cr, context) # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint) cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'") if not cr.fetchone(): cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters (lower(name), model_id, COALESCE(user_id,-1), COALESCE(action_id,-1))""") return result _columns = { 'name': fields.char('Filter Name', translate=True, required=True), 'user_id': fields.many2one('res.users', 'User', ondelete='cascade', help="The user this filter is private to. When left empty the filter is public " "and available to all users."), 'domain': fields.text('Domain', required=True), 'context': fields.text('Context', required=True), 'sort': fields.text('Sort', required=True), 'model_id': fields.selection(_list_all_models, 'Model', required=True), 'is_default': fields.boolean('Default filter'), 'action_id': fields.many2one('ir.actions.actions', 'Action', ondelete='cascade', help="The menu action this filter applies to. " "When left empty the filter applies to all menus " "for this model."), 'active': fields.boolean('Active') } _defaults = { 'domain': '[]', 'context':'{}', 'sort': '[]', 'user_id': lambda self,cr,uid,context=None: uid, 'is_default': False, 'active': True } _order = 'model_id, name, id desc'
class payroll_advice(osv.osv): ''' Bank Advice ''' _name = 'hr.payroll.advice' _description = 'Bank Advice' _columns = { 'name': fields.char( 'Name', readonly=True, required=True, states={'draft': [('readonly', False)]}, ), 'note': fields.text('Description'), 'date': fields.date('Date', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Advice Date is used to search Payslips"), 'state': fields.selection([ ('draft', 'Draft'), ('confirm', 'Confirmed'), ('cancel', 'Cancelled'), ], 'Status', select=True, readonly=True), 'number': fields.char('Reference', readonly=True), 'line_ids': fields.one2many('hr.payroll.advice.line', 'advice_id', 'Employee Salary', states={'draft': [('readonly', False)]}, readonly=True, copy=True), 'chaque_nos': fields.char('Cheque Numbers'), 'neft': fields.boolean( 'NEFT Transaction', help="Check this box if your company use online transfer for salary" ), 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'bank_id': fields.many2one( 'res.bank', 'Bank', readonly=True, states={'draft': [('readonly', False)] }, help="Select the Bank from which the salary is going to be paid"), 'batch_id': fields.many2one('hr.payslip.run', 'Batch', readonly=True) } _defaults = { 'date': lambda * a: time.strftime('%Y-%m-%d'), 'state': lambda * a: 'draft', 'company_id': lambda self, cr, uid, context: \ self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id, 'note': "Please make the payroll transfer from above account number to the below mentioned account numbers towards employee salaries:" } def compute_advice(self, cr, uid, ids, context=None): """ Advice - Create Advice lines in Payment Advice and compute Advice lines. @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, @param ids: List of Advice’s IDs @return: Advice lines @param context: A standard dictionary for contextual values """ payslip_pool = self.pool.get('hr.payslip') advice_line_pool = self.pool.get('hr.payroll.advice.line') payslip_line_pool = self.pool.get('hr.payslip.line') for advice in self.browse(cr, uid, ids, context=context): old_line_ids = advice_line_pool.search( cr, uid, [('advice_id', '=', advice.id)], context=context) if old_line_ids: advice_line_pool.unlink(cr, uid, old_line_ids, context=context) slip_ids = payslip_pool.search(cr, uid, [('date_from', '<=', advice.date), ('date_to', '>=', advice.date), ('state', '=', 'done')], context=context) for slip in payslip_pool.browse(cr, uid, slip_ids, context=context): if not slip.employee_id.bank_account_id and not slip.employee_id.bank_account_id.acc_number: raise UserError( _('Please define bank account for the %s employee') % (slip.employee_id.name, )) line_ids = payslip_line_pool.search(cr, uid, [('slip_id', '=', slip.id), ('code', '=', 'NET')], context=context) if line_ids: line = payslip_line_pool.browse(cr, uid, line_ids, context=context)[0] advice_line = { 'advice_id': advice.id, 'name': slip.employee_id.bank_account_id.acc_number, 'employee_id': slip.employee_id.id, 'bysal': line.total } advice_line_pool.create(cr, uid, advice_line, context=context) payslip_pool.write(cr, uid, slip_ids, {'advice_id': advice.id}, context=context) return True def confirm_sheet(self, cr, uid, ids, context=None): """ confirm Advice - confirmed Advice after computing Advice Lines.. @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, @param ids: List of confirm Advice’s IDs @return: confirmed Advice lines and set sequence of Advice. @param context: A standard dictionary for contextual values """ seq_obj = self.pool.get('ir.sequence') for advice in self.browse(cr, uid, ids, context=context): if not advice.line_ids: raise UserError( _('You can not confirm Payment advice without advice lines.' )) advice_date = datetime.strptime(advice.date, DATETIME_FORMAT) advice_year = advice_date.strftime( '%m') + '-' + advice_date.strftime('%Y') number = seq_obj.next_by_code(cr, uid, 'payment.advice') sequence_num = 'PAY' + '/' + advice_year + '/' + number self.write(cr, uid, [advice.id], { 'number': sequence_num, 'state': 'confirm' }, context=context) return True def set_to_draft(self, cr, uid, ids, context=None): """Resets Advice as draft. """ return self.write(cr, uid, ids, {'state': 'draft'}, context=context) def cancel_sheet(self, cr, uid, ids, context=None): """Marks Advice as cancelled. """ return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) def onchange_company_id(self, cr, uid, ids, company_id=False, context=None): res = {} if company_id: company = self.pool.get('res.company').browse(cr, uid, [company_id], context=context)[0] if company.partner_id.bank_ids: res.update( {'bank_id': company.partner_id.bank_ids[0].bank_id.id}) return {'value': res}
class base_module_upgrade(osv.osv_memory): """ Module Upgrade """ _name = "base.module.upgrade" _description = "Module Upgrade" _columns = { 'module_info': fields.text('Apps to Update', readonly=True), } def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=False) if view_type != 'form': return res context = {} if context is None else context record_id = context and context.get('active_id', False) or False active_model = context.get('active_model') if (not record_id) or (not active_model): return res ids = self.get_module_list(cr, uid, context=context) if not ids: res['arch'] = '''<form string="Upgrade Completed" version="7.0"> <separator string="Upgrade Completed" colspan="4"/> <footer> <button name="config" string="Start Configuration" type="object" class="btn-primary"/> <button special="cancel" string="Close" class="btn-default"/> </footer> </form>''' return res def get_module_list(self, cr, uid, context=None): mod_obj = self.pool.get('ir.module.module') ids = mod_obj.search( cr, uid, [('state', 'in', ['to upgrade', 'to remove', 'to install'])]) return ids def default_get(self, cr, uid, fields, context=None): mod_obj = self.pool.get('ir.module.module') ids = self.get_module_list(cr, uid, context=context) res = mod_obj.read(cr, uid, ids, ['name', 'state'], context) return { 'module_info': '\n'.join(map(lambda x: x['name'] + ' : ' + x['state'], res)) } def upgrade_module_cancel(self, cr, uid, ids, context=None): mod_obj = self.pool.get('ir.module.module') to_installed_ids = mod_obj.search( cr, uid, [('state', 'in', ['to upgrade', 'to remove'])]) if to_installed_ids: mod_obj.write(cr, uid, to_installed_ids, {'state': 'installed'}, context=context) to_uninstalled_ids = mod_obj.search(cr, uid, [('state', '=', 'to install')]) if to_uninstalled_ids: mod_obj.write(cr, uid, to_uninstalled_ids, {'state': 'uninstalled'}, context=context) return {'type': 'ir.actions.act_window_close'} def upgrade_module(self, cr, uid, ids, context=None): ir_module = self.pool.get('ir.module.module') # install/upgrade: double-check preconditions ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])]) if ids: cr.execute( """SELECT d.name FROM ir_module_module m JOIN ir_module_module_dependency d ON (m.id = d.module_id) LEFT JOIN ir_module_module m2 ON (d.name = m2.name) WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""", (tuple(ids), ('uninstalled', ))) unmet_packages = [x[0] for x in cr.fetchall()] if unmet_packages: raise UserError( _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages))) ir_module.download(cr, uid, ids, context=context) cr.commit() # save before re-creating cursor below ecore.api.Environment.reset() ecore.modules.registry.RegistryManager.new(cr.dbname, update_module=True) return {'type': 'ir.actions.act_window_close'} def config(self, cr, uid, ids, context=None): return self.pool.get('res.config').next(cr, uid, [], context=context)
class res_company(osv.osv): _name = "res.company" _description = 'Companies' _order = 'name' def _get_address_data(self, cr, uid, ids, field_names, arg, context=None): """ Read the 'address' functional fields. """ result = {} part_obj = self.pool.get('res.partner') for company in self.browse(cr, uid, ids, context=context): result[company.id] = {}.fromkeys(field_names, False) if company.partner_id: address_data = part_obj.address_get(cr, ecore.SUPERUSER_ID, [company.partner_id.id], adr_pref=['contact']) if address_data['contact']: address = part_obj.read(cr, ecore.SUPERUSER_ID, [address_data['contact']], field_names, context=context)[0] for field in field_names: result[company.id][field] = address[field] or False return result def _set_address_data(self, cr, uid, company_id, name, value, arg, context=None): """ Write the 'address' functional fields. """ company = self.browse(cr, uid, company_id, context=context) if company.partner_id: part_obj = self.pool.get('res.partner') address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['contact']) address = address_data['contact'] if address: part_obj.write(cr, uid, [address], {name: value or False}, context=context) else: part_obj.create(cr, uid, { name: value or False, 'parent_id': company.partner_id.id }, context=context) return True def _get_logo_web(self, cr, uid, ids, _field_name, _args, context=None): result = dict.fromkeys(ids, False) for record in self.browse(cr, uid, ids, context=context): size = (180, None) result[record.id] = image_resize_image(record.partner_id.image, size) return result def _get_companies_from_partner(self, cr, uid, ids, context=None): return self.pool['res.company'].search(cr, uid, [('partner_id', 'in', ids)], context=context) _columns = { 'name': fields.related('partner_id', 'name', string='Company Name', size=128, required=True, store=True, type='char'), 'parent_id': fields.many2one('res.company', 'Parent Company', select=True), 'child_ids': fields.one2many('res.company', 'parent_id', 'Child Companies'), 'partner_id': fields.many2one('res.partner', 'Partner', required=True), 'rml_header': fields.text('RML Header', required=True), 'rml_header1': fields.char( 'Company Tagline', help= "Appears by default on the top right corner of your printed documents (report header)." ), 'rml_header2': fields.text('RML Internal Header', required=True), 'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True), 'rml_footer': fields.text( 'Report Footer', help="Footer text displayed at the bottom of all reports."), 'rml_footer_readonly': fields.related('rml_footer', type='text', string='Report Footer', readonly=True), 'custom_footer': fields.boolean( 'Custom Footer', help= "Check this to define the report footer manually. Otherwise it will be filled in automatically." ), 'font': fields.many2one( 'res.font', string="Font", domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))], help= "Set the font into the report header, it will be used as default font in the RML reports of the user company" ), 'logo': fields.related('partner_id', 'image', string="Logo", type="binary"), # logo_web: do not store in attachments, since the image is retrieved in SQL for # performance reasons (see addons/web/controllers/main.py, Binary.company_logo) 'logo_web': fields.function(_get_logo_web, string="Logo Web", type="binary", store={ 'res.company': (lambda s, c, u, i, x: i, ['partner_id'], 10), 'res.partner': (_get_companies_from_partner, ['image'], 10), }), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), 'user_ids': fields.many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', 'Accepted Users'), 'account_no': fields.char('Account No.'), 'street': fields.function(_get_address_data, fnct_inv=_set_address_data, size=128, type='char', string="Street", multi='address'), 'street2': fields.function(_get_address_data, fnct_inv=_set_address_data, size=128, type='char', string="Street2", multi='address'), 'zip': fields.function(_get_address_data, fnct_inv=_set_address_data, size=24, type='char', string="Zip", multi='address'), 'city': fields.function(_get_address_data, fnct_inv=_set_address_data, size=24, type='char', string="City", multi='address'), 'state_id': fields.function(_get_address_data, fnct_inv=_set_address_data, type='many2one', relation='res.country.state', string="Fed. State", multi='address'), 'country_id': fields.function(_get_address_data, fnct_inv=_set_address_data, type='many2one', relation='res.country', string="Country", multi='address'), 'email': fields.related('partner_id', 'email', size=64, type='char', string="Email", store=True), 'phone': fields.related('partner_id', 'phone', size=64, type='char', string="Phone", store=True), 'fax': fields.function(_get_address_data, fnct_inv=_set_address_data, size=64, type='char', string="Fax", multi='address'), 'website': fields.related('partner_id', 'website', string="Website", type="char", size=64), 'vat': fields.related('partner_id', 'vat', string="Tax ID", type="char", size=32), 'company_registry': fields.char('Company Registry', size=64), 'rml_paper_format': fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')], "Paper Format", required=True, oldname='paper_format'), } _sql_constraints = [('name_uniq', 'unique (name)', 'The company name must be unique !')] @api.onchange('custom_footer', 'phone', 'fax', 'email', 'website', 'vat', 'company_registry') def onchange_footer(self): if not self.custom_footer: # first line (notice that missing elements are filtered out before the join) res = ' | '.join( filter(bool, [ self.phone and '%s: %s' % (_('Phone'), self.phone), self.fax and '%s: %s' % (_('Fax'), self.fax), self.email and '%s: %s' % (_('Email'), self.email), self.website and '%s: %s' % (_('Website'), self.website), self.vat and '%s: %s' % (_('TIN'), self.vat), self.company_registry and '%s: %s' % (_('Reg'), self.company_registry), ])) self.rml_footer_readonly = res self.rml_footer = res def onchange_state(self, cr, uid, ids, state_id, context=None): if state_id: return { 'value': { 'country_id': self.pool.get('res.country.state').browse( cr, uid, state_id, context).country_id.id } } return {} def onchange_font_name(self, cr, uid, ids, font, rml_header, rml_header2, rml_header3, context=None): """ To change default header style of all <para> and drawstring. """ def _change_header(header, font): """ Replace default fontname use in header and setfont tag """ default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"' % font, header) return re.sub('(<setFont.?name.?=.?)(".*?")(.)', '\g<1>"%s"\g<3>' % font, default_para) if not font: return True fontname = self.pool.get('res.font').browse(cr, uid, font, context=context).name return { 'value': { 'rml_header': _change_header(rml_header, fontname), 'rml_header2': _change_header(rml_header2, fontname), 'rml_header3': _change_header(rml_header3, fontname) } } def on_change_country(self, cr, uid, ids, country_id, context=None): res = {'domain': {'state_id': []}} currency_id = self._get_euro(cr, uid, context=context) if country_id: currency_id = self.pool.get('res.country').browse( cr, uid, country_id, context=context).currency_id.id res['domain'] = {'state_id': [('country_id', '=', country_id)]} res['value'] = {'currency_id': currency_id} return res def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100): context = dict(context or {}) if context.pop('user_preference', None): # We browse as superuser. Otherwise, the user would be able to # select only the currently visible companies (according to rules, # which are probably to allow to see the child companies) even if # she belongs to some other companies. user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context) cmp_ids = list( set([user.company_id.id] + [cmp.id for cmp in user.company_ids])) uid = SUPERUSER_ID args = (args or []) + [('id', 'in', cmp_ids)] return super(res_company, self).name_search(cr, uid, name=name, args=args, operator=operator, context=context, limit=limit) @api.returns('self') def _company_default_get(self, cr, uid, object=False, field=False, context=None): """ Returns the default company (the user's company) The 'object' and 'field' arguments are ignored but left here for backward compatibility and potential override. """ return self.pool['res.users']._get_company(cr, uid, context=context) @tools.ormcache('uid', 'company') def _get_company_children(self, cr, uid=None, company=None): if not company: return [] ids = self.search(cr, uid, [('parent_id', 'child_of', [company])]) return ids def _get_partner_hierarchy(self, cr, uid, company_id, context=None): if company_id: parent_id = self.browse(cr, uid, company_id)['parent_id'] if parent_id: return self._get_partner_hierarchy(cr, uid, parent_id.id, context) else: return self._get_partner_descendance(cr, uid, company_id, [], context) return [] def _get_partner_descendance(self, cr, uid, company_id, descendance, context=None): descendance.append(self.browse(cr, uid, company_id).partner_id.id) for child_id in self._get_company_children(cr, uid, company_id): if child_id != company_id: descendance = self._get_partner_descendance( cr, uid, child_id, descendance) return descendance # # This function restart the cache on the _get_company_children method # def cache_restart(self, cr): self._get_company_children.clear_cache(self) def create(self, cr, uid, vals, context=None): if not vals.get('name', False) or vals.get('partner_id', False): self.cache_restart(cr) return super(res_company, self).create(cr, uid, vals, context=context) obj_partner = self.pool.get('res.partner') partner_id = obj_partner.create(cr, uid, { 'name': vals['name'], 'is_company': True, 'image': vals.get('logo', False) }, context=context) vals.update({'partner_id': partner_id}) self.cache_restart(cr) company_id = super(res_company, self).create(cr, uid, vals, context=context) obj_partner.write(cr, uid, [partner_id], {'company_id': company_id}, context=context) return company_id def write(self, cr, uid, ids, values, context=None): self.cache_restart(cr) return super(res_company, self).write(cr, uid, ids, values, context=context) def _get_euro(self, cr, uid, context=None): rate_obj = self.pool.get('res.currency.rate') rate_id = rate_obj.search(cr, uid, [('rate', '=', 1)], context=context) return rate_id and rate_obj.browse( cr, uid, rate_id[0], context=context).currency_id.id or False def _get_logo(self, cr, uid, ids): return open( os.path.join(tools.config['root_path'], 'addons', 'base', 'res', 'res_company_logo.png'), 'rb').read().encode('base64') def _get_font(self, cr, uid, ids): font_obj = self.pool.get('res.font') res = font_obj.search(cr, uid, [('family', '=', 'Helvetica'), ('mode', '=', 'all')], limit=1) return res and res[0] or False _header = """ <header> <pageTemplate> <frame id="first" x1="28.0" y1="28.0" width="%s" height="%s"/> <stylesheet> <!-- Set here the default font to use for all <para> tags --> <paraStyle name='Normal' fontName="DejaVuSans"/> </stylesheet> <pageGraphics> <fill color="black"/> <stroke color="black"/> <setFont name="DejaVuSans" size="8"/> <drawString x="%s" y="%s"> [[ formatLang(time.strftime("%%Y-%%m-%%d"), date=True) ]] [[ time.strftime("%%H:%%M") ]]</drawString> <setFont name="DejaVuSans-Bold" size="10"/> <drawCentredString x="%s" y="%s">[[ company.partner_id.name ]]</drawCentredString> <stroke color="#000000"/> <lines>%s</lines> <!-- Set here the default font to use for all <drawString> tags --> <!-- don't forget to change the 2 other occurence of <setFont> above if needed --> <setFont name="DejaVuSans" size="8"/> </pageGraphics> </pageTemplate> </header>""" _header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm", "1.0cm 28.1cm 20.1cm 28.1cm") _header3 = _header % (786, 525, 25, 555, 440, 555, "25 550 818 550") def _get_header(self, cr, uid, ids): try: header_file = tools.file_open( os.path.join('base', 'report', 'corporate_rml_header.rml')) try: return header_file.read() finally: header_file.close() except: return self._header_a4 _header_main = """ <header> <pageTemplate> <frame id="first" x1="1.3cm" y1="3.0cm" height="%s" width="19.0cm"/> <stylesheet> <!-- Set here the default font to use for all <para> tags --> <paraStyle name='Normal' fontName="DejaVuSans"/> <paraStyle name="main_footer" fontSize="8.0" alignment="CENTER"/> <paraStyle name="main_header" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/> </stylesheet> <pageGraphics> <!-- Set here the default font to use for all <drawString> tags --> <setFont name="DejaVuSans" size="8"/> <!-- You Logo - Change X,Y,Width and Height --> <image x="1.3cm" y="%s" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image> <fill color="black"/> <stroke color="black"/> <!-- page header --> <lines>1.3cm %s 20cm %s</lines> <drawRightString x="20cm" y="%s">[[ company.rml_header1 ]]</drawRightString> <drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString> <place x="1.3cm" y="%s" height="1.8cm" width="15.0cm"> <para style="main_header">[[ display_address(company.partner_id) or '' ]]</para> </place> <drawString x="1.3cm" y="%s">Phone:</drawString> <drawRightString x="7cm" y="%s">[[ company.partner_id.phone or '' ]]</drawRightString> <drawString x="1.3cm" y="%s">Mail:</drawString> <drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString> <lines>1.3cm %s 7cm %s</lines> <!-- left margin --> <rotate degrees="90"/> <fill color="grey"/> <drawString x="2.65cm" y="-0.4cm">generated by eCore.com</drawString> <fill color="black"/> <rotate degrees="-90"/> <!--page bottom--> <lines>1.2cm 2.65cm 19.9cm 2.65cm</lines> <place x="1.3cm" y="0cm" height="2.55cm" width="19.0cm"> <para style="main_footer">[[ company.rml_footer ]]</para> <para style="main_footer">Contact : [[ user.name ]] - Page: <pageNumber/></para> </place> </pageGraphics> </pageTemplate> </header>""" _header_a4 = _header_main % ( '21.7cm', '27.7cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm', '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm') _header_letter = _header_main % ( '20cm', '26.0cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm', '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm') def onchange_rml_paper_format(self, cr, uid, ids, rml_paper_format, context=None): if rml_paper_format == 'us_letter': return {'value': {'rml_header': self._header_letter}} return {'value': {'rml_header': self._header_a4}} def act_discover_fonts(self, cr, uid, ids, context=None): return self.pool.get("res.font").font_scan(cr, uid, context=context) _defaults = { 'currency_id': _get_euro, 'rml_paper_format': 'a4', 'rml_header': _get_header, 'rml_header2': _header2, 'rml_header3': _header3, 'logo': _get_logo, 'font': _get_font, } _constraints = [ (osv.osv._check_recursion, 'Error! You can not create recursive companies.', ['parent_id']) ]
class gamification_badge(osv.Model): """Badge object that users can send and receive""" CAN_GRANT = 1 NOBODY_CAN_GRANT = 2 USER_NOT_VIP = 3 BADGE_REQUIRED = 4 TOO_MANY = 5 _name = 'gamification.badge' _description = 'Gamification badge' _inherit = ['mail.thread'] def _get_owners_info(self, cr, uid, ids, name, args, context=None): """Return: the list of unique res.users ids having received this badge the total number of time this badge was granted the total number of users this badge was granted to """ result = dict((res_id, {'stat_count': 0, 'stat_count_distinct': 0, 'unique_owner_ids': []}) for res_id in ids) cr.execute(""" SELECT badge_id, count(user_id) as stat_count, count(distinct(user_id)) as stat_count_distinct, array_agg(distinct(user_id)) as unique_owner_ids FROM gamification_badge_user WHERE badge_id in %s GROUP BY badge_id """, (tuple(ids),)) for (badge_id, stat_count, stat_count_distinct, unique_owner_ids) in cr.fetchall(): result[badge_id] = { 'stat_count': stat_count, 'stat_count_distinct': stat_count_distinct, 'unique_owner_ids': unique_owner_ids, } return result def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None): """Return stats related to badge users""" result = dict.fromkeys(ids, False) badge_user_obj = self.pool.get('gamification.badge.user') first_month_day = date.today().replace(day=1).strftime(DF) for bid in ids: result[bid] = { 'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True), 'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True), 'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True), 'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True) } return result def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None): """Computes the number of badges remaining the user can send 0 if not allowed or no remaining integer if limited sending -1 if infinite (should not be displayed) """ result = dict.fromkeys(ids, False) for badge in self.browse(cr, uid, ids, context=context): if self._can_grant_badge(cr, uid, badge.id, context) != 1: # if the user cannot grant this badge at all, result is 0 result[badge.id] = 0 elif not badge.rule_max: # if there is no limitation, -1 is returned which means 'infinite' result[badge.id] = -1 else: result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending return result _columns = { 'name': fields.char('Badge', required=True, translate=True), 'description': fields.text('Description', translate=True), 'image': fields.binary("Image", attachment=True, help="This field holds the image used for the badge, limited to 256x256"), 'rule_auth': fields.selection([ ('everyone', 'Everyone'), ('users', 'A selected list of users'), ('having', 'People having some badges'), ('nobody', 'No one, assigned through challenges'), ], string="Allowance to Grant", help="Who can grant this badge", required=True), 'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users', string='Authorized Users', help="Only these people can give this badge"), 'rule_auth_badge_ids': fields.many2many('gamification.badge', 'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id', string='Required Badges', help="Only the people having these badges can give this badge"), 'rule_max': fields.boolean('Monthly Limited Sending', help="Check to set a monthly limit per person of sending this badge"), 'rule_max_number': fields.integer('Limitation Number', help="The maximum number of time this badge can be sent per month per person."), 'stat_my_monthly_sending': fields.function(_get_badge_user_stats, type="integer", string='My Monthly Sending Total', multi='badge_users', help="The number of time the current user has sent this badge this month."), 'remaining_sending': fields.function(_remaining_sending_calc, type='integer', string='Remaining Sending Allowed', help="If a maxium is set"), 'challenge_ids': fields.one2many('gamification.challenge', 'reward_id', string="Reward of Challenges"), 'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel', string='Rewarded by', help="The users that have succeeded theses goals will receive automatically the badge."), 'owner_ids': fields.one2many('gamification.badge.user', 'badge_id', string='Owners', help='The list of instances of this badge granted to users'), 'active': fields.boolean('Active'), 'unique_owner_ids': fields.function(_get_owners_info, string='Unique Owners', help="The list of unique users having received this badge.", multi='unique_users', type="many2many", relation="res.users"), 'stat_count': fields.function(_get_owners_info, string='Total', type="integer", multi='unique_users', help="The number of time this badge has been received."), 'stat_count_distinct': fields.function(_get_owners_info, type="integer", string='Number of users', multi='unique_users', help="The number of time this badge has been received by unique users."), 'stat_this_month': fields.function(_get_badge_user_stats, type="integer", string='Monthly total', multi='badge_users', help="The number of time this badge has been received this month."), 'stat_my': fields.function(_get_badge_user_stats, string='My Total', type="integer", multi='badge_users', help="The number of time the current user has received this badge."), 'stat_my_this_month': fields.function(_get_badge_user_stats, type="integer", string='My Monthly Total', multi='badge_users', help="The number of time the current user has received this badge this month."), } _defaults = { 'rule_auth': 'everyone', 'active': True, } def check_granting(self, cr, uid, badge_id, context=None): """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception if not Do not check for SUPERUSER_ID """ status_code = self._can_grant_badge(cr, uid, badge_id, context=context) if status_code == self.CAN_GRANT: return True elif status_code == self.NOBODY_CAN_GRANT: raise UserError(_('This badge can not be sent by users.')) elif status_code == self.USER_NOT_VIP: raise UserError(_('You are not in the user allowed list.')) elif status_code == self.BADGE_REQUIRED: raise UserError(_('You do not have the required badges.')) elif status_code == self.TOO_MANY: raise UserError(_('You have already sent this badge too many time this month.')) else: _logger.exception("Unknown badge status code: %d" % int(status_code)) return False def _can_grant_badge(self, cr, uid, badge_id, context=None): """Check if a user can grant a badge to another user :param uid: the id of the res.users trying to send the badge :param badge_id: the granted badge id :return: integer representing the permission. """ if uid == SUPERUSER_ID: return self.CAN_GRANT badge = self.browse(cr, uid, badge_id, context=context) if badge.rule_auth == 'nobody': return self.NOBODY_CAN_GRANT elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]: return self.USER_NOT_VIP elif badge.rule_auth == 'having': all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context) for required_badge in badge.rule_auth_badge_ids: if required_badge.id not in all_user_badges: return self.BADGE_REQUIRED if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number: return self.TOO_MANY # badge.rule_auth == 'everyone' -> no check return self.CAN_GRANT def check_progress(self, cr, uid, context=None): try: model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden') except ValueError: return True badge_user_obj = self.pool.get('gamification.badge.user') if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context): values = { 'user_id': uid, 'badge_id': res_id, } badge_user_obj.create(cr, SUPERUSER_ID, values, context=context) return True
class mrp_repair(osv.osv): _name = 'mrp.repair' _inherit = 'mail.thread' _description = 'Repair Order' def _amount_untaxed(self, cr, uid, ids, field_name, arg, context=None): """ Calculates untaxed amount. @param self: The object pointer @param cr: The current row, from the database cursor, @param uid: The current user ID for security checks @param ids: List of selected IDs @param field_name: Name of field. @param arg: Argument @param context: A standard dictionary for contextual values @return: Dictionary of values. """ res = {} cur_obj = self.pool.get('res.currency') for repair in self.browse(cr, uid, ids, context=context): res[repair.id] = 0.0 for line in repair.operations: res[repair.id] += line.price_subtotal for line in repair.fees_lines: res[repair.id] += line.price_subtotal cur = repair.pricelist_id.currency_id res[repair.id] = cur_obj.round(cr, uid, cur, res[repair.id]) return res def _amount_tax(self, cr, uid, ids, field_name, arg, context=None): """ Calculates taxed amount. @param field_name: Name of field. @param arg: Argument @return: Dictionary of values. """ res = {} #return {}.fromkeys(ids, 0) cur_obj = self.pool.get('res.currency') tax_obj = self.pool.get('account.tax') for repair in self.browse(cr, uid, ids, context=context): val = 0.0 cur = repair.pricelist_id.currency_id for line in repair.operations: #manage prices with tax included use compute_all instead of compute if line.to_invoice and line.tax_id: tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur, line.product_uom_qty, line.product_id.id, repair.partner_id.id) for c in tax_calculate['taxes']: val += c['amount'] for line in repair.fees_lines: if line.to_invoice and line.tax_id: tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur, line.product_uom_qty, line.product_id.id, repair.partner_id.id) for c in tax_calculate['taxes']: val += c['amount'] res[repair.id] = cur_obj.round(cr, uid, cur, val) return res def _amount_total(self, cr, uid, ids, field_name, arg, context=None): """ Calculates total amount. @param field_name: Name of field. @param arg: Argument @return: Dictionary of values. """ res = {} untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context=context) tax = self._amount_tax(cr, uid, ids, field_name, arg, context=context) cur_obj = self.pool.get('res.currency') for id in ids: repair = self.browse(cr, uid, id, context=context) cur = repair.pricelist_id.currency_id res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0)) return res def _get_default_address(self, cr, uid, ids, field_name, arg, context=None): res = {} partner_obj = self.pool.get('res.partner') for data in self.browse(cr, uid, ids, context=context): adr_id = False if data.partner_id: adr_id = partner_obj.address_get(cr, uid, [data.partner_id.id], ['contact'])['contact'] res[data.id] = adr_id return res def _get_lines(self, cr, uid, ids, context=None): return self.pool['mrp.repair'].search(cr, uid, [('operations', 'in', ids)], context=context) def _get_fee_lines(self, cr, uid, ids, context=None): return self.pool['mrp.repair'].search(cr, uid, [('fees_lines', 'in', ids)], context=context) _columns = { 'name': fields.char('Repair Reference', required=True, states={'confirmed': [('readonly', True)]}, copy=False), 'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, states={'draft': [('readonly', False)]}), 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'partner_id': fields.many2one('res.partner', 'Partner', select=True, help='Choose partner for whom the order will be invoiced and delivered.', states={'confirmed': [('readonly', True)]}), 'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed': [('readonly', True)]}), 'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"), 'state': fields.selection([ ('draft', 'Quotation'), ('cancel', 'Cancelled'), ('confirmed', 'Confirmed'), ('under_repair', 'Under Repair'), ('ready', 'Ready to Repair'), ('2binvoiced', 'To be Invoiced'), ('invoice_except', 'Invoice Exception'), ('done', 'Repaired') ], 'Status', readonly=True, track_visibility='onchange', copy=False, help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \ \n* The \'Confirmed\' status is used when a user confirms the repair order. \ \n* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed. \ \n* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done. \ \n* The \'Done\' status is set when repairing is completed.\ \n* The \'Cancelled\' status is used when user cancel repair order.'), 'location_id': fields.many2one('stock.location', 'Current Location', select=True, required=True, readonly=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}), 'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, required=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}), 'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot", oldname="prodlot_id"), 'guarantee_limit': fields.date('Warranty Expiration', states={'confirmed': [('readonly', True)]}), 'operations': fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft': [('readonly', False)]}, copy=True), 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='Pricelist of the selected partner.'), 'partner_invoice_id': fields.many2one('res.partner', 'Invoicing Address'), 'invoice_method': fields.selection([ ("none", "No Invoice"), ("b4repair", "Before Repair"), ("after_repair", "After Repair") ], "Invoice Method", select=True, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'), 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True, track_visibility="onchange", copy=False), 'move_id': fields.many2one('stock.move', 'Move', readonly=True, help="Move created by the repair order", track_visibility="onchange", copy=False), 'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees', readonly=True, states={'draft': [('readonly', False)]}, copy=True), 'internal_notes': fields.text('Internal Notes'), 'quotation_notes': fields.text('Quotation Notes'), 'company_id': fields.many2one('res.company', 'Company'), 'invoiced': fields.boolean('Invoiced', readonly=True, copy=False), 'repaired': fields.boolean('Repaired', readonly=True, copy=False), 'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount', store={ 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10), 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), }), 'amount_tax': fields.function(_amount_tax, string='Taxes', store={ 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10), 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), }), 'amount_total': fields.function(_amount_total, string='Total', store={ 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10), 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10), }), } def _default_stock_location(self, cr, uid, context=None): try: warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0') return warehouse.lot_stock_id.id except: return False _defaults = { 'state': lambda *a: 'draft', 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(cr, uid, 'mrp.repair'), 'invoice_method': lambda *a: 'none', 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.repair', context=context), 'pricelist_id': lambda self, cr, uid, context: self.pool['product.pricelist'].search(cr, uid, [], limit=1)[0], 'product_qty': 1.0, 'location_id': _default_stock_location, } _sql_constraints = [ ('name', 'unique (name)', 'The name of the Repair Order must be unique!'), ] def onchange_product_id(self, cr, uid, ids, product_id=None): """ On change of product sets some values. @param product_id: Changed product @return: Dictionary of values. """ product = False if product_id: product = self.pool.get("product.product").browse(cr, uid, product_id) return {'value': { 'guarantee_limit': False, 'lot_id': False, 'product_uom': product and product.uom_id.id or False, } } def onchange_product_uom(self, cr, uid, ids, product_id, product_uom, context=None): res = {'value': {}} if not product_uom or not product_id: return res product = self.pool.get('product.product').browse(cr, uid, product_id, context=context) uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context) if uom.category_id.id != product.uom_id.category_id.id: res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')} res['value'].update({'product_uom': product.uom_id.id}) return res def onchange_location_id(self, cr, uid, ids, location_id=None): """ On change of location """ return {'value': {'location_dest_id': location_id}} def button_dummy(self, cr, uid, ids, context=None): return True def onchange_partner_id(self, cr, uid, ids, part, address_id): """ On change of partner sets the values of partner address, partner invoice address and pricelist. @param part: Changed id of partner. @param address_id: Address id from current record. @return: Dictionary of values. """ part_obj = self.pool.get('res.partner') pricelist_obj = self.pool.get('product.pricelist') if not part: return {'value': { 'address_id': False, 'partner_invoice_id': False, 'pricelist_id': pricelist_obj.search(cr, uid, [], limit=1)[0] } } addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'contact']) partner = part_obj.browse(cr, uid, part) pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False return {'value': { 'address_id': addr['delivery'] or addr['contact'], 'partner_invoice_id': addr['invoice'], 'pricelist_id': pricelist } } def action_cancel_draft(self, cr, uid, ids, *args): """ Cancels repair order when it is in 'Draft' state. @param *arg: Arguments @return: True """ if not len(ids): return False mrp_line_obj = self.pool.get('mrp.repair.line') for repair in self.browse(cr, uid, ids): mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'}) self.write(cr, uid, ids, {'state': 'draft'}) return self.create_workflow(cr, uid, ids) def action_confirm(self, cr, uid, ids, *args): """ Repair order state is set to 'To be invoiced' when invoice method is 'Before repair' else state becomes 'Confirmed'. @param *arg: Arguments @return: True """ mrp_line_obj = self.pool.get('mrp.repair.line') for o in self.browse(cr, uid, ids): if (o.invoice_method == 'b4repair'): self.write(cr, uid, [o.id], {'state': '2binvoiced'}) else: self.write(cr, uid, [o.id], {'state': 'confirmed'}) for line in o.operations: if line.product_id.tracking != 'none' and not line.lot_id: raise UserError(_("Serial number is required for operation line with product '%s'") % (line.product_id.name)) mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'}) return True def action_cancel(self, cr, uid, ids, context=None): """ Cancels repair order. @return: True """ mrp_line_obj = self.pool.get('mrp.repair.line') for repair in self.browse(cr, uid, ids, context=context): if not repair.invoiced: mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context) else: raise UserError(_('Repair order is already invoiced.')) return self.write(cr, uid, ids, {'state': 'cancel'}) def wkf_invoice_create(self, cr, uid, ids, *args): self.action_invoice_create(cr, uid, ids) return True def action_invoice_create(self, cr, uid, ids, group=False, context=None): """ Creates invoice(s) for repair order. @param group: It is set to true when group invoice is to be generated. @return: Invoice Ids. """ res = {} invoices_group = {} inv_line_obj = self.pool.get('account.invoice.line') inv_obj = self.pool.get('account.invoice') repair_line_obj = self.pool.get('mrp.repair.line') repair_fee_obj = self.pool.get('mrp.repair.fee') for repair in self.browse(cr, uid, ids, context=context): res[repair.id] = False if repair.state in ('draft', 'cancel') or repair.invoice_id: continue if not (repair.partner_id.id and repair.partner_invoice_id.id): raise UserError(_('You have to select a Partner Invoice Address in the repair form!')) comment = repair.quotation_notes if (repair.invoice_method != 'none'): if group and repair.partner_invoice_id.id in invoices_group: inv_id = invoices_group[repair.partner_invoice_id.id] invoice = inv_obj.browse(cr, uid, inv_id) invoice_vals = { 'name': invoice.name + ', ' + repair.name, 'origin': invoice.origin + ', ' + repair.name, 'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''), } inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context) else: if not repair.partner_id.property_account_receivable_id: raise UserError(_('No account defined for partner "%s".') % repair.partner_id.name) account_id = repair.partner_id.property_account_receivable_id.id inv = { 'name': repair.name, 'origin': repair.name, 'type': 'out_invoice', 'account_id': account_id, 'partner_id': repair.partner_invoice_id.id or repair.partner_id.id, 'currency_id': repair.pricelist_id.currency_id.id, 'comment': repair.quotation_notes, 'fiscal_position_id': repair.partner_id.property_account_position_id.id } inv_id = inv_obj.create(cr, uid, inv) invoices_group[repair.partner_invoice_id.id] = inv_id self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id}) for operation in repair.operations: if operation.to_invoice: if group: name = repair.name + '-' + operation.name else: name = operation.name if operation.product_id.property_account_income_id: account_id = operation.product_id.property_account_income_id.id elif operation.product_id.categ_id.property_account_income_categ_id: account_id = operation.product_id.categ_id.property_account_income_categ_id.id else: raise UserError(_('No account defined for product "%s".') % operation.product_id.name) invoice_line_id = inv_line_obj.create(cr, uid, { 'invoice_id': inv_id, 'name': name, 'origin': repair.name, 'account_id': account_id, 'quantity': operation.product_uom_qty, 'invoice_line_tax_ids': [(6, 0, [x.id for x in operation.tax_id])], 'uom_id': operation.product_uom.id, 'price_unit': operation.price_unit, 'price_subtotal': operation.product_uom_qty * operation.price_unit, 'product_id': operation.product_id and operation.product_id.id or False }) repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id}) for fee in repair.fees_lines: if fee.to_invoice: if group: name = repair.name + '-' + fee.name else: name = fee.name if not fee.product_id: raise UserError(_('No product defined on Fees!')) if fee.product_id.property_account_income_id: account_id = fee.product_id.property_account_income_id.id elif fee.product_id.categ_id.property_account_income_categ_id: account_id = fee.product_id.categ_id.property_account_income_categ_id.id else: raise UserError(_('No account defined for product "%s".') % fee.product_id.name) invoice_fee_id = inv_line_obj.create(cr, uid, { 'invoice_id': inv_id, 'name': name, 'origin': repair.name, 'account_id': account_id, 'quantity': fee.product_uom_qty, 'invoice_line_tax_ids': [(6, 0, [x.id for x in fee.tax_id])], 'uom_id': fee.product_uom.id, 'product_id': fee.product_id and fee.product_id.id or False, 'price_unit': fee.price_unit, 'price_subtotal': fee.product_uom_qty * fee.price_unit }) repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id}) #inv_obj.button_reset_taxes(cr, uid, inv_id, context=context) res[repair.id] = inv_id return res def action_repair_ready(self, cr, uid, ids, context=None): """ Writes repair order state to 'Ready' @return: True """ for repair in self.browse(cr, uid, ids, context=context): self.pool.get('mrp.repair.line').write(cr, uid, [l.id for l in repair.operations], {'state': 'confirmed'}, context=context) self.write(cr, uid, [repair.id], {'state': 'ready'}) return True def action_repair_start(self, cr, uid, ids, context=None): """ Writes repair order state to 'Under Repair' @return: True """ repair_line = self.pool.get('mrp.repair.line') for repair in self.browse(cr, uid, ids, context=context): repair_line.write(cr, uid, [l.id for l in repair.operations], {'state': 'confirmed'}, context=context) repair.write({'state': 'under_repair'}) return True def action_repair_end(self, cr, uid, ids, context=None): """ Writes repair order state to 'To be invoiced' if invoice method is After repair else state is set to 'Ready'. @return: True """ for order in self.browse(cr, uid, ids, context=context): val = {} val['repaired'] = True if (not order.invoiced and order.invoice_method == 'after_repair'): val['state'] = '2binvoiced' elif (not order.invoiced and order.invoice_method == 'b4repair'): val['state'] = 'ready' else: pass self.write(cr, uid, [order.id], val) return True def wkf_repair_done(self, cr, uid, ids, *args): self.action_repair_done(cr, uid, ids) return True def action_repair_done(self, cr, uid, ids, context=None): """ Creates stock move for operation and stock move for final product of repair order. @return: Move ids of final products """ res = {} move_obj = self.pool.get('stock.move') repair_line_obj = self.pool.get('mrp.repair.line') for repair in self.browse(cr, uid, ids, context=context): move_ids = [] for move in repair.operations: move_id = move_obj.create(cr, uid, { 'name': move.name, 'product_id': move.product_id.id, 'restrict_lot_id': move.lot_id.id, 'product_uom_qty': move.product_uom_qty, 'product_uom': move.product_uom.id, 'partner_id': repair.address_id and repair.address_id.id or False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, }) move_ids.append(move_id) repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context) move_id = move_obj.create(cr, uid, { 'name': repair.name, 'product_id': repair.product_id.id, 'product_uom': repair.product_uom.id or repair.product_id.uom_id.id, 'product_uom_qty': repair.product_qty, 'partner_id': repair.address_id and repair.address_id.id or False, 'location_id': repair.location_id.id, 'location_dest_id': repair.location_dest_id.id, 'restrict_lot_id': repair.lot_id.id, }) move_ids.append(move_id) move_obj.action_done(cr, uid, move_ids, context=context) self.write(cr, uid, [repair.id], {'state': 'done', 'move_id': move_id}, context=context) res[repair.id] = move_id return res