def _graph_y_query(self): raise UserError( _('Undefined graph model for Sales Team: %s') % self.name)
def get_claim_report_user(self, employee_id, **post): if not request.env.user.has_group('fleet.fleet_group_manager'): return request.not_found() employee = request.env['hr.employee'].search( [('id', '=', employee_id)], limit=1) partner_ids = (employee.user_id.partner_id | employee.sudo().address_home_id).ids if not employee or not partner_ids: return request.not_found() car_assignation_logs = request.env[ 'fleet.vehicle.assignation.log'].search([('driver_id', 'in', partner_ids)]) doc_list = request.env['ir.attachment'].search( [('res_model', '=', 'fleet.vehicle.assignation.log'), ('res_id', 'in', car_assignation_logs.ids)], order='create_date') writer = PdfFileWriter() font = "Helvetica" normal_font_size = 14 for document in doc_list: car_line_doc = request.env['fleet.vehicle.assignation.log'].browse( document.res_id) try: reader = PdfFileReader(io.BytesIO( base64.b64decode(document.datas)), strict=False, overwriteWarnings=False) except Exception: continue width = float(reader.getPage(0).mediaBox.getUpperRight_x()) height = float(reader.getPage(0).mediaBox.getUpperRight_y()) header = io.BytesIO() can = canvas.Canvas(header) can.setFont(font, normal_font_size) can.setFillColorRGB(1, 0, 0) car_name = car_line_doc.vehicle_id.display_name date_start = car_line_doc.date_start date_end = car_line_doc.date_end or '...' text_to_print = _("%s (driven from: %s to %s)") % ( car_name, date_start, date_end) can.drawCentredString(width / 2, height - normal_font_size, text_to_print) can.save() header_pdf = PdfFileReader(header, overwriteWarnings=False) for page_number in range(0, reader.getNumPages()): page = reader.getPage(page_number) page.mergePage(header_pdf.getPage(0)) writer.addPage(page) _buffer = io.BytesIO() writer.write(_buffer) merged_pdf = _buffer.getvalue() _buffer.close() pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(merged_pdf))] return request.make_response(merged_pdf, headers=pdfhttpheaders)
def unlink(self): for order in self: if not order.state == 'cancel': raise UserError(_('In order to delete a purchase order, you must cancel it first.')) return super(PurchaseOrder, self).unlink()
def _get_quants_action(self, domain=None, extend=False): """ Returns an action to open quant view. Depending of the context (user have right to be inventory mode or not), the list view will be editable or readonly. :param domain: List for the domain, empty by default. :param extend: If True, enables form, graph and pivot views. False by default. """ self._quant_tasks() action = { 'name': _('Update Quantity'), 'view_type': 'tree', 'view_mode': 'list', 'res_model': 'stock.quant', 'type': 'ir.actions.act_window', 'context': dict(self.env.context), 'domain': domain or [], 'help': """ <p class="o_view_nocontent_empty_folder">No Stock On Hand</p> <p>This analysis gives you an overview of the current stock level of your products.</p> """ } if self._is_inventory_mode(): action['view_id'] = self.env.ref( 'stock.view_stock_quant_tree_editable').id # fixme: erase the following condition when it'll be possible to create a new record # from a empty grouped editable list without go through the form view. if not self.search_count( [('company_id', '=', self.env.company.id), ('location_id.usage', 'in', ['internal', 'transit'])]): action['context'].update({ 'search_default_productgroup': 0, 'search_default_locationgroup': 0 }) else: action['view_id'] = self.env.ref('stock.view_stock_quant_tree').id # Enables form view in readonly list action.update({ 'view_mode': 'tree,form', 'views': [ (action['view_id'], 'list'), (self.env.ref('stock.view_stock_quant_form').id, 'form'), ], }) if extend: action.update({ 'view_mode': 'tree,form,pivot,graph', 'views': [ (action['view_id'], 'list'), (self.env.ref('stock.view_stock_quant_form').id, 'form'), (self.env.ref('stock.view_stock_quant_pivot').id, 'pivot'), (self.env.ref('stock.stock_quant_view_graph').id, 'graph'), ], }) return action
def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('You cannot create recursive departments.'))
def _create_invoice(self, order, so_line, amount): if (self.advance_payment_method == 'percentage' and self.amount <= 0.00) or (self.advance_payment_method == 'fixed' and self.fixed_amount <= 0.00): raise UserError( _('The value of the down payment amount must be positive.')) if self.advance_payment_method == 'percentage': amount = order.amount_untaxed * self.amount / 100 name = _("Down payment of %s%%") % (self.amount, ) else: amount = self.fixed_amount name = _('Down Payment') invoice_vals = { 'type': 'out_invoice', 'invoice_origin': order.name, 'invoice_user_id': order.user_id.id, 'narration': order.note, 'partner_id': order.partner_invoice_id.id, 'fiscal_position_id': order.fiscal_position_id.id or order.partner_id.property_account_position_id.id, 'partner_shipping_id': order.partner_shipping_id.id, 'currency_id': order.pricelist_id.currency_id.id, 'invoice_payment_ref': order.client_order_ref, 'invoice_payment_term_id': order.payment_term_id.id, 'invoice_partner_bank_id': order.company_id.partner_id.bank_ids[:1], 'team_id': order.team_id.id, 'campaign_id': order.campaign_id.id, 'medium_id': order.medium_id.id, 'source_id': order.source_id.id, 'invoice_line_ids': [(0, 0, { 'name': name, 'price_unit': amount, 'quantity': 1.0, 'product_id': self.product_id.id, 'product_uom_id': so_line.product_uom.id, 'tax_ids': [(6, 0, so_line.tax_id.ids)], 'sale_line_ids': [(6, 0, [so_line.id])], 'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)], 'analytic_account_id': order.analytic_account_id.id or False, })], } if order.fiscal_position_id: invoice_vals['fiscal_position_id'] = order.fiscal_position_id.id invoice = self.env['account.move'].create(invoice_vals) invoice.message_post_with_view( 'mail.message_origin_link', values={ 'self': invoice, 'origin': order }, subtype_id=self.env.ref('mail.mt_note').id) return invoice
def check_location_id(self): for quant in self: if quant.location_id.usage == 'view': raise ValidationError( _('You cannot take products from or deliver products to a location of type "view".' ))
def _message_get_suggested_recipients(self): recipients = super(Partner, self)._message_get_suggested_recipients() for partner in self: partner._message_add_suggested_recipient( recipients, partner=partner, reason=_('Partner Profile')) return recipients
def _check_coupon_code(self, order): message = {} applicable_programs = order._get_applicable_programs() if self.state in ('used', 'expired') or \ (self.expiration_date and self.expiration_date < order.date_order.date()): message = { 'error': _('This coupon %s has been used or is expired.') % (self.code) } elif self.state == 'reserved': message = { 'error': _('This coupon %s exists but the origin sales order is not validated yet.' ) % (self.code) } # Minimum requirement should not be checked if the coupon got generated by a promotion program (the requirement should have only be checked to generate the coupon) elif self.program_id.program_type == 'coupon_program' and not self.program_id._filter_on_mimimum_amount( order): message = { 'error': _('A minimum of %s %s should be purchased to get the reward') % (self.program_id.rule_minimum_amount, self.program_id.currency_id.name) } elif not self.program_id.active: message = { 'error': _('The coupon program for %s is in draft or closed state') % (self.code) } elif self.partner_id and self.partner_id != order.partner_id: message = {'error': _('Invalid partner.')} elif self.program_id in order.applied_coupon_ids.mapped('program_id'): message = { 'error': _('A Coupon is already applied for the same reward') } elif self.program_id._is_global_discount_program( ) and order._is_global_discount_already_applied(): message = {'error': _('Global discounts are not cumulable.')} elif self.program_id.reward_type == 'product' and not order._is_reward_in_order_lines( self.program_id): message = { 'error': _('The reward products should be in the sales order lines to apply the discount.' ) } elif not self.program_id._is_valid_partner(order.partner_id): message = { 'error': _("The customer doesn't have access to this reward.") } # Product requirement should not be checked if the coupon got generated by a promotion program (the requirement should have only be checked to generate the coupon) elif self.program_id.program_type == 'coupon_program' and not self.program_id._filter_programs_on_products( order): message = { 'error': _("You don't have the required product quantities on your sales order. All the products should be recorded on the sales order. (Example: You need to have 3 T-shirts on your sales order if the promotion is 'Buy 2, Get 1 Free')." ) } else: if self.program_id not in applicable_programs and self.program_id.promo_applicability == 'on_current_order': message = { 'error': _('At least one of the required conditions is not met to get the reward!' ) } return message
def _check_promo_code(self, order, coupon_code): message = {} applicable_programs = order._get_applicable_programs() if self.maximum_use_number != 0 and self.order_count >= self.maximum_use_number: message = { 'error': _('Promo code %s has been expired.') % (coupon_code) } elif not self._filter_on_mimimum_amount(order): message = { 'error': _('A minimum of %s %s should be purchased to get the reward') % (self.rule_minimum_amount, self.currency_id.name) } elif self.promo_code and self.promo_code == order.promo_code: message = { 'error': _('The promo code is already applied on this order') } elif not self.promo_code and self in order.no_code_promo_program_ids: message = { 'error': _('The promotional offer is already applied on this order') } elif not self.active: message = {'error': _('Promo code is invalid')} elif self.rule_date_from and self.rule_date_from > order.date_order or self.rule_date_to and order.date_order > self.rule_date_to: message = {'error': _('Promo code is expired')} elif order.promo_code and self.promo_code_usage == 'code_needed': message = {'error': _('Promotionals codes are not cumulative.')} elif self._is_global_discount_program( ) and order._is_global_discount_already_applied(): message = {'error': _('Global discounts are not cumulative.')} elif self.promo_applicability == 'on_current_order' and self.reward_type == 'product' and not order._is_reward_in_order_lines( self): message = { 'error': _('The reward products should be in the sales order lines to apply the discount.' ) } elif not self._is_valid_partner(order.partner_id): message = { 'error': _("The customer doesn't have access to this reward.") } elif not self._filter_programs_on_products(order): message = { 'error': _("You don't have the required product quantities on your sales order. If the reward is same product quantity, please make sure that all the products are recorded on the sales order (Example: You need to have 3 T-shirts on your sales order if the promotion is 'Buy 2, Get 1 Free'." ) } else: if self not in applicable_programs and self.promo_applicability == 'on_current_order': message = { 'error': _('At least one of the required conditions is not met to get the reward!' ) } return message
def _merge(self, partner_ids, dst_partner=None, extra_checks=True): """ private implementation of merge partner :param partner_ids : ids of partner to merge :param dst_partner : record of destination res.partner :param extra_checks: pass False to bypass extra sanity check (e.g. email address) """ # super-admin can be used to bypass extra checks if self.env.is_admin(): extra_checks = False Partner = self.env['res.partner'] partner_ids = Partner.browse(partner_ids).exists() 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." )) # check if the list of partners to merge contains child/parent relation child_ids = self.env['res.partner'] for partner_id in partner_ids: child_ids |= Partner.search([('id', 'child_of', [partner_id.id]) ]) - partner_id if partner_ids & child_ids: raise UserError( _("You cannot merge a contact with one of his parent.")) if extra_checks and len(set(partner.email for partner in partner_ids)) > 1: raise UserError( _("All contacts must have the same email. Only the Administrator can merge contacts with different emails." )) # remove dst_partner from partners to merge if dst_partner and dst_partner in partner_ids: src_partners = partner_ids - dst_partner else: ordered_partners = self._get_ordered_partner(partner_ids.ids) dst_partner = ordered_partners[-1] src_partners = ordered_partners[:-1] _logger.info("dst_partner: %s", dst_partner.id) # FIXME: is it still required to make and exception for account.move.line since accounting v9.0 ? if extra_checks and 'account.move.line' in self.env and self.env[ 'account.move.line'].sudo().search([('partner_id', 'in', [ partner.id for partner in src_partners ])]): 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." )) # Make the company of all related users consistent for user in partner_ids.user_ids: user.sudo().write({ 'company_ids': [(6, 0, [dst_partner.company_id.id])], 'company_id': dst_partner.company_id.id }) # call sub methods to do the merge self._update_foreign_keys(src_partners, dst_partner) self._update_reference_fields(src_partners, dst_partner) self._update_values(src_partners, dst_partner) self._log_merge_operation(src_partners, dst_partner) # delete source partner, since they are merged src_partners.unlink()
def unlink(self): for program in self.filtered(lambda x: x.active): raise UserError(_('You can not delete a program in active state')) return super(SaleCouponProgram, self).unlink()
def do_print_checks(self): """ This method is a hook for l10n_xx_check_printing modules to implement actual check printing capabilities """ raise UserError( _("You have to choose a check layout. For this, go in Apps, search for 'Checks layout' and install one." ))
def _compute_dashboard_button_name(self): """ Sets the adequate dashboard button name depending on the Sales Team's options """ for team in self: team.dashboard_button_name = _( "Big Pretty Button :)") # placeholder
def _default_content(self): return ''' <p class="o_default_snippet_text">''' + _( "Start writing here...") + '''</p>
def _get_answer(self, record, body, values, command=False): # onboarding cofficebot_state = self.env.user.cofficebot_state if self._is_bot_in_private_channel(record): # main flow if cofficebot_state == 'onboarding_emoji' and self._body_contains_emoji( body): self.env.user.cofficebot_state = "onboarding_attachement" return _( "Great! 👍<br/>Now, try to <b>send an attachment</b>, like a picture of your cute dog..." ) elif cofficebot_state == 'onboarding_attachement' and values.get( "attachment_ids"): self.env.user.cofficebot_state = "onboarding_command" return _( "Not a cute dog, but you get it 😊<br/>To access special features, <b>start your sentence with '/'</b>. Try to get help." ) elif cofficebot_state == 'onboarding_command' and command == 'help': self.env.user.cofficebot_state = "onboarding_ping" return _( "Wow you are a natural!<br/>Ping someone to grab its attention with @nameoftheuser. <b>Try to ping me using @COfficeBot</b> in a sentence." ) elif cofficebot_state == 'onboarding_ping' and self._is_bot_pinged( values): self.env.user.cofficebot_state = "idle" return _( "Yep, I am here! 🎉 <br/>You finished the tour, you can <b>close this chat window</b>. Enjoy discovering COffice." ) elif cofficebot_state == "idle" and (_('start the tour') in body.lower()): self.env.user.cofficebot_state = "onboarding_emoji" return _("To start, try to send me an emoji :)") # easter eggs elif cofficebot_state == "idle" and body in [ '❤️', _('i love you'), _('love') ]: return _( "Aaaaaw that's really cute but, you know, bots don't work that way. You're too human for me! Let's keep it professional ❤️" ) elif cofficebot_state == "idle" and (('help' in body) or _('help') in body): return _( "I'm just a bot... :( You can check <a href=\"https://www.coffice.com/page/docs\">our documentation</a>) for more information!" ) elif _('f**k') in body or "f**k" in body: return _("That's not nice! I'm a bot but I have feelings... 💔") else: #repeat question if cofficebot_state == 'onboarding_emoji': return _( "Not exactly. To continue the tour, send an emoji, <b>type \":)\"</b> and press enter." ) elif cofficebot_state == 'onboarding_attachement': return _( "To <b>send an attachment</b>, click the 📎 icon on the right, and select a file." ) elif cofficebot_state == 'onboarding_command': return _( "Not sure wat you are doing. Please press / and wait for the propositions. Select \"help\" and press enter" ) elif cofficebot_state == 'onboarding_ping': return _( "Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write \"@cofficebot\" and select me." ) return random.choice([ _("I'm not smart enough to answer your question.<br/>To follow my guide, ask" ) + ": <b>" + _('start the tour') + "</b>", _("Hmmm..."), _("I'm afraid I don't understand. Sorry!"), _("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language...<br/>I can show you features if you write" ) + ": '<b>" + _('start the tour') + "</b>'.", ]) elif self._is_bot_pinged(values): return random.choice( [_("Yep, COfficeBot is in the place!"), _("Pong.")]) return False
def create_invoices(self): sale_orders = self.env['sale.order'].browse( self._context.get('active_ids', [])) if self.advance_payment_method == 'delivered': sale_orders._create_invoices(final=self.deduct_down_payments) else: # Create deposit product if necessary if not self.product_id: vals = self._prepare_deposit_product() self.product_id = self.env['product.product'].create(vals) self.env['ir.config_parameter'].sudo().set_param( 'sale.default_deposit_product_id', self.product_id.id) sale_line_obj = self.env['sale.order.line'] for order in sale_orders: if self.advance_payment_method == 'percentage': amount = order.amount_untaxed * self.amount / 100 else: amount = self.fixed_amount if self.product_id.invoice_policy != 'order': raise UserError( _('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.' )) if self.product_id.type != 'service': raise UserError( _("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product." )) taxes = self.product_id.taxes_id.filtered( lambda r: not order.company_id or r.company_id == order. company_id) if order.fiscal_position_id and taxes: tax_ids = order.fiscal_position_id.map_tax( taxes, self.product_id, order.partner_shipping_id).ids else: tax_ids = taxes.ids context = {'lang': order.partner_id.lang} analytic_tag_ids = [] for line in order.order_line: analytic_tag_ids = [ (4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids ] so_line = sale_line_obj.create({ 'name': _('Down Payment: %s') % (time.strftime('%m %Y'), ), 'price_unit': amount, 'product_uom_qty': 0.0, 'order_id': order.id, 'discount': 0.0, 'product_uom': self.product_id.uom_id.id, 'product_id': self.product_id.id, 'analytic_tag_ids': analytic_tag_ids, 'tax_id': [(6, 0, tax_ids)], 'is_downpayment': True, }) del context self._create_invoice(order, so_line, amount) if self._context.get('open_invoices', False): return sale_orders.action_view_invoice() return {'type': 'ir.actions.act_window_close'}
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] IrAttachment = self.env['ir.attachment'] for mail_id in self.ids: success_pids = [] failure_type = None processing_pid = None mail = None try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # remove attachments if user send the link with the access_token body = mail.body_html or '' attachments = mail.attachment_ids for link in re.findall(r'/web/(?:content|image)/([0-9]+)', body): attachments = attachments - IrAttachment.browse(int(link)) # load attachment binary data with a separate read(), as prefetching all # `datas` (binary field) could bloat the browse cache, triggerring # soft/hard mem limits with temporary data. attachments = [(a['name'], base64.b64decode(a['datas']), a['mimetype']) for a in attachments.sudo().read( ['name', 'datas', 'mimetype']) if a['datas'] is not False] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail._send_prepare_values()) for partner in mail.recipient_ids: values = mail._send_prepare_values(partner=partner) values['partner_id'] = partner email_list.append(values) # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.get_param("mail.catchall.domain") if bounce_alias and catchall_domain: if mail.mail_message_id.is_thread_message(): headers['Return-Path'] = '%s+%d-%s-%d@%s' % ( bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) else: headers['Return-Path'] = '%s+%d@%s' % ( bounce_alias, mail.id, catchall_domain) if mail.headers: try: headers.update(safe_eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({ 'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) # Update notification in a transient exception state to avoid concurrent # update in case an email bounces while sending all emails related to current # mail record. notifs = self.env['mail.notification'].search([ ('notification_type', '=', 'email'), ('mail_id', 'in', mail.ids), ('notification_status', 'not in', ('sent', 'canceled')) ]) if notifs: notif_msg = _( 'Error without exception. Probably due do concurrent access update of notification records. Please see with an administrator.' ) notifs.sudo().write({ 'notification_status': 'exception', 'failure_type': 'UNKNOWN', 'failure_reason': notif_msg, }) # `test_mail_bounce_during_send`, force immediate update to obtain the lock. # see rev. 56596e5240ef920df14d99087451ce6f06ac6d36 notifs.flush(fnames=[ 'notification_status', 'failure_type', 'failure_reason' ], records=notifs) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(mail.email_cc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) processing_pid = email.pop("partner_id", None) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) if processing_pid: success_pids.append(processing_pid) processing_pid = None except AssertionError as error: if str(error) == IrMailServer.NO_VALID_RECIPIENT: failure_type = "RECIPIENT" # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.info( "Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get('email_to')) else: raise if res: # mail has been sent at least once, no major exception occured mail.write({ 'state': 'sent', 'message_id': res, 'failure_reason': False }) _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 mail._postprocess_sent_message(success_pids=success_pids, failure_type=failure_type) except MemoryError: # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # instead of marking the mail as failed _logger.exception( 'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option', mail.id, mail.message_id) # mail status will stay on ongoing since transaction will be rollback raise except (psycopg2.Error, smtplib.SMTPServerDisconnected): # If an error with the database or SMTP session occurs, chances are that the cursor # or SMTP session are unusable, causing further errors when trying to save the state. _logger.exception( 'Exception while processing mail with ID %r and Msg-Id %r.', mail.id, mail.message_id) raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(success_pids=success_pids, failure_reason=failure_reason, failure_type='UNKNOWN') if raise_exception: if isinstance(e, (AssertionError, UnicodeEncodeError)): if isinstance(e, UnicodeEncodeError): value = "Invalid text: %s" % e.object else: # get the args of the original error, wrap into a value and throw a MailDeliveryException # that is an except_orm, with name and value as arguments value = '. '.join(e.args) raise MailDeliveryException(_("Mail Delivery Failed"), value) raise if auto_commit is True: self._cr.commit() return True
def check_product_id(self): if any(elem.product_id.type != 'product' for elem in self): raise ValidationError( _('Quants cannot be created for consumables or services.'))
def write(self, values): if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')): raise UserError(_("You cannot change the type of a sale quote line. Instead you should delete the current line and create a new line of the proper type.")) return super(SaleOrderTemplateLine, self).write(values)
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False): """ Increase the reserved quantity, i.e. increase `reserved_quantity` for the set of quants sharing the combination of `product_id, location_id` if `strict` is set to False or sharing the *exact same characteristics* otherwise. Typically, this method is called when reserving a move or updating a reserved move line. When reserving a chained move, the strict flag should be enabled (to reserve exactly what was brought). When the move is MTS,it could take anything from the stock, so we disable the flag. When editing a move line, we naturally enable the flag, to reflect the reservation according to the edition. :return: a list of tuples (quant, quantity_reserved) showing on which quant the reservation was done and how much the system was able to reserve on it """ self = self.sudo() rounding = product_id.uom_id.rounding quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) reserved_quants = [] if float_compare(quantity, 0, precision_rounding=rounding) > 0: # if we want to reserve available_quantity = self._get_available_quantity( product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) if float_compare(quantity, available_quantity, precision_rounding=rounding) > 0: raise UserError( _('It is not possible to reserve more products of %s than you have in stock.' ) % product_id.display_name) elif float_compare(quantity, 0, precision_rounding=rounding) < 0: # if we want to unreserve available_quantity = sum(quants.mapped('reserved_quantity')) if float_compare(abs(quantity), available_quantity, precision_rounding=rounding) > 0: raise UserError( _('It is not possible to unreserve more products of %s than you have in stock.' ) % product_id.display_name) else: return reserved_quants for quant in quants: if float_compare(quantity, 0, precision_rounding=rounding) > 0: max_quantity_on_quant = quant.quantity - quant.reserved_quantity if float_compare(max_quantity_on_quant, 0, precision_rounding=rounding) <= 0: continue max_quantity_on_quant = min(max_quantity_on_quant, quantity) quant.reserved_quantity += max_quantity_on_quant reserved_quants.append((quant, max_quantity_on_quant)) quantity -= max_quantity_on_quant available_quantity -= max_quantity_on_quant else: max_quantity_on_quant = min(quant.reserved_quantity, abs(quantity)) quant.reserved_quantity -= max_quantity_on_quant reserved_quants.append((quant, -max_quantity_on_quant)) quantity += max_quantity_on_quant available_quantity += max_quantity_on_quant if float_is_zero( quantity, precision_rounding=rounding) or float_is_zero( available_quantity, precision_rounding=rounding): break return reserved_quants
def _check_model_transience(self): if any(self.env[rule.model_id.model].is_transient() for rule in self): raise ValidationError(_('Rules can not be applied on Transient models.'))
class QuantPackage(models.Model): """ Packages containing quants and/or other packages """ _name = "stock.quant.package" _description = "Packages" _order = 'name' name = fields.Char( 'Package Reference', copy=False, index=True, default=lambda self: self.env['ir.sequence'].next_by_code( 'stock.quant.package') or _('Unknown Pack')) quant_ids = fields.One2many( 'stock.quant', 'package_id', 'Bulk Content', readonly=True, domain=['|', ('quantity', '!=', 0), ('reserved_quantity', '!=', 0)]) packaging_id = fields.Many2one('product.packaging', 'Package Type', index=True, check_company=True) location_id = fields.Many2one('stock.location', 'Location', compute='_compute_package_info', index=True, readonly=True, store=True) company_id = fields.Many2one('res.company', 'Company', compute='_compute_package_info', index=True, readonly=True, store=True) owner_id = fields.Many2one('res.partner', 'Owner', compute='_compute_package_info', search='_search_owner', index=True, readonly=True, compute_sudo=True) @api.depends('quant_ids.package_id', 'quant_ids.location_id', 'quant_ids.company_id', 'quant_ids.owner_id', 'quant_ids.quantity', 'quant_ids.reserved_quantity') def _compute_package_info(self): for package in self: values = {'location_id': False, 'owner_id': False} if package.quant_ids: values['location_id'] = package.quant_ids[0].location_id if all(q.owner_id == package.quant_ids[0].owner_id for q in package.quant_ids): values['owner_id'] = package.quant_ids[0].owner_id if all(q.company_id == package.quant_ids[0].company_id for q in package.quant_ids): values['company_id'] = package.quant_ids[0].company_id package.location_id = values['location_id'] package.company_id = values.get('company_id') package.owner_id = values['owner_id'] def name_get(self): return list(self._compute_complete_name().items()) def _compute_complete_name(self): """ Forms complete name of location from parent location to child location. """ res = {} for package in self: name = package.name res[package.id] = name return res def _search_owner(self, operator, value): if value: packs = self.search([('quant_ids.owner_id', operator, value)]) else: packs = self.search([('quant_ids', operator, value)]) if packs: return [('id', 'parent_of', packs.ids)] else: return [('id', '=', False)] def unpack(self): for package in self: move_line_to_modify = self.env['stock.move.line'].search([ ('package_id', '=', package.id), ('state', 'in', ('assigned', 'partially_available')), ('product_qty', '!=', 0), ]) move_line_to_modify.write({'package_id': False}) package.mapped('quant_ids').sudo().write({'package_id': False}) def action_view_picking(self): action = self.env.ref('stock.action_picking_tree_all').read()[0] domain = [ '|', ('result_package_id', 'in', self.ids), ('package_id', 'in', self.ids) ] pickings = self.env['stock.move.line'].search(domain).mapped( 'picking_id') action['domain'] = [('id', 'in', pickings.ids)] return action def view_content_package(self): action = self.env['ir.actions.act_window'].for_xml_id( 'stock', 'quantsact') action['domain'] = [('id', 'in', self._get_contained_quants().ids)] return action def _get_contained_quants(self): return self.env['stock.quant'].search([('package_id', 'in', self.ids)]) def _get_all_products_quantities(self): '''This function computes the different product quantities for the given package ''' # TDE CLEANME: probably to move somewhere else, like in pack op res = {} for quant in self._get_contained_quants(): if quant.product_id not in res: res[quant.product_id] = 0 res[quant.product_id] += quant.quantity return res
def _check_model_name(self): # Don't allow rules on rules records (this model). if any(rule.model_id.model == self._name for rule in self): raise ValidationError(_('Rules can not be applied on the Record Rules model.'))
def portal_my_timesheets(self, page=1, sortby=None, filterby=None, search=None, search_in='all', groupby='project', **kw): Timesheet_sudo = request.env['account.analytic.line'].sudo() values = self._prepare_portal_layout_values() domain = request.env[ 'account.analytic.line']._timesheet_get_portal_domain() searchbar_sortings = { 'date': { 'label': _('Newest'), 'order': 'date desc' }, 'name': { 'label': _('Name'), 'order': 'name' }, } searchbar_inputs = { 'all': { 'input': 'all', 'label': _('Search in All') }, } searchbar_groupby = { 'none': { 'input': 'none', 'label': _('None') }, 'project': { 'input': 'project', 'label': _('Project') }, } today = fields.Date.today() quarter_start, quarter_end = date_utils.get_quarter(today) last_week = today + relativedelta(weeks=-1) last_month = today + relativedelta(months=-1) last_year = today + relativedelta(years=-1) searchbar_filters = { 'all': { 'label': _('All'), 'domain': [] }, 'today': { 'label': _('Today'), 'domain': [("date", "=", today)] }, 'week': { 'label': _('This week'), 'domain': [('date', '>=', date_utils.start_of(today, "week")), ('date', '<=', date_utils.end_of(today, 'week'))] }, 'month': { 'label': _('This month'), 'domain': [('date', '>=', date_utils.start_of(today, 'month')), ('date', '<=', date_utils.end_of(today, 'month'))] }, 'year': { 'label': _('This year'), 'domain': [('date', '>=', date_utils.start_of(today, 'year')), ('date', '<=', date_utils.end_of(today, 'year'))] }, 'quarter': { 'label': _('This Quarter'), 'domain': [('date', '>=', quarter_start), ('date', '<=', quarter_end)] }, 'last_week': { 'label': _('Last week'), 'domain': [('date', '>=', date_utils.start_of(last_week, "week")), ('date', '<=', date_utils.end_of(last_week, 'week'))] }, 'last_month': { 'label': _('Last month'), 'domain': [('date', '>=', date_utils.start_of(last_month, 'month')), ('date', '<=', date_utils.end_of(last_month, 'month'))] }, 'last_year': { 'label': _('Last year'), 'domain': [('date', '>=', date_utils.start_of(last_year, 'year')), ('date', '<=', date_utils.end_of(last_year, 'year'))] }, } # default sort by value if not sortby: sortby = 'date' order = searchbar_sortings[sortby]['order'] # default filter by value if not filterby: filterby = 'all' domain = AND([domain, searchbar_filters[filterby]['domain']]) if search and search_in: domain = AND([domain, [('name', 'ilike', search)]]) timesheet_count = Timesheet_sudo.search_count(domain) # pager pager = portal_pager(url="/my/timesheets", url_args={ 'sortby': sortby, 'search_in': search_in, 'search': search, 'filterby': filterby }, total=timesheet_count, page=page, step=self._items_per_page) if groupby == 'project': order = "project_id, %s" % order timesheets = Timesheet_sudo.search(domain, order=order, limit=self._items_per_page, offset=pager['offset']) if groupby == 'project': grouped_timesheets = [ Timesheet_sudo.concat(*g) for k, g in groupbyelem(timesheets, itemgetter('project_id')) ] else: grouped_timesheets = [timesheets] values.update({ 'timesheets': timesheets, 'grouped_timesheets': grouped_timesheets, 'page_name': 'timesheet', 'default_url': '/my/timesheets', 'pager': pager, 'searchbar_sortings': searchbar_sortings, 'search_in': search_in, 'sortby': sortby, 'groupby': groupby, 'searchbar_inputs': searchbar_inputs, 'searchbar_groupby': searchbar_groupby, 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())), 'filterby': filterby, }) return request.render("hr_timesheet.portal_my_timesheets", values)
def _verify_pin(self): for employee in self: if employee.pin and not employee.pin.isdigit(): raise ValidationError( _("The PIN must be a sequence of digits."))
def _check_order_line_company_id(self): for order in self: companies = order.order_line.product_id.company_id if companies and companies != order.company_id: bad_products = order.order_line.product_id.filtered(lambda p: p.company_id and p.company_id != order.company_id) raise ValidationError((_("Your quotation contains products from company %s whereas your quotation belongs to company %s. \n Please change the company of your quotation or remove the products from other companies (%s).") % (', '.join(companies.mapped('display_name')), order.company_id.display_name, ', '.join(bad_products.mapped('display_name')))))
def get_import_templates(self): return [{ 'label': _('Import Template for Employees'), 'template': '/hr/static/xls/hr_employee.xls' }]
def unlink(self): for line in self: if line.order_id.state in ['purchase', 'done']: raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') % (line.state,)) return super(PurchaseOrderLine, self).unlink()
def _graph_get_model(self): """ skeleton function defined here because it'll be called by crm and/or sale """ raise UserError( _('Undefined graph model for Sales Team: %s') % self.name)