Beispiel #1
0
class InvoiceConditionText(Model):
    """add info condition in the invoice"""
    _name = "account.condition_text"
    _description = "Invoices conditions"

    _columns = {
        'name': fields.char('Condition summary', required=True, size=128),
        'type': fields.selection([('header', 'Top condition'),
                                  ('footer', 'Bottom condition')],
                                 'type', required=True),

        'text': fields.html('Condition', translate=True, required=True)}
Beispiel #2
0
class AccountInvoice(Model):
    """Inherit account.invoice in order to add bvr
    printing functionnalites. BVR is a Swiss payment vector"""
    _inherit = "account.invoice"

    _compile_get_ref = re.compile('[^0-9]')

    def _get_reference_type(self, cursor, user, context=None):
        """Function use by the function field reference_type in order to initalise available
        BVR Reference Types"""
        res = super(AccountInvoice, self)._get_reference_type(cursor,
                                                              user,
                                                              context=context)
        res.append(('bvr', 'BVR'))
        return res

    def _compute_full_bvr_name(self,
                               cursor,
                               uid,
                               ids,
                               field_names,
                               arg,
                               context=None):
        res = {}
        move_line_obj = self.pool.get('account.move.line')
        account_obj = self.pool.get('account.account')
        tier_account_id = account_obj.search(
            cursor, uid, [('type', 'in', ['receivable', 'payable'])])
        for inv in self.browse(cursor, uid, ids, context=context):
            move_lines = move_line_obj.search(
                cursor, uid, [('move_id', '=', inv.move_id.id),
                              ('account_id', 'in', tier_account_id)])
            if move_lines:
                if len(move_lines) == 1:
                    res[inv.id] = self._space(inv.get_bvr_ref())
                else:
                    refs = []
                    for move_line in move_line_obj.browse(cursor,
                                                          uid,
                                                          move_lines,
                                                          context=context):
                        refs.append(self._space(move_line.get_bvr_ref()))
                    res[inv.id] = ' ; '.join(refs)
        return res

    _columns = {
        ### BVR reference type BVR or FREE
        'reference_type':
        fields.selection(_get_reference_type, 'Reference Type', required=True),
        ### Partner bank link between bank and partner id
        'partner_bank_id':
        fields.many2one(
            'res.partner.bank',
            'Bank Account',
            help=
            'The partner bank account to pay\nKeep empty to use the default'),
        'bvr_reference':
        fields.function(_compute_full_bvr_name,
                        type="char",
                        size=512,
                        string="BVR REF.",
                        store=True,
                        readonly=True)
    }

    def get_bvr_ref(self, cursor, uid, inv_id, context=None):
        """Retrieve ESR/BVR reference form invoice in order to print it"""
        res = ''
        if isinstance(inv_id, list):
            inv_id = inv_id[0]
        inv = self.browse(cursor, uid, inv_id, context=context)
        ## We check if the type is bvr, if not we return false
        if inv.partner_bank_id.state != 'bvr':
            return ''
        ##
        if inv.partner_bank_id.bvr_adherent_num:
            res = inv.partner_bank_id.bvr_adherent_num
        invoice_number = ''
        if inv.number:
            invoice_number = self._compile_get_ref.sub('', inv.number)
        return mod10r(res + invoice_number.rjust(26 - len(res), '0'))

    def _space(self, nbr, nbrspc=5):
        """Spaces * 5.

        Example:
            self._space('123456789012345')
            '12 34567 89012 345'
        """
        return ''.join([' '[(i - 2) % nbrspc:] + c for i, c in enumerate(nbr)])

    def _update_ref_on_account_analytic_line(self,
                                             cr,
                                             uid,
                                             ref,
                                             move_id,
                                             context=None):
        cr.execute(
            'UPDATE account_analytic_line SET ref=%s'
            '   FROM account_move_line '
            ' WHERE account_move_line.move_id = %s '
            '   AND account_analytic_line.move_id = account_move_line.id',
            (ref, move_id))
        return True

    def action_number(self, cr, uid, ids, context=None):
        res = super(AccountInvoice, self).action_number(cr,
                                                        uid,
                                                        ids,
                                                        context=context)
        move_line_obj = self.pool.get('account.move.line')
        account_obj = self.pool.get('account.account')
        tier_account_id = account_obj.search(
            cr, uid, [('type', 'in', ['receivable', 'payable'])])

        for inv in self.browse(cr, uid, ids, context=context):
            if inv.type != 'out_invoice' and inv.partner_bank_id.state != 'bvr':
                continue
            move_lines = move_line_obj.search(
                cr, uid, [('move_id', '=', inv.move_id.id),
                          ('account_id', 'in', tier_account_id)])
            # We keep this branch for compatibility with single BVR report.
            # This should be cleaned when porting to V8
            if move_lines:
                if len(move_lines) == 1:
                    ref = inv.get_bvr_ref()
                    move_id = inv.move_id
                    if move_id:
                        cr.execute(
                            'UPDATE account_move_line SET transaction_ref=%s'
                            '  WHERE move_id=%s', (ref, move_id.id))
                        self._update_ref_on_account_analytic_line(
                            cr, uid, ref, move_id.id)
                else:
                    for move_line in move_line_obj.browse(cr,
                                                          uid,
                                                          move_lines,
                                                          context=context):
                        ref = move_line.get_bvr_ref()
                        if ref:
                            cr.execute(
                                'UPDATE account_move_line SET transaction_ref=%s'
                                '  WHERE id=%s', (ref, move_line.id))
                            self._update_ref_on_account_analytic_line(
                                cr, uid, ref, move_line.move_id.id)
        return res

    def copy(self, cursor, uid, inv_id, default=None, context=None):
        default = default or {}
        default.update({'reference': False})
        return super(AccountInvoice, self).copy(cursor, uid, inv_id, default,
                                                context)
Beispiel #3
0
class CreditControlPolicyLevel(Model):
    """Define a policy level. A level allows to determine if
    a move line is due and the level of overdue of the line"""

    _name = "credit.control.policy.level"
    _order = 'level'
    _description = """A credit control policy level"""
    _columns = {
        'policy_id':
        fields.many2one('credit.control.policy',
                        'Related Policy',
                        required=True),
        'name':
        fields.char('Name', size=128, required=True, translate=True),
        'level':
        fields.integer('Level', required=True),
        'computation_mode':
        fields.selection([('net_days', 'Due Date'),
                          ('end_of_month', 'Due Date, End Of Month'),
                          ('previous_date', 'Previous Reminder')],
                         'Compute Mode',
                         required=True),
        'delay_days':
        fields.integer('Delay (in days)', required='True'),
        'email_template_id':
        fields.many2one('email.template', 'Email Template', required=True),
        'channel':
        fields.selection([('letter', 'Letter'), ('email', 'Email')],
                         'Channel',
                         required=True),
        'custom_text':
        fields.text('Custom Message', required=True, translate=True),
        'custom_mail_text':
        fields.text('Custom Mail Message', required=True, translate=True),
    }

    def _check_level_mode(self, cr, uid, rids, context=None):
        """ The smallest level of a policy cannot be computed on the
        "previous_date". Return False if this happens.  """
        if isinstance(rids, (int, long)):
            rids = [rids]
        for level in self.browse(cr, uid, rids, context):
            smallest_level_id = self.search(
                cr,
                uid, [('policy_id', '=', level.policy_id.id)],
                order='level asc',
                limit=1,
                context=context)
            smallest_level = self.browse(cr, uid, smallest_level_id[0],
                                         context)
            if smallest_level.computation_mode == 'previous_date':
                return False
        return True

    _sql_constraint = [('unique level', 'UNIQUE (policy_id, level)',
                        'Level must be unique per policy')]

    _constraints = [
        (_check_level_mode,
         'The smallest level can not be of type Previous Reminder', ['level'])
    ]

    def _previous_level(self, cr, uid, policy_level, context=None):
        """ For one policy level, returns the id of the previous level

        If there is no previous level, it returns None, it means that's the
        first policy level

        :param browse_record policy_level: policy level
        :return: previous level id or None if there is no previous level
        """
        previous_level_ids = self.search(
            cr,
            uid, [('policy_id', '=', policy_level.policy_id.id),
                  ('level', '<', policy_level.level)],
            order='level desc',
            limit=1,
            context=context)
        return previous_level_ids[0] if previous_level_ids else None

    # ----- sql time related methods ---------

    def _net_days_get_boundary(self):
        return " (mv_line.date_maturity + %(delay)s)::date <= date(%(controlling_date)s)"

    def _end_of_month_get_boundary(self):
        return (
            "(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+INTERVAL '1 MONTH - 1 day')::date"
            "<= date(%(controlling_date)s)")

    def _previous_date_get_boundary(self):
        return "(cr_line.date + %(delay)s)::date <= date(%(controlling_date)s)"

    def _get_sql_date_boundary_for_computation_mode(self,
                                                    cr,
                                                    uid,
                                                    level,
                                                    controlling_date,
                                                    context=None):
        """Return a where clauses statement for the given
           controlling date and computation mode of the level"""
        fname = "_%s_get_boundary" % (level.computation_mode, )
        if hasattr(self, fname):
            fnc = getattr(self, fname)
            return fnc()
        else:
            raise NotImplementedError(
                _('Can not get function for computation mode: '
                  '%s is not implemented') % (fname, ))

    # -----------------------------------------

    def _get_first_level_move_line_ids(self,
                                       cr,
                                       uid,
                                       level,
                                       controlling_date,
                                       lines,
                                       context=None):
        """Retrieve all the move lines that are linked to a first level.
           We use Raw SQL for performance. Security rule where applied in
           policy object when the first set of lines were retrieved"""
        level_lines = set()
        if not lines:
            return level_lines
        sql = (
            "SELECT DISTINCT mv_line.id\n"
            " FROM account_move_line mv_line\n"
            " WHERE mv_line.id in %(line_ids)s\n"
            " AND NOT EXISTS (SELECT id\n"
            "                 FROM credit_control_line\n"
            "                 WHERE move_line_id = mv_line.id\n"
            # lines from a previous level with a draft or ignored state
            # have to be generated again for the previous level
            "                 AND state not in ('draft', 'ignored'))")
        sql += " AND"
        sql += self._get_sql_date_boundary_for_computation_mode(
            cr, uid, level, controlling_date, context)
        data_dict = {
            'controlling_date': controlling_date,
            'line_ids': tuple(lines),
            'delay': level.delay_days
        }
        cr.execute(sql, data_dict)
        res = cr.fetchall()
        if res:
            level_lines.update([x[0] for x in res])
        return level_lines

    def _get_other_level_move_line_ids(self,
                                       cr,
                                       uid,
                                       level,
                                       controlling_date,
                                       lines,
                                       context=None):
        """ Retrieve the move lines for other levels than first level.
        """
        level_lines = set()
        if not lines:
            return level_lines
        sql = (
            "SELECT mv_line.id\n"
            " FROM account_move_line mv_line\n"
            " JOIN credit_control_line cr_line\n"
            " ON (mv_line.id = cr_line.move_line_id)\n"
            " WHERE cr_line.id = (SELECT credit_control_line.id FROM credit_control_line\n"
            "                            WHERE credit_control_line.move_line_id = mv_line.id\n"
            "                            AND state != 'ignored'"
            "                              ORDER BY credit_control_line.level desc limit 1)\n"
            " AND cr_line.level = %(previous_level)s\n"
            # lines from a previous level with a draft or ignored state
            # have to be generated again for the previous level
            " AND cr_line.state not in ('draft', 'ignored')\n"
            " AND mv_line.id in %(line_ids)s\n")
        sql += " AND "
        sql += self._get_sql_date_boundary_for_computation_mode(
            cr, uid, level, controlling_date, context)
        previous_level_id = self._previous_level(cr,
                                                 uid,
                                                 level,
                                                 context=context)
        previous_level = self.browse(cr,
                                     uid,
                                     previous_level_id,
                                     context=context)
        data_dict = {
            'controlling_date': controlling_date,
            'line_ids': tuple(lines),
            'delay': level.delay_days,
            'previous_level': previous_level.level
        }

        # print cr.mogrify(sql, data_dict)
        cr.execute(sql, data_dict)
        res = cr.fetchall()
        if res:
            level_lines.update([x[0] for x in res])
        return level_lines

    def get_level_lines(self,
                        cr,
                        uid,
                        level_id,
                        controlling_date,
                        lines,
                        context=None):
        """get all move lines in entry lines that match the current level"""
        assert not (isinstance(level_id, list) and len(level_id) > 1), \
            "level_id: only one id expected"
        if isinstance(level_id, list):
            level_id = level_id[0]
        matching_lines = set()
        level = self.browse(cr, uid, level_id, context=context)
        if self._previous_level(cr, uid, level, context=context) is None:
            method = self._get_first_level_move_line_ids
        else:
            method = self._get_other_level_move_line_ids
        matching_lines.update(
            method(cr, uid, level, controlling_date, lines, context=context))
        return matching_lines
Beispiel #4
0
class scan_bvr(TransientModel):

    _name = "scan.bvr"
    _description = "BVR/ESR Scanning Wizard"

    _columns = {
        'journal_id': fields.many2one('account.journal',
                                      string="Invoice journal"),
        'bvr_string': fields.char(size=128,
                                  string='BVR String'),
        'partner_id': fields.many2one('res.partner',
                                      string="Partner"),
        'bank_account_id': fields.many2one('res.partner.bank',
                                           string="Partner Bank Account"),
        'state': fields.selection(
            [
                ('new', 'New'),
                ('valid', 'valid'),
                ('need_extra_info', 'Need extra information'),
            ],
            'State'
        ),
    }

    def _default_journal(self, cr, uid, context=None):
        pool = pooler.get_pool(cr.dbname)
        user = pool.get('res.users').browse(cr, uid, uid, context=context)
        if user.company_id:
            # We will get purchase journal linked with this company
            journal_ids = pool.get('account.journal').search(
                cr,
                uid,
                [('type', '=', 'purchase'),
                 ('company_id', '=', user.company_id.id)],
                context=context
            )
            if len(journal_ids) == 1:
                return journal_ids[0]
            else:
                return False
        else:
            return False

    _defaults = {
        'state': 'new',
        'journal_id': _default_journal,
    }

    def _check_number(self, part_validation):
        nTab = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]
        resultnumber = 0
        for number in part_validation:
            resultnumber = nTab[(resultnumber + int(number) - 0) % 10]
        return (10 - resultnumber) % 10

    def _construct_bvrplus_in_chf(self, bvr_string):

            if len(bvr_string) != 43:
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in first part')
                )
            elif self._check_number(bvr_string[0:2]) != int(bvr_string[2]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in second part')
                )
            elif self._check_number(bvr_string[4:30]) != int(bvr_string[30]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in third part')
                )
            elif self._check_number(bvr_string[33:41]) != int(bvr_string[41]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in fourth part')
                )
            else:
                bvr_struct = {
                    'type': bvr_string[0:2],
                    'amount': 0.0,
                    'reference': bvr_string[4:31],
                    'bvrnumber': bvr_string[4:10],
                    'beneficiaire': self._create_bvr_account(
                        bvr_string[33:42]
                    ),
                    'domain': '',
                    'currency': ''
                }
                return bvr_struct

    def _construct_bvr_in_chf(self, bvr_string):
            if len(bvr_string) != 53:
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in first part')
                )
            elif self._check_number(bvr_string[0:12]) != int(bvr_string[12]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in second part')
                )
            elif self._check_number(bvr_string[14:40]) != int(bvr_string[40]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in third part')
                )
            elif self._check_number(bvr_string[43:51]) != int(bvr_string[51]):
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in fourth part')
                )
            else:
                bvr_struct = {
                    'type': bvr_string[0:2],
                    'amount': float(bvr_string[2:12]) / 100,
                    'reference': bvr_string[14:41],
                    'bvrnumber': bvr_string[14:20],
                    'beneficiaire': self._create_bvr_account(
                        bvr_string[43:52]
                    ),
                    'domain': '',
                    'currency': ''
                }
                return bvr_struct

    def _construct_bvr_postal_in_chf(self, bvr_string):
            if len(bvr_string) != 42:
                raise orm.except_orm(
                    _('Validation Error'),
                    _('BVR CheckSum Error in first part')
                )
            else:

                bvr_struct = {
                    'type': bvr_string[0:2],
                    'amount': float(bvr_string[2:12]) / 100,
                    'reference': bvr_string[14:30],
                    'bvrnumber': '',
                    'beneficiaire': self._create_bvr_account(
                        bvr_string[32:41]
                    ),
                    'domain': '',
                    'currency': ''
                }
                return bvr_struct

    def _construct_bvr_postal_other_in_chf(self, bvr_string):
        if len(bvr_string) != 41:
            raise orm.except_orm(
                _('Validation Error'),
                _('BVR CheckSum Error in first part')
            )
        else:

            bvr_struct = {
                'type': bvr_string[0:2],
                'amount': float(bvr_string[7:16]) / 100,
                'reference': bvr_string[18:33],
                'bvrnumber': '000000',
                'beneficiaire': self._create_bvr_account(
                    bvr_string[34:40]
                ),
                'domain': '',
                'currency': ''
            }
            return bvr_struct

    def _create_invoice_line(self, cr, uid, ids, data, context):
            invoice_line_ids = False
            pool = pooler.get_pool(cr.dbname)
            invoice_line_obj = pool.get('account.invoice.line')
            # First we write partner_id
            self.write(cr, uid, ids, {'partner_id': data['partner_id']})
            # We check that this partner have a default product
            accounts_data = pool.get('res.partner').read(
                cr, uid,
                data['partner_id'],
                ['supplier_invoice_default_product'],
                context=context
            )
            if accounts_data['supplier_invoice_default_product']:
                product_onchange_result = invoice_line_obj.product_id_change(
                    cr, uid, ids,
                    accounts_data['supplier_invoice_default_product'][0],
                    uom_id=False,
                    qty=0,
                    name='',
                    type='in_invoice',
                    partner_id=data['partner_id'],
                    fposition_id=False,
                    price_unit=False,
                    currency_id=False,
                    context=context,
                    company_id=None
                )
                # We will check that the tax specified
                # on the product is price include or amount is 0
                if product_onchange_result['value']['invoice_line_tax_id']:
                    taxes = pool.get('account.tax').browse(
                        cr, uid,
                        product_onchange_result['value']['invoice_line_tax_id']
                    )
                    for taxe in taxes:
                        if not taxe.price_include and taxe.amount != 0.0:
                            raise orm.except_orm(
                                _('Error !'),
                                _('The default product in this partner has '
                                  'wrong taxes configuration')
                            )
                prod = accounts_data['supplier_invoice_default_product'][0]
                account = product_onchange_result['value']['account_id']
                taxes = product_onchange_result['value']['invoice_line_tax_id']
                invoice_line_vals = {
                    'product_id': prod,
                    'account_id': account,
                    'name': product_onchange_result['value']['name'],
                    'uos_id': product_onchange_result['value']['uos_id'],
                    'price_unit': data['bvr_struct']['amount'],
                    'invoice_id': data['invoice_id'],
                    'invoice_line_tax_id': [(6, 0, taxes)],
                }
                invoice_line_ids = invoice_line_obj.create(
                    cr, uid, invoice_line_vals, context=context)
            return invoice_line_ids

    def _create_direct_invoice(self, cr, uid, ids, data, context):
        pool = pooler.get_pool(cr.dbname)
        # We will call the function, that create invoice line
        account_invoice_obj = pool.get('account.invoice')
        account_invoice_tax_obj = pool.get('account.invoice.tax')
        if data['bank_account']:
            account_info = pool.get('res.partner.bank').browse(
                cr, uid, data['bank_account'],
                context=context
            )
        # We will now search the currency_id
        currency_search = pool.get('res.currency').search(
            cr, uid,
            [('name',
              '=',
              data['bvr_struct']['currency'])],
            context=context
        )
        currency_id = pool.get('res.currency').browse(cr, uid,
                                                      currency_search[0],
                                                      context=context)
        # Account Modification
        if data['bvr_struct']['domain'] == 'name':
            pool.get('res.partner.bank').write(
                cr, uid,
                data['bank_account'],
                {'post_number': data['bvr_struct']['beneficiaire']},
                context=context
            )
        else:
            pool.get('res.partner.bank').write(
                cr, uid,
                data['bank_account'],
                {'bvr_adherent_num': data['bvr_struct']['bvrnumber'],
                 'bvr_number': data['bvr_struct']['beneficiaire']},
                context=context
            )
        date_due = time.strftime('%Y-%m-%d')
        # We will now compute the due date and fixe the payment term
        payment_term_id = (account_info.partner_id.property_payment_term and
                           account_info.partner_id.property_payment_term.id or
                           False)
        if payment_term_id:
            # We Calculate due_date
            inv_mod = pool.get('account.invoice')
            res = inv_mod.onchange_payment_term_date_invoice(
                cr, uid, [],
                payment_term_id,
                time.strftime('%Y-%m-%d')
            )
            date_due = res['value']['date_due']

        curr_invoice = {
            'name': time.strftime('%Y-%m-%d'),
            'partner_id': account_info.partner_id.id,
            'account_id': account_info.partner_id.property_account_payable.id,
            'date_due': date_due,
            'date_invoice': time.strftime('%Y-%m-%d'),
            'payment_term': payment_term_id,
            'reference_type': 'bvr',
            'reference': data['bvr_struct']['reference'],
            'amount_total': data['bvr_struct']['amount'],
            'check_total': data['bvr_struct']['amount'],
            'partner_bank_id': account_info.id,
            'comment': '',
            'currency_id': currency_id.id,
            'journal_id': data['journal_id'],
            'type': 'in_invoice',
        }

        last_invoice = account_invoice_obj.create(
            cr, uid,
            curr_invoice,
            context=context
        )
        data['invoice_id'] = last_invoice
        self._create_invoice_line(cr, uid, ids, data, context)
        # Now we create taxes lines
        computed_tax = account_invoice_tax_obj.compute(cr,
                                                       uid,
                                                       last_invoice,
                                                       context=context)
        inv = account_invoice_obj.browse(cr,
                                         uid,
                                         last_invoice,
                                         context=context)
        account_invoice_obj.check_tax_lines(cr,
                                            uid,
                                            inv,
                                            computed_tax,
                                            account_invoice_tax_obj)
        action = {
            'domain': "[('id','=', " + str(last_invoice) + ")]",
            'name': 'Invoices',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'account.invoice',
            'view_id': False,
            'context': "{'type':'out_invoice'}",
            'type': 'ir.actions.act_window',
            'res_id': last_invoice
        }
        return action

    def _create_bvr_account(self, account_unformated):
        acc_len = len(account_unformated)
        account_formated = "%s-%s-%s" % (
            account_unformated[0:2],
            str(int(account_unformated[2:acc_len - 1])),
            account_unformated[acc_len - 1:acc_len]
        )
        return account_formated

    def _get_bvr_structurated(self, bvr_string):
        if bvr_string is not False:
            # We will get the 2 frist digit of the BVr string in order
            # to now the BVR type of this account
            bvr_type = bvr_string[0:2]
            if bvr_type == '01' and len(bvr_string) == 42:
                # This BVR is the type of BVR in CHF
                # WE will call the function and Call
                bvr_struct = self._construct_bvr_postal_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number if not we
                # will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'CHF'
            elif bvr_type == '01':
                # This BVR is the type of BVR in CHF
                # We will call the function and Call
                bvr_struct = self._construct_bvr_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number if not
                # we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'CHF'
            elif bvr_type == '03':
                # It will be (At this time) the same work
                # as for a standard BVR with 01 code
                bvr_struct = self._construct_bvr_postal_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number
                # if not we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'CHF'
            elif bvr_type == '04':
                # It the BVR postal in CHF
                bvr_struct = self._construct_bvrplus_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number
                # if not we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'CHF'
            elif bvr_type == '21':
                # It for a BVR in Euro
                bvr_struct = self._construct_bvr_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number if
                # not we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'EUR'
            ##
            elif bvr_type == '31':
                # It the BVR postal in CHF
                bvr_struct = self._construct_bvrplus_in_chf(bvr_string)
                # We will test if the BVR have an Adherent Number if not
                # we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'EUR'

            elif bvr_type[0:1] == '<' and len(bvr_string) == 41:
                # It the BVR postal in CHF
                bvr_struct = self._construct_bvr_postal_other_in_chf(
                    bvr_string)
                # We will test if the BVR have an Adherent Number
                # if not we will make the search of the account base on
                # his name non base on the BVR adherent number
                if (bvr_struct['bvrnumber'] == '000000'):
                    bvr_struct['domain'] = 'name'
                else:
                    bvr_struct['domain'] = 'bvr_adherent_num'
                # We will set the currency , in this case it's allways CHF
                bvr_struct['currency'] = 'CHF'
            else:
                raise orm.except_orm(_('BVR Type error'),
                                     _('This kind of BVR is not supported '
                                       'at this time'))
            return bvr_struct

    def validate_bvr_string(self, cr, uid, ids, context):
        # We will now retrive result
        bvr_data = self.browse(cr, uid, ids, context)[0]
        # BVR Standrard
        # 0100003949753>120000000000234478943216899+ 010001628>
        # BVR without BVr Reference
        # 0100000229509>000000013052001000111870316+ 010618955>
        # BVR + In CHF
        # 042>904370000000000000007078109+ 010037882>
        # BVR In euro
        # 2100000440001>961116900000006600000009284+ 030001625>
        # <060001000313795> 110880150449186+ 43435>
        # <010001000165865> 951050156515104+ 43435>
        # <010001000060190> 052550152684006+ 43435>
        #
        # Explode and check  the BVR Number and structurate it
        #
        data = {}
        data['bvr_struct'] = self._get_bvr_structurated(
            bvr_data.bvr_string)
        # We will now search the account linked with this BVR
        if data['bvr_struct']['domain'] == 'name':
            domain = [('acc_number', '=', data['bvr_struct']['beneficiaire'])]
            partner_bank_search = self.pool.get('res.partner.bank').search(
                cr,
                uid,
                domain,
                context=context
            )
        else:
            domain = [
                ('bvr_adherent_num', '=', data['bvr_struct']['bvrnumber'])
            ]
            partner_bank_search = self.pool.get('res.partner.bank').search(
                cr,
                uid,
                domain,
                context=context
            )
        # We will need to know if we need to create invoice line
        if partner_bank_search:
            # We have found the account corresponding to the
            # bvr_adhreent_number
            # so we can directly create the account
            partner_bank_result = self.pool.get('res.partner.bank').browse(
                cr,
                uid,
                partner_bank_search[0],
                context=context
            )
            data['id'] = bvr_data.id
            data['partner_id'] = partner_bank_result.partner_id.id
            data['bank_account'] = partner_bank_result.id
            data['journal_id'] = bvr_data.journal_id.id
            action = self._create_direct_invoice(cr, uid, ids, data, context)
            return action
        elif bvr_data.bank_account_id:
            data['id'] = bvr_data.id
            data['partner_id'] = bvr_data.partner_id.id
            data['journal_id'] = bvr_data.journal_id.id
            data['bank_account'] = bvr_data.bank_account_id.id
            action = self._create_direct_invoice(cr, uid, ids, data, context)
            return action
        else:
            # we haven't found a valid bvr_adherent_number
            # we will need to create or update a bank account
            self.write(cr, uid, ids, {'state': 'need_extra_info'})
            return {
                'type': 'ir.actions.act_window',
                'res_model': 'scan.bvr',
                'view_mode': 'form',
                'view_type': 'form',
                'res_id': bvr_data.id,
                'views': [(False, 'form')],
                'target': 'new',
            }
class CreditControlMarker(TransientModel):
    """Change the state of lines in mass"""

    _name = 'credit.control.marker'
    _description = 'Mass marker'

    def _get_line_ids(self, cr, uid, context=None):
        if context is None:
            context = {}
        res = False
        if (context.get('active_model') == 'credit.control.line'
                and context.get('active_ids')):
            res = self._filter_line_ids(cr,
                                        uid,
                                        context['active_ids'],
                                        context=context)
        return res

    _columns = {
        'name':
        fields.selection([('ignored', 'Ignored'),
                          ('to_be_sent', 'Ready To Send'), ('sent', 'Done')],
                         'Mark as',
                         required=True),
        'line_ids':
        fields.many2many('credit.control.line',
                         string='Credit Control Lines',
                         domain="[('state', '!=', 'sent')]"),
    }

    _defaults = {
        'name': 'to_be_sent',
        'line_ids': _get_line_ids,
    }

    def _filter_line_ids(self, cr, uid, active_ids, context=None):
        """get line to be marked filter done lines"""
        line_obj = self.pool.get('credit.control.line')
        domain = [('state', '!=', 'sent'), ('id', 'in', active_ids)]
        return line_obj.search(cr, uid, domain, context=context)

    def _mark_lines(self, cr, uid, filtered_ids, state, context=None):
        """write hook"""
        line_obj = self.pool.get('credit.control.line')
        if not state:
            raise ValueError(_('state can not be empty'))
        line_obj.write(cr,
                       uid,
                       filtered_ids, {'state': state},
                       context=context)
        return filtered_ids

    def mark_lines(self, cr, uid, wiz_id, context=None):
        """Write state of selected credit lines to the one in entry
        done credit line will be ignored"""
        assert not (isinstance(wiz_id, list) and len(wiz_id) > 1), \
                "wiz_id: only one id expected"
        if isinstance(wiz_id, list):
            wiz_id = wiz_id[0]
        form = self.browse(cr, uid, wiz_id, context)

        if not form.line_ids:
            raise except_osv(_('Error'),
                             _('No credit control lines selected.'))

        line_ids = [l.id for l in form.line_ids]

        filtered_ids = self._filter_line_ids(cr, uid, line_ids, context)
        if not filtered_ids:
            raise except_osv(
                _('Information'),
                _('No lines will be changed. All the selected lines are already done.'
                  ))

        self._mark_lines(cr, uid, filtered_ids, form.name, context)

        return {
            'domain': unicode([('id', 'in', filtered_ids)]),
            'view_type': 'form',
            'view_mode': 'tree,form',
            'view_id': False,
            'res_model': 'credit.control.line',
            'type': 'ir.actions.act_window'
        }
Beispiel #6
0
class AccountInvoice(Model):
    """Inherit account.invoice in order to add bvr
    printing functionnalites. BVR is a Swiss payment vector"""
    _inherit = "account.invoice"

    _compile_get_ref = re.compile('[^0-9]')

    def _get_reference_type(self, cr, user, context=None):
        """Function used by the function field 'reference_type'
        in order to initalise available BVR Reference Types
        """
        res = super(AccountInvoice, self)._get_reference_type(cr,
                                                              user,
                                                              context=context)
        res.append(('bvr', 'BVR'))
        return res

    def _compute_full_bvr_name(self,
                               cr,
                               uid,
                               ids,
                               field_names,
                               arg,
                               context=None):
        res = {}
        move_line_obj = self.pool.get('account.move.line')
        account_obj = self.pool.get('account.account')
        tier_account_id = account_obj.search(
            cr,
            uid, [('type', 'in', ['receivable', 'payable'])],
            context=context)
        for inv in self.browse(cr, uid, ids, context=context):
            move_lines = move_line_obj.search(
                cr,
                uid, [('move_id', '=', inv.move_id.id),
                      ('account_id', 'in', tier_account_id)],
                context=context)
            if move_lines:
                refs = []
                for move_line in move_line_obj.browse(cr,
                                                      uid,
                                                      move_lines,
                                                      context=context):
                    refs.append(AccountInvoice._space(move_line.get_bvr_ref()))
                res[inv.id] = ' ; '.join(refs)
        return res

    _columns = {
        # BVR reference type BVR or FREE
        'reference_type':
        fields.selection(_get_reference_type, 'Reference Type', required=True),

        # Partner bank link between bank and partner id
        'partner_bank_id':
        fields.many2one('res.partner.bank',
                        'Bank Account',
                        help='The partner bank account to pay\n'
                        'Keep empty to use the default'),
        'bvr_reference':
        fields.function(_compute_full_bvr_name,
                        type="char",
                        size=512,
                        string="BVR REF.",
                        store=True,
                        readonly=True)
    }

    @staticmethod
    def _space(nbr, nbrspc=5):
        """Spaces * 5.

        Example:
            AccountInvoice._space('123456789012345')
            '12 34567 89012 345'
        """
        return ''.join([' '[(i - 2) % nbrspc:] + c for i, c in enumerate(nbr)])

    def _update_ref_on_account_analytic_line(self,
                                             cr,
                                             uid,
                                             ref,
                                             move_id,
                                             context=None):
        """Propagate reference on analytic line"""
        cr.execute(
            'UPDATE account_analytic_line SET ref=%s'
            '   FROM account_move_line '
            ' WHERE account_move_line.move_id = %s '
            '   AND account_analytic_line.move_id = account_move_line.id',
            (ref, move_id))
        return True

    def _action_bvr_number_move_line(self,
                                     cr,
                                     uid,
                                     invoice,
                                     move_line,
                                     ref,
                                     context=None):
        """Propagate reference on move lines and analytic lines"""
        if not ref:
            return
        cr.execute(
            'UPDATE account_move_line SET transaction_ref=%s'
            '  WHERE id=%s', (ref, move_line.id))
        self._update_ref_on_account_analytic_line(cr, uid, ref,
                                                  move_line.move_id.id)

    def action_number(self, cr, uid, ids, context=None):
        """ Copy the BVR/ESR reference in the transaction_ref of move lines.

        For customers invoices: the BVR reference is computed using
        ``get_bvr_ref()`` on the invoice or move lines.

        For suppliers invoices: the BVR reference is stored in the reference
        field of the invoice.

        """
        res = super(AccountInvoice, self).action_number(cr,
                                                        uid,
                                                        ids,
                                                        context=context)
        move_line_obj = self.pool.get('account.move.line')

        for inv in self.browse(cr, uid, ids, context=context):
            move_line_ids = move_line_obj.search(
                cr,
                uid, [('move_id', '=', inv.move_id.id),
                      ('account_id', '=', inv.account_id.id)],
                context=context)
            if not move_line_ids:
                continue
            move_lines = move_line_obj.browse(cr,
                                              uid,
                                              move_line_ids,
                                              context=context)
            for move_line in move_lines:
                if inv.type in ('out_invoice', 'out_refund'):
                    ref = move_line.get_bvr_ref()
                elif inv.reference_type == 'bvr' and inv.reference:
                    ref = inv.reference
                else:
                    ref = False
                self._action_bvr_number_move_line(cr,
                                                  uid,
                                                  inv,
                                                  move_line,
                                                  ref,
                                                  context=context)
        return res

    def copy(self, cr, uid, inv_id, default=None, context=None):
        default = default or {}
        default.update({'reference': False})
        return super(AccountInvoice, self).copy(cr, uid, inv_id, default,
                                                context)
Beispiel #7
0
class CreditControlRun(Model):
    """Credit Control run generate all credit control lines and reject"""

    _name = "credit.control.run"
    _rec_name = 'date'
    _description = """Credit control line generator"""
    _columns = {
        'date':
        fields.date('Controlling Date', required=True),
        'policy_ids':
        fields.many2many('credit.control.policy',
                         rel="credit_run_policy_rel",
                         id1='run_id',
                         id2='policy_id',
                         string='Policies',
                         readonly=True,
                         states={'draft': [('readonly', False)]}),
        'report':
        fields.text('Report', readonly=True),
        'state':
        fields.selection([
            ('draft', 'Draft'),
            ('done', 'Done'),
        ],
                         string='State',
                         required=True,
                         readonly=True),
        'manual_ids':
        fields.many2many(
            'account.move.line',
            rel="credit_runreject_rel",
            string='Lines to handle manually',
            help=('If a credit control line has been generated on a policy '
                  'and the policy has been changed meantime, '
                  'it has to be handled manually'),
            readonly=True),
    }

    def _get_policies(self, cr, uid, context=None):
        return self.pool.get('credit.control.policy').\
                search(cr, uid, [], context=context)

    _defaults = {
        'state': 'draft',
        'policy_ids': _get_policies,
    }

    def _check_run_date(self, cr, uid, ids, controlling_date, context=None):
        """Ensure that there is no credit line in the future using controlling_date"""
        line_obj = self.pool.get('credit.control.line')
        lines = line_obj.search(cr,
                                uid, [('date', '>', controlling_date)],
                                order='date DESC',
                                limit=1,
                                context=context)
        if lines:
            line = line_obj.browse(cr, uid, lines[0], context=context)
            raise except_osv(
                _('Error'),
                _('A run has already been executed more recently than %s') %
                (line.date))
        return True

    def _generate_credit_lines(self, cr, uid, run_id, context=None):
        """ Generate credit control lines. """
        cr_line_obj = self.pool.get('credit.control.line')
        assert not (isinstance(run_id, list) and len(run_id) > 1), \
                "run_id: only one id expected"
        if isinstance(run_id, list):
            run_id = run_id[0]

        run = self.browse(cr, uid, run_id, context=context)
        manually_managed_lines = set()  # line who changed policy
        credit_line_ids = []  # generated lines
        run._check_run_date(run.date, context=context)

        policies = run.policy_ids
        if not policies:
            raise except_osv(_('Error'), _('Please select a policy'))

        report = ''
        for policy in policies:
            if policy.do_nothing:
                continue

            lines = policy._get_move_lines_to_process(run.date,
                                                      context=context)
            manual_lines = policy._lines_different_policy(lines,
                                                          context=context)
            lines.difference_update(manual_lines)
            manually_managed_lines.update(manual_lines)

            policy_generated_ids = []
            if lines:
                # policy levels are sorted by level so iteration is in the correct order
                for level in reversed(policy.level_ids):
                    level_lines = level.get_level_lines(run.date,
                                                        lines,
                                                        context=context)
                    policy_generated_ids += cr_line_obj.create_or_update_from_mv_lines(
                        cr,
                        uid, [],
                        list(level_lines),
                        level.id,
                        run.date,
                        context=context)

            if policy_generated_ids:
                report += _("Policy \"%s\" has generated %d Credit Control Lines.\n") % \
                        (policy.name, len(policy_generated_ids))
                credit_line_ids += policy_generated_ids
            else:
                report += _(
                    "Policy \"%s\" has not generated any Credit Control Lines.\n"
                    % policy.name)

        vals = {
            'state': 'done',
            'report': report,
            'manual_ids': [(6, 0, manually_managed_lines)]
        }
        run.write(vals, context=context)

    def generate_credit_lines(self, cr, uid, run_id, context=None):
        """Generate credit control lines

        Lock the ``credit_control_run`` Postgres table to avoid concurrent
        calls of this method.
        """
        try:
            cr.execute('SELECT id FROM credit_control_run'
                       ' LIMIT 1 FOR UPDATE NOWAIT')
        except Exception, exc:
            # in case of exception openerp will do a rollback for us and free the lock
            raise except_osv(
                _('Error'),
                _('A credit control run is already running'
                  ' in background, please try later.'), str(exc))

        self._generate_credit_lines(cr, uid, run_id, context)
        return True
Beispiel #8
0
class CreditControlLine(Model):
    """A credit control line describes an amount due by a customer for a due date.

    A line is created once the due date of the payment is exceeded.
    It is created in "draft" and some actions are available (send by email,
    print, ...)
    """

    _name = "credit.control.line"
    _description = "A credit control line"
    _rec_name = "id"

    _columns = {
        'date':
        fields.date('Controlling date', required=True),
        # maturity date of related move line we do not use a related field in order to
        # allow manual changes
        'date_due':
        fields.date('Due date',
                    required=True,
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'date_sent':
        fields.date('Sent date',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'state':
        fields.selection([('draft', 'Draft'), ('ignored', 'Ignored'),
                          ('to_be_sent', 'Ready To Send'), ('sent', 'Done'),
                          ('error', 'Error'),
                          ('email_error', 'Emailing Error')],
                         'State',
                         required=True,
                         readonly=True,
                         help=("Draft lines need to be triaged.\n"
                               "Ignored lines are lines for which we do "
                               "not want to send something.\n"
                               "Draft and ignored lines will be "
                               "generated again on the next run.")),
        'channel':
        fields.selection([('letter', 'Letter'), ('email', 'Email')],
                         'Channel',
                         required=True,
                         readonly=True,
                         states={'draft': [('readonly', False)]}),
        'invoice_id':
        fields.many2one('account.invoice', 'Invoice', readonly=True),
        'partner_id':
        fields.many2one('res.partner', "Partner", required=True),
        'amount_due':
        fields.float('Due Amount Tax incl.', required=True, readonly=True),
        'balance_due':
        fields.float('Due balance', required=True, readonly=True),
        'mail_message_id':
        fields.many2one('mail.message', 'Sent Email', readonly=True),
        'move_line_id':
        fields.many2one('account.move.line',
                        'Move line',
                        required=True,
                        readonly=True),
        'account_id':
        fields.related('move_line_id',
                       'account_id',
                       type='many2one',
                       relation='account.account',
                       string='Account',
                       store=True,
                       readonly=True),
        'currency_id':
        fields.related('move_line_id',
                       'currency_id',
                       type='many2one',
                       relation='res.currency',
                       string='Currency',
                       store=True,
                       readonly=True),
        'company_id':
        fields.related('move_line_id',
                       'company_id',
                       type='many2one',
                       relation='res.company',
                       string='Company',
                       store=True,
                       readonly=True),

        # we can allow a manual change of policy in draft state
        'policy_level_id':
        fields.many2one('credit.control.policy.level',
                        'Overdue Level',
                        required=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'policy_id':
        fields.related('policy_level_id',
                       'policy_id',
                       type='many2one',
                       relation='credit.control.policy',
                       string='Policy',
                       store=True,
                       readonly=True),
        'level':
        fields.related('policy_level_id',
                       'level',
                       type='integer',
                       relation='credit.control.policy',
                       string='Level',
                       store=True,
                       readonly=True),
    }

    _defaults = {'state': 'draft'}

    def _prepare_from_move_line(self,
                                cr,
                                uid,
                                move_line,
                                level,
                                controlling_date,
                                open_amount,
                                context=None):
        """Create credit control line"""
        data = {}
        data['date'] = controlling_date
        data['date_due'] = move_line.date_maturity
        data['state'] = 'draft'
        data['channel'] = level.channel
        data[
            'invoice_id'] = move_line.invoice_id.id if move_line.invoice_id else False
        data['partner_id'] = move_line.partner_id.id
        data['amount_due'] = (move_line.amount_currency or move_line.debit
                              or move_line.credit)
        data['balance_due'] = open_amount
        data['policy_level_id'] = level.id
        data['company_id'] = move_line.company_id.id
        data['move_line_id'] = move_line.id
        return data

    def create_or_update_from_mv_lines(self,
                                       cr,
                                       uid,
                                       ids,
                                       lines,
                                       level_id,
                                       controlling_date,
                                       context=None):
        """Create or update line based on levels"""
        currency_obj = self.pool.get('res.currency')
        level_obj = self.pool.get('credit.control.policy.level')
        ml_obj = self.pool.get('account.move.line')
        user = self.pool.get('res.users').browse(cr, uid, uid)
        currency_ids = currency_obj.search(cr, uid, [], context=context)

        tolerance = {}
        tolerance_base = user.company_id.credit_control_tolerance
        for c_id in currency_ids:
            tolerance[c_id] = currency_obj.compute(
                cr,
                uid,
                c_id,
                user.company_id.currency_id.id,
                tolerance_base,
                context=context)

        level = level_obj.browse(cr, uid, level_id, context)
        line_ids = []
        for line in ml_obj.browse(cr, uid, lines, context):

            open_amount = line.amount_residual_currency

            if open_amount > tolerance.get(line.currency_id.id,
                                           tolerance_base):
                vals = self._prepare_from_move_line(cr,
                                                    uid,
                                                    line,
                                                    level,
                                                    controlling_date,
                                                    open_amount,
                                                    context=context)
                line_id = self.create(cr, uid, vals, context=context)
                line_ids.append(line_id)

                # when we have lines generated earlier in draft,
                # on the same level, it means that we have left
                # them, so they are to be considered as ignored
                previous_draft_ids = self.search(
                    cr,
                    uid, [('move_line_id', '=', line.id),
                          ('level', '=', level.id), ('state', '=', 'draft'),
                          ('id', '!=', line_id)],
                    context=context)
                if previous_draft_ids:
                    self.write(cr,
                               uid,
                               previous_draft_ids, {'state': 'ignored'},
                               context=context)

        return line_ids

    def unlink(self, cr, uid, ids, context=None, check=True):
        for line in self.browse(cr, uid, ids, context=context):
            if line.state != 'draft':
                raise osv.except_osv(
                    _('Error !'),
                    _('You are not allowed to delete a credit control line that '
                      'is not in draft state.'))

        return super(CreditControlLine, self).unlink(cr,
                                                     uid,
                                                     ids,
                                                     context=context)