class One2ManyMultiple(orm.Model): _name = 'export.one2many.multiple' _columns = { 'parent_id': fields.many2one('export.one2many.recursive'), 'const': fields.integer(), 'child1': fields.one2many('export.one2many.child.1', 'parent_id'), 'child2': fields.one2many('export.one2many.child.2', 'parent_id'), } _defaults = { 'const': 36, }
class pos_config(osv.osv): _inherit = 'pos.config' _columns = { 'iface_splitbill': fields.boolean('Bill Splitting', help='Enables Bill Splitting in the Point of Sale'), 'iface_printbill': fields.boolean('Bill Printing', help='Allows to print the Bill before payment'), 'iface_orderline_notes': fields.boolean('Orderline Notes', help='Allow custom notes on Orderlines'), 'floor_ids': fields.one2many( 'restaurant.floor', 'pos_config_id', 'Restaurant Floors', help='The restaurant floors served by this point of sale'), 'printer_ids': fields.many2many('restaurant.printer', 'pos_config_printer_rel', 'config_id', 'printer_id', string='Order Printers'), } _defaults = { 'iface_splitbill': False, 'iface_printbill': False, }
class PaymentMethod(osv.Model): _name = 'payment.method' _order = 'partner_id' _columns = { 'name': fields.char('Name', help='Name of the payment method'), 'partner_id': fields.many2one('res.partner', 'Partner', required=True), 'acquirer_id': fields.many2one('payment.acquirer', 'Acquirer Account', required=True), 'acquirer_ref': fields.char('Acquirer Ref.', required=True), 'active': fields.boolean('Active'), 'payment_ids': fields.one2many('payment.transaction', 'payment_method_id', 'Payment Transactions'), } _defaults = { 'active': True } def create(self, cr, uid, values, context=None): # 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) # 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)) return super(PaymentMethod, self).create(cr, uid, values, context=context)
class product_putaway_strategy(osv.osv): _name = 'product.putaway' _description = 'Put Away Strategy' def _get_putaway_options(self, cr, uid, context=None): return [('fixed', 'Fixed Location')] _columns = { 'name': fields.char('Name', required=True), 'method': fields.selection(_get_putaway_options, "Method", required=True), 'fixed_location_ids': fields.one2many( 'stock.fixed.putaway.strat', 'putaway_id', 'Fixed Locations Per Product Category', help= "When the method is fixed, this location will be used to store the products", copy=True), } _defaults = { 'method': 'fixed', } def putaway_apply(self, cr, uid, putaway_strat, product, context=None): if putaway_strat.method == 'fixed': for strat in putaway_strat.fixed_location_ids: categ = product.categ_id while categ: if strat.category_id.id == categ.id: return strat.fixed_location_id.id categ = categ.parent_id
class res_partner(osv.osv): _name = 'res.partner' _inherit = 'res.partner' def _compute_payment_method_count(self, cr, uid, ids, field_names, arg, context=None): result = {} payment_data = self.pool['payment.method'].read_group( cr, uid, [('partner_id', 'in', ids)], ['partner_id'], ['partner_id'], context=context) mapped_data = dict([(payment['partner_id'][0], payment['partner_id_count']) for payment in payment_data]) for partner in self.browse(cr, uid, ids, context=context): result[partner.id] = mapped_data.get(partner.id, 0) return result _columns = { 'payment_method_ids': fields.one2many('payment.method', 'partner_id', 'Payment Methods'), 'payment_method_count': fields.function(_compute_payment_method_count, string='Count Payment Method', type="integer"), }
class mrp_bom(osv.osv): _name = 'mrp.bom' _description = 'Bill of Material' _inherit='mrp.bom' _columns={ 'sub_products':fields.one2many('mrp.subproduct', 'bom_id', 'Byproducts', copy=True), }
class project_issue(osv.osv): _inherit = 'project.issue' _description = 'project issue' def _hours_get(self, cr, uid, ids, field_names, args, context=None): res = {} for issue in self.browse(cr, uid, ids, context=context): res[issue.id] = {'progress': issue.task_id.progress or 0.0} return res def _get_issue_task(self, cr, uid, task_ids, context=None): return self.pool['project.issue'].search(cr, uid, [('task_id', 'in', task_ids)], context=context) _columns = { 'progress': fields.function(_hours_get, string='Progress (%)', multi='line_id', group_operator="avg", help="Computed as: Time Spent / Total Time.", store={ 'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['task_id'], 10), 'project.task': (_get_issue_task, ['progress'], 10), }), 'timesheet_ids': fields.one2many('account.analytic.line', 'issue_id', 'Timesheets'), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'), } def on_change_project(self, cr, uid, ids, project_id, context=None): if not project_id: return {'value': {'analytic_account_id': False}} result = super(project_issue, self).on_change_project(cr, uid, ids, project_id, context=context) project = self.pool.get('project.project').browse(cr, uid, project_id, context=context) if 'value' not in result: result['value'] = {} account = project.analytic_account_id if account: result['value']['analytic_account_id'] = account.id return result
class crm_team(osv.Model): _name = "crm.team" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Sales Team" _order = "name" _period_number = 5 def _get_default_team_id(self, cr, uid, context=None, user_id=None): if context is None: context = {} if user_id is None: user_id = uid team_id = context.get('default_team_id') if not team_id: team_ids = self.search(cr, uid, [ '|', ('user_id', '=', user_id), ('member_ids', 'in', user_id) ], limit=1, context=context) team_id = team_ids[0] if team_ids else False if not team_id: team_id = self.pool['ir.model.data'].xmlid_to_res_id( cr, uid, 'sales_team.team_sales_department') return team_id _columns = { 'name': fields.char('Sales Team', size=64, required=True, translate=True), 'code': fields.char('Code', size=8), 'active': fields.boolean('Active', help="If the active field is set to "\ "false, it will allow you to hide the sales team without removing it."), 'company_id': fields.many2one('res.company', 'Company'), 'user_id': fields.many2one('res.users', 'Team Leader'), 'member_ids': fields.one2many('res.users', 'sale_team_id', 'Team Members'), 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by YuanCloud about cases in this sales team"), 'working_hours': fields.float('Working Hours', digits=(16, 2)), 'color': fields.integer('Color Index'), } _defaults = { 'active': 1, 'company_id': lambda self, cr, uid, context: self.pool.get('res.company'). _company_default_get(cr, uid, 'crm.team', context=context), } _sql_constraints = [('code_uniq', 'unique (code)', 'The code of the sales team must be unique !')] def create(self, cr, uid, values, context=None): if context is None: context = {} context['mail_create_nosubscribe'] = True return super(crm_team, self).create(cr, uid, values, context=context)
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 Documentation(osv.Model): _name = 'forum.documentation.toc' _description = 'Documentation ToC' _inherit = ['website.seo.metadata'] _order = "parent_left" _parent_order = "sequence, name" _parent_store = True def name_get(self, cr, uid, ids, context=None): if isinstance(ids, (list, tuple)) and not len(ids): return [] if isinstance(ids, (long, int)): ids = [ids] 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 # TODO master remove me def _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 = { 'sequence': fields.integer('Sequence'), 'name': fields.char('Name', required=True, translate=True), 'introduction': fields.html('Introduction', translate=True), 'parent_id': fields.many2one('forum.documentation.toc', 'Parent Table Of Content', ondelete='cascade'), 'child_ids': fields.one2many('forum.documentation.toc', 'parent_id', 'Children Table Of Content'), 'parent_left': fields.integer('Left Parent', select=True), 'parent_right': fields.integer('Right Parent', select=True), 'post_ids': fields.one2many('forum.post', 'documentation_toc_id', 'Posts'), 'forum_id': fields.many2one('forum.forum', 'Forum', required=True), } _constraints = [ (osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id']) ]
class res_users(osv.Model): """ Update of res.users class - add field for the related employee of the user - if adding groups to an user, check if base.group_user is in it (member of 'Employee'), create an employee form linked to it. """ _name = 'res.users' _inherit = ['res.users'] _columns = { 'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'), } def _message_post_get_eid(self, cr, uid, thread_id, context=None): assert thread_id, "res.users does not support posting global messages" if context and 'thread_model' in context: context = dict(context or {}) context['thread_model'] = 'hr.employee' if isinstance(thread_id, (list, tuple)): thread_id = thread_id[0] return self.pool.get('hr.employee').search( cr, uid, [('user_id', '=', thread_id)], context=context) @api.cr_uid_ids_context def message_post(self, cr, uid, thread_id, context=None, **kwargs): """ Redirect the posting of message on res.users to the related employee. This is done because when giving the context of Chatter on the various mailboxes, we do not have access to the current partner_id. """ if kwargs.get('message_type') == 'email': return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs) res = None employee_ids = self._message_post_get_eid(cr, uid, thread_id, context=context) if not employee_ids: # no employee: fall back on previous behavior return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs) for employee_id in employee_ids: res = self.pool.get('hr.employee').message_post(cr, uid, employee_id, context=context, **kwargs) return res
class product(osv.osv): _inherit = 'product.product' _columns = { 'event_ticket_ids': fields.one2many('event.event.ticket', 'product_id', 'Event Tickets'), } def onchange_event_ok(self, cr, uid, ids, type, event_ok, context=None): """ Redirection, inheritance mechanism hides the method on the model """ if event_ok: return {'value': {'type': 'service'}} return {}
class sale_order_line(osv.Model): _inherit = "sale.order.line" _columns = { 'linked_line_id': fields.many2one('sale.order.line', 'Linked Order Line', domain="[('order_id','!=',order_id)]", ondelete='cascade'), 'option_line_ids': fields.one2many('sale.order.line', 'linked_line_id', string='Options Linked'), }
class res_partner(osv.osv): def _task_count(self, cr, uid, ids, field_name, arg, context=None): Task = self.pool['project.task'] return { partner_id: Task.search_count(cr,uid, [('partner_id', '=', partner_id)], context=context) for partner_id in ids } """ Inherits partner and adds Tasks information in the partner form """ _inherit = 'res.partner' _columns = { 'task_ids': fields.one2many('project.task', 'partner_id', 'Tasks'), 'task_count': fields.function(_task_count, string='# Tasks', type='integer'), }
class MailThread(osv.AbstractModel): _inherit = 'mail.thread' _columns = { 'website_message_ids': fields.one2many( 'mail.message', 'res_id', domain=lambda self: [ '&', ('model', '=', self._name), ('message_type', '=', 'comment') ], string='Website Messages', help="Website communication history", ), }
class sale_order_line(osv.osv): _inherit = "sale.order.line" _description = "Sales Order Line" _columns = { 'website_description': fields.html('Line Description'), 'option_line_id': fields.one2many('sale.order.option', 'line_id', 'Optional Products Lines'), } def _inject_quote_description(self, cr, uid, values, context=None): values = dict(values or {}) if not values.get('website_description') and values.get('product_id'): product = self.pool['product.product'].browse(cr, uid, values['product_id'], context=context) values[ 'website_description'] = product.quote_description or product.website_description return values def create(self, cr, uid, values, context=None): values = self._inject_quote_description(cr, uid, values, context) ret = super(sale_order_line, self).create(cr, uid, values, context=context) # hack because create don t make the job for a related field if values.get('website_description'): self.write(cr, uid, ret, {'website_description': values['website_description']}, context=context) return ret def write(self, cr, uid, ids, values, context=None): values = self._inject_quote_description(cr, uid, values, context) return super(sale_order_line, self).write(cr, uid, ids, values, context=context)
class procurement_group(osv.osv): ''' The procurement group class is used to group products together when computing procurements. (tasks, physical products, ...) The goal is that when you have one sale order of several products and the products are pulled from the same or several location(s), to keep having the moves grouped into pickings that represent the sale order. Used in: sales order (to group delivery order lines like the so), pull/push rules (to pack like the delivery order), on orderpoints (e.g. for wave picking all the similar products together). Grouping is made only if the source and the destination is the same. Suppose you have 4 lines on a picking from Output where 2 lines will need to come from Input (crossdock) and 2 lines coming from Stock -> Output As the four procurement orders will have the same group ids from the SO, the move from input will have a stock.picking with 2 grouped lines and the move from stock will have 2 grouped lines also. The name is usually the name of the original document (sale order) or a sequence computed if created manually. ''' _name = 'procurement.group' _description = 'Procurement Requisition' _order = "id desc" _columns = { 'name': fields.char('Reference', required=True), 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True), 'procurement_ids': fields.one2many('procurement.order', 'group_id', 'Procurements'), } _defaults = { 'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').next_by_code( cr, uid, 'procurement.group') or '', 'move_type': lambda self, cr, uid, c: 'direct' }
class project(osv.Model): _inherit = "project.project" def _get_alias_models(self, cr, uid, context=None): res = super(project, self)._get_alias_models(cr, uid, context=context) res.append(("project.issue", "Issues")) return res def _issue_count(self, cr, uid, ids, field_name, arg, context=None): Issue = self.pool['project.issue'] return { project_id: Issue.search_count(cr, uid, [('project_id', '=', project_id), ('stage_id.fold', '=', False)], context=context) for project_id in ids } _columns = { 'issue_count': fields.function( _issue_count, type='integer', string="Issues", ), 'issue_ids': fields.one2many('project.issue', 'project_id', string="Issues", domain=[('stage_id.fold', '=', False)]), } @api.multi def write(self, vals): res = super(project, self).write(vals) if 'active' in vals: # archiving/unarchiving a project does it on its issues, too issues = self.with_context(active_test=False).mapped('issue_ids') issues.write({'active': vals['active']}) return res
class restaurant_floor(osv.osv): _name = 'restaurant.floor' _columns = { 'name': fields.char('Floor Name', required=True, help='An internal identification of the restaurant floor'), 'pos_config_id': fields.many2one('pos.config', 'Point of Sale'), 'background_image': fields.binary( 'Background Image', attachment=True, help= 'A background image used to display a floor layout in the point of sale interface' ), 'background_color': fields.char( 'Background Color', help= 'The background color of the floor layout, (must be specified in a html-compatible format)' ), 'table_ids': fields.one2many('restaurant.table', 'floor_id', 'Tables', help='The list of tables in this floor'), 'sequence': fields.integer('Sequence', help='Used to sort Floors'), } _defaults = { 'sequence': 1, 'background_color': 'rgb(210, 210, 210)', } def set_background_color(self, cr, uid, id, background, context=None): self.write(cr, uid, [id], {'background_color': background}, context=context)
class hr_evaluation_plan(osv.Model): _name = "hr_evaluation.plan" _description = "Appraisal Plan" _columns = { 'name': fields.char("Appraisal Plan", required=True), 'company_id': fields.many2one('res.company', 'Company', required=True), 'phase_ids': fields.one2many('hr_evaluation.plan.phase', 'plan_id', 'Appraisal Phases', copy=True), 'month_first': fields.integer( 'First Appraisal in (months)', help= "This number of months will be used to schedule the first evaluation date of the employee when selecting an evaluation plan. " ), 'month_next': fields.integer( 'Periodicity of Appraisal (months)', help= "The number of month that depicts the delay between each evaluation of this plan (after the first one)." ), 'active': fields.boolean('Active') } _defaults = { 'active': True, 'month_first': 6, 'month_next': 12, 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get( cr, uid, 'account.account', context=c), }
class product_template(osv.osv): _inherit = "product.template" def _bom_orders_count(self, cr, uid, ids, field_name, arg, context=None): Bom = self.pool('mrp.bom') res = {} for product_tmpl_id in ids: nb = Bom.search_count(cr, uid, [('product_tmpl_id', '=', product_tmpl_id)], context=context) res[product_tmpl_id] = { 'bom_count': nb, } return res def _bom_orders_count_mo(self, cr, uid, ids, name, arg, context=None): res = {} for product_tmpl_id in self.browse(cr, uid, ids): res[product_tmpl_id.id] = sum([p.mo_count for p in product_tmpl_id.product_variant_ids]) return res _columns = { 'bom_ids': fields.one2many('mrp.bom', 'product_tmpl_id','Bill of Materials'), 'bom_count': fields.function(_bom_orders_count, string='# Bill of Material', type='integer', multi="_bom_order_count"), 'mo_count': fields.function(_bom_orders_count_mo, string='# Manufacturing Orders', type='integer'), 'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."), } _defaults = { 'produce_delay': 1, } def action_view_mos(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) result = self._get_act_window_dict(cr, uid, 'mrp.act_product_mrp_production', 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
class res_partner(osv.osv): _inherit = 'res.partner' def _sale_order_count(self, cr, uid, ids, field_name, arg, context=None): res = dict(map(lambda x: (x, 0), ids)) # The current user may not have access rights for sale orders try: for partner in self.browse(cr, uid, ids, context): res[partner.id] = len(partner.sale_order_ids) + len( partner.mapped('child_ids.sale_order_ids')) except: pass return res _columns = { 'sale_order_count': fields.function(_sale_order_count, string='# of Sales Order', type='integer'), 'sale_order_ids': fields.one2many('sale.order', 'partner_id', 'Sales Order') }
class subscription_document(osv.osv): _name = "subscription.document" _description = "Subscription Document" _columns = { 'name': fields.char('Name', required=True), 'active': fields.boolean( 'Active', help= "If the active field is set to False, it will allow you to hide the subscription document without removing it." ), 'model': fields.many2one('ir.model', 'Object', required=True), 'field_ids': fields.one2many('subscription.document.fields', 'document_id', 'Fields', copy=True) } _defaults = { 'active': lambda *a: True, }
class BlogPost(osv.Model): _name = "blog.post" _description = "Blog Post" _inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin'] _order = 'id DESC' _mail_post_access = 'read' def _website_url(self, cr, uid, ids, field_name, arg, context=None): res = super(BlogPost, self)._website_url(cr, uid, ids, field_name, arg, context=context) for blog_post in self.browse(cr, uid, ids, context=context): res[blog_post.id] = "/blog/%s/post/%s" % (slug(blog_post.blog_id), slug(blog_post)) return res def _compute_ranking(self, cr, uid, ids, name, arg, context=None): res = {} for blog_post in self.browse(cr, uid, ids, context=context): age = datetime.now() - datetime.strptime(blog_post.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) res[blog_post.id] = blog_post.visits * (0.5+random.random()) / max(3, age.days) return res def _default_content(self, cr, uid, context=None): return ''' <div class="container"> <section class="mt16 mb16"> <p class="o_default_snippet_text">''' + _("Start writing here...") + '''</p> </section> </div> ''' _columns = { 'name': fields.char('Title', required=True, translate=True), 'subtitle': fields.char('Sub Title', translate=True), 'author_id': fields.many2one('res.partner', 'Author'), 'cover_properties': fields.text('Cover Properties'), 'blog_id': fields.many2one( 'blog.blog', 'Blog', required=True, ondelete='cascade', ), 'tag_ids': fields.many2many( 'blog.tag', string='Tags', ), 'content': fields.html('Content', translate=True, sanitize=False), 'website_message_ids': fields.one2many( 'mail.message', 'res_id', domain=lambda self: [ '&', '&', ('model', '=', self._name), ('message_type', '=', 'comment'), ('path', '=', False) ], string='Website Messages', help="Website communication history", ), # creation / update stuff 'create_date': fields.datetime( 'Created on', select=True, readonly=True, ), 'create_uid': fields.many2one( 'res.users', 'Author', select=True, readonly=True, ), 'write_date': fields.datetime( 'Last Modified on', select=True, readonly=True, ), 'write_uid': fields.many2one( 'res.users', 'Last Contributor', select=True, readonly=True, ), 'author_avatar': fields.related( 'author_id', 'image_small', string="Avatar", type="binary"), 'visits': fields.integer('No of Views'), 'ranking': fields.function(_compute_ranking, string='Ranking', type='float'), } _defaults = { 'name': '', 'content': _default_content, 'cover_properties': '{"background-image": "none", "background-color": "oe_none", "opacity": "0.6", "resize_class": ""}', 'author_id': lambda self, cr, uid, ctx=None: self.pool['res.users'].browse(cr, uid, uid, context=ctx).partner_id.id, } def html_tag_nodes(self, html, attribute=None, tags=None, context=None): """ Processing of html content to tag paragraphs and set them an unique ID. :return result: (html, mappin), where html is the updated html with ID and mapping is a list of (old_ID, new_ID), where old_ID is None is the paragraph is a new one. """ existing_attributes = [] mapping = [] if not html: return html, mapping if tags is None: tags = ['p'] if attribute is None: attribute = 'data-unique-id' # form a tree root = lxml.html.fragment_fromstring(html, create_parent='div') if not len(root) and root.text is None and root.tail is None: return html, mapping # check all nodes, replace : # - img src -> check URL # - a href -> check URL for node in root.iter(): if node.tag not in tags: continue ancestor_tags = [parent.tag for parent in node.iterancestors()] old_attribute = node.get(attribute) new_attribute = old_attribute if not new_attribute or (old_attribute in existing_attributes): if ancestor_tags: ancestor_tags.pop() counter = random.randint(10000, 99999) ancestor_tags.append('counter_%s' % counter) new_attribute = '/'.join(reversed(ancestor_tags)) node.set(attribute, new_attribute) existing_attributes.append(new_attribute) mapping.append((old_attribute, new_attribute)) html = lxml.html.tostring(root, pretty_print=False, method='html') # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that if html.startswith('<div>') and html.endswith('</div>'): html = html[5:-6] return html, mapping def _postproces_content(self, cr, uid, id, content=None, context=None): if content is None: content = self.browse(cr, uid, id, context=context).content if content is False: return content content, mapping = self.html_tag_nodes(content, attribute='data-chatter-id', tags=['p'], context=context) if id: # not creating existing = [x[0] for x in mapping if x[0]] msg_ids = self.pool['mail.message'].search(cr, SUPERUSER_ID, [ ('res_id', '=', id), ('model', '=', self._name), ('path', 'not in', existing), ('path', '!=', False) ], context=context) self.pool['mail.message'].unlink(cr, SUPERUSER_ID, msg_ids, context=context) return content def _check_for_publication(self, cr, uid, ids, vals, context=None): if vals.get('website_published'): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') for post in self.browse(cr, uid, ids, context=context): post.blog_id.message_post( body='<p>%(post_publication)s <a href="%(base_url)s/blog/%(blog_slug)s/post/%(post_slug)s">%(post_link)s</a></p>' % { 'post_publication': _('A new post %s has been published on the %s blog.') % (post.name, post.blog_id.name), 'post_link': _('Click here to access the post.'), 'base_url': base_url, 'blog_slug': slug(post.blog_id), 'post_slug': slug(post), }, subtype='website_blog.mt_blog_blog_published') return True return False def create(self, cr, uid, vals, context=None): if context is None: context = {} if 'content' in vals: vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context) create_context = dict(context, mail_create_nolog=True) post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context) self._check_for_publication(cr, uid, [post_id], vals, context=context) return post_id def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] if 'content' in vals: vals['content'] = self._postproces_content(cr, uid, ids[0], vals['content'], context=context) result = super(BlogPost, self).write(cr, uid, ids, vals, context) self._check_for_publication(cr, uid, ids, vals, context=context) return result def get_access_action(self, cr, uid, ids, context=None): """ Override method that generated the link to access the document. Instead of the classic form view, redirect to the post on the website directly """ post = self.browse(cr, uid, ids[0], context=context) return { 'type': 'ir.actions.act_url', 'url': '/blog/%s/post/%s' % (post.blog_id.id, post.id), 'target': 'self', 'res_id': self.id, } def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None): """ Override to set the access button: everyone can see an access button on their notification email. It will lead on the website view of the post. """ res = super(BlogPost, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context) access_action = self._notification_link_helper('view', model=message.model, res_id=message.res_id) for category, data in res.iteritems(): res[category]['button_access'] = {'url': access_action, 'title': _('View Blog Post')} return res
class stock_picking_wave(osv.osv): _inherit = "mail.thread" _name = "stock.picking.wave" _description = "Picking Wave" _order = "name desc" _columns = { 'name': fields.char('Picking Wave Name', required=True, help='Name of the picking wave', copy=False), 'user_id': fields.many2one('res.users', 'Responsible', track_visibility='onchange', help='Person responsible for this wave'), 'picking_ids': fields.one2many('stock.picking', 'wave_id', 'Pickings', help='List of picking associated to this wave'), 'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Running'), ('done', 'Done'), ('cancel', 'Cancelled')], string="State", track_visibility='onchange', required=True, copy=False), } _defaults = { 'name': '/', 'state': 'draft', } def confirm_picking(self, cr, uid, ids, context=None): picking_todo = self.pool.get('stock.picking').search( cr, uid, [('wave_id', 'in', ids)], context=context) self.write(cr, uid, ids, {'state': 'in_progress'}, context=context) return self.pool.get('stock.picking').action_assign(cr, uid, picking_todo, context=context) def cancel_picking(self, cr, uid, ids, context=None): picking_todo = self.pool.get('stock.picking').search( cr, uid, [('wave_id', 'in', ids)], context=context) self.pool.get('stock.picking').action_cancel(cr, uid, picking_todo, context=context) return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) def print_picking(self, cr, uid, ids, context=None): ''' This function print the report for all picking_ids associated to the picking wave ''' context = dict(context or {}) picking_ids = [] for wave in self.browse(cr, uid, ids, context=context): picking_ids += [picking.id for picking in wave.picking_ids] if not picking_ids: raise UserError(_('Nothing to print.')) context['active_ids'] = picking_ids context['active_model'] = 'stock.picking' return self.pool.get("report").get_action(cr, uid, [], 'stock.report_picking', context=context) def create(self, cr, uid, vals, context=None): if vals.get('name', '/') == '/': vals['name'] = self.pool.get('ir.sequence').next_by_code( cr, uid, 'picking.wave') or '/' return super(stock_picking_wave, self).create(cr, uid, vals, context=context) def done(self, cr, uid, ids, context=None): picking_todo = set() for wave in self.browse(cr, uid, ids, context=context): for picking in wave.picking_ids: if picking.state in ('cancel', 'done'): continue if picking.state != 'assigned': raise UserError( _('Some pickings are still waiting for goods. Please check or force their availability before setting this wave to done.' )) message_body = "<b>%s:</b> %s <a href=#id=%s&view_type=form&model=stock.picking.wave>%s</a>" % ( _("Transferred by"), _("Picking Wave"), wave.id, wave.name) picking.message_post(body=message_body) picking_todo.add(picking.id) if picking_todo: self.pool.get('stock.picking').action_done(cr, uid, list(picking_todo), context=context) return self.write(cr, uid, ids, {'state': 'done'}, context=context) def _track_subtype(self, cr, uid, ids, init_values, context=None): if 'state' in init_values: return 'stock_picking_wave.mt_wave_state' return super(stock_picking_wave, self)._track_subtype(cr, uid, ids, init_values, context=context)
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
class hr_timesheet_sheet(osv.osv): _name = "hr_timesheet_sheet.sheet" _inherit = ['mail.thread', 'ir.needaction_mixin'] _table = 'hr_timesheet_sheet_sheet' _order = "id desc" _description = "Timesheet" def _total(self, cr, uid, ids, name, args, context=None): """ Compute the attendances, analytic lines timesheets and differences between them for all the days of a timesheet and the current day """ res = dict.fromkeys( ids, { 'total_attendance': 0.0, 'total_timesheet': 0.0, 'total_difference': 0.0, }) cr.execute( """ SELECT sheet_id as id, sum(total_attendance) as total_attendance, sum(total_timesheet) as total_timesheet, sum(total_difference) as total_difference FROM hr_timesheet_sheet_sheet_day WHERE sheet_id IN %s GROUP BY sheet_id """, (tuple(ids), )) res.update(dict((x.pop('id'), x) for x in cr.dictfetchall())) return res def check_employee_attendance_state(self, cr, uid, sheet_id, context=None): ids_signin = self.pool.get('hr.attendance').search( cr, uid, [('sheet_id', '=', sheet_id), ('action', '=', 'sign_in')]) ids_signout = self.pool.get('hr.attendance').search( cr, uid, [('sheet_id', '=', sheet_id), ('action', '=', 'sign_out')]) if len(ids_signin) != len(ids_signout): raise UserError( _('The timesheet cannot be validated as it does not contain an equal number of sign ins and sign outs.' )) return True def copy(self, cr, uid, ids, *args, **argv): raise UserError(_('You cannot duplicate a timesheet.')) def create(self, cr, uid, vals, context=None): if 'employee_id' in vals: if not self.pool.get('hr.employee').browse( cr, uid, vals['employee_id'], context=context).user_id: raise UserError( _('In order to create a timesheet for this employee, you must link him/her to a user.' )) if vals.get('attendances_ids'): # If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint vals['attendances_ids'] = self.sort_attendances( cr, uid, vals['attendances_ids'], context=context) return super(hr_timesheet_sheet, self).create(cr, uid, vals, context=context) def write(self, cr, uid, ids, vals, context=None): if 'employee_id' in vals: new_user_id = self.pool.get('hr.employee').browse( cr, uid, vals['employee_id'], context=context).user_id.id or False if not new_user_id: raise UserError( _('In order to create a timesheet for this employee, you must link him/her to a user.' )) if not self._sheet_date( cr, uid, ids, forced_user_id=new_user_id, context=context): raise UserError( _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.' )) if not self.pool.get('hr.employee').browse( cr, uid, vals['employee_id'], context=context).product_id: raise UserError( _('In order to create a timesheet for this employee, you must link the employee to a product.' )) if vals.get('attendances_ids'): # If attendances, we sort them by date asc before writing them, to satisfy the alternance constraint # In addition to the date order, deleting attendances are done before inserting attendances vals['attendances_ids'] = self.sort_attendances( cr, uid, vals['attendances_ids'], context=context) res = super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, context=context) if vals.get('attendances_ids'): for timesheet in self.browse(cr, uid, ids): if not self.pool['hr.attendance']._altern_si_so( cr, uid, [att.id for att in timesheet.attendances_ids]): raise UserError( _('Error ! Sign in (resp. Sign out) must follow Sign out (resp. Sign in)' )) return res def sort_attendances(self, cr, uid, attendance_tuples, context=None): date_attendances = [] for att_tuple in attendance_tuples: if att_tuple[0] in [0, 1, 4]: if att_tuple[0] in [0, 1]: if att_tuple[2] and att_tuple[2].has_key('name'): name = att_tuple[2]['name'] else: name = self.pool['hr.attendance'].browse( cr, uid, att_tuple[1]).name else: name = self.pool['hr.attendance'].browse( cr, uid, att_tuple[1]).name date_attendances.append((1, name, att_tuple)) elif att_tuple[0] in [2, 3]: date_attendances.append((0, self.pool['hr.attendance'].browse( cr, uid, att_tuple[1]).name, att_tuple)) else: date_attendances.append((0, False, att_tuple)) date_attendances.sort() return [att[2] for att in date_attendances] def button_confirm(self, cr, uid, ids, context=None): for sheet in self.browse(cr, uid, ids, context=context): if sheet.employee_id and sheet.employee_id.parent_id and sheet.employee_id.parent_id.user_id: self.message_subscribe_users( cr, uid, [sheet.id], user_ids=[sheet.employee_id.parent_id.user_id.id], context=context) self.check_employee_attendance_state(cr, uid, sheet.id, context=context) di = sheet.user_id.company_id.timesheet_max_difference if (abs(sheet.total_difference) < di) or not di: sheet.signal_workflow('confirm') else: raise UserError( _('Please verify that the total difference of the sheet is lower than %.2f.' ) % (di, )) return True def attendance_action_change(self, cr, uid, ids, context=None): hr_employee = self.pool.get('hr.employee') employee_ids = [] for sheet in self.browse(cr, uid, ids, context=context): if sheet.employee_id.id not in employee_ids: employee_ids.append(sheet.employee_id.id) return hr_employee.attendance_action_change(cr, uid, employee_ids, context=context) def _count_attendances(self, cr, uid, ids, field_name, arg, context=None): res = dict.fromkeys(ids, 0) attendances_groups = self.pool['hr.attendance'].read_group( cr, uid, [('sheet_id', 'in', ids)], ['sheet_id'], 'sheet_id', context=context) for attendances in attendances_groups: res[attendances['sheet_id'][0]] = attendances['sheet_id_count'] return res _columns = { 'name': fields.char('Note', select=1, states={ 'confirm': [('readonly', True)], 'done': [('readonly', True)] }), 'employee_id': fields.many2one('hr.employee', 'Employee', required=True), 'user_id': fields.related( 'employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True ), #fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}), 'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new': [('readonly', False)]}), 'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new': [('readonly', False)]}), 'timesheet_ids': fields.one2many('account.analytic.line', 'sheet_id', 'Timesheet lines', readonly=True, states={ 'draft': [('readonly', False)], 'new': [('readonly', False)] }), 'attendances_ids': fields.one2many('hr.attendance', 'sheet_id', 'Attendances'), 'state': fields.selection( [('new', 'New'), ('draft', 'Open'), ('confirm', 'Waiting Approval'), ('done', 'Approved')], 'Status', select=True, required=True, readonly=True, track_visibility='onchange', help= ' * The \'Draft\' status is used when a user is encoding a new and unconfirmed timesheet. \ \n* The \'Confirmed\' status is used for to confirm the timesheet by user. \ \n* The \'Done\' status is used when users timesheet is accepted by his/her senior.' ), 'state_attendance': fields.related('employee_id', 'state', type='selection', selection=[('absent', 'Absent'), ('present', 'Present')], string='Current Status', readonly=True), 'total_attendance': fields.function(_total, method=True, string='Total Attendance', multi="_total"), 'total_timesheet': fields.function(_total, method=True, string='Total Timesheet', multi="_total"), 'total_difference': fields.function(_total, method=True, string='Difference', multi="_total"), 'period_ids': fields.one2many('hr_timesheet_sheet.sheet.day', 'sheet_id', 'Period', readonly=True), 'account_ids': fields.one2many('hr_timesheet_sheet.sheet.account', 'sheet_id', 'Analytic accounts', readonly=True), 'company_id': fields.many2one('res.company', 'Company'), 'department_id': fields.many2one('hr.department', 'Department'), 'attendance_count': fields.function(_count_attendances, type='integer', string="Attendances"), } def _default_date_from(self, cr, uid, context=None): user = self.pool.get('res.users').browse(cr, uid, uid, context=context) r = user.company_id and user.company_id.timesheet_range or 'month' if r == 'month': return time.strftime('%Y-%m-01') elif r == 'week': return (datetime.today() + relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d') elif r == 'year': return time.strftime('%Y-01-01') return fields.date.context_today(self, cr, uid, context) def _default_date_to(self, cr, uid, context=None): user = self.pool.get('res.users').browse(cr, uid, uid, context=context) r = user.company_id and user.company_id.timesheet_range or 'month' if r == 'month': return ( datetime.today() + relativedelta(months=+1, day=1, days=-1)).strftime('%Y-%m-%d') elif r == 'week': return (datetime.today() + relativedelta(weekday=6)).strftime('%Y-%m-%d') elif r == 'year': return time.strftime('%Y-12-31') return fields.date.context_today(self, cr, uid, context) def _default_employee(self, cr, uid, context=None): emp_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context) return emp_ids and emp_ids[0] or False _defaults = { 'date_from': _default_date_from, 'date_to': _default_date_to, 'state': 'new', 'employee_id': _default_employee, 'company_id': lambda self, cr, uid, c: self.pool.get('res.company'). _company_default_get(cr, uid, 'hr_timesheet_sheet.sheet', context=c) } def _sheet_date(self, cr, uid, ids, forced_user_id=False, context=None): for sheet in self.browse(cr, uid, ids, context=context): new_user_id = forced_user_id or sheet.employee_id.user_id and sheet.employee_id.user_id.id if new_user_id: cr.execute( 'SELECT id \ FROM hr_timesheet_sheet_sheet \ WHERE (date_from <= %s and %s <= date_to) \ AND user_id=%s \ AND id <> %s', (sheet.date_to, sheet.date_from, new_user_id, sheet.id)) if cr.fetchall(): return False return True _constraints = [ (_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from', 'date_to']), ] def action_set_to_draft(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'draft'}) self.create_workflow(cr, uid, ids) return True def name_get(self, cr, uid, ids, context=None): if not ids: return [] if isinstance(ids, (long, int)): ids = [ids] # week number according to ISO 8601 Calendar return [(r['id'], _('Week ')+str(datetime.strptime(r['date_from'], '%Y-%m-%d').isocalendar()[1])) \ for r in self.read(cr, uid, ids, ['date_from'], context=context, load='_classic_write')] def unlink(self, cr, uid, ids, context=None): sheets = self.read(cr, uid, ids, ['state', 'total_attendance'], context=context) for sheet in sheets: if sheet['state'] in ('confirm', 'done'): raise UserError( _('You cannot delete a timesheet which is already confirmed.' )) elif sheet['total_attendance'] <> 0.00: raise UserError( _('You cannot delete a timesheet which have attendance entries.' )) toremove = [] analytic_timesheet = self.pool.get('account.analytic.line') for sheet in self.browse(cr, uid, ids, context=context): for timesheet in sheet.timesheet_ids: toremove.append(timesheet.id) analytic_timesheet.unlink(cr, uid, toremove, context=context) return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context) def onchange_employee_id(self, cr, uid, ids, employee_id, context=None): department_id = False user_id = False if employee_id: empl_id = self.pool.get('hr.employee').browse(cr, uid, employee_id, context=context) department_id = empl_id.department_id.id user_id = empl_id.user_id.id return { 'value': { 'department_id': department_id, 'user_id': user_id, } } # ------------------------------------------------ # OpenChatter methods and notifications # ------------------------------------------------ def _track_subtype(self, cr, uid, ids, init_values, context=None): record = self.browse(cr, uid, ids[0], context=context) if 'state' in init_values and record.state == 'confirm': return 'hr_timesheet_sheet.mt_timesheet_confirmed' elif 'state' in init_values and record.state == 'done': return 'hr_timesheet_sheet.mt_timesheet_approved' return super(hr_timesheet_sheet, self)._track_subtype(cr, uid, ids, init_values, context=context) def _needaction_domain_get(self, cr, uid, context=None): emp_obj = self.pool.get('hr.employee') empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context) if not empids: return False dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)] return dom
class task(osv.osv): _inherit = "project.task" # Compute: effective_hours, total_hours, progress def _hours_get(self, cr, uid, ids, field_names, args, context=None): res = {} tasks_data = self.pool['account.analytic.line'].read_group( cr, uid, [('task_id', 'in', ids)], ['task_id', 'unit_amount'], ['task_id'], context=context) for data in tasks_data: task = self.browse(cr, uid, data['task_id'][0], context=context) res[data['task_id'][0]] = { 'effective_hours': data.get('unit_amount', 0.0), 'remaining_hours': task.planned_hours - data.get('unit_amount', 0.0) } res[data['task_id'][0]]['total_hours'] = res[ data['task_id'][0]]['remaining_hours'] + data.get( 'unit_amount', 0.0) res[data['task_id'][0]]['delay_hours'] = res[ data['task_id'][0]]['total_hours'] - task.planned_hours res[data['task_id'][0]]['progress'] = 0.0 if (task.planned_hours > 0.0 and data.get('unit_amount', 0.0)): res[data['task_id'][0]]['progress'] = round( min( 100.0 * data.get('unit_amount', 0.0) / task.planned_hours, 99.99), 2) # TDE CHECK: if task.state in ('done','cancelled'): if task.stage_id and task.stage_id.fold: res[data['task_id'][0]]['progress'] = 100.0 return res def _get_task(self, cr, uid, id, context=None): res = [] for line in self.pool.get('account.analytic.line').search_read( cr, uid, [('task_id', '!=', False), ('id', 'in', id)], context=context): res.append(line['task_id'][0]) return res def _get_total_hours(self): return super(task, self)._get_total_hours() + self.effective_hours _columns = { 'remaining_hours': fields.function( _hours_get, string='Remaining Hours', multi='line_id', help= "Total remaining time, can be re-estimated periodically by the assignee of the task.", store={ 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10), 'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10), }), 'effective_hours': fields.function(_hours_get, string='Hours Spent', multi='line_id', help="Computed using the sum of the task work done.", store={ 'project.task': (lambda self, cr, uid, ids, c={}: ids, [ 'timesheet_ids', 'remaining_hours', 'planned_hours' ], 10), 'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10), }), 'total_hours': fields.function(_hours_get, string='Total', multi='line_id', help="Computed as: Time Spent + Remaining Time.", store={ 'project.task': (lambda self, cr, uid, ids, c={}: ids, [ 'timesheet_ids', 'remaining_hours', 'planned_hours' ], 10), 'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10), }), 'progress': fields.function( _hours_get, string='Working Time Progress (%)', multi='line_id', group_operator="avg", help= "If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time", store={ 'project.task': (lambda self, cr, uid, ids, c={}: ids, [ 'timesheet_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id' ], 10), 'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10), }), 'delay_hours': fields.function( _hours_get, string='Delay Hours', multi='line_id', help= "Computed as difference between planned hours by the project manager and the total hours of the task.", store={ 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10), 'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10), }), 'timesheet_ids': fields.one2many('account.analytic.line', 'task_id', 'Timesheets'), 'analytic_account_id': fields.related('project_id', 'analytic_account_id', type='many2one', relation='account.analytic.account', string='Analytic Account', store=True), } _defaults = { 'progress': 0, } def _prepare_delegate_values(self, cr, uid, ids, delegate_data, context=None): vals = super(task, self)._prepare_delegate_values(cr, uid, ids, delegate_data, context) for task in self.browse(cr, uid, ids, context=context): vals[task.id]['planned_hours'] += task.effective_hours return vals def onchange_project(self, cr, uid, ids, project_id, context=None): result = super(task, self).onchange_project(cr, uid, ids, project_id, context=context) if not project_id: return result if 'value' not in result: result['value'] = {} project = self.pool['project.project'].browse(cr, uid, project_id, context=context) return result
class stock_move(osv.osv): _inherit = 'stock.move' _columns = { 'move_dest_id_lines': fields.one2many('stock.move', 'move_dest_id', 'Children Moves') }
class purchase_order_line(Model): _inherit = "purchase.order.line" _columns = { 'goodie_for_line_id': fields.many2one('purchase.order.line', 'Goodies for', help='The product linked to this goodie lines'), 'goodies_line_ids': fields.one2many('purchase.order.line', 'goodie_for_line_id', 'Goodies linked', help=''), } def write(self, cr, uid, ids, vals, context=None): if context is None: context = {} #TODO I should apply this only for automatic po need a read only mode if context.get("updated_from_op"): if not context.get('goodies_create_update'): ctx = context.copy() ctx['goodies_create_update'] = True for line in self.browse(cr, uid, ids, context=None): if line.product_id.is_purchase_goodies(context=ctx): vals[ 'product_qty'] = self._get_new_qty_for_none_goodies_line( cr, uid, vals['product_qty'], line.product_id.id, line.order_id.id, context=ctx) super(purchase_order_line, self).write(cr, uid, line.id, vals, context=ctx) qty_added = vals['product_qty'] - line.product_qty for goodie in line.product_id.supplier_goodies_ids: qty = goodie.get_quantity(qty_added, context=ctx) po_line_for_goodie = False for goodies_line in line.goodies_line_ids: if goodies_line.product_id.id == goodie.linked_product_id.id: po_line_for_goodie = goodies_line break #TODO manage correctly uom print 'po_line_for_goodie', po_line_for_goodie if po_line_for_goodie: po_line_for_goodie.write( { 'product_qty': po_line_for_goodie.product_qty + qty }, context=ctx) else: self.create(cr, uid, self._prepare_goodies_line( cr, uid, line.id, goodie, qty, line.order_id, line.date_planned, context=ctx), context=ctx) self.update_none_goodies_line( cr, uid, qty, goodie.linked_product_id.id, line.order_id.id, context=ctx) return True return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context) def create(self, cr, uid, vals, context=None): if context is None: context = {} if not context.get('goodies_create_update'): ctx = context.copy() ctx['goodies_create_update'] = True product_obj = self.pool.get('product.product') product = product_obj.browse(cr, uid, vals['product_id'], context=ctx) if product.is_purchase_goodies(context=ctx): vals['product_qty'] = self._get_new_qty_for_none_goodies_line( cr, uid, vals['product_qty'], vals['product_id'], vals['order_id'], context=ctx) line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context) order = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context) for goodie in product.supplier_goodies_ids: qty = goodie.get_quantity(vals['product_qty'], context=ctx) self.create(cr, uid, self._prepare_goodies_line( cr, uid, line_id, goodie, qty, order, vals.get('date_planned'), context=ctx), context=ctx) self.update_none_goodies_line(cr, uid, qty, goodie.linked_product_id.id, order.id, context=ctx) return line_id else: return super(purchase_order_line, self).create(cr, uid, vals, context=context) def _get_new_qty_for_none_goodies_line(self, cr, uid, qty, product_id, order_id, context=None): """If we want to buy X more product B we have to check if there is not already goodies line that containt this product. If yes we have to reduce the qty to buy by the the total of goodies lines :params qty float: quantity of product to buy :params product_id int: product id :params order_id: order id :return: the quantity for the none goodies line reduced by the quantity of goodies line :rtype: float """ goodies_line_ids = self.search( cr, uid, [['order_id', '=', order_id], ['product_id', '=', product_id], ['goodie_for_line_id', '!=', False]], context=context) for goodie_line in self.browse(cr, uid, goodies_line_ids, context=context): qty -= goodie_line.product_qty if qty < 0: qty = 0 return qty def update_none_goodies_line(self, cr, uid, goodies_qty, product_id, order_id, context=None): """Update the none line goodies, by this I mean : If you sold a product A with a goodies B If the scheduler have run a minimal rule for B before running the A rule. We have a line for the B product and we should remove the qty added by the goodies :params goodies_qty float: quantity of goodies product :params product_id int: product id :params order_id: order id :return: True :rtype: Boolean """ product_line_id = self.search( cr, uid, [['order_id', '=', order_id], ['product_id', '=', product_id], ['goodie_for_line_id', '=', False]], context=context) if product_line_id: product_line = self.browse(cr, uid, product_line_id[0], context=context) new_qty = product_line.product_qty - goodies_qty if new_qty < 0: new_qty = 0 product_line.write({'product_qty': new_qty}, context=context) return True def _prepare_goodies_line(self, cr, uid, line_id, goodie, qty, order, date_planned, context=None): """Prepare the purchase order line for goodies :params goodies browse_record: browse_record of product_links :params qty float: quantity of goodies to buy :params order browse_record: purchase order that contain this line :params schedule_date str: planned to for receiving the product :return: dictionnary of value for creating the purchase order line :rtype: dict """ #TODO manage correctly uom acc_pos_obj = self.pool.get('account.fiscal.position') taxes_ids = goodie.product_id.supplier_taxes_id taxes = acc_pos_obj.map_tax(cr, uid, order.partner_id.property_account_position, taxes_ids) ctx = context.copy() #set the partner id in the context in order to have the good name for product ctx['partner_id'] = order.partner_id.id product = self.pool.get('product.product').browse( cr, uid, goodie.linked_product_id.id, context=ctx) return { 'name': ">>>%s" % product.partner_ref, 'product_qty': qty, 'product_id': product.id, 'product_uom': product.uom_po_id.id, 'price_unit': goodie.cost_price or 0.0, 'date_planned': date_planned, 'notes': product.description_purchase, 'taxes_id': [(6, 0, taxes)], 'order_id': order.id, 'goodie_for_line_id': line_id }