Пример #1
0
 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 {}
Пример #2
0
    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',
        }
Пример #3
0
 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')]
Пример #4
0
    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)
Пример #5
0
    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
Пример #6
0
 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
Пример #7
0
    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)"))
Пример #8
0
 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))
Пример #9
0
    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,
        }
Пример #10
0
    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}
Пример #11
0
    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
Пример #12
0
    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)
Пример #13
0
 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'})
Пример #14
0
    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
Пример #15
0
    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}]
Пример #16
0
 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,
     }
Пример #17
0
    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)
Пример #18
0
 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))
Пример #19
0
 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
Пример #20
0
    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),
        })
Пример #21
0
 def _get_other_roles(self):
     return [
         ('Foster parent', _('foster parent')),
         ('Friend', _('friend')),
         ('Other non-relative', 'Other non-relative'),
         ('Other relative', 'Other relative'),
     ]
Пример #22
0
    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'))
Пример #23
0
 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
Пример #24
0
    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()
Пример #25
0
 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
Пример #26
0
    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},
        }
Пример #27
0
 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 {}
Пример #28
0
    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
Пример #29
0
Файл: hr.py Проект: bitctrl/odoo
 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.'))
Пример #30
0
 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)
Пример #31
0
    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,
        }
Пример #32
0
    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)
Пример #33
0
 def get_import_templates(self):
     return [{
         'label': _('Import Template for Customers'),
         'template': '/base/static/xls/res_partner.xls'
     }]
Пример #34
0
 def _check_parent_id(self):
     if not self._check_recursion():
         raise ValidationError(_('You can not create recursive tags.'))
Пример #35
0
 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
Пример #36
0
 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)
Пример #37
0
 def _check_parent_id(self):
     if not self._check_recursion():
         raise ValidationError(_('You cannot create recursive Partner hierarchies.'))
Пример #38
0
 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
Пример #39
0
 def get_import_templates(self):
     return [{
         'label': _('Import Template for Vendor Pricelists'),
         'template': '/product/static/xls/product_supplierinfo.xls'
     }]
Пример #40
0
 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)
Пример #41
0
 def name_get(self):
     return [
         (rec.id, '{} - {} - {}'.format(
             _('Barcode reader'),
             rec.inventory_id.name, self.env.user.name)) for rec in self]
Пример #42
0
 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)
Пример #44
0
 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()
Пример #45
0
 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)
Пример #46
0
 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')),
     ]
Пример #50
0
    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
Пример #51
0
 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')),
     ]
Пример #53
0
 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)
Пример #54
0
 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."))
Пример #55
0
 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"))
Пример #56
0
 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()
Пример #57
0
 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"))
Пример #58
0
    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
Пример #59
0
 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"))
Пример #60
0
 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"))