class mrp_production(osv.osv): _inherit = 'mrp.production' _columns = { 'allow_reorder': fields.boolean( 'Free Serialisation', help= "Check this to be able to move independently all production orders, without moving dependent ones." ), } def _production_date_end(self, cr, uid, ids, prop, unknow_none, context=None): """ Calculates planned end date of production order. @return: Dictionary of values """ result = {} for prod in self.browse(cr, uid, ids, context=context): result[prod.id] = prod.date_planned for line in prod.workcenter_lines: result[prod.id] = max(line.date_planned_end, result[prod.id]) return result def action_production_end(self, cr, uid, ids, context=None): """ Finishes work order if production order is done. @return: Super method """ obj = self.browse(cr, uid, ids, context=context)[0] workcenter_pool = self.pool.get('mrp.production.workcenter.line') for workcenter_line in obj.workcenter_lines: if workcenter_line.state == 'draft': workcenter_line.signal_workflow('button_start_working') workcenter_line.signal_workflow('button_done') return super(mrp_production, self).action_production_end(cr, uid, ids, context=context) def action_in_production(self, cr, uid, ids, context=None): """ Changes state to In Production and writes starting date. @return: True """ workcenter_pool = self.pool.get('mrp.production.workcenter.line') for prod in self.browse(cr, uid, ids): if prod.workcenter_lines: workcenter_pool.signal_workflow(cr, uid, [prod.workcenter_lines[0].id], 'button_start_working') return super(mrp_production, self).action_in_production(cr, uid, ids, context=context) def action_cancel(self, cr, uid, ids, context=None): """ Cancels work order if production order is canceled. @return: Super method """ workcenter_pool = self.pool.get('mrp.production.workcenter.line') obj = self.browse(cr, uid, ids, context=context)[0] workcenter_pool.signal_workflow( cr, uid, [record.id for record in obj.workcenter_lines], 'button_cancel') return super(mrp_production, self).action_cancel(cr, uid, ids, context=context) def _compute_planned_workcenter(self, cr, uid, ids, context=None, mini=False): """ Computes planned and finished dates for work order. @return: Calculated date """ dt_end = datetime.now() if context is None: context = {} for po in self.browse(cr, uid, ids, context=context): dt_end = datetime.strptime(po.date_planned, '%Y-%m-%d %H:%M:%S') if not po.date_start: self.write(cr, uid, [po.id], {'date_start': po.date_planned}, context=context, update=False) old = None for wci in range(len(po.workcenter_lines)): wc = po.workcenter_lines[wci] if (old is None) or (wc.sequence > old): dt = dt_end if context.get('__last_update'): del context['__last_update'] if (wc.date_planned < dt.strftime('%Y-%m-%d %H:%M:%S')) or mini: self.pool.get('mrp.production.workcenter.line').write( cr, uid, [wc.id], {'date_planned': dt.strftime('%Y-%m-%d %H:%M:%S')}, context=context, update=False) i = self.pool.get('resource.calendar').interval_get( cr, uid, #passing False makes resource_resource._schedule_hours run 1000 iterations doing nothing wc.workcenter_id.calendar_id and wc.workcenter_id.calendar_id.id or None, dt, wc.hour or 0.0) if i: dt_end = max(dt_end, i[-1][1]) else: dt_end = datetime.strptime(wc.date_planned_end, '%Y-%m-%d %H:%M:%S') old = wc.sequence or 0 super(mrp_production, self).write(cr, uid, [po.id], {'date_finished': dt_end}) return dt_end def _move_pass(self, cr, uid, ids, context=None): """ Calculates start date for stock moves finding interval from resource calendar. @return: True """ for po in self.browse(cr, uid, ids, context=context): if po.allow_reorder: continue todo = list(po.move_lines) dt = datetime.strptime(po.date_start, '%Y-%m-%d %H:%M:%S') while todo: l = todo.pop(0) if l.state in ('done', 'cancel', 'draft'): continue todo += l.move_dest_id_lines date_end = l.production_id.date_finished if date_end and datetime.strptime(date_end, '%Y-%m-%d %H:%M:%S') > dt: if l.production_id.state not in ('done', 'cancel'): for wc in l.production_id.workcenter_lines: i = self.pool.get( 'resource.calendar').interval_min_get( cr, uid, wc.workcenter_id.calendar_id.id or False, dt, wc.hour or 0.0) dt = i[0][0] if l.production_id.date_start > dt.strftime( '%Y-%m-%d %H:%M:%S'): self.write(cr, uid, [l.production_id.id], { 'date_start': dt.strftime('%Y-%m-%d %H:%M:%S') }, mini=True) return True def _move_futur(self, cr, uid, ids, context=None): """ Calculates start date for stock moves. @return: True """ for po in self.browse(cr, uid, ids, context=context): if po.allow_reorder: continue for line in po.move_created_ids: l = line while l.move_dest_id: l = l.move_dest_id if l.state in ('done', 'cancel', 'draft'): break if l.production_id.state in ('done', 'cancel'): break if l.production_id and (l.production_id.date_start < po.date_finished): self.write(cr, uid, [l.production_id.id], {'date_start': po.date_finished}) break return True def write(self, cr, uid, ids, vals, context=None, update=True, mini=True): direction = {} if vals.get('date_start', False): for po in self.browse(cr, uid, ids, context=context): direction[po.id] = cmp(po.date_start, vals.get('date_start', False)) result = super(mrp_production, self).write(cr, uid, ids, vals, context=context) if (vals.get('workcenter_lines', False) or vals.get('date_start', False) or vals.get('date_planned', False)) and update: self._compute_planned_workcenter(cr, uid, ids, context=context, mini=mini) for d in direction: if direction[d] == 1: # the production order has been moved to the passed self._move_pass(cr, uid, [d], context=context) pass elif direction[d] == -1: self._move_futur(cr, uid, [d], context=context) # the production order has been moved to the future pass return result def action_compute(self, cr, uid, ids, properties=None, context=None): """ Computes bills of material of a product and planned date of work order. @param properties: List containing dictionaries of properties. @return: No. of products. """ result = super(mrp_production, self).action_compute(cr, uid, ids, properties=properties, context=context) self._compute_planned_workcenter(cr, uid, ids, context=context) return result
class PaymentAcquirer(osv.Model): """ Acquirer Model. Each specific acquirer can extend the model by adding its own fields, using the acquirer_name as a prefix for the new fields. Using the required_if_provider='<name>' attribute on fields it is possible to have required fields that depend on a specific acquirer. Each acquirer has a link to an ir.ui.view record that is a template of a button used to display the payment form. See examples in ``payment_ogone`` and ``payment_paypal`` modules. Methods that should be added in an acquirer-specific implementation: - ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None)``: method that generates the values used to render the form button template. - ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method that returns the url of the button form. It is used for example in ecommerce application, if you want to post some data to the acquirer. - ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id, context=None)``: computed the fees of the acquirer, using generic fields defined on the acquirer model (see fields definition). Each acquirer should also define controllers to handle communication between eCore and the acquirer. It generally consists in return urls given to the button form and that the acquirer uses to send the customer back after the transaction, with transaction details given as a POST request. """ _name = 'payment.acquirer' _description = 'Payment Acquirer' _order = 'sequence' def _get_providers(self, cr, uid, context=None): return [] # indirection to ease inheritance _provider_selection = lambda self, *args, **kwargs: self._get_providers(*args, **kwargs) _columns = { 'name': fields.char('Name', required=True, translate=True), 'provider': fields.selection(_provider_selection, string='Provider', required=True), 'company_id': fields.many2one('res.company', 'Company', required=True), 'pre_msg': fields.html('Help Message', translate=True, help='Message displayed to explain and help the payment process.'), 'post_msg': fields.html('Thanks Message', help='Message displayed after having done the payment process.'), 'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True), 'registration_view_template_id': fields.many2one('ir.ui.view', 'S2S Form Template', domain=[('type', '=', 'qweb')], help="Template for method registration"), 'environment': fields.selection( [('test', 'Test'), ('prod', 'Production')], string='Environment', oldname='env'), 'website_published': fields.boolean( 'Visible in Portal / Website', copy=False, help="Make this payment acquirer available (Customer invoices, etc.)"), 'auto_confirm': fields.selection( [('none', 'No automatic confirmation'), ('at_pay_confirm', 'At payment with acquirer confirmation'), ('at_pay_now', 'At payment no acquirer confirmation needed')], string='Order Confirmation', required=True), 'pending_msg': fields.html('Pending Message', translate=True, help='Message displayed, if order is in pending state after having done the payment process.'), 'done_msg': fields.html('Done Message', translate=True, help='Message displayed, if order is done successfully after having done the payment process.'), 'cancel_msg': fields.html('Cancel Message', translate=True, help='Message displayed, if order is cancel during the payment process.'), 'error_msg': fields.html('Error Message', translate=True, help='Message displayed, if error is occur during the payment process.'), # Fees 'fees_active': fields.boolean('Add Extra Fees'), 'fees_dom_fixed': fields.float('Fixed domestic fees'), 'fees_dom_var': fields.float('Variable domestic fees (in percents)'), 'fees_int_fixed': fields.float('Fixed international fees'), 'fees_int_var': fields.float('Variable international fees (in percents)'), 'sequence': fields.integer('Sequence', help="Determine the display order"), } image = ecore.fields.Binary("Image", attachment=True, help="This field holds the image used for this provider, limited to 1024x1024px") image_medium = ecore.fields.Binary("Medium-sized image", compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, help="Medium-sized image of this provider. 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 image", compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, help="Small-sized image of this provider. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") @ecore.api.depends('image') def _compute_images(self): for rec in self: rec.image_medium = ecore.tools.image_resize_image_medium(rec.image) rec.image_small = ecore.tools.image_resize_image_small(rec.image) def _inverse_image_medium(self): for rec in self: rec.image = ecore.tools.image_resize_image_big(rec.image_medium) def _inverse_image_small(self): for rec in self: rec.image = ecore.tools.image_resize_image_big(rec.image_small) _defaults = { 'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id, 'environment': 'prod', 'website_published': False, 'auto_confirm': 'at_pay_confirm', 'pending_msg': '<i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.', 'done_msg': '<i>Done,</i> Your online payment has been successfully processed. Thank you for your order.', 'cancel_msg': '<i>Cancel,</i> Your payment has been cancelled.', 'error_msg': "<i>Error,</i> Please be aware that an error occurred during the transaction. The order has been confirmed but won't be paid. Don't hesitate to contact us if you have any questions on the status of your order." } def _check_required_if_provider(self, cr, uid, ids, context=None): """ If the field has 'required_if_provider="<provider>"' attribute, then it required if record.provider is <provider>. """ for acquirer in self.browse(cr, uid, ids, context=context): if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()): return False return True _constraints = [ (_check_required_if_provider, 'Required fields not filled', ['required for this provider']), ] def get_form_action_url(self, cr, uid, id, context=None): """ Returns the form action URL, for form-based acquirer implementations. """ acquirer = self.browse(cr, uid, id, context=context) if hasattr(self, '%s_get_form_action_url' % acquirer.provider): return getattr(self, '%s_get_form_action_url' % acquirer.provider)(cr, uid, id, context=context) return False def render(self, cr, uid, id, reference, amount, currency_id, partner_id=False, values=None, context=None): """ Renders the form template of the given acquirer as a qWeb template. :param string reference: the transaction reference :param float amount: the amount the buyer has to pay :param currency_id: currency id :param dict partner_id: optional partner_id to fill values :param dict values: a dictionary of values for the transction that is given to the acquirer-specific method generating the form values :param dict context: eCore context All templates will receive: - acquirer: the payment.acquirer browse record - user: the current user browse record - currency_id: id of the transaction currency - amount: amount of the transaction - reference: reference of the transaction - partner_*: partner-related values - partner: optional partner browse record - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME - 'cancel_url': URL if the client cancels the payment -> FIXME - 'error_url': URL if there is an issue with the payment -> FIXME - context: eCore context dictionary """ if context is None: context = {} if values is None: values = {} acquirer = self.browse(cr, uid, id, context=context) # reference and amount values.setdefault('reference', reference) amount = float_round(amount, 2) values.setdefault('amount', amount) # currency id currency_id = values.setdefault('currency_id', currency_id) if currency_id: currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context) else: currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id values['currency'] = currency # Fill partner_* using values['partner_id'] or partner_id arguement partner_id = values.get('partner_id', partner_id) if partner_id: partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) values.update({ 'partner': partner, 'partner_id': partner_id, 'partner_name': partner.name, 'partner_lang': partner.lang, 'partner_email': partner.email, 'partner_zip': partner.zip, 'partner_city': partner.city, 'partner_address': _partner_format_address(partner.street, partner.street2), 'partner_country_id': partner.country_id.id, 'partner_country': partner.country_id, 'partner_phone': partner.phone, 'partner_state': partner.state_id, }) if values.get('partner_name'): values.update({ 'partner_first_name': _partner_split_name(values.get('partner_name'))[0], 'partner_last_name': _partner_split_name(values.get('partner_name'))[1], }) # Fix address, country fields if not values.get('partner_address'): values['address'] = _partner_format_address(values.get('partner_street', ''), values.get('partner_street2', '')) if not values.get('partner_country') and values.get('partner_country_id'): values['country'] = self.pool['res.country'].browse(cr, uid, values.get('partner_country_id'), context=context) # compute fees fees_method_name = '%s_compute_fees' % acquirer.provider if hasattr(self, fees_method_name): fees = getattr(self, fees_method_name)(cr, uid, id, values['amount'], values['currency_id'], values['partner_country_id'], context=None) values['fees'] = float_round(fees, 2) # call <name>_form_generate_values to update the tx dict with acqurier specific values cust_method_name = '%s_form_generate_values' % (acquirer.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) values = method(cr, uid, id, values, context=context) values.update({ 'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)), 'submit_class': context.get('submit_class', 'btn btn-link'), 'submit_txt': context.get('submit_txt'), 'acquirer': acquirer, 'user': self.pool.get("res.users").browse(cr, uid, uid, context=context), 'context': context, 'type': values.get('type') or 'form', }) values.setdefault('return_url', False) # because render accepts view ids but not qweb -> need to use the xml_id return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, values, engine='ir.qweb', context=context) def _registration_render(self, cr, uid, id, partner_id, qweb_context=None, context=None): acquirer = self.browse(cr, uid, id, context=context) if qweb_context is None: qweb_context = {} qweb_context.update(id=id, partner_id=partner_id) method_name = '_%s_registration_form_generate_values' % (acquirer.provider,) if hasattr(self, method_name): method = getattr(self, method_name) qweb_context.update(method(cr, uid, id, qweb_context, context=context)) return self.pool['ir.ui.view'].render(cr, uid, acquirer.registration_view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context) def s2s_process(self, cr, uid, id, data, context=None): acquirer = self.browse(cr, uid, id, context=context) cust_method_name = '%s_s2s_form_process' % (acquirer.provider) if not self.s2s_validate(cr, uid, id, data, context=context): return False if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) return method(cr, uid, data, context=context) return True def s2s_validate(self, cr, uid, id, data, context=None): acquirer = self.browse(cr, uid, id, context=context) cust_method_name = '%s_s2s_form_validate' % (acquirer.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) return method(cr, uid, id, data, context=context) return True
class procurement_rule(osv.osv): _inherit = 'procurement.rule' def _get_action(self, cr, uid, context=None): result = super(procurement_rule, self)._get_action(cr, uid, context=context) return result + [('move', _('Move From Another Location'))] def _get_rules(self, cr, uid, ids, context=None): res = [] for route in self.browse(cr, uid, ids): res += [x.id for x in route.pull_ids] return res _columns = { 'location_id': fields.many2one('stock.location', 'Procurement Location'), 'location_src_id': fields.many2one('stock.location', 'Source Location', help="Source location is action=move"), 'route_id': fields.many2one('stock.location.route', 'Route', help="If route_id is False, the rule is global"), 'procure_method': fields.selection( [('make_to_stock', 'Take From Stock'), ('make_to_order', 'Create Procurement')], 'Move Supply Method', required=True, help= """Determines the procurement method of the stock move that will be generated: whether it will need to 'take from the available stock' in its source location or needs to ignore its stock and create a procurement over there.""" ), 'route_sequence': fields.related('route_id', 'sequence', string='Route Sequence', store={ 'stock.location.route': (_get_rules, ['sequence'], 10), 'procurement.rule': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 10), }), 'picking_type_id': fields.many2one( 'stock.picking.type', 'Picking Type', help= "Picking Type determines the way the picking should be shown in the view, reports, ..." ), 'delay': fields.integer('Number of Days'), 'partner_address_id': fields.many2one('res.partner', 'Partner Address'), 'propagate': fields.boolean( 'Propagate cancel and split', help= 'If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too' ), 'warehouse_id': fields.many2one('stock.warehouse', 'Served Warehouse', help='The warehouse this rule is for'), 'propagate_warehouse_id': fields.many2one( 'stock.warehouse', 'Warehouse to Propagate', help= "The warehouse to propagate on the created move/procurement, which can be different of the warehouse this rule is for (e.g for resupplying rules from another warehouse)" ), } _defaults = { 'procure_method': 'make_to_stock', 'propagate': True, 'delay': 0, }
class config(osv.Model): _name = 'google.drive.config' _description = "Google Drive templates config" def get_google_drive_url(self, cr, uid, config_id, res_id, template_id, context=None): config = self.browse(cr, SUPERUSER_ID, config_id, context=context) model = config.model_id filter_name = config.filter_id and config.filter_id.name or False record = self.pool.get(model.model).read(cr, uid, [res_id], context=context)[0] record.update({'model': model.name, 'filter': filter_name}) name_gdocs = config.name_template try: name_gdocs = name_gdocs % record except: raise UserError( _("At least one key cannot be found in your Google Drive name pattern" )) attach_pool = self.pool.get("ir.attachment") attach_ids = attach_pool.search(cr, uid, [('res_model', '=', model.model), ('name', '=', name_gdocs), ('res_id', '=', res_id)]) url = False if attach_ids: attachment = attach_pool.browse(cr, uid, attach_ids[0], context) url = attachment.url else: url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context).get('url') return url def get_access_token(self, cr, uid, scope=None, context=None): ir_config = self.pool['ir.config_parameter'] google_drive_refresh_token = ir_config.get_param( cr, SUPERUSER_ID, 'google_drive_refresh_token') if not google_drive_refresh_token: if self.pool['res.users']._is_admin(cr, uid, [uid]): model, action_id = self.pool[ 'ir.model.data'].get_object_reference( cr, uid, 'base_setup', 'action_general_configuration') msg = _( "You haven't configured 'Authorization Code' generated from google, Please generate and configure it ." ) raise ecore.exceptions.RedirectWarning( msg, action_id, _('Go to the configuration panel')) else: raise UserError( _("Google Drive is not yet configured. Please contact your administrator." )) google_drive_client_id = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_id') google_drive_client_secret = ir_config.get_param( cr, SUPERUSER_ID, 'google_drive_client_secret') #For Getting New Access Token With help of old Refresh Token data = werkzeug.url_encode( dict(client_id=google_drive_client_id, refresh_token=google_drive_refresh_token, client_secret=google_drive_client_secret, grant_type="refresh_token", scope=scope or 'https://www.googleapis.com/auth/drive')) headers = {"Content-type": "application/x-www-form-urlencoded"} try: req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers) content = urllib2.urlopen(req, timeout=TIMEOUT).read() except urllib2.HTTPError: if user_is_admin: model, action_id = self.pool[ 'ir.model.data'].get_object_reference( cr, uid, 'base_setup', 'action_general_configuration') msg = _( "Something went wrong during the token generation. Please request again an authorization code ." ) raise ecore.exceptions.RedirectWarning( msg, action_id, _('Go to the configuration panel')) else: raise UserError( _("Google Drive is not yet configured. Please contact your administrator." )) content = json.loads(content) return content.get('access_token') def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None): ir_config = self.pool['ir.config_parameter'] google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url') access_token = self.get_access_token(cr, uid, context=context) # Copy template in to drive with help of new access token request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % ( template_id, access_token) headers = {"Content-type": "application/x-www-form-urlencoded"} try: req = urllib2.Request(request_url, None, headers) parents = urllib2.urlopen(req, timeout=TIMEOUT).read() except urllib2.HTTPError: raise UserError( _("The Google Template cannot be found. Maybe it has been deleted." )) parents_dict = json.loads(parents) record_url = "Click on link to open Record in eCore\n %s/?db=%s#id=%s&model=%s" % ( google_web_base_url, cr.dbname, res_id, res_model) data = { "title": name_gdocs, "description": record_url, "parents": parents_dict['parents'] } request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % ( template_id, access_token) headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} data_json = json.dumps(data) # resp, content = Http().request(request_url, "POST", data_json, headers) req = urllib2.Request(request_url, data_json, headers) content = urllib2.urlopen(req, timeout=TIMEOUT).read() content = json.loads(content) res = {} if content.get('alternateLink'): attach_pool = self.pool.get("ir.attachment") attach_vals = { 'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink'] } res['id'] = attach_pool.create(cr, uid, attach_vals) # Commit in order to attach the document to the current object instance, even if the permissions has not been written. cr.commit() res['url'] = content['alternateLink'] key = self._get_key_from_url(res['url']) request_url = "https://www.googleapis.com/drive/v2/files/%s/permissions?emailMessage=This+is+a+drive+file+created+by+eCore&sendNotificationEmails=false&access_token=%s" % ( key, access_token) data = { 'role': 'writer', 'type': 'anyone', 'value': '', 'withLink': True } try: req = urllib2.Request(request_url, json.dumps(data), headers) urllib2.urlopen(req, timeout=TIMEOUT) except urllib2.HTTPError: raise self.pool.get('res.config.settings').get_config_warning( cr, _("The permission 'reader' for 'anyone with the link' has not been written on the document" ), context=context) user = self.pool['res.users'].browse(cr, uid, uid, context=context) if user.email: data = {'role': 'writer', 'type': 'user', 'value': user.email} try: req = urllib2.Request(request_url, json.dumps(data), headers) urllib2.urlopen(req, timeout=TIMEOUT) except urllib2.HTTPError: pass return res def get_google_drive_config(self, cr, uid, res_model, res_id, context=None): ''' Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It will first seek for a google.docs.config associated with the model `res_model` to find out what's the template of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name different than the default values). If no config is associated with the `res_model`, then a blank text document with a default name is created. :param res_model: the object for which the google doc is created :param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it) :return: the config id and config name ''' if not res_id: raise UserError( _("Creating google drive may only be done by one at a time.")) # check if a model is configured with a template config_ids = self.search(cr, uid, [('model_id', '=', res_model)], context=context) configs = [] for config in self.browse(cr, uid, config_ids, context=context): if config.filter_id: if (config.filter_id.user_id and config.filter_id.user_id.id != uid): #Private continue domain = [('id', 'in', [res_id])] + eval( config.filter_id.domain) local_context = context and context.copy() or {} local_context.update(eval(config.filter_id.context)) google_doc_configs = self.pool.get( config.filter_id.model_id).search(cr, uid, domain, context=local_context) if google_doc_configs: configs.append({'id': config.id, 'name': config.name}) else: configs.append({'id': config.id, 'name': config.name}) return configs def _get_key_from_url(self, url): mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", url) if mo: return mo.group(2) return None def _resource_get(self, cr, uid, ids, name, arg, context=None): result = {} for data in self.browse(cr, uid, ids, context): mo = self._get_key_from_url(data.google_drive_template_url) if mo: result[data.id] = mo else: raise UserError(_("Please enter a valid Google Document URL.")) return result def _client_id_get(self, cr, uid, ids, name, arg, context=None): result = {} client_id = self.pool['ir.config_parameter'].get_param( cr, SUPERUSER_ID, 'google_drive_client_id') for config_id in ids: result[config_id] = client_id return result _columns = { 'name': fields.char('Template Name', required=True), 'model_id': fields.many2one('ir.model', 'Model', ondelete='set null', required=True), 'model': fields.related('model_id', 'model', type='char', string='Model', readonly=True), 'filter_id': fields.many2one('ir.filters', 'Filter', domain="[('model_id', '=', model)]"), 'google_drive_template_url': fields.char('Template URL', required=True, size=1024), 'google_drive_resource_id': fields.function(_resource_get, type="char", string='Resource Id'), 'google_drive_client_id': fields.function(_client_id_get, type="char", string='Google Client '), 'name_template': fields.char( 'Google Drive Name Pattern', help= 'Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s', required=True), 'active': fields.boolean('Active'), } def onchange_model_id(self, cr, uid, ids, model_id, context=None): res = {} if model_id: model = self.pool['ir.model'].browse(cr, uid, model_id, context=context) res['value'] = {'model': model.model} else: res['value'] = {'filter_id': False, 'model': False} return res _defaults = { 'name_template': 'Document %(name)s', 'active': True, } def _check_model_id(self, cr, uid, ids, context=None): config_id = self.browse(cr, uid, ids[0], context=context) if config_id.filter_id and config_id.model_id.model != config_id.filter_id.model_id: return False return True _constraints = [ (_check_model_id, 'Model of selected filter is not matching with model of current template.', ['model_id', 'filter_id']), ] def get_google_scope(self): return 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file'
class hr_payslip_run(osv.osv): _inherit = 'hr.payslip.run' _description = 'Payslip Batches' _columns = { 'available_advice': fields.boolean( 'Made Payment Advice?', help= "If this box is checked which means that Payment Advice exists for current batch", readonly=False, copy=False), } def draft_payslip_run(self, cr, uid, ids, context=None): res = super(hr_payslip_run, self).draft_payslip_run(cr, uid, ids, context=context) self.write(cr, uid, ids, {'available_advice': False}, context=context) return res def create_advice(self, cr, uid, ids, context=None): payslip_pool = self.pool.get('hr.payslip') payslip_line_pool = self.pool.get('hr.payslip.line') advice_pool = self.pool.get('hr.payroll.advice') advice_line_pool = self.pool.get('hr.payroll.advice.line') users = self.pool.get('res.users').browse(cr, uid, [uid], context=context) for run in self.browse(cr, uid, ids, context=context): if run.available_advice: raise UserError( _("Payment advice already exists for %s, 'Set to Draft' to create a new advice." ) % (run.name, )) advice_data = { 'batch_id': run.id, 'company_id': users[0].company_id.id, 'name': run.name, 'date': run.date_end, 'bank_id': users[0].company_id.partner_id.bank_ids and users[0].company_id.partner_id.bank_ids[0].id or False } advice_id = advice_pool.create(cr, uid, advice_data, context=context) slip_ids = [] for slip_id in run.slip_ids: # TODO is it necessary to interleave the calls ? payslip_pool.signal_workflow(cr, uid, [slip_id.id], 'hr_verify_sheet') payslip_pool.signal_workflow(cr, uid, [slip_id.id], 'process_sheet') slip_ids.append(slip_id.id) for slip in payslip_pool.browse(cr, uid, slip_ids, context=context): if not slip.employee_id.bank_account_id or 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) return self.write(cr, uid, ids, {'available_advice': True})
class restaurant_table(osv.osv): _name = 'restaurant.table' _columns = { 'name': fields.char('Table Name', size=32, required=True, help='An internal identification of a table'), 'floor_id': fields.many2one('restaurant.floor', 'Floor'), 'shape': fields.selection([('square', 'Square'), ('round', 'Round')], 'Shape', required=True), 'position_h': fields.float( 'Horizontal Position', help= "The table's horizontal position from the left side to the table's center, in pixels" ), 'position_v': fields.float( 'Vertical Position', help= "The table's vertical position from the top to the table's center, in pixels" ), 'width': fields.float('Width', help="The table's width in pixels"), 'height': fields.float('Height', help="The table's height in pixels"), 'seats': fields.integer( 'Seats', help="The default number of customer served at this table."), 'color': fields.char( 'Color', help= "The table's color, expressed as a valid 'background' CSS property value" ), 'active': fields.boolean( 'Active', help= 'If false, the table is deactivated and will not be available in the point of sale' ), 'pos_order_ids': fields.one2many('pos.order', 'table_id', 'Pos Orders', help='The orders served at this table'), } _defaults = { 'shape': 'square', 'seats': 1, 'position_h': 10, 'position_v': 10, 'height': 50, 'width': 50, 'active': True, } def create_from_ui(self, cr, uid, table, context=None): """ create or modify a table from the point of sale UI. table contains the table's fields. If it contains an id, it will modify the existing table. It then returns the id of the table. """ if table.get('floor_id', False): floor_id = table['floor_id'][0] table['floor_id'] = floor_id if table.get('id', False): # Modifiy existing table table_id = table['id'] del table['id'] self.write(cr, uid, [table_id], table, context=context) else: table_id = self.create(cr, uid, table, context=context) return table_id
class consult_export_csv_excel_reportcustomer(osv.osv_memory): _name = 'consult.export.csv.excel.reportcustomer' _description = 'Exportar Reporte a Excel o CSV' _columns = { 'datas_fname': fields.char('File Name', size=256), 'file': fields.binary('Layout'), 'download_file': fields.boolean('Descargar Archivo'), 'cadena_decoding': fields.text('Binario sin encoding'), 'type': fields.selection( [('csv', 'CSV')], 'Tipo Exportacion', required=False, ), } _defaults = { 'download_file': False, 'type': 'csv', } def export_csv_file(self, cr, uid, ids, context=None): document_csv = "" active_ids = context['active_ids'] consult_obj = self.pool.get('stock.reportcustomer.model') if active_ids: for active in active_ids: da_list = [] for rec in self.browse(cr, uid, ids, context=None): consult_br = consult_obj.browse(cr, uid, active, context=None) da_list.append(consult_br.date) salto_line = "\n" cabeceras_p = "Cliente" + "," + "Fecha Inicio" + "," + "Fecha Fin" document_csv = document_csv + cabeceras_p linea_1 = consult_br.name.name+","+\ consult_br.date+","+consult_br.date_end document_csv = document_csv + salto_line + linea_1 + salto_line cabeceras_l = "Factura" + "," + "Fecha" + "," + "Monto" texto_x = "Facturas del Cliente" + "," + "," document_csv = document_csv + salto_line + texto_x document_csv = document_csv + salto_line + cabeceras_l detalle_lineas = "" for linea in consult_br.reportcustomer_invoice_lines: linea_str = "" if linea.invoice_id: linea_str = str(linea.invoice_id.number)+","+str(linea.invoice_id.date_invoice)+\ ","+str(linea.invoice_id.amount_total) detalle_lineas = detalle_lineas + salto_line + linea_str document_csv = document_csv + detalle_lineas + salto_line + salto_line cabeceras_l = "Producto"+","+"Cantidad"+","+\ "Unidad de Medida"+","+"Total Facturado" texto_x = "Detalle Productos Facturados" + "," + "," + "," document_csv = document_csv + salto_line + texto_x document_csv = document_csv + salto_line + cabeceras_l detalle_lineas = "" for linea in consult_br.reportcustomer_lines: linea_str = "" if linea.product_id: linea_str = str(linea.product_id.name)+\ ","+str(linea.qty)+","+str(linea.uom_id.name)+","+str(linea.amount_total) detalle_lineas = detalle_lineas + salto_line + linea_str document_csv = document_csv + detalle_lineas + salto_line + salto_line date = datetime.now().strftime('%d-%m-%Y') if len(da_list) > 1: datas_fname = "Reporte Facturacion Client " + str( date) + ".csv" # Nombre del Archivo else: datas_fname = "Reporte Facturacion Client " + consult_br.date + " - " + consult_br.date_end + ".csv" # Nombre del Archivo rec.write({ 'cadena_decoding': document_csv, 'datas_fname': datas_fname, 'file': base64.encodestring(document_csv), 'download_file': True }) return { 'type': 'ir.actions.act_window', 'res_model': 'consult.export.csv.excel.reportcustomer', 'view_mode': 'form', 'view_type': 'form', 'res_id': ids[0], 'views': [(False, 'form')], 'target': 'new', } def process_export(self, cr, uid, ids, context=None): for rec in self.browse(cr, uid, ids, context=context): if rec.type == 'csv': result = self.export_csv_file(cr, uid, ids, context=context) return result return True
class actions_server(osv.Model): """ Add website option in server actions. """ _name = 'ir.actions.server' _inherit = ['ir.actions.server'] def _compute_website_url(self, cr, uid, id, website_path, xml_id, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', context=context) link = website_path or xml_id or (id and '%d' % id) or '' if base_url and link: path = '%s/%s' % ('/website/action', link) return '%s' % urlparse.urljoin(base_url, path) return '' def _get_website_url(self, cr, uid, ids, name, args, context=None): res = dict.fromkeys(ids, False) for action in self.browse(cr, uid, ids, context=context): if action.state == 'code' and action.website_published: res[action.id] = self._compute_website_url(cr, uid, action.id, action.website_path, action.xml_id, context=context) return res _columns = { 'xml_id': fields.function(osv.osv.get_xml_id, type='char', string="External ID", help="ID of the action if defined in a XML file"), 'website_path': fields.char('Website Path'), 'website_url': fields.function( _get_website_url, type='char', string='Website URL', help='The full URL to access the server action through the website.' ), 'website_published': fields.boolean( 'Available on the Website', copy=False, help= 'A code server action can be executed from the website, using a dedicated' 'controller. The address is <base>/website/action/<website_path>.' 'Set this field as True to allow users to run this action. If it' 'set to is False the action cannot be run through the website.'), } def on_change_website_path(self, cr, uid, ids, website_path, xml_id, context=None): values = { 'website_url': self._compute_website_url(cr, uid, ids and ids[0] or None, website_path, xml_id, context=context) } return {'value': values} def _get_eval_context(self, cr, uid, action, context=None): """ Override to add the request object in eval_context. """ eval_context = super(actions_server, self)._get_eval_context(cr, uid, action, context=context) if action.state == 'code': eval_context['request'] = request return eval_context def run_action_code_multi(self, cr, uid, action, eval_context=None, context=None): """ Override to allow returning response the same way action is already returned by the basic server action behavior. Note that response has priority over action, avoid using both. """ res = super(actions_server, self).run_action_code_multi(cr, uid, action, eval_context, context) if 'response' in eval_context: return eval_context['response'] return res
class base_config_settings(osv.osv_memory): _name = 'base.config.settings' _inherit = 'res.config.settings' _columns = { 'group_light_multi_company': fields.boolean( 'Manage multiple companies', help= 'Work in multi-company environments, with appropriate security access between companies.', implied_group='base.group_light_multi_company'), 'module_share': fields.boolean('Allow documents sharing', help="""Share or embbed any screen of eCore."""), 'module_portal': fields.boolean( 'Activate the customer portal', help="""Give your customers access to their documents."""), 'module_auth_oauth': fields.boolean( 'Use external authentication providers, sign in with Google...'), 'module_base_import': fields.boolean( "Allow users to import data from CSV/XLS/XLSX/ODS files"), 'module_google_drive': fields.boolean('Attach Google documents to any record', help="""This installs the module google_docs."""), 'module_google_calendar': fields.boolean( 'Allow the users to synchronize their calendar with Google Calendar', help="""This installs the module google_calendar."""), 'module_inter_company_rules': fields.boolean( 'Manage Inter Company', help= """This installs the module inter_company_rules.\n Configure company rules to automatically create SO/PO when one of your company sells/buys to another of your company.""" ), 'company_share_partner': fields.boolean( 'Share partners to all companies', help= "Share your partners to all companies defined in your instance.\n" " * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n" " * Unchecked : Each company can see only its partner (partners where company is defined). Partners not related to a company are visible for all companies." ), } def open_company(self, cr, uid, ids, context=None): user = self.pool.get('res.users').browse(cr, uid, uid, context) return { 'type': 'ir.actions.act_window', 'name': 'My Company', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'res.company', 'res_id': user.company_id.id, 'target': 'current', } def get_default_company_share_partner(self, cr, uid, ids, fields, context=None): partner_rule = self.pool['ir.model.data'].xmlid_to_object( cr, uid, 'base.res_partner_rule', context=context) return {'company_share_partner': not bool(partner_rule.active)} def set_default_company_share_partner(self, cr, uid, ids, context=None): partner_rule = self.pool['ir.model.data'].xmlid_to_object( cr, uid, 'base.res_partner_rule', context=context) for wizard in self.browse(cr, uid, ids, context=context): self.pool['ir.rule'].write( cr, uid, [partner_rule.id], {'active': not bool(wizard.company_share_partner)}, context=context)
class sale_order(osv.osv): _inherit = 'sale.order' def _get_total(self, cr, uid, ids, name, arg, context=None): res = {} for order in self.browse(cr, uid, ids, context=context): total = 0.0 for line in order.order_line: total += line.price_subtotal + line.price_unit * ( (line.discount or 0.0) / 100.0) * line.product_uom_qty res[order.id] = total return res _columns = { 'access_token': fields.char('Security Token', required=True, copy=False), 'template_id': fields.many2one('sale.quote.template', 'Quotation Template', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }), 'website_description': fields.html('Description', translate=True), 'options': fields.one2many('sale.order.option', 'order_id', 'Optional Products Lines', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }, copy=True), 'amount_undiscounted': fields.function(_get_total, string='Amount Before Discount', type="float", digits=0), 'quote_viewed': fields.boolean('Quotation Viewed'), '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 _get_template_id(self, cr, uid, context=None): try: template_id = self.pool.get('ir.model.data').get_object_reference( cr, uid, 'website_quote', 'website_quote_template_default')[1] except ValueError: template_id = False return template_id _defaults = { 'access_token': lambda self, cr, uid, ctx={}: str(uuid.uuid4()), 'template_id': _get_template_id, } def open_quotation(self, cr, uid, quote_id, context=None): quote = self.browse(cr, uid, quote_id[0], context=context) self.write(cr, uid, quote_id[0], {'quote_viewed': True}, context=context) return { 'type': 'ir.actions.act_url', 'target': 'self', 'url': '/quote/%s/%s' % (quote.id, quote.access_token) } def onchange_template_id(self, cr, uid, ids, template_id, partner=False, fiscal_position_id=False, pricelist_id=False, context=None): if not template_id: return {} if partner: context = dict(context or {}) context['lang'] = self.pool['res.partner'].browse( cr, uid, partner, context).lang pricelist_obj = self.pool['product.pricelist'] lines = [(5, )] quote_template = self.pool.get('sale.quote.template').browse( cr, uid, template_id, context=context) for line in quote_template.quote_line: res = self.pool.get('sale.order.line').product_id_change( cr, uid, False, False, line.product_id.id, line.product_uom_qty, line.product_uom_id.id, line.product_uom_qty, line.product_uom_id.id, line.name, partner, False, True, time.strftime('%Y-%m-%d'), False, fiscal_position_id, True, context) data = res.get('value', {}) if pricelist_id: uom_context = context.copy() uom_context['uom'] = line.product_uom_id.id price = pricelist_obj.price_get( cr, uid, [pricelist_id], line.product_id.id, 1, context=uom_context)[pricelist_id] else: price = line.price_unit if 'tax_id' in data: data['tax_id'] = [(6, 0, data['tax_id'])] else: fpos = (fiscal_position_id and self.pool['account.fiscal.position'].browse( cr, uid, fiscal_position_id)) or False taxes = fpos.map_tax( line.product_id.product_tmpl_id.taxes_id ).ids if fpos else line.product_id.product_tmpl_id.taxes_id.ids data['tax_id'] = [(6, 0, taxes)] data.update({ 'name': line.name, 'price_unit': price, 'discount': line.discount, 'product_uom_qty': line.product_uom_qty, 'product_id': line.product_id.id, 'product_uom': line.product_uom_id.id, 'website_description': line.website_description, 'state': 'draft', }) lines.append((0, 0, data)) options = [] for option in quote_template.options: if pricelist_id: uom_context = context.copy() uom_context['uom'] = option.uom_id.id price = pricelist_obj.price_get( cr, uid, [pricelist_id], option.product_id.id, 1, context=uom_context)[pricelist_id] else: price = option.price_unit options.append((0, 0, { 'product_id': option.product_id.id, 'name': option.name, 'quantity': option.quantity, 'uom_id': option.uom_id.id, 'price_unit': price, 'discount': option.discount, 'website_description': option.website_description, })) date = False if quote_template.number_of_days > 0: date = (datetime.datetime.now() + datetime.timedelta( quote_template.number_of_days)).strftime("%Y-%m-%d") data = { 'order_line': lines, 'website_description': quote_template.website_description, 'options': options, 'validity_date': date, 'require_payment': quote_template.require_payment } if quote_template.note: data['note'] = quote_template.note return {'value': data} def recommended_products(self, cr, uid, ids, context=None): order_line = self.browse(cr, uid, ids[0], context=context).order_line product_pool = self.pool.get('product.product') products = [] for line in order_line: products += line.product_id.product_tmpl_id.recommended_products( context=context) return products 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 online quote if exists. """ quote = self.browse(cr, uid, ids[0], context=context) if not quote.template_id: return super(sale_order, self).get_access_action(cr, uid, ids, context=context) return { 'type': 'ir.actions.act_url', 'url': '/quote/%s' % quote.id, 'target': 'self', 'res_id': quote.id, } def action_quotation_send(self, cr, uid, ids, context=None): action = super(sale_order, self).action_quotation_send(cr, uid, ids, context=context) ir_model_data = self.pool.get('ir.model.data') quote_template_id = self.read(cr, uid, ids, ['template_id'], context=context)[0]['template_id'] if quote_template_id: try: template_id = ir_model_data.get_object_reference( cr, uid, 'website_quote', 'email_template_edi_sale')[1] except ValueError: pass else: action['context'].update({ 'default_template_id': template_id, 'default_use_template': True }) return action def _confirm_online_quote(self, cr, uid, order_id, tx, context=None): """ Payment callback: validate the order and write tx details in chatter """ order = self.browse(cr, uid, order_id, context=context) # create draft invoice if transaction is ok if tx and tx.state == 'done': if order.state in ['draft', 'sent']: self.signal_workflow(cr, SUPERUSER_ID, [order.id], 'manual_invoice', context=context) message = _('Order payed by %s. Transaction: %s. Amount: %s.') % ( tx.partner_id.name, tx.acquirer_reference, tx.amount) self.message_post(cr, uid, order_id, body=message, type='comment', subtype='mt_comment', context=context) return True return False def create(self, cr, uid, values, context=None): if not values.get('template_id'): defaults = self.default_get(cr, uid, ['template_id'], context=context) template_values = self.onchange_template_id( cr, uid, [], defaults.get('template_id'), partner=values.get('partner_id'), fiscal_position_id=values.get('fiscal_position'), context=context).get('value', {}) values = dict(template_values, **values) return super(sale_order, self).create(cr, uid, values, context=context)
class crm_configuration(osv.TransientModel): _name = 'sale.config.settings' _inherit = ['sale.config.settings', 'fetchmail.config.settings'] _columns = { 'generate_sales_team_alias': fields.boolean( "Automatically generate an email alias at the sales team creation", help= "eCore will generate an email alias based on the sales team name"), 'alias_prefix': fields.char('Default Alias Name for Leads'), 'alias_domain': fields.char('Alias Domain'), 'group_use_lead': fields.selection([ (0, "Each mail sent to the alias creates a new opportunity"), (1, "Use leads if you need a qualification step before creating an opportunity or a customer" ) ], "Leads", implied_group='crm.group_use_lead'), 'module_crm_voip': fields.boolean("VoIP integration", help="Integration with Asterisk"), 'module_website_sign': fields.boolean("eCore Sign"), } _defaults = { 'alias_domain': lambda self, cr, uid, context: self.pool["ir.config_parameter"]. get_param(cr, uid, "mail.catchall.domain", context=context), } def _find_default_lead_alias_id(self, cr, uid, context=None): alias_id = self.pool['ir.model.data'].xmlid_to_res_id( cr, uid, 'crm.mail_alias_lead_info') if not alias_id: alias_ids = self.pool['mail.alias'].search( cr, uid, [('alias_model_id.model', '=', 'crm.lead'), ('alias_force_thread_id', '=', False), ('alias_parent_model_id.model', '=', 'crm.team'), ('alias_parent_thread_id', '=', False), ('alias_defaults', '=', '{}')], context=context) alias_id = alias_ids and alias_ids[0] or False return alias_id def get_default_generate_sales_team_alias(self, cr, uid, ids, context=None): return { 'generate_sales_team_alias': self.pool['ir.values'].get_default(cr, uid, 'sales.config.settings', 'generate_sales_team_alias') } def set_default_generate_sales_team_alias(self, cr, uid, ids, context=None): config_value = self.browse(cr, uid, ids, context=context).generate_sales_team_alias self.pool['ir.values'].set_default(cr, uid, 'sales.config.settings', 'generate_sales_team_alias', config_value) def get_default_alias_prefix(self, cr, uid, ids, context=None): alias_name = False alias_id = self._find_default_lead_alias_id(cr, uid, context=context) if alias_id: alias_name = self.pool['mail.alias'].browse( cr, uid, alias_id, context=context).alias_name return {'alias_prefix': alias_name} def set_default_alias_prefix(self, cr, uid, ids, context=None): mail_alias = self.pool['mail.alias'] for record in self.browse(cr, uid, ids, context=context): alias_id = self._find_default_lead_alias_id(cr, uid, context=context) if not alias_id: create_ctx = dict(context, alias_model_name='crm.lead', alias_parent_model_name='crm.team') alias_id = self.pool['mail.alias'].create( cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx) else: mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context) return True
class MergePartnerAutomatic(osv.TransientModel): """ The idea behind this wizard is to create a list of potential partners to merge. We use two objects, the first one is the wizard for the end-user. And the second will contain the partner list to merge. """ _name = 'base.partner.merge.automatic.wizard' _columns = { # Group by 'group_by_email': fields.boolean('Email'), 'group_by_name': fields.boolean('Name'), 'group_by_is_company': fields.boolean('Is Company'), 'group_by_vat': fields.boolean('VAT'), 'group_by_parent_id': fields.boolean('Parent Company'), 'state': fields.selection([('option', 'Option'), ('selection', 'Selection'), ('finished', 'Finished')], 'State', readonly=True, required=True), 'number_group': fields.integer("Group of Contacts", readonly=True), 'current_line_id': fields.many2one('base.partner.merge.line', 'Current Line'), 'line_ids': fields.one2many('base.partner.merge.line', 'wizard_id', 'Lines'), 'partner_ids': fields.many2many('res.partner', string='Contacts'), 'dst_partner_id': fields.many2one('res.partner', string='Destination Contact'), 'exclude_contact': fields.boolean('A user associated to the contact'), 'exclude_journal_item': fields.boolean('Journal Items associated to the contact'), 'maximum_group': fields.integer("Maximum of Group of Contacts"), } def default_get(self, cr, uid, fields, context=None): if context is None: context = {} res = super(MergePartnerAutomatic, self).default_get(cr, uid, fields, context) if context.get('active_model') == 'res.partner' and context.get( 'active_ids'): partner_ids = context['active_ids'] res['state'] = 'selection' res['partner_ids'] = partner_ids res['dst_partner_id'] = self._get_ordered_partner( cr, uid, partner_ids, context=context)[-1].id return res _defaults = {'state': 'option'} def get_fk_on(self, cr, table): q = """ SELECT cl1.relname as table, att1.attname as column FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, pg_attribute as att1, pg_attribute as att2 WHERE con.conrelid = cl1.oid AND con.confrelid = cl2.oid AND array_lower(con.conkey, 1) = 1 AND con.conkey[1] = att1.attnum AND att1.attrelid = cl1.oid AND cl2.relname = %s AND att2.attname = 'id' AND array_lower(con.confkey, 1) = 1 AND con.confkey[1] = att2.attnum AND att2.attrelid = cl2.oid AND con.contype = 'f' """ return cr.execute(q, (table, )) def _update_foreign_keys(self, cr, uid, src_partners, dst_partner, context=None): _logger.debug( '_update_foreign_keys for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners))) # find the many2one relation to a partner proxy = self.pool.get('res.partner') self.get_fk_on(cr, 'res_partner') # ignore two tables for table, column in cr.fetchall(): if 'base_partner_merge_' in table: continue partner_ids = tuple(map(int, src_partners)) query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % ( table) cr.execute(query, ()) columns = [] for data in cr.fetchall(): if data[0] != column: columns.append(data[0]) query_dic = { 'table': table, 'column': column, 'value': columns[0], } if len(columns) <= 1: # unique key treated query = """ UPDATE "%(table)s" as ___tu SET %(column)s = %%s WHERE %(column)s = %%s AND NOT EXISTS ( SELECT 1 FROM "%(table)s" as ___tw WHERE %(column)s = %%s AND ___tu.%(value)s = ___tw.%(value)s )""" % query_dic for partner_id in partner_ids: cr.execute(query, (dst_partner.id, partner_id, dst_partner.id)) else: try: with mute_logger('ecore.sql_db'), cr.savepoint(): query = 'UPDATE "%(table)s" SET %(column)s = %%s WHERE %(column)s IN %%s' % query_dic cr.execute(query, ( dst_partner.id, partner_ids, )) if column == proxy._parent_name and table == 'res_partner': query = """ WITH RECURSIVE cycle(id, parent_id) AS ( SELECT id, parent_id FROM res_partner UNION SELECT cycle.id, res_partner.parent_id FROM res_partner, cycle WHERE res_partner.id = cycle.parent_id AND cycle.id != cycle.parent_id ) SELECT id FROM cycle WHERE id = parent_id AND id = %s """ cr.execute(query, (dst_partner.id, )) except psycopg2.Error: # updating fails, most likely due to a violated unique constraint # keeping record with nonexistent partner_id is useless, better delete it query = 'DELETE FROM %(table)s WHERE %(column)s = %%s' % query_dic cr.execute(query, (partner_id, )) def _update_reference_fields(self, cr, uid, src_partners, dst_partner, context=None): _logger.debug( '_update_reference_fields for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners))) def update_records(model, src, field_model='model', field_id='res_id', context=None): proxy = self.pool.get(model) if proxy is None: return domain = [(field_model, '=', 'res.partner'), (field_id, '=', src.id)] ids = proxy.search(cr, ecore.SUPERUSER_ID, domain, context=context) try: with mute_logger('ecore.sql_db'), cr.savepoint(): return proxy.write(cr, ecore.SUPERUSER_ID, ids, {field_id: dst_partner.id}, context=context) except psycopg2.Error: # updating fails, most likely due to a violated unique constraint # keeping record with nonexistent partner_id is useless, better delete it return proxy.unlink(cr, ecore.SUPERUSER_ID, ids, context=context) update_records = functools.partial(update_records, context=context) for partner in src_partners: update_records('calendar', src=partner, field_model='model_id.model') update_records('ir.attachment', src=partner, field_model='res_model') update_records('mail.followers', src=partner, field_model='res_model') update_records('mail.message', src=partner) update_records('marketing.campaign.workitem', src=partner, field_model='object_id.model') update_records('ir.model.data', src=partner) proxy = self.pool['ir.model.fields'] domain = [('ttype', '=', 'reference')] record_ids = proxy.search(cr, ecore.SUPERUSER_ID, domain, context=context) for record in proxy.browse(cr, ecore.SUPERUSER_ID, record_ids, context=context): try: proxy_model = self.pool[record.model] column = proxy_model._columns[record.name] except KeyError: # unknown model or field => skip continue if isinstance(column, fields.function): continue for partner in src_partners: domain = [(record.name, '=', 'res.partner,%d' % partner.id)] model_ids = proxy_model.search(cr, ecore.SUPERUSER_ID, domain, context=context) values = { record.name: 'res.partner,%d' % dst_partner.id, } proxy_model.write(cr, ecore.SUPERUSER_ID, model_ids, values, context=context) def _update_values(self, cr, uid, src_partners, dst_partner, context=None): _logger.debug( '_update_values for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners))) columns = dst_partner._columns def write_serializer(column, item): if isinstance(item, browse_record): return item.id else: return item values = dict() for column, field in columns.iteritems(): if field._type not in ('many2many', 'one2many') and not isinstance( field, fields.function): for item in itertools.chain(src_partners, [dst_partner]): if item[column]: values[column] = write_serializer(column, item[column]) values.pop('id', None) parent_id = values.pop('parent_id', None) dst_partner.write(values) if parent_id and parent_id != dst_partner.id: try: dst_partner.write({'parent_id': parent_id}) except ValidationError: _logger.info( 'Skip recursive partner hierarchies for parent_id %s of partner: %s', parent_id, dst_partner.id) @mute_logger('ecore.osv.expression', 'ecore.models') def _merge(self, cr, uid, partner_ids, dst_partner=None, context=None): proxy = self.pool.get('res.partner') partner_ids = proxy.exists(cr, uid, list(partner_ids), context=context) if len(partner_ids) < 2: return if len(partner_ids) > 3: raise UserError( _("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed." )) if ecore.SUPERUSER_ID != uid and len( set(partner.email for partner in proxy.browse( cr, uid, partner_ids, context=context))) > 1: raise UserError( _("All contacts must have the same email. Only the Administrator can merge contacts with different emails." )) if dst_partner and dst_partner.id in partner_ids: src_partners = proxy.browse( cr, uid, [id for id in partner_ids if id != dst_partner.id], context=context) else: ordered_partners = self._get_ordered_partner( cr, uid, partner_ids, context) dst_partner = ordered_partners[-1] src_partners = ordered_partners[:-1] _logger.info("dst_partner: %s", dst_partner.id) if ecore.SUPERUSER_ID != uid and self._model_is_installed(cr, uid, 'account.move.line', context=context) and \ self.pool.get('account.move.line').search(cr, ecore.SUPERUSER_ID, [('partner_id', 'in', [partner.id for partner in src_partners])], context=context): raise UserError( _("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items." )) call_it = lambda function: function( cr, uid, src_partners, dst_partner, context=context) call_it(self._update_foreign_keys) call_it(self._update_reference_fields) call_it(self._update_values) _logger.info('(uid = %s) merged the partners %r with %s', uid, list(map(operator.attrgetter('id'), src_partners)), dst_partner.id) dst_partner.message_post(body='%s %s' % (_("Merged with the following partners:"), ", ".join('%s<%s>(ID %s)' % (p.name, p.email or 'n/a', p.id) for p in src_partners))) for partner in src_partners: partner.unlink() def clean_emails(self, cr, uid, context=None): """ Clean the email address of the partner, if there is an email field with a mimum of two addresses, the system will create a new partner, with the information of the previous one and will copy the new cleaned email into the email field. """ context = dict(context or {}) proxy_model = self.pool['ir.model.fields'] field_ids = proxy_model.search(cr, uid, [('model', '=', 'res.partner'), ('ttype', 'like', '%2many')], context=context) fields = proxy_model.read(cr, uid, field_ids, context=context) reset_fields = dict((field['name'], []) for field in fields) proxy_partner = self.pool['res.partner'] context['active_test'] = False ids = proxy_partner.search(cr, uid, [], context=context) fields = ['name', 'var' 'partner_id' 'is_company', 'email'] partners = proxy_partner.read(cr, uid, ids, fields, context=context) partners.sort(key=operator.itemgetter('id')) partners_len = len(partners) _logger.info('partner_len: %r', partners_len) for idx, partner in enumerate(partners): if not partner['email']: continue percent = (idx / float(partners_len)) * 100.0 _logger.info('idx: %r', idx) _logger.info('percent: %r', percent) try: emails = sanitize_email(partner['email']) head, tail = emails[:1], emails[1:] email = head[0] if head else False proxy_partner.write(cr, uid, [partner['id']], {'email': email}, context=context) for email in tail: values = dict(reset_fields, email=email) proxy_partner.copy(cr, uid, partner['id'], values, context=context) except Exception: _logger.exception("There is a problem with this partner: %r", partner) raise return True def close_cb(self, cr, uid, ids, context=None): return {'type': 'ir.actions.act_window_close'} def _generate_query(self, fields, maximum_group=100): sql_fields = [] for field in fields: if field in ['email', 'name']: sql_fields.append('lower(%s)' % field) elif field in ['vat']: sql_fields.append("replace(%s, ' ', '')" % field) else: sql_fields.append(field) group_fields = ', '.join(sql_fields) filters = [] for field in fields: if field in ['email', 'name', 'vat']: filters.append((field, 'IS NOT', 'NULL')) criteria = ' AND '.join('%s %s %s' % (field, operator, value) for field, operator, value in filters) text = [ "SELECT min(id), array_agg(id)", "FROM res_partner", ] if criteria: text.append('WHERE %s' % criteria) text.extend([ "GROUP BY %s" % group_fields, "HAVING COUNT(*) >= 2", "ORDER BY min(id)", ]) if maximum_group: text.extend([ "LIMIT %s" % maximum_group, ]) return ' '.join(text) def _compute_selected_groupby(self, this): group_by_str = 'group_by_' group_by_len = len(group_by_str) fields = [ key[group_by_len:] for key in self._columns.keys() if key.startswith(group_by_str) ] groups = [ field for field in fields if getattr(this, '%s%s' % (group_by_str, field), False) ] if not groups: raise UserError( _("You have to specify a filter for your selection")) return groups def next_cb(self, cr, uid, ids, context=None): """ Don't compute any thing """ context = dict(context or {}, active_test=False) this = self.browse(cr, uid, ids[0], context=context) if this.current_line_id: this.current_line_id.unlink() return self._next_screen(cr, uid, this, context) def _get_ordered_partner(self, cr, uid, partner_ids, context=None): partners = self.pool.get('res.partner').browse(cr, uid, list(partner_ids), context=context) ordered_partners = sorted(sorted( partners, key=operator.attrgetter('create_date'), reverse=True), key=operator.attrgetter('active'), reverse=True) return ordered_partners def _next_screen(self, cr, uid, this, context=None): this.refresh() values = {} if this.line_ids: # in this case, we try to find the next record. current_line = this.line_ids[0] current_partner_ids = literal_eval(current_line.aggr_ids) values.update({ 'current_line_id': current_line.id, 'partner_ids': [(6, 0, current_partner_ids)], 'dst_partner_id': self._get_ordered_partner(cr, uid, current_partner_ids, context)[-1].id, 'state': 'selection', }) else: values.update({ 'current_line_id': False, 'partner_ids': [], 'state': 'finished', }) this.write(values) return { 'type': 'ir.actions.act_window', 'res_model': this._name, 'res_id': this.id, 'view_mode': 'form', 'target': 'new', } def _model_is_installed(self, cr, uid, model, context=None): proxy = self.pool.get('ir.model') domain = [('model', '=', model)] return proxy.search_count(cr, uid, domain, context=context) > 0 def _partner_use_in(self, cr, uid, aggr_ids, models, context=None): """ Check if there is no occurence of this group of partner in the selected model """ for model, field in models.iteritems(): proxy = self.pool.get(model) domain = [(field, 'in', aggr_ids)] if proxy.search_count(cr, uid, domain, context=context): return True return False def compute_models(self, cr, uid, ids, context=None): """ Compute the different models needed by the system if you want to exclude some partners. """ assert is_integer_list(ids) this = self.browse(cr, uid, ids[0], context=context) models = {} if this.exclude_contact: models['res.users'] = 'partner_id' if self._model_is_installed( cr, uid, 'account.move.line', context=context) and this.exclude_journal_item: models['account.move.line'] = 'partner_id' return models def _process_query(self, cr, uid, ids, query, context=None): """ Execute the select request and write the result in this wizard """ proxy = self.pool.get('base.partner.merge.line') this = self.browse(cr, uid, ids[0], context=context) models = self.compute_models(cr, uid, ids, context=context) cr.execute(query) counter = 0 for min_id, aggr_ids in cr.fetchall(): if models and self._partner_use_in( cr, uid, aggr_ids, models, context=context): continue values = { 'wizard_id': this.id, 'min_id': min_id, 'aggr_ids': aggr_ids, } proxy.create(cr, uid, values, context=context) counter += 1 values = { 'state': 'selection', 'number_group': counter, } this.write(values) _logger.info("counter: %s", counter) def start_process_cb(self, cr, uid, ids, context=None): """ Start the process. * Compute the selected groups (with duplication) * If the user has selected the 'exclude_XXX' fields, avoid the partners. """ assert is_integer_list(ids) context = dict(context or {}, active_test=False) this = self.browse(cr, uid, ids[0], context=context) groups = self._compute_selected_groupby(this) query = self._generate_query(groups, this.maximum_group) self._process_query(cr, uid, ids, query, context=context) return self._next_screen(cr, uid, this, context) def automatic_process_cb(self, cr, uid, ids, context=None): assert is_integer_list(ids) this = self.browse(cr, uid, ids[0], context=context) this.start_process_cb() this.refresh() for line in this.line_ids: partner_ids = literal_eval(line.aggr_ids) self._merge(cr, uid, partner_ids, context=context) line.unlink() cr.commit() this.write({'state': 'finished'}) return { 'type': 'ir.actions.act_window', 'res_model': this._name, 'res_id': this.id, 'view_mode': 'form', 'target': 'new', } def parent_migration_process_cb(self, cr, uid, ids, context=None): assert is_integer_list(ids) context = dict(context or {}, active_test=False) this = self.browse(cr, uid, ids[0], context=context) query = """ SELECT min(p1.id), array_agg(DISTINCT p1.id) FROM res_partner as p1 INNER join res_partner as p2 ON p1.email = p2.email AND p1.name = p2.name AND (p1.parent_id = p2.id OR p1.id = p2.parent_id) WHERE p2.id IS NOT NULL GROUP BY p1.email, p1.name, CASE WHEN p1.parent_id = p2.id THEN p2.id ELSE p1.id END HAVING COUNT(*) >= 2 ORDER BY min(p1.id) """ self._process_query(cr, uid, ids, query, context=context) for line in this.line_ids: partner_ids = literal_eval(line.aggr_ids) self._merge(cr, uid, partner_ids, context=context) line.unlink() cr.commit() this.write({'state': 'finished'}) cr.execute(""" UPDATE res_partner SET is_company = NULL, parent_id = NULL WHERE parent_id = id """) return { 'type': 'ir.actions.act_window', 'res_model': this._name, 'res_id': this.id, 'view_mode': 'form', 'target': 'new', } def update_all_process_cb(self, cr, uid, ids, context=None): assert is_integer_list(ids) # WITH RECURSIVE cycle(id, parent_id) AS ( # SELECT id, parent_id FROM res_partner # UNION # SELECT cycle.id, res_partner.parent_id # FROM res_partner, cycle # WHERE res_partner.id = cycle.parent_id AND # cycle.id != cycle.parent_id # ) # UPDATE res_partner # SET parent_id = NULL # WHERE id in (SELECT id FROM cycle WHERE id = parent_id); this = self.browse(cr, uid, ids[0], context=context) self.parent_migration_process_cb(cr, uid, ids, context=None) list_merge = [ { 'group_by_vat': True, 'group_by_email': True, 'group_by_name': True }, # {'group_by_name': True, 'group_by_is_company': True, 'group_by_parent_id': True}, # {'group_by_email': True, 'group_by_is_company': True, 'group_by_parent_id': True}, # {'group_by_name': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True}, # {'group_by_email': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True}, # {'group_by_email': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True}, # {'group_by_name': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True} ] for merge_value in list_merge: id = self.create(cr, uid, merge_value, context=context) self.automatic_process_cb(cr, uid, [id], context=context) cr.execute(""" UPDATE res_partner SET is_company = NULL WHERE parent_id IS NOT NULL AND is_company IS NOT NULL """) # cr.execute(""" # UPDATE # res_partner as p1 # SET # is_company = NULL, # parent_id = ( # SELECT p2.id # FROM res_partner as p2 # WHERE p2.email = p1.email AND # p2.parent_id != p2.id # LIMIT 1 # ) # WHERE # p1.parent_id = p1.id # """) return self._next_screen(cr, uid, this, context) def merge_cb(self, cr, uid, ids, context=None): assert is_integer_list(ids) context = dict(context or {}, active_test=False) this = self.browse(cr, uid, ids[0], context=context) partner_ids = set(map(int, this.partner_ids)) if not partner_ids: this.write({'state': 'finished'}) return { 'type': 'ir.actions.act_window', 'res_model': this._name, 'res_id': this.id, 'view_mode': 'form', 'target': 'new', } self._merge(cr, uid, partner_ids, this.dst_partner_id, context=context) if this.current_line_id: this.current_line_id.unlink() return self._next_screen(cr, uid, this, context) def auto_set_parent_id(self, cr, uid, ids, context=None): assert is_integer_list(ids) # select partner who have one least invoice partner_treated = ['@gmail.com'] cr.execute(""" SELECT p.id, p.email FROM res_partner as p LEFT JOIN account_invoice as a ON p.id = a.partner_id AND a.state in ('open','paid') WHERE p.grade_id is NOT NULL GROUP BY p.id ORDER BY COUNT(a.id) DESC """) re_email = re.compile(r".*@") for id, email in cr.fetchall(): # check email domain email = re_email.sub("@", email or "") if not email or email in partner_treated: continue partner_treated.append(email) # don't update the partners if they are more of one who have invoice cr.execute( """ SELECT * FROM res_partner as p WHERE p.id != %s AND p.email LIKE %s AND EXISTS (SELECT * FROM account_invoice as a WHERE p.id = a.partner_id AND a.state in ('open','paid')) """, (id, '%' + email)) if len(cr.fetchall()) > 1: _logger.info("%s MORE OF ONE COMPANY", email) continue # to display changed values cr.execute( """ SELECT id,email FROM res_partner WHERE parent_id != %s AND id != %s AND email LIKE %s """, (id, id, '%' + email)) _logger.info("%r", cr.fetchall()) # upgrade cr.execute( """ UPDATE res_partner SET parent_id = %s WHERE id != %s AND email LIKE %s """, (id, id, '%' + email)) return False
class website_menu(osv.osv): _name = "website.menu" _description = "Website Menu" _columns = { 'name': fields.char('Menu', required=True, translate=True), 'url': fields.char('Url'), 'new_window': fields.boolean('New Window'), 'sequence': fields.integer('Sequence'), # TODO: support multiwebsite once done for ir.ui.views 'website_id': fields.many2one('website', 'Website'), 'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"), 'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'), 'parent_left': fields.integer('Parent Left', select=True), 'parent_right': fields.integer('Parent Right', select=True), } def __defaults_sequence(self, cr, uid, context): menu = self.search_read(cr, uid, [(1, "=", 1)], ["sequence"], limit=1, order="sequence DESC", context=context) return menu and menu[0]["sequence"] or 0 _defaults = { 'url': '', 'sequence': __defaults_sequence, 'new_window': False, } _parent_store = True _parent_order = 'sequence' _order = "sequence" # would be better to take a menu_id as argument def get_tree(self, cr, uid, website_id, menu_id=None, context=None): def make_tree(node): menu_node = dict( id=node.id, name=node.name, url=node.url, new_window=node.new_window, sequence=node.sequence, parent_id=node.parent_id.id, children=[], ) for child in node.child_id: menu_node['children'].append(make_tree(child)) return menu_node if menu_id: menu = self.browse(cr, uid, menu_id, context=context) else: menu = self.pool.get('website').browse(cr, uid, website_id, context=context).menu_id return make_tree(menu) def save(self, cr, uid, website_id, data, context=None): def replace_id(old_id, new_id): for menu in data['data']: if menu['id'] == old_id: menu['id'] = new_id if menu['parent_id'] == old_id: menu['parent_id'] = new_id to_delete = data['to_delete'] if to_delete: self.unlink(cr, uid, to_delete, context=context) for menu in data['data']: mid = menu['id'] if isinstance(mid, basestring): new_id = self.create(cr, uid, {'name': menu['name']}, context=context) replace_id(mid, new_id) for menu in data['data']: self.write(cr, uid, [menu['id']], menu, context=context) return True
class website(osv.osv): def _get_menu(self, cr, uid, ids, name, arg, context=None): res = {} menu_obj = self.pool.get('website.menu') for id in ids: menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False), ('website_id', '=', id)], order='id', context=context) res[id] = menu_ids and menu_ids[0] or False return res _name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco _description = "Website" _columns = { 'name': fields.char('Website Name'), 'domain': fields.char('Website Domain'), 'company_id': fields.many2one('res.company', string="Company"), 'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'), 'default_lang_id': fields.many2one('res.lang', string="Default language"), 'default_lang_code': fields.related('default_lang_id', 'code', type="char", string="Default language code", store=True), 'social_twitter': fields.char('Twitter Account'), 'social_facebook': fields.char('Facebook Account'), 'social_github': fields.char('GitHub Account'), 'social_linkedin': fields.char('LinkedIn Account'), 'social_youtube': fields.char('Youtube Account'), 'social_googleplus': fields.char('Google+ Account'), 'google_analytics_key': fields.char('Google Analytics Key'), 'user_id': fields.many2one('res.users', string='Public User'), 'compress_html': fields.boolean('Compress HTML'), 'cdn_activated': fields.boolean('Activate CDN for assets'), 'cdn_url': fields.char('CDN Base URL'), 'cdn_filters': fields.text( 'CDN Filters', help= "URL matching those filters will be rewritten using the CDN Base URL" ), 'partner_id': fields.related('user_id', 'partner_id', type='many2one', relation='res.partner', string='Public Partner'), 'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu') } _defaults = { 'user_id': lambda self, cr, uid, c: self.pool['ir.model.data'].xmlid_to_res_id( cr, ecore.SUPERUSER_ID, 'base.public_user'), 'company_id': lambda self, cr, uid, c: self.pool['ir.model.data'].xmlid_to_res_id( cr, ecore.SUPERUSER_ID, 'base.main_company'), 'compress_html': False, 'cdn_activated': False, 'cdn_url': '', 'cdn_filters': '\n'.join(DEFAULT_CDN_FILTERS), } # cf. Wizard hack in website_views.xml def noop(self, *args, **kwargs): pass def write(self, cr, uid, ids, vals, context=None): self._get_languages.clear_cache(self) return super(website, self).write(cr, uid, ids, vals, context) def new_page(self, cr, uid, name, template='website.default_page', ispage=True, context=None): context = context or {} imd = self.pool.get('ir.model.data') view = self.pool.get('ir.ui.view') template_module, template_name = template.split('.') # completely arbitrary max_length page_name = slugify(name, max_length=50) page_xmlid = "%s.%s" % (template_module, page_name) # find a free xmlid inc = 0 dom = [('website_id', '=', False), ('website_id', '=', context.get('website_id'))] while view.search(cr, ecore.SUPERUSER_ID, [('key', '=', page_xmlid), '|'] + dom, context=dict(context or {}, active_test=False)): inc += 1 page_xmlid = "%s.%s" % (template_module, page_name + (inc and "-%s" % inc or "")) page_name += (inc and "-%s" % inc or "") # new page _, template_id = imd.get_object_reference(cr, uid, template_module, template_name) website_id = context.get('website_id') key = template_module + '.' + page_name page_id = view.copy(cr, uid, template_id, { 'website_id': website_id, 'key': key }, context=context) page = view.browse(cr, uid, page_id, context=dict(context, lang=None)) page.write({ 'arch': page.arch.replace(template, page_xmlid), 'name': page_name, 'page': ispage, }) return page_xmlid def key_to_view_id(self, cr, uid, view_id, context=None): View = self.pool.get('ir.ui.view') return View.search(cr, uid, [('id', '=', view_id), "|", ('website_id', '=', context.get('website_id')), ('website_id', '=', False), ('page', '=', True), ('type', '=', 'qweb')], context=context) def delete_page(self, cr, uid, view_id, context=None): if context is None: context = {} View = self.pool.get('ir.ui.view') view_find = self.key_to_view_id(cr, uid, view_id, context=context) if view_find: View.unlink(cr, uid, view_find, context=context) def rename_page(self, cr, uid, view_id, new_name, context=None): if context is None: context = {} View = self.pool.get('ir.ui.view') view_find = self.key_to_view_id(cr, uid, view_id, context=context) if view_find: v = View.browse(cr, uid, view_find, context=context) new_name = slugify(new_name, max_length=50) # Prefix by module if not already done by end user prefix = v.key.split('.')[0] if not new_name.startswith(prefix): new_name = "%s.%s" % (prefix, new_name) View.write(cr, uid, view_find, { 'key': new_name, 'arch_db': v.arch_db.replace(v.key, new_name, 1) }) return new_name def page_search_dependencies(self, cr, uid, view_id=False, context=None): dep = {} if not view_id: return dep # search dependencies just for information. # It will not catch 100% of dependencies and False positive is more than possible # Each module could add dependences in this dict if context is None: context = {} View = self.pool.get('ir.ui.view') Menu = self.pool.get('website.menu') view = View.browse(cr, uid, view_id, context=context) website_id = context.get('website_id') name = view.key.replace("website.", "") fullname = "website.%s" % name if view.page: # search for page with link page_search_dom = [ '|', ('website_id', '=', website_id), ('website_id', '=', False), '|', ('arch_db', 'ilike', '/page/%s' % name), ('arch_db', 'ilike', '/page/%s' % fullname) ] pages = View.search(cr, uid, page_search_dom, context=context) if pages: page_key = _('Page') dep[page_key] = [] for page in View.browse(cr, uid, pages, context=context): if page.page: dep[page_key].append({ 'text': _('Page <b>%s</b> seems to have a link to this page !') % page.key, 'link': '/page/%s' % page.key }) else: dep[page_key].append({ 'text': _('Template <b>%s (id:%s)</b> seems to have a link to this page !' ) % (page.key, page.id), 'link': '#' }) # search for menu with link menu_search_dom = [ '|', ('website_id', '=', website_id), ('website_id', '=', False), '|', ('url', 'ilike', '/page/%s' % name), ('url', 'ilike', '/page/%s' % fullname) ] menus = Menu.search(cr, uid, menu_search_dom, context=context) if menus: menu_key = _('Menu') dep[menu_key] = [] for menu in Menu.browse(cr, uid, menus, context=context): dep[menu_key].append({ 'text': _('Menu <b>%s</b> seems to have a link to this page !') % menu.name, 'link': False }) return dep def page_for_name(self, cr, uid, ids, name, module='website', context=None): # whatever return '%s.%s' % (module, slugify(name, max_length=50)) def page_exists(self, cr, uid, ids, name, module='website', context=None): try: name = (name or "").replace("/page/website.", "").replace("/page/", "") if not name: return False return self.pool["ir.model.data"].get_object_reference( cr, uid, module, name) except: return False @ecore.tools.ormcache('id') def _get_languages(self, cr, uid, id, context=None): website = self.browse(cr, uid, id) return [(lg.code, lg.name) for lg in website.language_ids] def get_cdn_url(self, cr, uid, uri, context=None): # Currently only usable in a website_enable request context if request and request.website and not request.debug and request.website.user_id.id == request.uid: cdn_url = request.website.cdn_url cdn_filters = (request.website.cdn_filters or '').splitlines() for flt in cdn_filters: if flt and re.match(flt, uri): return urlparse.urljoin(cdn_url, uri) return uri def get_languages(self, cr, uid, ids, context=None): return self._get_languages(cr, uid, ids[0]) def get_alternate_languages(self, cr, uid, ids, req=None, context=None): langs = [] if req is None: req = request.httprequest default = self.get_current_website(cr, uid, context=context).default_lang_code shorts = [] def get_url_localized(router, lang): arguments = dict(request.endpoint_arguments) for k, v in arguments.items(): if isinstance(v, orm.browse_record): arguments[k] = v.with_context(lang=lang) return router.build(request.endpoint, arguments) router = request.httprequest.app.get_db_router(request.db).bind('') for code, name in self.get_languages(cr, uid, ids, context=context): lg_path = ('/' + code) if code != default else '' lg = code.split('_') shorts.append(lg[0]) uri = request.endpoint and get_url_localized( router, code) or request.httprequest.path if req.query_string: uri += '?' + req.query_string lang = { 'hreflang': ('-'.join(lg)).lower(), 'short': lg[0], 'href': req.url_root[0:-1] + lg_path + uri, } langs.append(lang) for lang in langs: if shorts.count(lang['short']) == 1: lang['hreflang'] = lang['short'] return langs @ecore.tools.ormcache('domain_name') def _get_current_website_id(self, cr, uid, domain_name, context=None): ids = self.search(cr, uid, [('domain', '=', domain_name)], limit=1, context=context) return ids and ids[0] or self.search(cr, uid, [], limit=1)[0] def get_current_website(self, cr, uid, context=None): domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0] website_id = self._get_current_website_id(cr, uid, domain_name) request.context['website_id'] = website_id return self.browse(cr, uid, website_id, context=context) def is_publisher(self, cr, uid, ids, context=None): Access = self.pool['ir.model.access'] is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context=context) return is_website_publisher def is_user(self, cr, uid, ids, context=None): Access = self.pool['ir.model.access'] return Access.check(cr, uid, 'ir.ui.menu', 'read', False, context=context) def get_template(self, cr, uid, ids, template, context=None): View = self.pool['ir.ui.view'] if isinstance(template, (int, long)): view_id = template else: if '.' not in template: template = 'website.%s' % template view_id = View.get_view_id(cr, uid, template, context=context) if not view_id: raise NotFound return View.browse(cr, uid, view_id, context=context) def _render(self, cr, uid, ids, template, values=None, context=None): # TODO: remove this. (just kept for backward api compatibility for saas-3) return self.pool['ir.ui.view'].render(cr, uid, template, values=values, context=context) def render(self, cr, uid, ids, template, values=None, status_code=None, context=None): # TODO: remove this. (just kept for backward api compatibility for saas-3) return request.render(template, values, uid=uid) def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None): # Compute Pager page_count = int(math.ceil(float(total) / step)) page = max(1, min(int(page if str(page).isdigit() else 1), page_count)) scope -= 1 pmin = max(page - int(math.floor(scope / 2)), 1) pmax = min(pmin + scope, page_count) if pmax - pmin < scope: pmin = pmax - scope if pmax - scope > 0 else 1 def get_url(page): _url = "%s/page/%s" % (url, page) if page > 1 else url if url_args: _url = "%s?%s" % (_url, werkzeug.url_encode(url_args)) return _url return { "page_count": page_count, "offset": (page - 1) * step, "page": { 'url': get_url(page), 'num': page }, "page_start": { 'url': get_url(pmin), 'num': pmin }, "page_previous": { 'url': get_url(max(pmin, page - 1)), 'num': max(pmin, page - 1) }, "page_next": { 'url': get_url(min(pmax, page + 1)), 'num': min(pmax, page + 1) }, "page_end": { 'url': get_url(pmax), 'num': pmax }, "pages": [{ 'url': get_url(page), 'num': page } for page in xrange(pmin, pmax + 1)] } def rule_is_enumerable(self, rule): """ Checks that it is possible to generate sensible GET queries for a given rule (if the endpoint matches its own requirements) :type rule: werkzeug.routing.Rule :rtype: bool """ endpoint = rule.endpoint methods = endpoint.routing.get('methods') or ['GET'] converters = rule._converters.values() if not ('GET' in methods and endpoint.routing['type'] == 'http' and endpoint.routing['auth'] in ('none', 'public') and endpoint.routing.get('website', False) and all( hasattr(converter, 'generate') for converter in converters) and endpoint.routing.get('website')): return False # dont't list routes without argument having no default value or converter spec = inspect.getargspec(endpoint.method.original_func) # remove self and arguments having a default value defaults_count = len(spec.defaults or []) args = spec.args[1:(-defaults_count or None)] # check that all args have a converter return all((arg in rule._converters) for arg in args) def enumerate_pages(self, cr, uid, ids, query_string=None, context=None): """ Available pages in the website/CMS. This is mostly used for links generation and can be overridden by modules setting up new HTML controllers for dynamic pages (e.g. blog). By default, returns template views marked as pages. :param str query_string: a (user-provided) string, fetches pages matching the string :returns: a list of mappings with two keys: ``name`` is the displayable name of the resource (page), ``url`` is the absolute URL of the same. :rtype: list({name: str, url: str}) """ router = request.httprequest.app.get_db_router(request.db) # Force enumeration to be performed as public user url_set = set() for rule in router.iter_rules(): if not self.rule_is_enumerable(rule): continue converters = rule._converters or {} if query_string and not converters and ( query_string not in rule.build([{}], append_unknown=False)[1]): continue values = [{}] convitems = converters.items() # converters with a domain are processed after the other ones gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain <> '[]') convitems.sort(lambda x, y: cmp(gd(x), gd(y))) for (i, (name, converter)) in enumerate(convitems): newval = [] for val in values: query = i == (len(convitems) - 1) and query_string for v in converter.generate(request.cr, uid, query=query, args=val, context=context): newval.append(val.copy()) v[name] = v['loc'] del v['loc'] newval[-1].update(v) values = newval for value in values: domain_part, url = rule.build(value, append_unknown=False) page = {'loc': url} for key, val in value.items(): if key.startswith('__'): page[key[2:]] = val if url in ('/sitemap.xml', ): continue if url in url_set: continue url_set.add(url) yield page def search_pages(self, cr, uid, ids, needle=None, limit=None, context=None): name = re.sub(r"^/p(a(g(e(/(w(e(b(s(i(t(e(\.)?)?)?)?)?)?)?)?)?)?)?)?", "", needle or "") res = [] for page in self.enumerate_pages(cr, uid, ids, query_string=name, context=context): if needle in page['loc']: res.append(page) if len(res) == limit: break return res def image_url(self, cr, uid, record, field, size=None, context=None): """Returns a local url that points to the image field of a given browse record.""" sudo_record = record.sudo() sha = hashlib.sha1(getattr(sudo_record, '__last_update')).hexdigest()[0:7] size = '' if size is None else '/%s' % size return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
class module(osv.osv): _name = "ir.module.module" _rec_name = "shortdesc" _description = "Module" def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): res = super(module, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=False) result = self.pool.get('ir.model.data').get_object_reference( cr, uid, 'base', 'action_server_module_immediate_install')[1] if view_type == 'form': if res.get('toolbar', False): list = [ rec for rec in res['toolbar']['action'] if rec.get('id', False) != result ] res['toolbar'] = {'action': list} return res @classmethod def get_module_info(cls, name): info = {} try: info = modules.load_information_from_description_file(name) except Exception: _logger.debug( 'Error when trying to fetch informations for ' 'module %s', name, exc_info=True) return info def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None): res = dict.fromkeys(ids, '') for module in self.browse(cr, uid, ids, context=context): path = get_module_resource(module.name, 'static/description/index.html') if path: with tools.file_open(path, 'rb') as desc_file: doc = desc_file.read() html = lxml.html.document_fromstring(doc) for element, attribute, link, pos in html.iterlinks(): if element.get('src') and not '//' in element.get( 'src') and not 'static/' in element.get('src'): element.set( 'src', "/%s/static/description/%s" % (module.name, element.get('src'))) res[module.id] = html_sanitize(lxml.html.tostring(html)) else: overrides = { 'embed_stylesheet': False, 'doctitle_xform': False, 'output_encoding': 'unicode', 'xml_declaration': False, } output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter()) res[module.id] = html_sanitize(output) return res def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None): default_version = modules.adapt_version('1.0') res = dict.fromkeys(ids, default_version) for m in self.browse(cr, uid, ids): res[m.id] = self.get_module_info(m.name).get( 'version', default_version) return res def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None): res = {} model_data_obj = self.pool.get('ir.model.data') dmodels = [] if field_name is None or 'views_by_module' in field_name: dmodels.append('ir.ui.view') if field_name is None or 'reports_by_module' in field_name: dmodels.append('ir.actions.report.xml') if field_name is None or 'menus_by_module' in field_name: dmodels.append('ir.ui.menu') assert dmodels, "no models for %s" % field_name for module_rec in self.browse(cr, uid, ids, context=context): res_mod_dic = res[module_rec.id] = { 'menus_by_module': [], 'reports_by_module': [], 'views_by_module': [] } # Skip uninstalled modules below, no data to find anyway. if module_rec.state not in ('installed', 'to upgrade', 'to remove'): continue # then, search and group ir.model.data records imd_models = dict([(m, []) for m in dmodels]) imd_ids = model_data_obj.search(cr, uid, [('module', '=', module_rec.name), ('model', 'in', tuple(dmodels))]) for imd_res in model_data_obj.read(cr, uid, imd_ids, ['model', 'res_id'], context=context): imd_models[imd_res['model']].append(imd_res['res_id']) def browse(model): M = self.pool[model] # as this method is called before the module update, some xmlid may be invalid at this stage # explictly filter records before reading them ids = M.exists(cr, uid, imd_models.get(model, []), context) return M.browse(cr, uid, ids, context) def format_view(v): aa = v.inherit_id and '* INHERIT ' or '' return '%s%s (%s)' % (aa, v.name, v.type) res_mod_dic['views_by_module'] = map(format_view, browse('ir.ui.view')) res_mod_dic['reports_by_module'] = map( attrgetter('name'), browse('ir.actions.report.xml')) res_mod_dic['menus_by_module'] = map(attrgetter('complete_name'), browse('ir.ui.menu')) for key in res.iterkeys(): for k, v in res[key].iteritems(): res[key][k] = "\n".join(sorted(v)) return res def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None): res = dict.fromkeys(ids, '') for module in self.browse(cr, uid, ids, context=context): path = get_module_resource(module.name, 'static', 'description', 'icon.png') if path: image_file = tools.file_open(path, 'rb') try: res[module.id] = image_file.read().encode('base64') finally: image_file.close() return res _columns = { 'name': fields.char("Technical Name", readonly=True, required=True, select=True), 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True), 'shortdesc': fields.char('Module Name', readonly=True, translate=True), 'summary': fields.char('Summary', readonly=True, translate=True), 'description': fields.text("Description", readonly=True, translate=True), 'description_html': fields.function(_get_desc, string='Description HTML', type='html', method=True, readonly=True), 'author': fields.char("Author", readonly=True), 'maintainer': fields.char('Maintainer', readonly=True), 'contributors': fields.text('Contributors', readonly=True), 'website': fields.char("Website", readonly=True), # attention: Incorrect field names !! # installed_version refers the latest version (the one on disk) # latest_version refers the installed version (the one in database) # published_version refers the version available on the repository 'installed_version': fields.function(_get_latest_version, string='Latest Version', type='char'), 'latest_version': fields.char('Installed Version', readonly=True), 'published_version': fields.char('Published Version', readonly=True), 'url': fields.char('URL', readonly=True), 'sequence': fields.integer('Sequence'), 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies', readonly=True), 'auto_install': fields.boolean( 'Automatic Installation', help='An auto-installable module is automatically installed by the ' 'system when all its dependencies are satisfied. ' 'If the module has no dependency, it is always installed.'), 'state': fields.selection([('uninstallable', 'Not Installable'), ('uninstalled', 'Not Installed'), ('installed', 'Installed'), ('to upgrade', 'To be upgraded'), ('to remove', 'To be removed'), ('to install', 'To be installed')], string='Status', readonly=True, select=True), 'demo': fields.boolean('Demo Data', readonly=True), 'license': fields.selection( [('GPL-2', 'GPL Version 2'), ('GPL-2 or any later version', 'GPL-2 or later version'), ('GPL-3', 'GPL Version 3'), ('GPL-3 or any later version', 'GPL-3 or later version'), ('AGPL-3', 'Affero GPL-3'), ('LGPL-3', 'LGPL Version 3'), ('Other OSI approved licence', 'Other OSI Approved Licence'), ('OEEL-1', 'eCore Enterprise Edition License v1.0'), ('Other proprietary', 'Other Proprietary')], string='License', readonly=True), 'menus_by_module': fields.function(_get_views, string='Menus', type='text', multi="meta", store=True), 'reports_by_module': fields.function(_get_views, string='Reports', type='text', multi="meta", store=True), 'views_by_module': fields.function(_get_views, string='Views', type='text', multi="meta", store=True), 'application': fields.boolean('Application', readonly=True), 'icon': fields.char('Icon URL'), 'icon_image': fields.function(_get_icon_image, string='Icon', type="binary"), } _defaults = { 'state': 'uninstalled', 'sequence': 100, 'demo': False, 'license': 'LGPL-3', } _order = 'sequence,name' def _name_uniq_msg(self, cr, uid, ids, context=None): return _('The name of the module must be unique !') _sql_constraints = [ ('name_uniq', 'UNIQUE (name)', _name_uniq_msg), ] def unlink(self, cr, uid, ids, context=None): if not ids: return True if isinstance(ids, (int, long)): ids = [ids] mod_names = [] for mod in self.read(cr, uid, ids, ['state', 'name'], context): if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'): raise UserError( _('You try to remove a module that is installed or will be installed' )) mod_names.append(mod['name']) #Removing the entry from ir_model_data #ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)]) #if ids_meta: # self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context) self.clear_caches() return super(module, self).unlink(cr, uid, ids, context=context) @staticmethod def _check_external_dependencies(terp): depends = terp.get('external_dependencies') if not depends: return for pydep in depends.get('python', []): try: importlib.import_module(pydep) except ImportError: raise ImportError('No module named %s' % (pydep, )) for binary in depends.get('bin', []): try: tools.find_in_path(binary) except IOError: raise Exception('Unable to find %r in path' % (binary, )) @classmethod def check_external_dependencies(cls, module_name, newstate='to install'): terp = cls.get_module_info(module_name) try: cls._check_external_dependencies(terp) except Exception, e: if newstate == 'to install': msg = _( 'Unable to install module "%s" because an external dependency is not met: %s' ) elif newstate == 'to upgrade': msg = _( 'Unable to upgrade module "%s" because an external dependency is not met: %s' ) else: msg = _( 'Unable to process module "%s" because an external dependency is not met: %s' ) raise UserError(msg % (module_name, e.args[0]))
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 lang(osv.osv): _name = "res.lang" _description = "Languages" _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys() _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice def install_lang(self, cr, uid, **args): """ This method is called from ecore/addons/base/base_data.xml to load some language and set it as the default for every partners. The language is set via tools.config by the RPC 'create' method on the 'db' object. This is a fragile solution and something else should be found. """ lang = tools.config.get('lang') if not lang: return False lang_ids = self.search(cr, uid, [('code','=', lang)]) if not lang_ids: self.load_lang(cr, uid, lang) ir_values_obj = self.pool.get('ir.values') default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner']) if not default_value: ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang) return True def load_lang(self, cr, uid, lang, lang_name=None): # create the language with locale information fail = True iso_lang = tools.get_iso_codes(lang) for ln in tools.get_locales(lang): try: locale.setlocale(locale.LC_ALL, str(ln)) fail = False break except locale.Error: continue if fail: lc = locale.getdefaultlocale()[0] msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.' _logger.warning(msg, lang, lc) if not lang_name: lang_name = tools.ALL_LANGUAGES.get(lang, lang) def fix_xa0(s): """Fix badly-encoded non-breaking space Unicode character from locale.localeconv(), coercing to utf-8, as some platform seem to output localeconv() in their system encoding, e.g. Windows-1252""" if s == '\xa0': return '\xc2\xa0' return s def fix_datetime_format(format): """Python's strftime supports only the format directives that are available on the platform's libc, so in order to be 100% cross-platform we map to the directives required by the C standard (1989 version), always available on platforms with a C standard implementation.""" # For some locales, nl_langinfo returns a D_FMT/T_FMT that contains # unsupported '%-' patterns, e.g. for cs_CZ format = format.replace('%-', '%') for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems(): format = format.replace(pattern, replacement) return str(format) lang_info = { 'code': lang, 'iso_code': iso_lang, 'name': lang_name, 'translatable': 1, 'date_format' : fix_datetime_format(locale.nl_langinfo(locale.D_FMT)), 'time_format' : fix_datetime_format(locale.nl_langinfo(locale.T_FMT)), 'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])), 'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])), } lang_id = False try: lang_id = self.create(cr, uid, lang_info) finally: tools.resetlocale() return lang_id def _check_format(self, cr, uid, ids, context=None): for lang in self.browse(cr, uid, ids, context=context): for pattern in self._disallowed_datetime_patterns: if (lang.time_format and pattern in lang.time_format)\ or (lang.date_format and pattern in lang.date_format): return False return True def _check_grouping(self, cr, uid, ids, context=None): for lang in self.browse(cr, uid, ids, context=context): try: if not all(isinstance(x, int) for x in eval(lang.grouping)): return False except Exception: return False return True def _get_default_date_format(self, cursor, user, context=None): return '%m/%d/%Y' def _get_default_time_format(self, cursor, user, context=None): return '%H:%M:%S' _columns = { 'name': fields.char('Name', required=True), 'code': fields.char('Locale Code', size=16, required=True, help='This field is used to set/get locales for user'), 'iso_code': fields.char('ISO code', size=16, required=False, help='This ISO code is the name of po files to use for translations'), 'translatable': fields.boolean('Translatable'), 'active': fields.boolean('Active'), 'direction': fields.selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], 'Direction', required=True), 'date_format':fields.char('Date Format', required=True), 'time_format':fields.char('Time Format', required=True), 'grouping':fields.char('Separator Format', required=True,help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case."), 'decimal_point':fields.char('Decimal Separator', required=True), 'thousands_sep':fields.char('Thousands Separator'), } _defaults = { 'active': 1, 'translatable': 0, 'direction': 'ltr', 'date_format':_get_default_date_format, 'time_format':_get_default_time_format, 'grouping': '[]', 'decimal_point': '.', 'thousands_sep': ',', } _sql_constraints = [ ('name_uniq', 'unique (name)', 'The name of the language must be unique !'), ('code_uniq', 'unique (code)', 'The code of the language must be unique !'), ] _constraints = [ (_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format']), (_check_grouping, "The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case.", ['grouping']) ] @tools.ormcache('lang') def _lang_get(self, cr, uid, lang): lang_ids = self.search(cr, uid, [('code', '=', lang)]) or \ self.search(cr, uid, [('code', '=', 'en_US')]) return lang_ids[0] @tools.ormcache('lang', 'monetary') def _lang_data_get(self, cr, uid, lang, monetary=False): if type(lang) in (str, unicode): lang = self._lang_get(cr, uid, lang) conv = localeconv() lang_obj = self.browse(cr, uid, lang) thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep'] decimal_point = lang_obj.decimal_point grouping = lang_obj.grouping return grouping, thousands_sep, decimal_point def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] if 'code' in vals: for rec in self.browse(cr, uid, ids, context): if rec.code != vals['code']: raise UserError(_("Language code cannot be modified.")) if vals.get('active') == False: users = self.pool.get('res.users') if self.search_count(cr, uid, [('id', 'in', ids), ('code', '=', 'en_US')], context=context): raise UserError(_("Base Language 'en_US' can not be deactivated!")) for current_id in ids: current_language = self.browse(cr, uid, current_id, context=context) if users.search(cr, uid, [('lang', '=', current_language.code)], context=context): raise UserError(_("Cannot unactivate a language that is currently used by users.")) self._lang_get.clear_cache(self) self._lang_data_get.clear_cache(self) return super(lang, self).write(cr, uid, ids, vals, context) def unlink(self, cr, uid, ids, context=None): if context is None: context = {} languages = self.read(cr, uid, ids, ['code','active'], context=context) for language in languages: ctx_lang = context.get('lang') if language['code']=='en_US': raise UserError(_("Base Language 'en_US' can not be deleted!")) if ctx_lang and (language['code']==ctx_lang): raise UserError(_("You cannot delete the language which is User's Preferred Language!")) if language['active']: raise UserError(_("You cannot delete the language which is Active!\nPlease de-activate the language first.")) trans_obj = self.pool.get('ir.translation') trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context) trans_obj.unlink(cr, uid, trans_ids, context=context) self._lang_get.clear_cache(self) self._lang_data_get.clear_cache(self) return super(lang, self).unlink(cr, uid, ids, context=context) # # IDS: can be a list of IDS or a list of XML_IDS # def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False, context=None): """ Format() will return the language-specific output for float values""" if percent[0] != '%': raise ValueError("format() must be given exactly one %char format specifier") formatted = percent % value # floats and decimal ints need special action! if grouping: lang_grouping, thousands_sep, decimal_point = \ self._lang_data_get(cr, uid, ids[0], monetary) eval_lang_grouping = eval(lang_grouping) if percent[-1] in 'eEfFgG': parts = formatted.split('.') parts[0], _ = intersperse(parts[0], eval_lang_grouping, thousands_sep) formatted = decimal_point.join(parts) elif percent[-1] in 'diu': formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0] return formatted
class website_config_settings(osv.osv_memory): _name = 'website.config.settings' _inherit = 'res.config.settings' _columns = { 'website_id': fields.many2one('website', string="website", required=True), 'website_name': fields.related('website_id', 'name', type="char", string="Website Name"), 'language_ids': fields.related('website_id', 'language_ids', type='many2many', relation='res.lang', string='Languages'), 'default_lang_id': fields.related('website_id', 'default_lang_id', type='many2one', relation='res.lang', string='Default language'), 'default_lang_code': fields.related('website_id', 'default_lang_code', type="char", string="Default language code"), 'google_analytics_key': fields.related('website_id', 'google_analytics_key', type="char", string='Google Analytics Key'), 'social_twitter': fields.related('website_id', 'social_twitter', type="char", string='Twitter Account'), 'social_facebook': fields.related('website_id', 'social_facebook', type="char", string='Facebook Account'), 'social_github': fields.related('website_id', 'social_github', type="char", string='GitHub Account'), 'social_linkedin': fields.related('website_id', 'social_linkedin', type="char", string='LinkedIn Account'), 'social_youtube': fields.related('website_id', 'social_youtube', type="char", string='Youtube Account'), 'social_googleplus': fields.related('website_id', 'social_googleplus', type="char", string='Google+ Account'), 'compress_html': fields.related( 'website_id', 'compress_html', type="boolean", string='Compress rendered HTML for a better Google PageSpeed result' ), 'cdn_activated': fields.related('website_id', 'cdn_activated', type="boolean", string='Use a Content Delivery Network (CDN)'), 'cdn_url': fields.related('website_id', 'cdn_url', type="char", string='CDN Base URL'), 'cdn_filters': fields.related('website_id', 'cdn_filters', type="text", string='CDN Filters'), 'module_website_form_editor': fields.boolean("Form builder: create and customize forms"), 'module_website_version': fields.boolean("A/B testing and versioning"), } def on_change_website_id(self, cr, uid, ids, website_id, context=None): if not website_id: return {'value': {}} website_data = self.pool.get('website').read(cr, uid, [website_id], [], context=context)[0] values = {'website_name': website_data['name']} for fname, v in website_data.items(): if fname in self._columns: values[fname] = v[ 0] if v and self._columns[fname]._type == 'many2one' else v return {'value': values} # FIXME in trunk for god sake. Change the fields above to fields.char instead of fields.related, # and create the function set_website who will set the value on the website_id # create does not forward the values to the related many2one. Write does. def create(self, cr, uid, vals, context=None): config_id = super(website_config_settings, self).create(cr, uid, vals, context=context) self.write(cr, uid, config_id, vals, context=context) return config_id _defaults = { 'website_id': lambda self, cr, uid, c: self.pool.get('website').search( cr, uid, [], context=c)[0], }
class ir_actions_report_xml(orm.Model): _inherit = 'ir.actions.report.xml' _columns = { 'webkit_header': fields.property(type='many2one', relation='ir.header_webkit', string='Webkit Header', help="The header linked to the report", required=True), 'webkit_debug': fields.boolean('Webkit debug', help="Enable the webkit engine debugger"), 'report_webkit_data': fields.text( 'Webkit Template', help= "This template will be used if the main report file is not found"), 'precise_mode': fields.boolean( 'Precise Mode', help="This mode allow more precise element position as each object" " is printed on a separate HTML but memory and disk usage are wider." ) } def _lookup_report(self, cr, name): """ Look up a report definition. """ import operator import os opj = os.path.join # First lookup in the deprecated place, because if the report definition # has not been updated, it is more likely the correct definition is there. # Only reports with custom parser specified in Python are still there. if 'report.' + name in ecore.report.interface.report_int._reports: new_report = ecore.report.interface.report_int._reports['report.' + name] if not isinstance(new_report, WebKitParser): new_report = None else: cr.execute( "SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s", (name, 'webkit')) r = cr.dictfetchone() if r: if r['parser']: parser = operator.attrgetter(r['parser'])(ecore.addons) kwargs = {'parser': parser} else: kwargs = {} new_report = WebKitParser('report.' + r['report_name'], r['model'], opj('addons', r['report_rml'] or '/'), header=r['header'], register=False, **kwargs) else: new_report = None if new_report: return new_report else: return super(ir_actions_report_xml, self)._lookup_report(cr, name)
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 payslip_report(osv.osv): _name = "payslip.report" _description = "Payslip Analysis" _auto = False _columns = { 'name': fields.char('Name', readonly=True), 'date_from': fields.date( 'Date From', readonly=True, ), 'date_to': fields.date( 'Date To', readonly=True, ), 'year': fields.char('Year', size=4, readonly=True), 'month': fields.selection([('01', 'January'), ('02', 'February'), ('03', 'March'), ('04', 'April'), ('05', 'May'), ('06', 'June'), ('07', 'July'), ('08', 'August'), ('09', 'September'), ('10', 'October'), ('11', 'November'), ('12', 'December')], 'Month', readonly=True), 'day': fields.char('Day', size=128, readonly=True), 'state': fields.selection([ ('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Rejected'), ], 'Status', readonly=True), 'employee_id': fields.many2one('hr.employee', 'Employee', readonly=True), 'nbr': fields.integer('# Payslip lines', readonly=True), 'number': fields.char('Number', readonly=True), 'struct_id': fields.many2one('hr.payroll.structure', 'Structure', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'paid': fields.boolean('Made Payment Order ? ', readonly=True), 'total': fields.float('Total', readonly=True), 'category_id': fields.many2one('hr.salary.rule.category', 'Category', readonly=True), } def init(self, cr): tools.drop_view_if_exists(cr, 'payslip_report') cr.execute(""" create or replace view payslip_report as ( select min(l.id) as id, l.name, p.struct_id, p.state, p.date_from, p.date_to, p.number, p.company_id, p.paid, l.category_id, l.employee_id, sum(l.total) as total, to_char(p.date_from, 'YYYY') as year, to_char(p.date_from, 'MM') as month, to_char(p.date_from, 'YYYY-MM-DD') as day, to_char(p.date_to, 'YYYY') as to_year, to_char(p.date_to, 'MM') as to_month, to_char(p.date_to, 'YYYY-MM-DD') as to_day, 1 AS nbr from hr_payslip as p left join hr_payslip_line as l on (p.id=l.slip_id) where l.employee_id IS NOT NULL group by p.number,l.name,p.date_from,p.date_to,p.state,p.company_id,p.paid, l.employee_id,p.struct_id,l.category_id ) """)
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_goal(osv.Model): """Goal instance for a user An individual goal for a user on a specified time period""" _name = 'gamification.goal' _description = 'Gamification goal instance' def _get_completion(self, cr, uid, ids, field_name, arg, context=None): """Return the percentage of completeness of the goal, between 0 and 100""" res = dict.fromkeys(ids, 0.0) for goal in self.browse(cr, uid, ids, context=context): if goal.definition_condition == 'higher': if goal.current >= goal.target_goal: res[goal.id] = 100.0 else: res[goal.id] = round(100.0 * goal.current / goal.target_goal, 2) elif goal.current < goal.target_goal: # a goal 'lower than' has only two values possible: 0 or 100% res[goal.id] = 100.0 else: res[goal.id] = 0.0 return res def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None): goal_definition = self.pool.get('gamification.goal.definition') if not definition_id: return {'value': {'definition_id': False}} goal_definition = goal_definition.browse(cr, uid, definition_id, context=context) return {'value': {'computation_mode': goal_definition.computation_mode, 'definition_condition': goal_definition.condition}} _columns = { 'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"), 'user_id': fields.many2one('res.users', string='User', required=True, auto_join=True, ondelete="cascade"), 'line_id': fields.many2one('gamification.challenge.line', string='Challenge Line', ondelete="cascade"), 'challenge_id': fields.related('line_id', 'challenge_id', string="Challenge", type='many2one', relation='gamification.challenge', store=True, readonly=True, help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."), 'start_date': fields.date('Start Date'), 'end_date': fields.date('End Date'), # no start and end = always active 'target_goal': fields.float('To Reach', required=True, track_visibility='always'), # no goal = global index 'current': fields.float('Current Value', required=True, track_visibility='always'), 'completeness': fields.function(_get_completion, type='float', string='Completeness'), 'state': fields.selection([ ('draft', 'Draft'), ('inprogress', 'In progress'), ('reached', 'Reached'), ('failed', 'Failed'), ('canceled', 'Canceled'), ], string='State', required=True, track_visibility='always'), 'to_update': fields.boolean('To update'), 'closed': fields.boolean('Closed goal', help="These goals will not be recomputed."), 'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"), 'remind_update_delay': fields.integer('Remind delay', help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."), 'last_update': fields.date('Last Update', help="In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."), 'definition_description': fields.related('definition_id', 'description', type='char', string='Definition Description', readonly=True), 'definition_condition': fields.related('definition_id', 'condition', type='char', string='Definition Condition', readonly=True), 'definition_suffix': fields.related('definition_id', 'full_suffix', type="char", string="Suffix", readonly=True), 'definition_display': fields.related('definition_id', 'display_mode', type="char", string="Display Mode", readonly=True), } _defaults = { 'current': 0, 'state': 'draft', 'start_date': fields.date.today, } _order = 'start_date desc, end_date desc, definition_id, id' def _check_remind_delay(self, cr, uid, goal, context=None): """Verify if a goal has not been updated for some time and send a reminder message of needed. :return: data to write on the goal object """ temp_obj = self.pool['mail.template'] if goal.remind_update_delay and goal.last_update: delta_max = timedelta(days=goal.remind_update_delay) last_update = datetime.strptime(goal.last_update, DF).date() if date.today() - last_update > delta_max: # generate a remind report temp_obj = self.pool.get('mail.template') template_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'gamification', 'email_template_goal_reminder')[0] template = temp_obj.get_email_template(cr, uid, template_id, goal.id, context=context) body_html = temp_obj.render_template(cr, uid, template.body_html, 'gamification.goal', goal.id, context=template._context) self.pool['mail.thread'].message_post(cr, uid, 0, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment') return {'to_update': True} return {} def _get_write_values(self, cr, uid, goal, new_value, context=None): """Generate values to write after recomputation of a goal score""" if new_value == goal.current: # avoid useless write if the new value is the same as the old one return {} result = {goal.id: {'current': new_value}} if (goal.definition_id.condition == 'higher' and new_value >= goal.target_goal) \ or (goal.definition_id.condition == 'lower' and new_value <= goal.target_goal): # success, do no set closed as can still change result[goal.id]['state'] = 'reached' elif goal.end_date and fields.date.today() > goal.end_date: # check goal failure result[goal.id]['state'] = 'failed' result[goal.id]['closed'] = True return result def update(self, cr, uid, ids, context=None): """Update the goals to recomputes values and change of states If a manual goal is not updated for enough time, the user will be reminded to do so (done only once, in 'inprogress' state). If a goal reaches the target value, the status is set to reached If the end date is passed (at least +1 day, time not considered) without the target value being reached, the goal is set as failed.""" if context is None: context = {} commit = context.get('commit_gamification', False) goals_by_definition = {} for goal in self.browse(cr, uid, ids, context=context): goals_by_definition.setdefault(goal.definition_id, []).append(goal) for definition, goals in goals_by_definition.items(): goals_to_write = dict((goal.id, {}) for goal in goals) if definition.computation_mode == 'manually': for goal in goals: goals_to_write[goal.id].update(self._check_remind_delay(cr, uid, goal, context)) elif definition.computation_mode == 'python': # TODO batch execution for goal in goals: # execute the chosen method cxt = { 'self': self.pool.get('gamification.goal'), 'object': goal, 'pool': self.pool, 'cr': cr, 'context': dict(context), # copy context to prevent side-effects of eval 'uid': uid, 'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time } code = definition.compute_code.strip() safe_eval(code, cxt, mode="exec", nocopy=True) # the result of the evaluated codeis put in the 'result' local variable, propagated to the context result = cxt.get('result') if result is not None and type(result) in (float, int, long): goals_to_write.update( self._get_write_values(cr, uid, goal, result, context=context) ) else: _logger.exception(_('Invalid return content from the evaluation of code for definition %s') % definition.name) else: # count or sum obj = self.pool.get(definition.model_id.model) field_date_name = definition.field_date_id and definition.field_date_id.name or False if definition.computation_mode == 'count' and definition.batch_mode: # batch mode, trying to do as much as possible in one request general_domain = safe_eval(definition.domain) field_name = definition.batch_distinctive_field.name subqueries = {} for goal in goals: start_date = field_date_name and goal.start_date or False end_date = field_date_name and goal.end_date or False subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})}) # the global query should be split by time periods (especially for recurrent goals) for (start_date, end_date), query_goals in subqueries.items(): subquery_domain = list(general_domain) subquery_domain.append((field_name, 'in', list(set(query_goals.values())))) if start_date: subquery_domain.append((field_date_name, '>=', start_date)) if end_date: subquery_domain.append((field_date_name, '<=', end_date)) if field_name == 'id': # grouping on id does not work and is similar to search anyway user_ids = obj.search(cr, uid, subquery_domain, context=context) user_values = [{'id': user_id, 'id_count': 1} for user_id in user_ids] else: user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context) # user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...] for goal in [g for g in goals if g.id in query_goals.keys()]: for user_value in user_values: queried_value = field_name in user_value and user_value[field_name] or False if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)): queried_value = queried_value[0] if queried_value == query_goals[goal.id]: new_value = user_value.get(field_name+'_count', goal.current) goals_to_write.update( self._get_write_values(cr, uid, goal, new_value, context=context) ) else: for goal in goals: # eval the domain with user replaced by goal user object domain = safe_eval(definition.domain, {'user': goal.user_id}) # add temporal clause(s) to the domain if fields are filled on the goal if goal.start_date and field_date_name: domain.append((field_date_name, '>=', goal.start_date)) if goal.end_date and field_date_name: domain.append((field_date_name, '<=', goal.end_date)) if definition.computation_mode == 'sum': field_name = definition.field_id.name # TODO for master: group on user field in batch mode res = obj.read_group(cr, uid, domain, [field_name], [], context=context) new_value = res and res[0][field_name] or 0.0 else: # computation mode = count new_value = obj.search(cr, uid, domain, context=context, count=True) goals_to_write.update( self._get_write_values(cr, uid, goal, new_value, context=context) ) for goal_id, value in goals_to_write.items(): if not value: continue self.write(cr, uid, [goal_id], value, context=context) if commit: cr.commit() return True def action_start(self, cr, uid, ids, context=None): """Mark a goal as started. This should only be used when creating goals manually (in draft state)""" self.write(cr, uid, ids, {'state': 'inprogress'}, context=context) return self.update(cr, uid, ids, context=context) def action_reach(self, cr, uid, ids, context=None): """Mark a goal as reached. If the target goal condition is not met, the state will be reset to In Progress at the next goal update until the end date.""" return self.write(cr, uid, ids, {'state': 'reached'}, context=context) def action_fail(self, cr, uid, ids, context=None): """Set the state of the goal to failed. A failed goal will be ignored in future checks.""" return self.write(cr, uid, ids, {'state': 'failed'}, context=context) def action_cancel(self, cr, uid, ids, context=None): """Reset the completion after setting a goal as reached or failed. This is only the current state, if the date and/or target criterias match the conditions for a change of state, this will be applied at the next goal update.""" return self.write(cr, uid, ids, {'state': 'inprogress'}, context=context) def create(self, cr, uid, vals, context=None): """Overwrite the create method to add a 'no_remind_goal' field to True""" context = dict(context or {}) context['no_remind_goal'] = True return super(gamification_goal, self).create(cr, uid, vals, context=context) def write(self, cr, uid, ids, vals, context=None): """Overwrite the write method to update the last_update field to today If the current value is changed and the report frequency is set to On change, a report is generated """ if context is None: context = {} vals['last_update'] = fields.date.today() result = super(gamification_goal, self).write(cr, uid, ids, vals, context=context) for goal in self.browse(cr, uid, ids, context=context): if goal.state != "draft" and ('definition_id' in vals or 'user_id' in vals): # avoid drag&drop in kanban view raise UserError(_('Can not modify the configuration of a started goal')) if vals.get('current'): if 'no_remind_goal' in context: # new goals should not be reported continue if goal.challenge_id and goal.challenge_id.report_message_frequency == 'onchange': self.pool.get('gamification.challenge').report_progress(cr, SUPERUSER_ID, goal.challenge_id, users=[goal.user_id], context=context) return result def get_action(self, cr, uid, goal_id, context=None): """Get the ir.action related to update the goal In case of a manual goal, should return a wizard to update the value :return: action description in a dictionnary """ goal = self.browse(cr, uid, goal_id, context=context) if goal.definition_id.action_id: # open a the action linked to the goal action = goal.definition_id.action_id.read()[0] if goal.definition_id.res_id_field: current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) action['res_id'] = safe_eval(goal.definition_id.res_id_field, {'user': current_user}) # if one element to display, should see it in form mode if possible action['views'] = [(view_id, mode) for (view_id, mode) in action['views'] if mode == 'form'] or action['views'] return action if goal.computation_mode == 'manually': # open a wizard window to update the value manually action = { 'name': _("Update %s") % goal.definition_id.name, 'id': goal_id, 'type': 'ir.actions.act_window', 'views': [[False, 'form']], 'target': 'new', 'context': {'default_goal_id': goal_id, 'default_current': goal.current}, 'res_model': 'gamification.goal.wizard' } return action return False
class payment_advice_report(osv.osv): _name = "payment.advice.report" _description = "Payment Advice Analysis" _auto = False _columns = { 'name': fields.char('Name', readonly=True), 'date': fields.date( 'Date', readonly=True, ), 'year': fields.char('Year', size=4, readonly=True), 'month': fields.selection([('01', 'January'), ('02', 'February'), ('03', 'March'), ('04', 'April'), ('05', 'May'), ('06', 'June'), ('07', 'July'), ('08', 'August'), ('09', 'September'), ('10', 'October'), ('11', 'November'), ('12', 'December')], 'Month', readonly=True), 'day': fields.char('Day', size=128, readonly=True), 'state': fields.selection([ ('draft', 'Draft'), ('confirm', 'Confirmed'), ('cancel', 'Cancelled'), ], 'Status', select=True, readonly=True), 'employee_id': fields.many2one('hr.employee', 'Employee', readonly=True), 'nbr': fields.integer('# Payment Lines', readonly=True), 'number': fields.char('Number', readonly=True), 'bysal': fields.float('By Salary', readonly=True), 'bank_id': fields.many2one('res.bank', 'Bank', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'cheque_nos': fields.char('Cheque Numbers', readonly=True), 'neft': fields.boolean('NEFT Transaction', readonly=True), 'ifsc_code': fields.char('IFSC Code', size=32, readonly=True), 'employee_bank_no': fields.char('Employee Bank Account', required=True), } def init(self, cr): tools.drop_view_if_exists(cr, 'payment_advice_report') cr.execute(""" create or replace view payment_advice_report as ( select min(l.id) as id, sum(l.bysal) as bysal, p.name, p.state, p.date, p.number, p.company_id, p.bank_id, p.chaque_nos as cheque_nos, p.neft, l.employee_id, l.ifsc_code, l.name as employee_bank_no, to_char(p.date, 'YYYY') as year, to_char(p.date, 'MM') as month, to_char(p.date, 'YYYY-MM-DD') as day, 1 as nbr from hr_payroll_advice as p left join hr_payroll_advice_line as l on (p.id=l.advice_id) where l.employee_id IS NOT NULL group by p.number,p.name,p.date,p.state,p.company_id,p.bank_id,p.chaque_nos,p.neft, l.employee_id,l.advice_id,l.bysal,l.ifsc_code, l.name ) """)
class gamification_goal_definition(osv.Model): """Goal definition A goal definition contains the way to evaluate an objective Each module wanting to be able to set goals to the users needs to create a new gamification_goal_definition """ _name = 'gamification.goal.definition' _description = 'Gamification goal definition' def _get_suffix(self, cr, uid, ids, field_name, arg, context=None): res = dict.fromkeys(ids, '') for goal in self.browse(cr, uid, ids, context=context): if goal.suffix and not goal.monetary: res[goal.id] = goal.suffix elif goal.monetary: # use the current user's company currency user = self.pool.get('res.users').browse(cr, uid, uid, context) if goal.suffix: res[goal.id] = "%s %s" % (user.company_id.currency_id.symbol, goal.suffix) else: res[goal.id] = user.company_id.currency_id.symbol else: res[goal.id] = "" return res _columns = { 'name': fields.char('Goal Definition', required=True, translate=True), 'description': fields.text('Goal Description'), 'monetary': fields.boolean('Monetary Value', help="The target and current value are defined in the company currency."), 'suffix': fields.char('Suffix', help="The unit of the target and current values", translate=True), 'full_suffix': fields.function(_get_suffix, type="char", string="Full Suffix", help="The currency and suffix field"), 'computation_mode': fields.selection([ ('manually', 'Recorded manually'), ('count', 'Automatic: number of records'), ('sum', 'Automatic: sum on a field'), ('python', 'Automatic: execute a specific Python code'), ], string="Computation Mode", help="Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'.", required=True), 'display_mode': fields.selection([ ('progress', 'Progressive (using numerical values)'), ('boolean', 'Exclusive (done or not-done)'), ], string="Displayed as", required=True), 'model_id': fields.many2one('ir.model', string='Model', help='The model object for the field to evaluate'), 'model_inherited_model_ids': fields.related('model_id', 'inherited_model_ids', type="many2many", obj="ir.model", string="Inherited models", readonly="True"), 'field_id': fields.many2one('ir.model.fields', string='Field to Sum', help='The field containing the value to evaluate'), 'field_date_id': fields.many2one('ir.model.fields', string='Date Field', help='The date to use for the time period evaluated'), 'domain': fields.char("Filter Domain", help="Domain for filtering records. General rule, not user depending, e.g. [('state', '=', 'done')]. The expression can contain reference to 'user' which is a browse record of the current user if not in batch mode.", required=True), 'batch_mode': fields.boolean('Batch Mode', help="Evaluate the expression in batch instead of once for each user"), 'batch_distinctive_field': fields.many2one('ir.model.fields', string="Distinctive field for batch user", help="In batch mode, this indicates which field distinct one user form the other, e.g. user_id, partner_id..."), 'batch_user_expression': fields.char("Evaluted expression for batch mode", help="The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id..."), 'compute_code': fields.text('Python Code', help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."), 'condition': fields.selection([ ('higher', 'The higher the better'), ('lower', 'The lower the better') ], string='Goal Performance', help='A goal is considered as completed when the current value is compared to the value to reach', required=True), 'action_id': fields.many2one('ir.actions.act_window', string="Action", help="The action that will be called to update the goal value."), 'res_id_field': fields.char("ID Field of user", help="The field name on the user profile (res.users) containing the value for res_id for action."), } _defaults = { 'condition': 'higher', 'computation_mode': 'manually', 'domain': "[]", 'monetary': False, 'display_mode': 'progress', } def number_following(self, cr, uid, model_name="mail.thread", context=None): """Return the number of 'model_name' objects the user is following The model specified in 'model_name' must inherit from mail.thread """ user = self.pool.get('res.users').browse(cr, uid, uid, context=context) return self.pool.get('mail.followers').search(cr, uid, [('res_model', '=', model_name), ('partner_id', '=', user.partner_id.id)], count=True, context=context) def _check_domain_validity(self, cr, uid, ids, context=None): # take admin as should always be present superuser = self.pool['res.users'].browse(cr, uid, SUPERUSER_ID, context=context) for definition in self.browse(cr, uid, ids, context=context): if definition.computation_mode not in ('count', 'sum'): continue obj = self.pool[definition.model_id.model] try: domain = safe_eval(definition.domain, {'user': superuser}) # demmy search to make sure the domain is valid obj.search(cr, uid, domain, context=context, count=True) except (ValueError, SyntaxError), e: msg = e.message or (e.msg + '\n' + e.text) raise UserError(_("The domain for the definition %s seems incorrect, please check it.\n\n%s" % (definition.name, msg))) 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 crm_opportunity_report(osv.Model): """ CRM Opportunity Analysis """ _name = "crm.opportunity.report" _auto = False _description = "CRM Opportunity Analysis" _rec_name = 'date_deadline' _inherit = ["utm.mixin"] _columns = { 'date_deadline': fields.date('Expected Closing', readonly=True), 'create_date': fields.datetime('Creation Date', readonly=True), 'opening_date': fields.datetime('Assignation Date', readonly=True), 'date_closed': fields.datetime('Close Date', readonly=True), 'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True), 'active': fields.boolean('Active', readonly=True), # durations 'delay_open': fields.float('Delay to Assign', digits=(16, 2), readonly=True, group_operator="avg", help="Number of Days to open the case"), 'delay_close': fields.float('Delay to Close', digits=(16, 2), readonly=True, group_operator="avg", help="Number of Days to close the case"), 'delay_expected': fields.float('Overpassed Deadline', digits=(16, 2), readonly=True, group_operator="avg"), 'user_id': fields.many2one('res.users', 'User', readonly=True), 'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id', readonly=True), 'nbr_activities': fields.integer('# of Activities', readonly=True), 'country_id': fields.many2one('res.country', 'Country', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'probability': fields.float('Probability', digits=(16, 2), readonly=True, group_operator="avg"), 'total_revenue': fields.float('Total Revenue', digits=(16, 2), readonly=True), 'expected_revenue': fields.float('Expected Revenue', digits=(16, 2), readonly=True), 'stage_id': fields.many2one('crm.stage', 'Stage', readonly=True, domain="[('team_ids', '=', team_id)]"), 'stage_name': fields.char('Stage Name', readonly=True), 'partner_id': fields.many2one('res.partner', 'Partner', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'priority': fields.selection(crm_stage.AVAILABLE_PRIORITIES, 'Priority'), 'type': fields.selection( [ ('lead', 'Lead'), ('opportunity', 'Opportunity'), ], 'Type', help="Type is used to separate Leads and Opportunities"), 'lost_reason': fields.many2one('crm.lost.reason', 'Lost Reason', readonly=True), 'date_conversion': fields.datetime('Conversion Date', readonly=True), } def init(self, cr): tools.drop_view_if_exists(cr, 'crm_opportunity_report') cr.execute(""" CREATE OR REPLACE VIEW crm_opportunity_report AS ( SELECT c.id, c.date_deadline, c.date_open as opening_date, c.date_closed as date_closed, c.date_last_stage_update as date_last_stage_update, c.user_id, c.probability, c.stage_id, stage.name as stage_name, c.type, c.company_id, c.priority, c.team_id, activity.nbr_activities, c.active, c.campaign_id, c.source_id, c.medium_id, c.partner_id, c.country_id, c.planned_revenue as total_revenue, c.planned_revenue*(c.probability/100) as expected_revenue, c.create_date as create_date, extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as delay_close, abs(extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24)) as delay_expected, extract('epoch' from (c.date_open-c.create_date))/(3600*24) as delay_open, c.lost_reason, c.date_conversion as date_conversion FROM "crm_lead" c LEFT JOIN ( SELECT m.res_id, COUNT(*) nbr_activities FROM "mail_message" m WHERE m.model = 'crm.lead' GROUP BY m.res_id ) activity ON (activity.res_id = c.id) LEFT JOIN "crm_stage" stage ON stage.id = c.stage_id GROUP BY c.id, activity.nbr_activities, stage.name )""")
class stock_warehouse(osv.osv): _inherit = 'stock.warehouse' _columns = { 'manufacture_to_resupply': fields.boolean( 'Manufacture in this Warehouse', help= "When products are manufactured, they can be manufactured in this warehouse." ), 'manufacture_pull_id': fields.many2one('procurement.rule', 'Manufacture Rule'), } _defaults = { 'manufacture_to_resupply': True, } def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None): route_obj = self.pool.get('stock.location.route') data_obj = self.pool.get('ir.model.data') try: manufacture_route_id = data_obj.get_object_reference( cr, uid, 'mrp', 'route_warehouse0_manufacture')[1] except: manufacture_route_id = route_obj.search( cr, uid, [('name', 'like', _('Manufacture'))], context=context) manufacture_route_id = manufacture_route_id and manufacture_route_id[ 0] or False if not manufacture_route_id: raise UserError(_('Can\'t find any generic Manufacture route.')) return { 'name': self._format_routename(cr, uid, warehouse, _(' Manufacture'), context=context), 'location_id': warehouse.lot_stock_id.id, 'route_id': manufacture_route_id, 'action': 'manufacture', 'picking_type_id': warehouse.int_type_id.id, 'propagate': False, 'warehouse_id': warehouse.id, } def create_routes(self, cr, uid, ids, warehouse, context=None): pull_obj = self.pool.get('procurement.rule') res = super(stock_warehouse, self).create_routes(cr, uid, ids, warehouse, context=context) if warehouse.manufacture_to_resupply: manufacture_pull_vals = self._get_manufacture_pull_rule( cr, uid, warehouse, context=context) manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context) res['manufacture_pull_id'] = manufacture_pull_id return res def write(self, cr, uid, ids, vals, context=None): pull_obj = self.pool.get('procurement.rule') if isinstance(ids, (int, long)): ids = [ids] if 'manufacture_to_resupply' in vals: if vals.get("manufacture_to_resupply"): for warehouse in self.browse(cr, uid, ids, context=context): if not warehouse.manufacture_pull_id: manufacture_pull_vals = self._get_manufacture_pull_rule( cr, uid, warehouse, context=context) manufacture_pull_id = pull_obj.create( cr, uid, manufacture_pull_vals, context=context) vals['manufacture_pull_id'] = manufacture_pull_id else: for warehouse in self.browse(cr, uid, ids, context=context): if warehouse.manufacture_pull_id: pull_obj.unlink(cr, uid, warehouse.manufacture_pull_id.id, context=context) return super(stock_warehouse, self).write(cr, uid, ids, vals, context=None) def get_all_routes_for_wh(self, cr, uid, warehouse, context=None): all_routes = super(stock_warehouse, self).get_all_routes_for_wh(cr, uid, warehouse, context=context) if warehouse.manufacture_to_resupply and warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id: all_routes += [warehouse.manufacture_pull_id.route_id.id] return all_routes def _handle_renaming(self, cr, uid, warehouse, name, code, context=None): res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, code, context=context) pull_obj = self.pool.get('procurement.rule') #change the manufacture procurement rule name if warehouse.manufacture_pull_id: pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, { 'name': warehouse.manufacture_pull_id.name.replace( warehouse.name, name, 1) }, context=context) return res def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None): res = super(stock_warehouse, self)._get_all_products_to_resupply(cr, uid, warehouse, context=context) if warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id: for product_id in res: for route in self.pool.get('product.product').browse( cr, uid, product_id, context=context).route_ids: if route.id == warehouse.manufacture_pull_id.route_id.id: res.remove(product_id) break return res
class mrp_repair_line(osv.osv, ProductChangeMixin): _name = 'mrp.repair.line' _description = 'Repair Line' def _amount_line(self, cr, uid, ids, field_name, arg, context=None): """ Calculates amount. @param field_name: Name of field. @param arg: Argument @return: Dictionary of values. """ res = {} tax_obj = self.pool.get('account.tax') # cur_obj = self.pool.get('res.currency') for line in self.browse(cr, uid, ids, context=context): if line.to_invoice: cur = line.repair_id.pricelist_id.currency_id taxes = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur.id, line.product_uom_qty, line.product_id.id, line.repair_id.partner_id.id) #res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) res[line.id] = taxes['total_included'] else: res[line.id] = 0 return res _columns = { 'name': fields.char('Description', required=True), 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', ondelete='cascade', select=True), 'type': fields.selection([('add', 'Add'), ('remove', 'Remove')], 'Type', required=True), 'to_invoice': fields.boolean('To Invoice'), 'product_id': fields.many2one('product.product', 'Product', required=True), 'invoiced': fields.boolean('Invoiced', readonly=True, copy=False), 'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price')), 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits=0), 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'), 'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True), 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True), 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True, copy=False), 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True), 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True), 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True, copy=False), 'lot_id': fields.many2one('stock.production.lot', 'Lot'), 'state': fields.selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True, copy=False, help=' * The \'Draft\' status is set automatically as draft when repair order in draft status. \ \n* The \'Confirmed\' status is set automatically as confirm when repair order in confirm status. \ \n* The \'Done\' status is set automatically when repair order is completed.\ \n* The \'Cancelled\' status is set automatically when user cancel repair order.'), } _defaults = { 'state': lambda *a: 'draft', 'product_uom_qty': lambda *a: 1, } def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None): """ On change of operation type it sets source location, destination location and to invoice field. @param product: Changed operation type. @param guarantee_limit: Guarantee limit of current record. @return: Dictionary of values. """ if not type: return {'value': { 'location_id': False, 'location_dest_id': False }} location_obj = self.pool.get('stock.location') warehouse_obj = self.pool.get('stock.warehouse') location_id = location_obj.search(cr, uid, [('usage', '=', 'production')], context=context) location_id = location_id and location_id[0] or False if type == 'add': # TOCHECK: Find stock location for user's company warehouse or # repair order's company's warehouse (company_id field is added in fix of lp:831583) args = company_id and [('company_id', '=', company_id)] or [] warehouse_ids = warehouse_obj.search(cr, uid, args, context=context) stock_id = False if warehouse_ids: stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now()) return {'value': { 'to_invoice': to_invoice, 'location_id': stock_id, 'location_dest_id': location_id }} scrap_location_ids = location_obj.search(cr, uid, [('scrap_location', '=', True)], context=context) return {'value': { 'to_invoice': False, 'location_id': location_id, 'location_dest_id': scrap_location_ids and scrap_location_ids[0] or False, }}
class AcquirerPaypal(osv.Model): _inherit = 'payment.acquirer' def _get_paypal_urls(self, cr, uid, environment, context=None): """ Paypal URLS """ if environment == 'prod': return { 'paypal_form_url': 'https://www.paypal.com/cgi-bin/webscr', 'paypal_rest_url': 'https://api.paypal.com/v1/oauth2/token', } else: return { 'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token', } def _get_providers(self, cr, uid, context=None): providers = super(AcquirerPaypal, self)._get_providers(cr, uid, context=context) providers.append(['paypal', 'Paypal']) return providers _columns = { 'paypal_email_account': fields.char('Paypal Email ID', required_if_provider='paypal'), 'paypal_seller_account': fields.char( 'Paypal Merchant ID', help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.'), 'paypal_use_ipn': fields.boolean('Use IPN', help='Paypal Instant Payment Notification'), # Server 2 server 'paypal_api_enabled': fields.boolean('Use Rest API'), 'paypal_api_username': fields.char('Rest API Username'), 'paypal_api_password': fields.char('Rest API Password'), 'paypal_api_access_token': fields.char('Access Token'), 'paypal_api_access_token_validity': fields.datetime('Access Token Validity'), } _defaults = { 'paypal_use_ipn': True, 'fees_active': False, 'fees_dom_fixed': 0.35, 'fees_dom_var': 3.4, 'fees_int_fixed': 0.35, 'fees_int_var': 3.9, 'paypal_api_enabled': False, } def _migrate_paypal_account(self, cr, uid, context=None): """ COMPLETE ME """ cr.execute('SELECT id, paypal_account FROM res_company') res = cr.fetchall() for (company_id, company_paypal_account) in res: if company_paypal_account: company_paypal_ids = self.search(cr, uid, [('company_id', '=', company_id), ('provider', '=', 'paypal')], limit=1, context=context) if company_paypal_ids: self.write(cr, uid, company_paypal_ids, {'paypal_email_account': company_paypal_account}, context=context) else: paypal_view = self.pool['ir.model.data'].get_object(cr, uid, 'payment_paypal', 'paypal_acquirer_button') self.create(cr, uid, { 'name': 'Paypal', 'provider': 'paypal', 'paypal_email_account': company_paypal_account, 'view_template_id': paypal_view.id, }, context=context) return True def paypal_compute_fees(self, cr, uid, id, amount, currency_id, country_id, context=None): """ Compute paypal fees. :param float amount: the amount to pay :param integer country_id: an ID of a res.country, or None. This is the customer's country, to be compared to the acquirer company country. :return float fees: computed fees """ acquirer = self.browse(cr, uid, id, context=context) if not acquirer.fees_active: return 0.0 country = self.pool['res.country'].browse(cr, uid, country_id, context=context) if country and acquirer.company_id.country_id.id == country.id: percentage = acquirer.fees_dom_var fixed = acquirer.fees_dom_fixed else: percentage = acquirer.fees_int_var fixed = acquirer.fees_int_fixed fees = (percentage / 100.0 * amount + fixed ) / (1 - percentage / 100.0) return fees def paypal_form_generate_values(self, cr, uid, id, values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'web.base.url') acquirer = self.browse(cr, uid, id, context=context) paypal_tx_values = dict(values) paypal_tx_values.update({ 'cmd': '_xclick', 'business': acquirer.paypal_email_account, 'item_name': '%s: %s' % (acquirer.company_id.name, values['reference']), 'item_number': values['reference'], 'amount': values['amount'], 'currency_code': values['currency'] and values['currency'].name or '', 'address1': values.get('partner_address'), 'city': values.get('partner_city'), 'country': values.get('partner_country') and values.get('partner_country').name or '', 'state': values.get('partner_state') and values.get('partner_state').name or '', 'email': values.get('partner_email'), 'zip_code': values.get('partner_zip'), 'first_name': values.get('partner_first_name'), 'last_name': values.get('partner_last_name'), 'paypal_return': '%s' % urlparse.urljoin(base_url, PaypalController._return_url), 'notify_url': '%s' % urlparse.urljoin(base_url, PaypalController._notify_url), 'cancel_return': '%s' % urlparse.urljoin(base_url, PaypalController._cancel_url), 'handling': '%.2f' % paypal_tx_values.pop('fees', 0.0) if acquirer.fees_active else False, 'custom': json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) if paypal_tx_values.get('return_url') else False, }) return paypal_tx_values def paypal_get_form_action_url(self, cr, uid, id, context=None): acquirer = self.browse(cr, uid, id, context=context) return self._get_paypal_urls(cr, uid, acquirer.environment, context=context)['paypal_form_url'] def _paypal_s2s_get_access_token(self, cr, uid, ids, context=None): """ Note: see # see http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem for explanation why we use Authorization header instead of urllib2 password manager """ res = dict.fromkeys(ids, False) parameters = werkzeug.url_encode({'grant_type': 'client_credentials'}) for acquirer in self.browse(cr, uid, ids, context=context): tx_url = self._get_paypal_urls(cr, uid, acquirer.environment)['paypal_rest_url'] request = urllib2.Request(tx_url, parameters) # add other headers (https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/) request.add_header('Accept', 'application/json') request.add_header('Accept-Language', 'en_US') # add authorization header base64string = base64.encodestring('%s:%s' % ( acquirer.paypal_api_username, acquirer.paypal_api_password) ).replace('\n', '') request.add_header("Authorization", "Basic %s" % base64string) request = urllib2.urlopen(request) result = request.read() res[acquirer.id] = json.loads(result).get('access_token') request.close() return res
# -*- coding: utf-8 -*- from ecore.osv import orm, fields def selection_fn(obj, cr, uid, context=None): return list(enumerate(["Corge", "Grault", "Wheee", "Moog"])) def function_fn(model, cr, uid, ids, field_name, arg, context): return dict((id, 3) for id in ids) 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')),