def _onchange_product_id_check_availability(self): if not self.product_id or not self.product_uom_qty or not self.product_uom: self.product_packaging = False return {} if self.product_id.type == 'product': precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') product = self.product_id.with_context(warehouse=self.order_id.warehouse_id.id) product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id) if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1: is_available = self._check_routing() if not is_available: message = _('You plan to sell %s %s but you only have %s %s available in %s warehouse.') % \ (self.product_uom_qty, self.product_uom.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name) # We check if some products are available in other warehouses. if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1: message += _('\nThere are %s %s available across all warehouses.\n\n') % \ (self.product_id.virtual_available, product.uom_id.name) for warehouse in self.env['stock.warehouse'].search([]): quantity = self.product_id.with_context(warehouse=warehouse.id).virtual_available if quantity > 0: message += "%s: %s %s\n" % (warehouse.name, quantity, self.product_id.uom_id.name) warning_mess = { 'title': _('Not enough inventory!'), 'message' : message } return {'warning': warning_mess} return {}
def refund(self): """Create a copy of order for refund order""" PosOrder = self.env['pos.order'] current_session = self.env['pos.session'].search([('state', '!=', 'closed'), ('user_id', '=', self.env.uid)], limit=1) if not current_session: raise UserError(_('To return product(s), you need to open a session that will be used to register the refund.')) for order in self: clone = order.copy({ # ot used, name forced by create 'name': order.name + _(' REFUND'), 'session_id': current_session.id, 'date_order': fields.Datetime.now(), 'pos_reference': order.pos_reference, }) PosOrder += clone for clone in PosOrder: for order_line in clone.lines: order_line.write({'qty': -order_line.qty}) return { 'name': _('Return Products'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'pos.order', 'res_id': PosOrder.ids[0], 'view_id': False, 'context': self.env.context, 'type': 'ir.actions.act_window', 'target': 'current', }
def _graph_title_and_key(self): if self.type in ['sale', 'purchase']: return ['', _('Residual amount')] elif self.type == 'cash': return ['', _('Cash: Balance')] elif self.type == 'bank': return ['', _('Bank: Balance')]
def create(self, vals): reference = vals.get('reference', False) reference_type = vals.get('reference_type', False) if vals.get('type') == 'out_invoice' and not reference_type: # fallback on default communication type for partner partner = self.env['res.partner'].browse(vals['partner_id']) reference_type = partner.out_inv_comm_type if reference_type == 'bba': reference = self.generate_bbacomm(vals['type'], reference_type, partner.id, '')['value']['reference'] vals.update({ 'reference_type': reference_type or 'none', 'reference': reference, }) if reference_type == 'bba': if not reference: raise UserError(_('Empty BBA Structured Communication!' '\nPlease fill in a unique BBA Structured Communication.')) if self.check_bbacomm(reference): reference = re.sub('\D', '', reference) vals['reference'] = '+++' + reference[0:3] + '/' + reference[3:7] + '/' + reference[7:] + '+++' same_ids = self.search([('type', '=', 'out_invoice'), ('reference_type', '=', 'bba'), ('reference', '=', vals['reference'])]) if same_ids: raise UserError(_('The BBA Structured Communication has already been used!' '\nPlease create manually a unique BBA Structured Communication.')) return super(AccountInvoice, self).create(vals)
def _get_accounting_data_for_valuation(self): """ Return the accounts and journal to use to post Journal Entries for the real-time valuation of the quant. """ self.ensure_one() accounts_data = self.product_id.product_tmpl_id.get_product_accounts() if self.location_id.valuation_out_account_id: acc_src = self.location_id.valuation_out_account_id.id else: acc_src = accounts_data['stock_input'].id if self.location_dest_id.valuation_in_account_id: acc_dest = self.location_dest_id.valuation_in_account_id.id else: acc_dest = accounts_data['stock_output'].id acc_valuation = accounts_data.get('stock_valuation', False) if acc_valuation: acc_valuation = acc_valuation.id if not accounts_data.get('stock_journal', False): raise UserError(_('You don\'t have any stock journal defined on your product category, check if you have installed a chart of accounts')) if not acc_src: raise UserError(_('Cannot find a stock input account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.name)) if not acc_dest: raise UserError(_('Cannot find a stock output account for the product %s. You must define one on the product category, or on the location, before processing this operation.') % (self.product_id.name)) if not acc_valuation: raise UserError(_('You don\'t have any stock valuation account defined on your product category. You must define one before processing this operation.')) journal_id = accounts_data['stock_journal'].id return journal_id, acc_src, acc_dest, acc_valuation
def _prepare_move_line_value(self): self.ensure_one() if self.account_id: account = self.account_id elif self.product_id: account = self.product_id.product_tmpl_id._get_product_accounts()['expense'] if not account: raise UserError( _("No Expense account found for the product %s (or for its category), please configure one.") % (self.product_id.name)) else: account = self.env['ir.property'].with_context(force_company=self.company_id.id).get('property_account_expense_categ_id', 'product.category') if not account: raise UserError( _('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) aml_name = self.employee_id.name + ': ' + self.name.split('\n')[0][:64] move_line = { 'type': 'src', 'name': aml_name, 'price_unit': self.unit_amount, 'quantity': self.quantity, 'price': self.total_amount, 'account_id': account.id, 'product_id': self.product_id.id, 'uom_id': self.product_uom_id.id, 'analytic_account_id': self.analytic_account_id.id, 'expense_id': self.id, } return move_line
def check_consistency(self): if any(sheet.employee_id != self[0].employee_id for sheet in self): raise UserError(_("Expenses must belong to the same Employee.")) expense_lines = self.mapped('expense_line_ids') if expense_lines and any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines): raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
def _compute_rule(self, localdict): """ :param localdict: dictionary containing the environement in which to compute the rule :return: returns a tuple build as the base/amount computed, the quantity and the rate :rtype: (float, float, float) """ self.ensure_one() if self.amount_select == 'fix': try: return self.amount_fix, float(safe_eval(self.quantity, localdict)), 100.0 except Exception as e: raise UserError(_('Wrong quantity defined for salary rule %s (%s).\nError: %s') % (self.name, self.code, e)) elif self.amount_select == 'percentage': try: return (float(safe_eval(self.amount_percentage_base, localdict)), float(safe_eval(self.quantity, localdict)), self.amount_percentage) except Exception as e: raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).\nError: %s') % (self.name, self.code, e)) else: try: safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True) return float(localdict['result']), 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0 except Exception as e: raise UserError(_('Wrong python code defined for salary rule %s (%s).\nError: %s') % (self.name, self.code, e))
def action_send_survey(self): """ Open a window to compose an email, pre-filled with the survey message """ # Ensure that this survey has at least one page with at least one question. if not self.page_ids or not [page.question_ids for page in self.page_ids if page.question_ids]: raise UserError(_('You cannot send an invitation for a survey that has no questions.')) if self.stage_id.closed: raise UserError(_("You cannot send invitations for closed surveys.")) template = self.env.ref('survey.email_template_survey', raise_if_not_found=False) local_context = dict( self.env.context, default_model='survey.survey', default_res_id=self.id, default_survey_id=self.id, default_use_template=bool(template), default_template_id=template and template.id or False, default_composition_mode='comment', notif_layout='mail.mail_notification_light', ) return { 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'survey.invite', 'target': 'new', 'context': local_context, }
def onchange_product_id(self): """ On change of product it sets product quantity, tax account, name, uom of product, unit price and price subtotal. """ if not self.product_id: return partner = self.repair_id.partner_id pricelist = self.repair_id.pricelist_id if partner and self.product_id: self.tax_id = partner.property_account_position_id.map_tax(self.product_id.taxes_id, self.product_id, partner).ids if self.product_id: self.name = self.product_id.display_name self.product_uom = self.product_id.uom_id.id warning = False if not pricelist: warning = { 'title': _('No Pricelist!'), 'message': _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')} else: price = pricelist.get_product_price(self.product_id, self.product_uom_qty, partner) if price is False: warning = { 'title': _('No valid pricelist line found !'), 'message': _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")} else: self.price_unit = price if warning: return {'warning': warning}
def _balance_check(self): for stmt in self: if not stmt.currency_id.is_zero(stmt.difference): if stmt.journal_type == 'cash': if stmt.difference < 0.0: account = stmt.journal_id.loss_account_id name = _('Loss') else: # statement.difference > 0.0 account = stmt.journal_id.profit_account_id name = _('Profit') if not account: raise UserError(_('There is no account defined on the journal %s for %s involved in a cash difference.') % (stmt.journal_id.name, name)) values = { 'statement_id': stmt.id, 'account_id': account.id, 'amount': stmt.difference, 'name': _("Cash difference observed during the counting (%s)") % name, } self.env['account.bank.statement.line'].create(values) else: balance_end_real = formatLang(self.env, stmt.balance_end_real, currency_obj=stmt.currency_id) balance_end = formatLang(self.env, stmt.balance_end, currency_obj=stmt.currency_id) raise UserError(_('The ending balance is incorrect !\nThe expected balance (%s) is different from the computed one. (%s)') % (balance_end_real, balance_end)) return True
def value_to_html(self, value, options): units = dict(TIMEDELTA_UNITS) if value < 0: raise ValueError(_("Durations can't be negative")) if not options or options.get('unit') not in units: raise ValueError(_("A unit must be provided to duration widgets")) locale = babel.Locale.parse(self.user_lang().code) factor = units[options['unit']] sections = [] r = value * factor if options.get('round') in units: round_to = units[options['round']] r = round(r / round_to) * round_to for unit, secs_per_unit in TIMEDELTA_UNITS: v, r = divmod(r, secs_per_unit) if not v: continue section = babel.dates.format_timedelta( v*secs_per_unit, threshold=1, locale=locale) if section: sections.append(section) return u' '.join(sections)
def action_repair_cancel(self): if self.filtered(lambda repair: repair.state == 'done'): raise UserError(_("Cannot cancel completed repairs.")) if any(repair.invoiced for repair in self): raise UserError(_('Repair order is already invoiced.')) self.mapped('operations').write({'state': 'cancel'}) return self.write({'state': 'cancel'})
def onchange_serial_number(self): """ When the user is encoding a move line for a tracked product, we apply some logic to help him. This includes: - automatically switch `qty_done` to 1.0 - warn if he has already encoded `lot_name` in another move line """ res = {} if self.product_id.tracking == 'serial': if not self.qty_done: self.qty_done = 1 message = None if self.lot_name or self.lot_id: move_lines_to_check = self._get_similar_move_lines() - self if self.lot_name: counter = Counter(move_lines_to_check.mapped('lot_name')) if counter.get(self.lot_name) and counter[self.lot_name] > 1: message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.') elif self.lot_id: counter = Counter(move_lines_to_check.mapped('lot_id.id')) if counter.get(self.lot_id.id) and counter[self.lot_id.id] > 1: message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.') if message: res['warning'] = {'title': _('Warning'), 'message': message} return res
def get_bar_graph_datas(self): data = [] today = datetime.strptime(fields.Date.context_today(self), DF) data.append({'label': _('Past'), 'value': 0.0, 'type': 'past'}) day_of_week = int(format_datetime(today, 'e', locale=self._context.get( 'lang') or 'en_US')) first_day_of_week = today + timedelta(days=-day_of_week + 1) for i in range(-1, 4): if i == 0: label = _('This Week') elif i == 3: label = _('Future') else: start_week = first_day_of_week + timedelta(days=i * 7) end_week = start_week + timedelta(days=6) if start_week.month == end_week.month: label = \ str(start_week.day) + '-' + str(end_week.day) + ' ' + \ format_date(end_week, 'MMM', locale=self._context.get( 'lang') or 'en_US') else: label = \ format_date(start_week, 'd MMM', locale=self._context.get('lang') or 'en_US' ) + '-' + format_date( end_week, 'd MMM', locale=self._context.get('lang') or 'en_US') data.append({ 'label': label, 'value': 0.0, 'type': 'past' if i < 0 else 'future'}) select_sql_clause = 'SELECT count(*) FROM helpdesk_ticket AS h ' \ 'WHERE issue_type_id = %(issue_type_id)s' query_args = {'issue_type_id': self.id} query = '' start_date = (first_day_of_week + timedelta(days=-7)) for i in range(0, 6): if i == 0: query += "(" + select_sql_clause + " and start_date < '" + \ start_date.strftime(DF) + "')" elif i == 5: query += " UNION ALL (" + select_sql_clause + \ " and start_date >= '" + \ start_date.strftime(DF) + "')" else: next_date = start_date + timedelta(days=7) query += " UNION ALL (" + select_sql_clause + \ " and start_date >= '" + start_date.strftime(DF) + \ "' and end_date < '" + next_date.strftime(DF) + \ "')" start_date = next_date self.env.cr.execute(query, query_args) query_results = self.env.cr.dictfetchall() for index in range(0, len(query_results)): if query_results[index]: data[index]['value'] = query_results[index].get('count') return [{'values': data}]
def action_send_mail(self): self.ensure_one() if not self.env.user.has_group('hr.group_hr_manager'): raise UserError(_("You don't have the right to do this. Please contact an Administrator.")) if not self.work_email: raise UserError(_("There is no professional email address for this employee.")) template = self.env.ref('hr_presence.mail_template_presence', False) compose_form = self.env.ref('mail.email_compose_message_wizard_form', False) ctx = dict( default_model="hr.employee", default_res_id=self.id, default_use_template=bool(template), default_template_id=template.id, default_composition_mode='comment', default_is_log=True, custom_layout='mail.mail_notification_light', ) return { 'name': _('Compose Email'), 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', 'views': [(compose_form.id, 'form')], 'view_id': compose_form.id, 'target': 'new', 'context': ctx, }
def import_zipfile(self, module_file, force=False): if not module_file: raise Exception(_("No file sent.")) if not zipfile.is_zipfile(module_file): raise UserError(_('File is not a zip file!')) success = [] errors = dict() module_names = [] with zipfile.ZipFile(module_file, "r") as z: for zf in z.filelist: if zf.file_size > MAX_FILE_SIZE: raise UserError(_("File '%s' exceed maximum allowed file size") % zf.filename) with tempdir() as module_dir: z.extractall(module_dir) dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))] for mod_name in dirs: module_names.append(mod_name) try: # assert mod_name.startswith('theme_') path = opj(module_dir, mod_name) self.import_module(mod_name, path, force=force) success.append(mod_name) except Exception, e: _logger.exception('Error while importing module') errors[mod_name] = exception_to_unicode(e)
def get_tweets(self, limit=20): key = request.website.twitter_api_key secret = request.website.twitter_api_secret screen_name = request.website.twitter_screen_name debug = request.env['res.users'].has_group('website.group_website_publisher') if not key or not secret: if debug: return {"error": _("Please set the Twitter API Key and Secret in the Website Settings.")} return [] if not screen_name: if debug: return {"error": _("Please set a Twitter screen name to load favorites from, " "in the Website Settings (it does not have to be yours)")} return [] TwitterTweets = request.env['website.twitter.tweet'] tweets = TwitterTweets.search( [('website_id', '=', request.website.id), ('screen_name', '=', screen_name)], limit=int(limit), order="tweet_id desc") if len(tweets) < 12: if debug: return {"error": _("Twitter user @%(username)s has less than 12 favorite tweets. " "Please add more or choose a different screen name.") % \ {'username': screen_name}} else: return [] return tweets.mapped(lambda t: json.loads(t.tweet))
def _get_mto_route(self): mto_route = self.env.ref('stock.route_warehouse0_mto', raise_if_not_found=False) if not mto_route: mto_route = self.env['stock.location.route'].search([('name', 'like', _('Make To Order'))], limit=1) if not mto_route: raise UserError(_('Can\'t find any generic Make To Order route.')) return mto_route
def _make_access_error(self, operation, records): _logger.info('Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, records.ids[:6], self._uid, records._name) model = records._name description = self.env['ir.model']._get(model).name or model if not self.env.user.has_group('base.group_no_one'): return AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: "%(document_kind)s" (%(document_model)s), Operation: %(operation)s)') % { 'document_kind': description, 'document_model': model, 'operation': operation, }) # This extended AccessError is only displayed in debug mode. # Note that by default, public and portal users do not have # the group "base.group_no_one", even if debug mode is enabled, # so it is relatively safe here to include the list of rules and # record names. rules = self._get_failing(records, mode=operation).sudo() return AccessError(_("""The requested operation ("%(operation)s" on "%(document_kind)s" (%(document_model)s)) was rejected because of the following rules: %(rules_list)s %(multi_company_warning)s (Records: %(example_records)s, User: %(user_id)s)""") % { 'operation': operation, 'document_kind': description, 'document_model': model, 'rules_list': '\n'.join('- %s' % rule.name for rule in rules), 'multi_company_warning': ('\n' + _('Note: this might be a multi-company issue.') + '\n') if any( 'company_id' in r.domain_force for r in rules) else '', 'example_records': ' - '.join(['%s (id=%s)' % (rec.display_name, rec.id) for rec in records[:6].sudo()]), 'user_id': '%s (id=%s)' % (self.env.user.name, self.env.user.id), })
def _get_other_roles(self): return [ ('Foster parent', _('foster parent')), ('Friend', _('friend')), ('Other non-relative', 'Other non-relative'), ('Other relative', 'Other relative'), ]
def button_upgrade(self): Dependency = self.env['ir.module.module.dependency'] self.update_list() todo = list(self) i = 0 while i < len(todo): module = todo[i] i += 1 if module.state not in ('installed', 'to upgrade'): raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (module.name,)) self.check_external_dependencies(module.name, 'to upgrade') for dep in Dependency.search([('name', '=', module.name)]): if dep.module_id.state == 'installed' and dep.module_id not in todo: todo.append(dep.module_id) self.browse(module.id for module in todo).write({'state': 'to upgrade'}) to_install = [] for module in todo: for dep in module.dependencies_id: if dep.state == 'unknown': raise UserError(_('You try to upgrade the module %s that depends on the module: %s.\nBut this module is not available in your system.') % (module.name, dep.name,)) if dep.state == 'uninstalled': to_install += self.search([('name', '=', dep.name)]).ids self.browse(to_install).button_install() return dict(ACTION_DICT, name=_('Apply Schedule Upgrade'))
def _plan_get_stat_button(self, projects): stat_buttons = [] stat_buttons.append({ 'name': _('Timesheets'), 'res_model': 'account.analytic.line', 'domain': [('project_id', 'in', projects.ids)], 'icon': 'fa fa-calendar', }) stat_buttons.append({ 'name': _('Tasks'), 'count': sum(projects.mapped('task_count')), 'res_model': 'project.task', 'domain': [('project_id', 'in', projects.ids)], 'icon': 'fa fa-tasks', }) if request.env.user.has_group('sales_team.group_sale_salesman_all_leads'): sale_orders = projects.mapped('sale_line_id.order_id') | projects.mapped('tasks.sale_order_id') if sale_orders: stat_buttons.append({ 'name': _('Sales Orders'), 'count': len(sale_orders), 'res_model': 'sale.order', 'domain': [('id', 'in', sale_orders.ids)], 'icon': 'fa fa-dollar', }) invoices = sale_orders.mapped('invoice_ids').filtered(lambda inv: inv.type == 'out_invoice') if invoices: stat_buttons.append({ 'name': _('Invoices'), 'count': len(invoices), 'res_model': 'account.invoice', 'domain': [('id', 'in', invoices.ids), ('type', '=', 'out_invoice')], 'icon': 'fa fa-pencil-square-o', }) return stat_buttons
def _create_or_update_picking(self): for line in self: if line.product_id.type in ('product', 'consu'): # Prevent decreasing below received quantity if float_compare(line.product_qty, line.qty_received, line.product_uom.rounding) < 0: raise UserError(_('You cannot decrease the ordered quantity below the received quantity.\n' 'Create a return first.')) if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1: # If the quantity is now below the invoiced quantity, create an activity on the vendor bill # inviting the user to create a refund. activity = self.env['mail.activity'].sudo().create({ 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, 'note': _('The quantities on your purchase order indicate less than billed. You should ask for a refund. '), 'res_id': line.invoice_lines[0].invoice_id.id, 'res_model_id': self.env.ref('account.model_account_invoice').id, }) activity._onchange_activity_type_id() # If the user increased quantity of existing line or created a new line pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit')) picking = pickings and pickings[0] or False if not picking: res = line.order_id._prepare_picking() picking = self.env['stock.picking'].create(res) move_vals = line._prepare_stock_moves(picking) for move_val in move_vals: self.env['stock.move']\ .create(move_val)\ ._action_confirm()\ ._action_assign()
def _plan_prepare_actions(self, projects): actions = [] if len(projects) == 1: if request.env.user.has_group('sales_team.group_sale_salesman'): if not projects.sale_line_id and not projects.tasks.mapped('sale_line_id'): actions.append({ 'label': _("Create a Sales Order"), 'type': 'action', 'action_id': 'sale_timesheet.project_project_action_multi_create_sale_order', 'context': json.dumps({'active_id': projects.id, 'active_model': 'project.project'}), }) if request.env.user.has_group('sales_team.group_sale_salesman_all_leads'): sale_orders = projects.tasks.mapped('sale_line_id.order_id').filtered(lambda so: so.invoice_status == 'to invoice') if sale_orders: if len(sale_orders) == 1: actions.append({ 'label': _("Create Invoice"), 'type': 'action', 'action_id': 'sale.action_view_sale_advance_payment_inv', 'context': json.dumps({'active_ids': sale_orders.ids, 'active_model': 'project.project'}), }) else: actions.append({ 'label': _("Create Invoice"), 'type': 'action', 'action_id': 'sale_timesheet.project_project_action_multi_create_invoice', 'context': json.dumps({'active_id': projects.id, 'active_model': 'project.project'}), }) return actions
def setting_opening_move_action(self): """ Called by the 'Initial Balances' button of the setup bar.""" company = self.env.user.company_id # If the opening move has already been posted, we open its form view if company.opening_move_posted(): form_view_id = self.env.ref('account.setup_posted_move_form').id return { 'type': 'ir.actions.act_window', 'name': _('Initial Balances'), 'view_mode': 'form', 'res_model': 'account.move', 'target': 'new', 'res_id': company.account_opening_move_id.id, 'views': [[form_view_id, 'form']], } # Otherwise, we open a custom wizard to post it. company.create_op_move_if_non_existant() new_wizard = self.env['account.opening'].create({'company_id': company.id}) view_id = self.env.ref('account.setup_opening_move_wizard_form').id return { 'type': 'ir.actions.act_window', 'name': _('Initial Balances'), 'view_mode': 'form', 'res_model': 'account.opening', 'target': 'new', 'res_id': new_wizard.id, 'views': [[view_id, 'form']], 'context': {'check_move_validity': False}, }
def _onchange_product_id_check_availability(self): if not self.product_id or not self.product_uom_qty or not self.product_uom: self.product_packaging = False return {} if self.product_id.type == "product": precision = self.env["decimal.precision"].precision_get("Product Unit of Measure") product_qty = self.env["product.uom"]._compute_qty_obj( self.product_uom, self.product_uom_qty, self.product_id.uom_id ) if float_compare(self.product_id.virtual_available, product_qty, precision_digits=precision) == -1: is_available = self._check_routing() if not is_available: warning_mess = { "title": _("Not enough inventory!"), "message": _( "You plan to sell %.2f %s but you only have %.2f %s available!\nThe stock on hand is %.2f %s." ) % ( self.product_uom_qty, self.product_uom.name, self.product_id.virtual_available, self.product_id.uom_id.name, self.product_id.qty_available, self.product_id.uom_id.name, ), } return {"warning": warning_mess} return {}
def state_update(self, newstate, states_to_update, level=100): if level < 1: raise UserError(_('Recursion error in modules dependencies !')) # whether some modules are installed with demo data demo = False for module in self: # determine dependency modules to update/others update_mods, ready_mods = self.browse(), self.browse() for dep in module.dependencies_id: if dep.state == 'unknown': raise UserError(_("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system.") % (module.name, dep.name,)) if dep.depend_id.state == newstate: ready_mods += dep.depend_id else: update_mods += dep.depend_id # update dependency modules that require it, and determine demo for module update_demo = update_mods.state_update(newstate, states_to_update, level=level-1) module_demo = module.demo or update_demo or any(mod.demo for mod in ready_mods) demo = demo or module_demo # check dependencies and update module itself self.check_external_dependencies(module.name, newstate) if module.state in states_to_update: module.write({'state': newstate, 'demo': module_demo}) return demo
def _inverse_remaining_leaves(self): status_list = self.env['hr.leave.type'].search([('limit', '=', False)]) # Create leaves (adding remaining leaves) or raise (reducing remaining leaves) actual_remaining = self._get_remaining_leaves() for employee in self.filtered(lambda employee: employee.remaining_leaves): # check the status list. This is done here and not before the loop to avoid raising # exception on employee creation (since we are in a computed field). if len(status_list) != 1: raise UserError(_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one " "leave type with the option 'Allow to Override Limit' unchecked. (%s Found). " "Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. " "\n You may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in Leaves Application " "to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_list))) status = status_list[0] if status_list else None if not status: continue # if a status is found, then compute remaing leave for current employee difference = float_round(employee.remaining_leaves - actual_remaining.get(employee.id, 0), precision_digits=2) if difference > 0: leave = self.env['hr.leave.allocation'].create({ 'name': _('Allocation for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status.id, 'holiday_type': 'employee', 'number_of_days_temp': difference }) leave.action_approve() if leave.validation_type == 'both': leave.action_validate() elif difference < 0: raise UserError(_('You cannot reduce validated allocation requests.'))
def _construct_constraint_msg(self, country_code): self.ensure_one() vat_no = "'CC##' (CC=Country Code, ##=VAT Number)" vat_no = _ref_vat.get(country_code) or vat_no if self.env.user.company_id.vat_check_vies: return '\n' + _('The VAT number [%s] for partner [%s] either failed the VIES VAT validation check or did not respect the expected format %s.') % (self.vat, self.name, vat_no) return '\n' + _('The VAT number [%s] for partner [%s] does not seem to be valid. \nNote: the expected format is %s') % (self.vat, self.name, vat_no)
def action_rfq_send(self): ''' This function opens a window to compose an email, with the edi purchase template message loaded by default ''' self.ensure_one() ir_model_data = self.env['ir.model.data'] try: if self.env.context.get('send_rfq', False): template_id = ir_model_data.get_object_reference( 'purchase', 'email_template_edi_purchase')[1] else: template_id = ir_model_data.get_object_reference( 'purchase', 'email_template_edi_purchase_done')[1] except ValueError: template_id = False try: compose_form_id = ir_model_data.get_object_reference( 'mail', 'email_compose_message_wizard_form')[1] except ValueError: compose_form_id = False ctx = dict(self.env.context or {}) ctx.update({ 'default_model': 'purchase.order', 'active_model': 'purchase.order', 'active_id': self.ids[0], 'default_res_id': self.ids[0], 'default_use_template': bool(template_id), 'default_template_id': template_id, 'default_composition_mode': 'comment', 'custom_layout': "mail.mail_notification_paynow", 'force_email': True, 'mark_rfq_as_sent': True, }) # In the case of a RFQ or a PO, we want the "View..." button in line with the state of the # object. Therefore, we pass the model description in the context, in the language in which # the template is rendered. lang = self.env.context.get('lang') if {'default_template_id', 'default_model', 'default_res_id' } <= ctx.keys(): template = self.env['mail.template'].browse( ctx['default_template_id']) if template and template.lang: lang = template._render_template(template.lang, ctx['default_model'], ctx['default_res_id']) self = self.with_context(lang=lang) if self.state in ['draft', 'sent']: ctx['model_description'] = _('Request for Quotation') else: ctx['model_description'] = _('Purchase Order') return { 'name': _('Compose Email'), 'type': 'ir.actions.act_window', 'view_mode': 'form', 'res_model': 'mail.compose.message', 'views': [(compose_form_id, 'form')], 'view_id': compose_form_id, 'target': 'new', 'context': ctx, }
def send_mail(self, auto_commit=False): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed. """ notif_layout = self._context.get('custom_layout') # Several custom layouts make use of the model description at rendering, e.g. in the # 'View <document>' button. Some models are used for different business concepts, such as # 'purchase.order' which is used for a RFQ and and PO. To avoid confusion, we must use a # different wording depending on the state of the object. # Therefore, we can set the description in the context from the beginning to avoid falling # back on the regular display_name retrieved in '_notify_prepare_template_context'. model_description = self._context.get('model_description') for wizard in self: # Duplicate attachments linked to the email.template. # Indeed, basic mail.compose.message wizard duplicates attachments in mass # mailing mode. But in 'single post' mode, attachments of an email template # also have to be duplicated to avoid changing their ownership. if wizard.attachment_ids and wizard.composition_mode != 'mass_mail' and wizard.template_id: new_attachment_ids = [] for attachment in wizard.attachment_ids: if attachment in wizard.template_id.attachment_ids: new_attachment_ids.append(attachment.copy({'res_model': 'mail.compose.message', 'res_id': wizard.id}).id) else: new_attachment_ids.append(attachment.id) new_attachment_ids.reverse() wizard.write({'attachment_ids': [(6, 0, new_attachment_ids)]}) # Mass Mailing mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post') ActiveModel = self.env[wizard.model] if wizard.model and hasattr(self.env[wizard.model], 'message_post') else self.env['mail.thread'] if wizard.composition_mode == 'mass_post': # do not send emails directly but use the queue instead # add context key to avoid subscribing the author ActiveModel = ActiveModel.with_context(mail_notify_force_send=False, mail_create_nosubscribe=True) # wizard works in batch mode: [res_id] or active_ids or active_domain if mass_mode and wizard.use_active_domain and wizard.model: res_ids = self.env[wizard.model].search(ast.literal_eval(wizard.active_domain)).ids elif mass_mode and wizard.model and self._context.get('active_ids'): res_ids = self._context['active_ids'] else: res_ids = [wizard.res_id] batch_size = int(self.env['ir.config_parameter'].sudo().get_param('mail.batch_size')) or self._batch_size sliced_res_ids = [res_ids[i:i + batch_size] for i in range(0, len(res_ids), batch_size)] if wizard.composition_mode == 'mass_mail' or wizard.is_log or (wizard.composition_mode == 'mass_post' and not wizard.notify): # log a note: subtype is False subtype_id = False elif wizard.subtype_id: subtype_id = wizard.subtype_id.id else: subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment') for res_ids in sliced_res_ids: # mass mail mode: mail are sudo-ed, as when going through get_mail_values # standard access rights on related records will be checked when browsing them # to compute mail values. If people have access to the records they have rights # to create lots of emails in sudo as it is consdiered as a technical model. batch_mails_sudo = self.env['mail.mail'].sudo() all_mail_values = wizard.get_mail_values(res_ids) for res_id, mail_values in all_mail_values.items(): if wizard.composition_mode == 'mass_mail': batch_mails_sudo |= self.env['mail.mail'].sudo().create(mail_values) else: post_params = dict( message_type=wizard.message_type, subtype_id=subtype_id, email_layout_xmlid=notif_layout, add_sign=not bool(wizard.template_id), mail_auto_delete=wizard.template_id.auto_delete if wizard.template_id else False, model_description=model_description) post_params.update(mail_values) if ActiveModel._name == 'mail.thread': if wizard.model: post_params['model'] = wizard.model post_params['res_id'] = res_id if not ActiveModel.message_notify(**post_params): # if message_notify returns an empty record set, no recipients where found. raise UserError(_("No recipient found.")) else: ActiveModel.browse(res_id).message_post(**post_params) if wizard.composition_mode == 'mass_mail': batch_mails_sudo.send(auto_commit=auto_commit)
def get_import_templates(self): return [{ 'label': _('Import Template for Customers'), 'template': '/base/static/xls/res_partner.xls' }]
def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('You can not create recursive tags.'))
def view_header_get(self, view_id, view_type): res = super(Partner, self).view_header_get(view_id, view_type) if res: return res if not self._context.get('category_id'): return False return _('Partners: ') + self.env['res.partner.category'].browse(self._context['category_id']).name
def copy(self, default=None): self.ensure_one() chosen_name = default.get('name') if default else '' new_name = chosen_name or _('%s (copy)') % self.name default = dict(default or {}, name=new_name) return super(Partner, self).copy(default)
def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('You cannot create recursive Partner hierarchies.'))
def view_header_get(self, view_id, view_type): res = super(ProductProduct, self).view_header_get(view_id, view_type) if self._context.get('categ_id'): return _('Products: ') + self.env['product.category'].browse( self._context['categ_id']).name return res
def get_import_templates(self): return [{ 'label': _('Import Template for Vendor Pricelists'), 'template': '/product/static/xls/product_supplierinfo.xls' }]
def get_empty_list_help(self, help): self = self.with_context(empty_list_help_document_name=_("product"), ) return super(ProductProduct, self).get_empty_list_help(help)
def name_get(self): return [ (rec.id, '{} - {} - {}'.format( _('Barcode reader'), rec.inventory_id.name, self.env.user.name)) for rec in self]
def _check_category_recursion(self): if not self._check_recursion(): raise ValidationError(_('You cannot create recursive categories.')) return True
class AccountPaymentGroup(models.Model): _inherit = 'account.payment.group' # We add signature states state = fields.Selection( selection_add=[ ('draft', 'Borrador'), ('confirmed', 'Confirmado'), ('signature_process', 'En Proceso de Firma'), ('signed', 'Firmado'), # we also change posted for paid ('posted', 'Pagado'), ('cancel', 'Cancelled'), ]) # agregamos reference que fue depreciado y estan acostumbrados a usar reference = fields.Char( string='Ref. pago', ) budget_id = fields.Many2one( related='transaction_id.budget_id', store=True, ) expedient_id = fields.Many2one( 'public_budget.expedient', context={'default_type': 'payment'}, states={'draft': [('readonly', False)]}, ondelete='restrict', ) transaction_id = fields.Many2one( 'public_budget.transaction', ) budget_position_ids = fields.Many2many( relation='voucher_position_rel', comodel_name='public_budget.budget_position', string='Partidas Relacionadas', help='Partidas Presupuestarias Relacionadas', compute='_compute_budget_positions_and_invoices', search='_search_budget_positions', ) # lo agregamos por compatiblidad hacia atras y tmb porque es mas facil invoice_ids = fields.Many2many( comodel_name='account.move', string='Facturas Relacionadas', compute='_compute_budget_positions_and_invoices' ) partner_ids = fields.Many2many( comodel_name='res.partner', compute='_compute_partners', string='Partners' ) advance_request_id = fields.Many2one( 'public_budget.advance_request', readonly=True, ) transaction_with_advance_payment = fields.Boolean( store=True, related='transaction_id.type_id.with_advance_payment', ) user_location_ids = fields.Many2many( compute='_compute_user_locations', comodel_name='public_budget.location', string='User Locations', ) payment_base_date = fields.Date( string='Payment Base Date', readonly=True, # nos pidieron que no haya valor por defecto # default=fields.Date.context_today, states={'draft': [('readonly', False)]}, help='Date used to calculate payment date', ) payment_days = fields.Integer( readonly=True, states={'draft': [('readonly', False)]}, help='Days added to payment base date to get the payment date', ) days_interval_type = fields.Selection([ ('business_days', 'Business Days'), ('calendar_days', 'Calendar Days')], readonly=True, states={'draft': [('readonly', False)]}, default='business_days', ) payment_min_date = fields.Date( compute='_compute_payment_min_date', string='Fecha Min. de Pago', help='El pago no puede ser validado antes de esta fecha', store=True, ) confirmation_date = fields.Date( 'Fecha de Confirmación', readonly=True, states={'draft': [('readonly', False)]}, copy=False, ) to_signature_date = fields.Date( 'Fecha a Proceso de Firma', help='Fecha en la que fue pasado a proceso de firma. Utilizada para ' 'acumular retenciones.', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)]}, readonly=True, copy=False, ) payment_date = fields.Date( required=False, track_visibility='onchange', # al final, para evitar que la seteen equivocadamente, la dejamos # editable solo en isgnature y signed states={ # 'draft': [('readonly', False)], # 'confirmed': [('readonly', False)], 'signature_process': [('readonly', False)], 'signed': [('readonly', False)], }, ) # TODO implementar # paid_withholding_ids = fields.Many2many( # comodel_name='account.voucher.withholding', # string='Retenciones Pagadas', # help='Retenciones pagadas con este voucher', # compute='_get_paid_withholding' # ) show_print_receipt_button = fields.Boolean( _('Show Print Receipt Button'), compute='_compute_show_print_receipt_button', ) withholding_line_ids = fields.Many2many( 'account.move.line', compute='_compute_withholding_lines' ) @api.model def default_get(self, fields): """ hacemos que la fecha de pago no sea required ya que seteamos fecha de validacion si no estaba seteada """ vals = super().default_get(fields) vals['payment_date'] = False return vals def _compute_withholding_lines(self): for rec in self: rec.withholding_line_ids = rec.move_line_ids.filtered( 'tax_line_id') def post(self): for rec in self: # si no estaba seteada la setamos if not rec.payment_date: rec.payment_date = fields.Date.today() # idem para los payments # como ellos no ven el campo payment date tiene mas sentido # pisarlo (por ejemplo por si validaron y luego cancelaron para # corregir fecha o si setearon fecha antes de crear las lineas # en cuyo caso se completa con esa fecha y luego la pudieron # cambiar) TODO faltaria contemplar el caso de cheques cambiados # porque por ahí sobre-escribimos una fecha (si se canceló el pago) # y se re-abrió (igualmente es dificil porque no se pueden cancelar # así nomas pagos con cheques cambiados rec.payment_ids.write({'payment_date': rec.payment_date}) # rec.payment_ids.filtered(lambda x: not x.payment_date).write( # {'payment_date': rec.payment_date}) if ( rec.expedient_id and rec.expedient_id.current_location_id not in rec.user_location_ids): raise ValidationError(_( 'No puede validar un pago si el expediente no está en ' 'una ubicación autorizada para ústed')) return super(AccountPaymentGroup, self.with_context(is_recipt=True)).post() # las seteamos directamente al postear total antes no se usan # @api.constrains('payment_date') # def update_payment_date(self): # for rec in self: # rec.payment_ids.write({'payment_date': rec.payment_date}) def unlink(self): if self.filtered('document_number'): raise ValidationError(_( 'No puede borrar una orden de pago que ya fue numerada')) return super().unlink() def confirm(self): for rec in self: msg = _('It is not possible' ' to confirm a payment if the payment' ' expedient is not in a users' ' allowed location or is in transit') rec.expedient_id and rec.expedient_id.\ check_location_allowed_for_current_user(msg) if not rec.payment_base_date: raise ValidationError(_( 'No puede confirmar una orden de pago sin fecha base de ' 'pago')) # si hay devoluciones entonces si se puede confirmar sin importe if not rec.to_pay_amount and not rec.payment_ids.mapped( 'returned_payment_ids'): raise ValidationError(_( 'No puede confirmar una orden de pago sin importe a pagar') ) if not rec.confirmation_date: rec.confirmation_date = fields.Date.today() # si bien este control lo podría hacer el mimso invoice cuando # se calcula el to_pay_amount (ya que se estaría mandando a pagar) # más de lo permitodo, en realidad el método de mandado a pagar, # si la factura está paga, considera el monto de factura para # por temas de performance y para ser más robusto por si se # pierde el link de to pay lines del pago already_paying = self.transaction_id.payment_group_ids.filtered( lambda x: x.state not in ['cancel', 'draft'] and x != self ).mapped('to_pay_move_line_ids') if rec.to_pay_move_line_ids & already_paying: raise ValidationError(_( 'No puede mandar a pagar líneas que ya se mandaron a ' 'pagar')) # In this case remove all followers when confirm a payment rec.message_unsubscribe(partner_ids=rec.message_partner_ids.ids) return super().confirm() def _get_receiptbook(self): # we dont want any receiptbook as default return False @api.depends('payment_ids.check_ids.state', 'state') def _compute_show_print_receipt_button(self): for rec in self: show_print_receipt_button = False not_handed_checks = rec.payment_ids.mapped('check_ids').filtered( lambda r: r.state in ( 'holding', 'to_be_handed')) if rec.state == 'posted' and not not_handed_checks: show_print_receipt_button = True rec.show_print_receipt_button = show_print_receipt_button @api.depends('payment_base_date', 'payment_days', 'days_interval_type') def _compute_payment_min_date(self): for rec in self: current_date = False business_days_to_add = rec.payment_days if rec.payment_base_date: if rec.days_interval_type == 'business_days': current_date = fields.Date.from_string( rec.payment_base_date) while business_days_to_add > 0: current_date = current_date + relativedelta(days=1) weekday = current_date.weekday() # sunday = 6 if weekday >= 5 or self.env[ 'hr.holidays.public'].is_public_holiday( current_date): continue # if current_date in holidays: # continue business_days_to_add -= 1 else: current_date = fields.Date.from_string( rec.payment_base_date) current_date = current_date + relativedelta( days=rec.payment_days) # además hacemos que la fecha mínima no pueda ser día no habil # sin Importar si el intervalo debe # considerar días habiles o no while current_date.weekday() >= 5 or self.env[ 'hr.holidays.public'].is_public_holiday( current_date): current_date = current_date + relativedelta(days=1) rec.payment_min_date = fields.Date.to_string(current_date) # TODO enable # def _get_paid_withholding(self): # paid_move_ids = [ # x.move_line_id.move_id.id for x in self.line_ids if x.amount] # paid_withholdings = self.env['account.voucher.withholding'].search([( # 'move_line_id.tax_settlement_move_id', 'in', paid_move_ids)]) # self.paid_withholding_ids = paid_withholdings def to_signature_process(self): for rec in self: for payment in rec.payment_ids.filtered( lambda x: x.payment_method_code == 'issue_check'): if not payment.check_number or not payment.check_name: raise ValidationError(_( 'Para mandar a proceso de firma debe definir número ' 'de cheque en cada línea de pago.\n' '* ID de orden de pago: %s' % rec.id)) if rec.currency_id.round(rec.payments_amount - rec.to_pay_amount): raise ValidationError(_( 'No puede mandar a pagar una orden de pago que tiene ' 'Importe a pagar distinto a Importe de los Pagos')) rec.state = 'signature_process' if not rec.to_signature_date: rec.to_signature_date = fields.Date.today() def to_signed(self): self.write({'state': 'signed'}) def back_to_confirmed(self): self.write({'state': 'confirmed'}) # dummy depends to compute values on create @api.depends('transaction_id') def _compute_user_locations(self): for rec in self: rec.user_location_ids = rec.env.user.location_ids @api.model def _search_budget_positions(self, operator, value): return [ ('to_pay_move_line_ids.move_id.invoice_move_ids.' 'definitive_line_id.preventive_line_id.budget_position_id', operator, value)] def _compute_budget_positions_and_invoices(self): for rec in self: # si esta validado entonces las facturas son las macheadas, si no # las seleccionadas move_lines = rec.matched_move_line_ids or rec.to_pay_move_line_ids rec.invoice_ids = move_lines.mapped('move_id') rec.budget_position_ids = rec.invoice_ids.mapped( 'invoice_line_ids.definitive_line_id.preventive_line_id.' 'budget_position_id') @api.depends( 'transaction_id', ) def _compute_partners(self): _logger.info('Get partners from transaction') for rec in self: rec.partner_ids = self.env['res.partner'] transaction = rec.transaction_id if transaction: if transaction.type_id.with_advance_payment and ( transaction.partner_id): # no hace falta que sea el comercial... partners = transaction.partner_id # partners = transaction.partner_id.commercial_partner_id else: # no hace falta que sea el comercial... partners = transaction.mapped( # 'supplier_ids.commercial_partner_id') 'supplier_ids') rec.partner_ids = partners def _get_to_pay_move_lines_domain(self): """ We add transaction to get_move_lines function """ domain = super()._get_to_pay_move_lines_domain() if self.transaction_id: # con esto validamos que no se haya mandado a pagar en otra # orden de pago (si dejamos si está cancelada) already_paying = self.transaction_id.payment_group_ids.filtered( lambda x: x.state != 'cancel').mapped('to_pay_move_line_ids') domain.extend([ ('move_id.transaction_id', '=', self.transaction_id.id), ('id', 'not in', already_paying.ids)]) return domain # modificamos estas funciones para que si esta en borrador no setee ningun # valor por defecto @api.onchange('company_regimenes_ganancias_ids') def change_company_regimenes_ganancias(self): if ( self.state != 'draft' and self.company_regimenes_ganancias_ids and self.partner_type == 'supplier'): self.retencion_ganancias = 'nro_regimen' @api.constrains('state') def update_invoice_amounts(self): _logger.info('Updating invoice amounts from payment group') # when payment state changes we recomputed related invoice values # we could improove this filtering by relevant states for rec in self: rec.invoice_ids.sudo()._compute_to_pay_amount() @api.constrains('confirmation_date', 'payment_min_date', 'payment_date') def check_dates(self): _logger.info('Checking dates') for rec in self: if not rec.confirmation_date: continue for invoice in rec.invoice_ids: if rec.confirmation_date < invoice.invoice_date: raise ValidationError(_( 'La fecha de confirmación no puede ser menor a la ' 'fecha de la factura que se esta pagando.\n' '* Id Factura / Fecha: %s - %s\n' '* Id Pago / Fecha Confirmación: %s - %s') % ( invoice.id, invoice.invoice_date, rec.id, rec.confirmation_date)) if not rec.payment_date: continue if rec.payment_date > fields.Date.context_today(rec): raise ValidationError(_( 'No puede usar una fecha de pago superior a hoy')) if rec.payment_date < rec.confirmation_date: raise ValidationError(_( 'La fecha de validacion del pago no puede ser menor a la ' 'fecha de confirmación.\n' '* Id de Pago: %s\n' '* Fecha de pago: %s\n' '* Fecha de confirmación: %s\n' % ( rec.id, rec.payment_date, rec.confirmation_date))) if rec.payment_date < rec.payment_min_date: raise ValidationError(_( 'La fecha de validacion del pago no puede ser menor a la ' 'fecha mínima de pago\n' '* Id de Pago: %s\n' '* Fecha de pago: %s\n' '* Fecha mínima de pago: %s\n' % ( rec.id, rec.payment_date, rec.payment_min_date))) @api.constrains('unreconciled_amount', 'transaction_id', 'state') def check_avance_transaction_amount(self): """ """ for rec in self.filtered('transaction_with_advance_payment'): _logger.info('Checking transaction amount on voucher %s' % rec.id) # forzamos el recalculo porque al ser store no lo recalculaba rec.transaction_id._compute_advance_remaining_amount() advance_remaining_amount = rec.currency_id.round( rec.transaction_id.advance_remaining_amount) if advance_remaining_amount < 0.0: raise ValidationError(_( 'In advance transactions, payment orders amount (%s) ' 'can not be greater than transaction advance remaining' ' amount (%s)') % ( rec.unreconciled_amount, advance_remaining_amount + rec.unreconciled_amount)) @api.model def create(self, vals): """ When the payment group is created, assing document number. """ res = super().create(vals) if res.receiptbook_id.sequence_id and not res.document_number: res = res.with_context(is_recipt=True) res.document_number = res.receiptbook_id.sequence_id.next_by_id() return res def write(self, vals): """ When the payment group is updated and without document number, assing document number. """ res = super().write(vals) if vals.get('receiptbook_id', False): for rec in self.filtered( lambda p: p.receiptbook_id.sequence_id and not p.document_number).with_context(is_recipt=True): rec.document_number = rec.receiptbook_id.sequence_id.next_by_id() return res def action_aeroo_certificado_de_retencion_report(self): self.ensure_one() payments = self.payment_ids.filtered( lambda x: x.payment_method_code == 'withholding' and x.partner_type == 'supplier') if not payments: return False return self.env['ir.actions.report'].search( [('report_name', '=', 'l10n_ar_account_withholding.report_withholding_certificate')], limit=1).report_action(payments)
def check_done_conditions(self): if (self.product_id.tracking != 'none' and not self.lot_id and not self.auto_lot): self._set_messagge_info('info', _('Waiting for input lot')) return False return super().check_done_conditions()
def add_product(self, name=None, category=0, **post): product = request.env['product.product'].create({ 'name': name or _("New Product"), 'public_categ_ids': category }) return "/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id)
def _check_sale_line_type(self): for project in self.filtered(lambda project: project.sale_line_id): if not project.sale_line_id.is_service: raise ValidationError(_("A billable project should be linked to a Sales Order Item having a Service product.")) if project.sale_line_id.is_expense: raise ValidationError(_("A billable project should be linked to a Sales Order Item that does not come from an expense or a vendor bill."))
class SponsorshipGift(models.Model): _name = 'sponsorship.gift' _inherit = ['translatable.model', 'mail.thread'] _description = 'Sponsorship Gift' _order = 'gift_date desc,id desc' ########################################################################## # FIELDS # ########################################################################## # Related records ################# sponsorship_id = fields.Many2one( 'recurring.contract', 'Sponsorship', required=True ) partner_id = fields.Many2one( 'res.partner', 'Partner', related='sponsorship_id.correspondent_id', store=True ) project_id = fields.Many2one( 'compassion.project', 'Project', related='sponsorship_id.project_id', store=True ) project_suspended = fields.Boolean( related='project_id.hold_gifts', track_visibility='onchange' ) child_id = fields.Many2one( 'compassion.child', 'Child', related='sponsorship_id.child_id', store=True ) invoice_line_ids = fields.One2many( 'account.invoice.line', 'gift_id', string='Invoice lines', readonly=True ) payment_id = fields.Many2one( 'account.move', 'GMC Payment', copy=False ) inverse_payment_id = fields.Many2one( 'account.move', 'Inverse move', copy=False ) message_id = fields.Many2one( 'gmc.message.pool', 'GMC message', copy=False ) # Gift information ################## name = fields.Char(compute='_compute_name', translate=False) gmc_gift_id = fields.Char(readonly=True, copy=False) gift_date = fields.Date( compute='_compute_invoice_fields', inverse=lambda g: True, store=True ) date_partner_paid = fields.Date( compute='_compute_invoice_fields', inverse=lambda g: True, store=True ) date_sent = fields.Datetime( related='message_id.process_date', store=True, readonly=True) amount = fields.Float( compute='_compute_invoice_fields', inverse=lambda g: True, store=True, track_visibility='onchange') currency_id = fields.Many2one('res.currency', default=lambda s: s.env.user.company_id.currency_id) currency_usd = fields.Many2one('res.currency', compute='_compute_usd') exchange_rate = fields.Float(readonly=True, copy=False, digits=(12, 6)) amount_us_dollars = fields.Float('Amount due', readonly=True, copy=False) instructions = fields.Char() gift_type = fields.Selection('get_gift_type_selection', required=True, string="Gift for") attribution = fields.Selection('get_gift_attributions', required=True) sponsorship_gift_type = fields.Selection('get_sponsorship_gifts', string="Gift type") state = fields.Selection([ ('draft', _('Draft')), ('verify', _('Verify')), ('open', _('Pending')), ('suspended', _('Suspended')), ('In Progress', _('In Progress')), ('Delivered', _('Delivered')), ('Undeliverable', _('Undeliverable')), ], default='draft', readonly=True, track_visibility='onchange') undeliverable_reason = fields.Selection([ ('Project Transitioned', 'Project Transitioned'), ('Beneficiary Exited', 'Beneficiary Exited'), ('Beneficiary Exited/Whereabouts Unknown', 'Beneficiary Exited/Whereabouts Unknown'), ('Beneficiary Exited More Than 90 Days Ago', 'Beneficiary Exited More Than 90 Days Ago'), ], readonly=True, copy=False) threshold_alert = fields.Boolean( help='Partner exceeded the maximum gift amount allowed', readonly=True, copy=False) threshold_alert_type = fields.Char(readonly=True, copy=False) field_office_notes = fields.Char(readonly=True, copy=False) status_change_date = fields.Datetime(readonly=True) ########################################################################## # FIELDS METHODS # ########################################################################## @api.model def get_gift_type_selection(self): return [ ('Project Gift', _('Project')), ('Family Gift', _('Family')), ('Beneficiary Gift', _('Beneficiary')), ] @api.model def get_gift_attributions(self): return [ ('Center Based Programming', 'CDSP'), ('Home Based Programming (Survival & Early Childhood)', 'CSP'), ('Sponsored Child Family', _('Sponsored Child Family')), ('Sponsorship', _('Sponsorship')), ('Survival', _('Survival')), ('Survival Neediest Families', _('Neediest Families')), ('Survival Neediest Families - Split', _( 'Neediest Families Split')), ] @api.model def get_sponsorship_gifts(self): return [ ('Birthday', _('Birthday')), ('General', _('General')), ('Graduation/Final', _('Graduation/Final')), ] @api.depends('invoice_line_ids', 'invoice_line_ids.state', 'invoice_line_ids.price_subtotal') def _compute_invoice_fields(self): for gift in self.filtered('invoice_line_ids'): invoice_lines = gift.invoice_line_ids pay_dates = invoice_lines.filtered('last_payment').mapped( 'last_payment') or [False] inv_dates = invoice_lines.filtered('due_date').mapped( 'due_date') or [False] amounts = invoice_lines.mapped('price_subtotal') gift.date_partner_paid = fields.Date.to_string(max( map(lambda d: fields.Date.from_string(d), pay_dates))) if gift.sponsorship_gift_type == 'Birthday': gift.gift_date = self.env['generate.gift.wizard'].\ compute_date_birthday_invoice( gift.child_id.birthdate, inv_dates[0]) else: gift_date = max( map(lambda d: fields.Date.from_string(d), inv_dates)) gift.gift_date = gift_date and fields.Date.to_string(gift_date) gift.amount = sum(amounts) def _compute_name(self): for gift in self: if gift.gift_type != 'Beneficiary Gift': name = gift.translate('gift_type') else: name = gift.translate('sponsorship_gift_type') + ' ' + _( 'Gift') name += ' [' + gift.sponsorship_id.name + ']' gift.name = name def _compute_usd(self): for gift in self: gift.currency_usd = self.env.ref('base.USD') ########################################################################## # ORM METHODS # ########################################################################## @api.model def create(self, vals): """ Try to find existing gifts before creating a new one. """ previous_gift = self._search_for_similar_pending_gifts(vals) if previous_gift: return previous_gift._blend_in_other_gift(vals) # If a gift for the same partner is to verify, put as well # the new one to verify. partner_id = self.env['recurring.contract'].browse( vals['sponsorship_id']).partner_id.id gift_to_verify = self.search_count([ ('partner_id', '=', partner_id), ('state', '=', 'verify') ]) if gift_to_verify: vals['state'] = 'verify' new_gift = super(SponsorshipGift, self).create(vals) if new_gift.invoice_line_ids: new_gift.invoice_line_ids.write({'gift_id': new_gift.id}) else: # Prevent computed fields to reset their values vals.pop('message_follower_ids') new_gift.write(vals) new_gift._create_gift_message() return new_gift @api.multi def _search_for_similar_pending_gifts(self, vals): gift_date = vals.get('gift_date') if not gift_date: invl = self.env['account.invoice.line'] dates = [] default = fields.Date.today() for invl_write in vals.get('invoice_line_ids', [[3]]): if invl_write[0] == 0: dates.append(invl_write[2].get('due_date', default)) elif invl_write[0] == 4: dates.append(invl.browse(invl_write[1]).due_date) elif invl_write[0] == 6: dates.extend(invl.browse(invl_write[2]).mapped('due_date')) else: dates.append(default) gift_date = max(dates) return self.search([ ('sponsorship_id', '=', vals['sponsorship_id']), ('gift_type', '=', vals['gift_type']), ('attribution', '=', vals['attribution']), ('gift_date', 'like', gift_date[:4]), ('sponsorship_gift_type', '=', vals.get('sponsorship_gift_type')), ('state', 'in', ['draft', 'verify', 'error']) ], limit=1) @api.multi def _blend_in_other_gift(self, other_gift_vals): self.ensure_one() # Update gift invoice lines invl_write = list() for line_write in other_gift_vals.get('invoice_line_ids', []): if line_write[0] == 6: # Avoid replacing all line_ids => change (6, 0, ids) to # [(4, id), (4, id), ...] invl_write.extend([(4, id) for id in line_write[2]]) else: invl_write.append(line_write) if invl_write: self.write({'invoice_line_ids': invl_write}) else: aggregated_amounts = self.amount + other_gift_vals['amount'] self.write({'amount': aggregated_amounts}) instructions = [self.instructions, other_gift_vals['instructions']] self.instructions = '; '.join(filter(lambda x: x, instructions)) return self @api.multi def unlink(self): # Cancel gmc messages self.mapped('message_id').unlink() to_remove = self.filtered(lambda g: g.state != 'Undeliverable') for gift in to_remove: if gift.gmc_gift_id: raise UserError( _("You cannot delete the %s." "It is already sent to GMC.") % gift.name ) return super(SponsorshipGift, to_remove).unlink() ########################################################################## # PUBLIC METHODS # ########################################################################## @api.model def create_from_invoice_line(self, invoice_line): """ Creates a sponsorship.gift record from an invoice_line :param invoice_line: account.invoice.line record :return: sponsorship.gift record """ gift_vals = self.get_gift_values_from_product(invoice_line) if not gift_vals: return False gift = self.create(gift_vals) if not gift.is_eligible(): gift.state = 'verify' gift.message_id.state = 'postponed' return gift @api.model def get_gift_values_from_product(self, invoice_line): """ Converts a product into sponsorship.gift values :param: invoice_line: account.invoice.line record :return: dictionary of sponsorship.gift values """ product = invoice_line.product_id sponsorship = invoice_line.contract_id if not product.categ_name == GIFT_CATEGORY: return False gift_vals = self.get_gift_types(product) if gift_vals: gift_vals.update({ 'sponsorship_id': sponsorship.id, 'invoice_line_ids': [(4, invoice_line.id)], 'instructions': invoice_line.invoice_id.comment, }) return gift_vals @api.multi def is_eligible(self): """ Verifies the amount is within the thresholds and that the fcp is currently accepting gifts. """ self.ensure_one() sponsorship = self.sponsorship_id if sponsorship.project_id.hold_gifts: return False threshold_rule = self.env['gift.threshold.settings'].search([ ('gift_type', '=', self.gift_type), ('gift_attribution', '=', self.attribution), ('sponsorship_gift_type', '=', self.sponsorship_gift_type), ], limit=1) if threshold_rule: current_rate = threshold_rule.currency_id.rate or 1.0 minimum_amount = threshold_rule.min_amount maximum_amount = threshold_rule.max_amount this_amount = self.amount * current_rate if this_amount < minimum_amount or this_amount > maximum_amount: return False if threshold_rule.yearly_threshold: # search other gifts for the same sponsorship. # we will compare the date with the first january of the # current year next_year = fields.Date.to_string( (date.today() + timedelta(days=365)).replace(month=1, day=1)) firstJanuaryOfThisYear = fields.Date.today()[0:4] + '-01-01' other_gifts = self.search([ ('sponsorship_id', '=', sponsorship.id), ('gift_type', '=', self.gift_type), ('attribution', '=', self.attribution), ('sponsorship_gift_type', '=', self.sponsorship_gift_type), ('gift_date', '>=', firstJanuaryOfThisYear), ('gift_date', '<', next_year), ]) total_amount = this_amount if other_gifts: total_amount += sum(other_gifts.mapped( lambda gift: gift.amount_us_dollars or gift.amount * current_rate)) return total_amount < (maximum_amount * threshold_rule.gift_frequency) return True @api.model def get_gift_types(self, product): """ Given a product, returns the correct values of a gift for GMC. :return: dictionary of sponsorship.gift values """ gift_type_vals = dict() if product.default_code == GIFT_REF[0]: gift_type_vals.update({ 'gift_type': 'Beneficiary Gift', 'attribution': 'Sponsorship', 'sponsorship_gift_type': 'Birthday', }) elif product.default_code == GIFT_REF[1]: gift_type_vals.update({ 'gift_type': 'Beneficiary Gift', 'attribution': 'Sponsorship', 'sponsorship_gift_type': 'General', }) elif product.default_code == GIFT_REF[2]: gift_type_vals.update({ 'gift_type': 'Family Gift', 'attribution': 'Sponsored Child Family', }) elif product.default_code == GIFT_REF[3]: gift_type_vals.update({ 'gift_type': 'Project Gift', 'attribution': 'Center Based Programming', }) elif product.default_code == GIFT_REF[4]: gift_type_vals.update({ 'gift_type': 'Beneficiary Gift', 'attribution': 'Sponsorship', 'sponsorship_gift_type': 'Graduation/Final', }) return gift_type_vals def on_send_to_connect(self): self.write({'state': 'open'}) @api.multi def on_gift_sent(self, data): """ Called when gifts message is received by GMC. Create a move record in the GMC Gift Due Account. :return: """ self.ensure_one() try: exchange_rate = float(data.get('exchange_rate')) except ValueError: exchange_rate = self.env.ref('base.USD').rate or 1.0 data.update({ 'state': 'In Progress', 'amount_us_dollars': exchange_rate * self.amount }) account_credit = self.env['account.account'].search([ ('code', '=', '2002')]) account_debit = self.env['account.account'].search([ ('code', '=', '5003')]) journal = self.env['account.journal'].search([ ('code', '=', 'OD')]) maturity = self.date_sent or fields.Date.today() move_data = { 'journal_id': journal.id, 'ref': 'Gift payment to GMC', 'date': maturity } move_lines_data = list() analytic = self.env['account.analytic.account'].search([ ('code', '=', 'ATT_CD')]) # Create the debit lines from the Gift Account invoiced_amount = sum( self.invoice_line_ids.mapped('price_subtotal') or [0]) if invoiced_amount: for invl in self.invoice_line_ids: move_lines_data.append({ 'partner_id': invl.partner_id.id, 'account_id': account_debit.id, 'name': invl.name, 'debit': invl.price_subtotal, 'credit': 0.0, 'analytic_account_id': analytic.id, 'date': maturity, 'date_maturity': maturity, 'currency_id': self.currency_usd.id, 'amount_currency': invl.price_subtotal * exchange_rate }) if invoiced_amount < self.amount: # Create a move line for the difference that is not invoiced. amount = self.amount - invoiced_amount move_lines_data.append({ 'partner_id': self.partner_id.id, 'account_id': account_debit.id, 'name': self.name, 'debit': amount, 'analytic_account_id': analytic.id, 'date': maturity, 'date_maturity': maturity, 'currency_id': self.currency_usd.id, 'amount_currency': amount * exchange_rate }) # Create the credit line in the GMC Gift Due Account move_lines_data.append({ 'partner_id': self.partner_id.id, 'account_id': account_credit.id, 'name': self.name, 'date': maturity, 'date_maturity': maturity, 'credit': self.amount, 'currency_id': self.currency_usd.id, 'amount_currency': self.amount * exchange_rate * -1 }) move_data['line_ids'] = [ (0, False, line_data) for line_data in move_lines_data] move = self.env['account.move'].create(move_data) move.post() data['payment_id'] = move.id self.write(data) @api.model def process_commkit(self, commkit_data): """" This function is automatically executed when an Update Gift Message is received. It will convert the message from json to odoo format and then update the concerned records :param commkit_data contains the data of the message (json) :return list of gift ids which are concerned by the message """ gift_update_mapping = CreateGiftMapping(self.env) # actually commkit_data is a dictionary with a single entry which # value is a list of dictionary (for each record) gifts_data = commkit_data['GiftUpdatesRequest'][ 'GiftUpdateRequestList'] gift_ids = [] changed_gifts = self # For each dictionary, we update the corresponding record for gift_data in gifts_data: vals = gift_update_mapping.get_vals_from_connect(gift_data) gift_id = vals['id'] gift_ids.append(gift_id) gift = self.env['sponsorship.gift'].browse([gift_id]) if vals.get('state', gift.state) != gift.state: changed_gifts += gift gift.write(vals) changed_gifts.filtered(lambda g: g.state == 'Delivered').\ _gift_delivered() changed_gifts.filtered(lambda g: g.state == 'Undeliverable').\ _gift_undeliverable() return gift_ids ########################################################################## # VIEW CALLBACKS # ########################################################################## @api.multi def view_invoices(self): return { 'name': _("Invoices"), 'domain': [('id', 'in', self.invoice_line_ids.mapped( 'invoice_id').ids)], 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'tree,form', 'views': [ (self.env.ref('account.invoice_tree').id, 'tree'), (self.env.ref('account.invoice_form').id, 'form'), ], 'res_model': 'account.invoice', 'target': 'current', 'context': self.with_context({ 'form_view_ref': 'account.invoice_form', }).env.context } @api.multi def action_ok(self): self.write({'state': 'draft'}) self.mapped('message_id').write({'state': 'new'}) return True @api.multi def action_send(self): self.mapped('message_id').process_messages() return True @api.multi def action_verify(self): self.write({'state': 'verify'}) self.mapped('message_id').write({'state': 'postponed'}) return True @api.multi def action_in_progress(self): self.write({'state': 'In Progress'}) self.mapped('payment_id').post() return True @api.multi def action_suspended(self): self.write({'state': 'suspended'}) self.mapped('payment_id').button_cancel() return True @api.multi def action_cancel(self): """ Cancel Invoices and delete Gifts. """ invoices = self.mapped('invoice_line_ids.invoice_id') invoices.mapped( 'payment_ids.move_line_ids.full_reconcile_id.' 'reconciled_line_ids').remove_move_reconcile() invoices.action_invoice_cancel() self.mapped('message_id').unlink() return self.unlink() @api.onchange('gift_type') def onchange_gift_type(self): if self.gift_type == 'Beneficiary Gift': self.attribution = 'Sponsorship' elif self.gift_type == 'Family Gift': self.attribution = 'Sponsored Child Family' self.sponsorship_gift_type = False elif self.gift_type == 'Project Gift': self.attribution = 'Center Based Programming' self.sponsorship_gift_type = False @api.multi def mark_sent(self): self.mapped('message_id').unlink() return self.write({ 'state': 'Delivered', 'status_change_date': fields.Datetime.now(), }) @api.model def process_gifts_cron(self): gifts = self.search([ ('state', '=', 'draft'), ('gift_date', '<=', fields.Date.today()) ]) gifts.mapped('message_id').process_messages() return True ########################################################################## # PRIVATE METHODS # ########################################################################## @api.multi def _create_gift_message(self): for gift in self: message_obj = self.env['gmc.message.pool'] action_id = self.env.ref( 'gift_compassion.create_gift').id message_vals = { 'action_id': action_id, 'object_id': gift.id, 'partner_id': gift.partner_id.id, 'child_id': gift.child_id.id, 'state': 'new' if gift.state != 'verify' else 'postponed', } gift.message_id = message_obj.create(message_vals) @api.multi def _gift_delivered(self): """ Called when gifts delivered notification is received from GMC. """ pass @api.multi def _gift_undeliverable(self): """ Create an inverse move Notify users defined in settings. """ inverse_credit_account = self.env['account.account'].search([ ('code', '=', '5003') ]) inverse_debit_account = self.env['account.account'].search([ ('code', '=', '2001') ]) analytic = self.env['account.analytic.account'].search([ ('code', '=', 'ATT_CD')]) for gift in self.filtered('payment_id'): pay_move = gift.payment_id inverse_move = pay_move.copy({ 'date': fields.Date.today() }) inverse_move.line_ids.write({'date_maturity': fields.Date.today()}) for line in inverse_move.line_ids: if line.debit > 0: line.write({ 'account_id': inverse_debit_account.id, 'analytic_account_id': False }) elif line.credit > 0: line.write({ 'account_id': inverse_credit_account.id, 'analytic_account_id': analytic.id }) inverse_move.post() gift.inverse_payment_id = inverse_move notify_ids = self.env['staff.notification.settings'].get_param( 'gift_notify_ids') if notify_ids: for gift in self: partner = gift.partner_id child = gift.child_id values = { 'name': partner.name, 'ref': partner.ref, 'child_name': child.name, 'child_code': child.local_id, 'reason': gift.undeliverable_reason } body = ( u"{name} ({ref}) made a gift to {child_name}" u" ({child_code}) which is undeliverable because {reason}." u"\nPlease inform the sponsor about it." ).format(**values) gift.message_post( body=body, subject=_("Gift Undeliverable Notification"), partner_ids=notify_ids, type='comment', subtype='mail.mt_comment', content_subtype='plaintext' )
def unlink(self): if self.filtered('document_number'): raise ValidationError(_( 'No puede borrar una orden de pago que ya fue numerada')) return super().unlink()
def get_gift_type_selection(self): return [ ('Project Gift', _('Project')), ('Family Gift', _('Family')), ('Beneficiary Gift', _('Beneficiary')), ]
def action_move_create(self): """ Create movements associated with retention and reconcile """ ctx = dict(self._context, vat_wh=True, company_id=self.env.user.company_id.id) for ret in self.with_context(ctx): for line in ret.wh_lines: if line.move_id or line.invoice_id.wh_iva: raise exceptions.except_orm( _('Invoice already withhold !'), _("You must omit the follow invoice '%s' !") % (line.invoice_id.name)) # TODO: Get rid of field in future versions? # We rather use the account in the invoice #acc_id = ret.account_id.id if ret.wh_lines: for line in ret.wh_lines: if line.invoice_id.type in ['in_invoice', 'in_refund']: name = ('COMP. RET. IVA ' + (ret.number if ret.number else str()) + ' Doc. ' + (line.invoice_id.supplier_invoice_number if line.invoice_id.supplier_invoice_number else str())) else: name = ('COMP. RET. IVA ' + (ret.number if ret.number else str()) + ' Doc. ' + (line.invoice_id.number if line.invoice_id.number else str())) #invoice = self.env['account.invoice'].with_context(ctx).browse(line.invoice_id.id) invoice = line.invoice_id amount = line.amount_tax_ret #amount = self.total_tax_ret account_id = line.invoice_id.account_id.id journal_id = ret.journal_id.id writeoff_account_id = False writeoff_journal_id = False date = ret.date_ret name = name line_tax_line = line.tax_line #print('line_id', line.id) wh_iva_tax_line = self.env['account.wh.iva.line.tax'].search([('wh_vat_line_id','=',line.id)]) #print("wh_iva_tax_line", wh_iva_tax_line ) ret_move = invoice.ret_and_reconcile( abs(amount), account_id, journal_id, writeoff_account_id,writeoff_journal_id, date,name,wh_iva_tax_line) if (line.invoice_id.currency_id.id != line.invoice_id.company_id.currency_id.id): f_xc = self.env['l10n.ut'].sxc( line.invoice_id.currency_id.id, line.invoice_id.company_id.currency_id.id, line.retention_id.date) for ml in self.env['account.move.line'].search([('move_id','=',ret_move.id )]): ml.write({ 'currency_id': line.invoice_id.currency_id.id}) if ml.credit: ml.write({ 'amount_currency': f_xc(ml.credit) * -1}) elif ml.debit: ml.write({ 'amount_currency': f_xc(ml.debit)}) ret_move.post() # make the withholding line point to that move rl = {'move_id': ret_move.id} lines = [(1, line.id, rl)] ret.write({'wh_lines': lines}) if (rl and line.invoice_id.type in ['out_invoice', 'out_refund']): invoice.write({'wh_iva_id': ret.id}) return True
def clean_selected_tables(self): if not self.table_ids: raise ValidationError(_('''Please select Tables''')) for table_id in self.table_ids: table_id.state = 'cleaning'
def get_sponsorship_gifts(self): return [ ('Birthday', _('Birthday')), ('General', _('General')), ('Graduation/Final', _('Graduation/Final')), ]
def copy(self, default=None): if default is None: default = {} if not default.has_key('name'): default.update(name=_('%s (copy)') % (self.name)) return super(Goods, self).copy(default=default)
def _check_active(self): for rec in self: if not rec.active and rec.report_ids: raise UserError(_("Can not inactive because there are some report reference this record."))
def _check_default_fiscal_position(self): if self.default_fiscal_position_id and self.default_fiscal_position_id not in self.fiscal_position_ids: raise UserError(_("The default fiscal position must be included in the available fiscal positions of the point of sale"))
def unlink(self): if any(requisition.state not in ('draft', 'cancel') for requisition in self): raise UserError(_('You can only delete draft requisitions.')) # Draft requisitions could have some requisition lines. self.mapped('line_ids').unlink() return super(PurchaseRequisition, self).unlink()
def _check_company_journal(self): if self.invoice_journal_id and self.invoice_journal_id.company_id.id != self.company_id.id: raise UserError(_("The invoice journal and the point of sale must belong to the same company"))
def compute_refund(self, mode='refund'): inv_obj = self.env['account.invoice'] inv_tax_obj = self.env['account.invoice.tax'] inv_line_obj = self.env['account.invoice.line'] context = dict(self._context or {}) xml_id = False for form in self: created_inv = [] date = False description = False for inv in inv_obj.browse(context.get('active_ids')): if inv.state in ['draft', 'proforma2', 'cancel']: raise UserError( _('Cannot refund draft/proforma/cancelled invoice.')) if inv.reconciled and mode in ('cancel', 'modify'): raise UserError( _('Cannot refund invoice which is already reconciled, invoice should be unreconciled first. You can only refund this invoice.' )) date = form.date or False description = form.description or inv.name refund = inv.refund(form.date_invoice, date, description, inv.journal_id.id) created_inv.append(refund.id) if mode in ('cancel', 'modify'): movelines = inv.move_id.line_ids to_reconcile_ids = {} to_reconcile_lines = self.env['account.move.line'] for line in movelines: if line.account_id.id == inv.account_id.id: to_reconcile_lines += line to_reconcile_ids.setdefault( line.account_id.id, []).append(line.id) if line.reconciled: line.remove_move_reconcile() refund.action_invoice_open() for tmpline in refund.move_id.line_ids: if tmpline.account_id.id == inv.account_id.id: to_reconcile_lines += tmpline to_reconcile_lines.filtered( lambda l: l.reconciled == False).reconcile() if mode == 'modify': invoice = inv.read( inv_obj._get_refund_modify_read_fields()) invoice = invoice[0] del invoice['id'] invoice_lines = inv_line_obj.browse( invoice['invoice_line_ids']) invoice_lines = inv_obj.with_context( mode='modify')._refund_cleanup_lines(invoice_lines) tax_lines = inv_tax_obj.browse(invoice['tax_line_ids']) tax_lines = inv_obj._refund_cleanup_lines(tax_lines) invoice.update({ 'type': inv.type, 'date_invoice': form.date_invoice, 'state': 'draft', 'number': False, 'invoice_line_ids': invoice_lines, 'tax_line_ids': tax_lines, 'date': date, 'origin': inv.origin, 'fiscal_position_id': inv.fiscal_position_id.id, }) for field in inv_obj._get_refund_common_fields(): if inv_obj._fields[field].type == 'many2one': invoice[field] = invoice[field] and invoice[ field][0] else: invoice[field] = invoice[field] or False inv_refund = inv_obj.create(invoice) if inv_refund.payment_term_id.id: inv_refund._onchange_payment_term_date_invoice() created_inv.append(inv_refund.id) xml_id = (inv.type in ['out_refund', 'out_invoice']) and 'action_invoice_tree1' or \ (inv.type in ['in_refund', 'in_invoice']) and 'action_invoice_tree2' # Put the reason in the chatter subject = _("Invoice refund") body = description refund.message_post(body=body, subject=subject) if xml_id: result = self.env.ref('account.%s' % (xml_id)).read()[0] invoice_domain = safe_eval(result['domain']) invoice_domain.append(('id', 'in', created_inv)) result['domain'] = invoice_domain return result return True
def _check_company_journal(self): if self.journal_id and self.journal_id.company_id.id != self.company_id.id: raise UserError(_("The company of the sales journal is different than the one of point of sale"))
def _check_company_payment(self): if self.env['account.journal'].search_count([('id', 'in', self.journal_ids.ids), ('company_id', '!=', self.company_id.id)]): raise UserError(_("The company of a payment method is different than the one of point of sale"))