1# -*- encoding: utf-8 -*-
2# Part of Odoo. See LICENSE file for full copyright and licensing details.
3
4from odoo import api, fields, models, _
5from odoo.exceptions import ValidationError
6
7import re
8
9
10class Efaktur(models.Model):
11    _name = "l10n_id_efaktur.efaktur.range"
12    _description = "Available E-faktur range"
13
14    company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
15    max = fields.Char(compute='_compute_default', store=True, readonly=False)
16    min = fields.Char(compute='_compute_default', store=True, readonly=False)
17    available = fields.Integer(compute='_compute_available', store=True)
18
19    @api.model
20    def pop_number(self, company_id):
21        range = self.search([('company_id', '=', company_id)], order="min ASC", limit=1)
22        if not range:
23            return None
24
25        popped = int(range.min)
26        if int(range.min) >= int(range.max):
27            range.unlink()
28        else:
29            range.min = '%013d' % (popped + 1)
30        return popped
31
32    @api.model
33    def push_number(self, company_id, number):
34        return self.push_numbers(company_id, number, number)
35
36    @api.model
37    def push_numbers(self, company_id, min, max):
38        range_sup = self.search([('min', '=', '%013d' % (int(max) + 1))])
39        if range_sup:
40            range_sup.min = '%013d' % int(min)
41            max = range_sup.max
42
43        range_low = self.search([('max', '=', '%013d' % (int(max) - 1))])
44        if range_low:
45            range_sup.unlink()
46            range_low.max = '%013d' % int(max)
47
48        if not range_sup and not range_low:
49            self.create({
50                'company_id': company_id,
51                'max': '%013d' % int(max),
52                'min': '%013d' % int(min),
53            })
54
55
56    @api.constrains('min', 'max')
57    def _constrains_min_max(self):
58        for record in self:
59            if not len(record.min) == 13 or not len(record.max) == 13:
60                raise ValidationError(_("There should be 13 digits in each number."))
61
62            if record.min[:-8] != record.max[:-8]:
63                raise ValidationError(_("First 5 digits should be same in Start Number and End Number."))
64
65            if int(record.min[-8:]) > int(record.max[-8:]):
66                raise ValidationError(_("Last 8 digits of End Number should be greater than the last 8 digit of Start Number"))
67
68            if (int(record.max) - int(record.min)) > 10000:
69                raise ValidationError(_("The difference between the two numbers must not be greater than 10.000"))
70
71            # The number of records should always be very small, so it is ok to search in loop
72            if self.search([
73                '&', ('id', '!=', record.id), '|', '|',
74                '&', ('min', '<=', record.max), ('max', '>=', record.max),
75                '&', ('min', '<=', record.min), ('max', '>=', record.min),
76                '&', ('min', '>=', record.min), ('max', '<=', record.max),
77            ]):
78                raise ValidationError(_('Efaktur interleaving range detected'))
79
80    @api.depends('min', 'max')
81    def _compute_available(self):
82        for record in self:
83            record.available = 1 + int(record.max) - int(record.min)
84
85    @api.depends('company_id')
86    def _compute_default(self):
87        for record in self:
88            query = """
89                SELECT MAX(SUBSTRING(l10n_id_tax_number FROM 4))
90                FROM account_move
91                WHERE l10n_id_tax_number IS NOT NULL
92                  AND company_id = %s
93            """
94            self.env.cr.execute(query, [record.company_id.id])
95            max_used = int(self.env.cr.fetchone()[0] or 0)
96            max_available = int(self.env['l10n_id_efaktur.efaktur.range'].search([('company_id', '=', record.company_id.id)], order='max DESC', limit=1).max)
97            record.min = record.max = '%013d' % (max(max_available, max_used) + 1)
98
99    @api.onchange('min')
100    def _onchange_min(self):
101        self.min = '%013d' % int(re.sub(r'\D', '', self.min))
102        if not self.max or int(self.min) > int(self.max):
103            self.max = self.min
104
105    @api.onchange('max')
106    def _onchange_max(self):
107        self.max = '%013d' % int(re.sub(r'\D', '', self.max))
108        if not self.min or int(self.min) > int(self.max):
109            self.min = self.max
110