Browse Source

Merge pull request #1431 from acsone/10.0-Add_hierarchy_in_date_range

[10.0] date_range 10.0.3.0.0: Added hierarchical feature for date_range, via parent_id
pull/1222/head
Laurent Mignon (ACSONE) 6 years ago
committed by GitHub
parent
commit
b644616228
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      date_range/__manifest__.py
  2. 71
      date_range/models/date_range.py
  3. 15
      date_range/models/date_range_type.py
  4. 1
      date_range/readme/CONTRIBUTORS.rst
  5. 6
      date_range/readme/HISTORY.rst
  6. 139
      date_range/tests/test_date_range.py
  7. 38
      date_range/tests/test_date_range_generator.py
  8. 12
      date_range/views/date_range_view.xml
  9. 22
      date_range/wizard/date_range_generator.py
  10. 1
      date_range/wizard/date_range_generator.xml

2
date_range/__manifest__.py

@ -4,7 +4,7 @@
{ {
"name": "Date Range", "name": "Date Range",
"summary": "Manage all kind of date range", "summary": "Manage all kind of date range",
"version": "10.0.2.0.1",
"version": "10.0.3.0.0",
"category": "Uncategorized", "category": "Uncategorized",
"website": "https://odoo-community.org/", "website": "https://odoo-community.org/",
"author": "ACSONE SA/NV, Odoo Community Association (OCA)", "author": "ACSONE SA/NV, Odoo Community Association (OCA)",

71
date_range/models/date_range.py

@ -31,6 +31,12 @@ class DateRange(models.Model):
active = fields.Boolean( active = fields.Boolean(
help="The active field allows you to hide the date range without " help="The active field allows you to hide the date range without "
"removing it.", default=True) "removing it.", default=True)
parent_type_id = fields.Many2one(
related='type_id.parent_type_id',
store=True,
readonly=True)
parent_id = fields.Many2one(
comodel_name='date.range', string="Parent", index=1)
_sql_constraints = [ _sql_constraints = [
('date_range_uniq', 'unique (name,type_id, company_id)', ('date_range_uniq', 'unique (name,type_id, company_id)',
@ -53,6 +59,45 @@ class DateRange(models.Model):
_('The Company in the Date Range and in ' _('The Company in the Date Range and in '
'Date Range Type must be the same.')) 'Date Range Type must be the same.'))
@api.constrains('parent_id', 'date_start', 'date_end')
def _validate_child_range(self):
for this in self:
if not this.parent_id:
continue
start = this.parent_id.date_start <= this.date_start
end = this.parent_id.date_end >= this.date_end
child_range = start and end
if not child_range:
text_dict = {
'name': this.name,
'start': this.date_start,
'end': this.date_end,
'parent_name': this.parent_id.name,
'parent_start': this.parent_id.date_start,
'parent_end': this.parent_id.date_end,
}
if (not start) and end:
text = _(
"Start date %(start)s of %(name)s must be greater than"
" or equal to "
"start date %(parent_start)s of %(parent_name)s"
) % text_dict
elif (not end) and start:
text = _(
"End date %(end)s of %(name)s must be smaller than"
" or equal to "
"end date %(parent_end)s of %(parent_name)s"
) % text_dict
else:
text = _(
"%(name)s range not in "
"%(parent_start)s - %(parent_end)s"
) % text_dict
raise ValidationError(
_("%(name)s not a subrange of"
" %(parent_name)s: " % text_dict) + text
)
@api.constrains('type_id', 'date_start', 'date_end', 'company_id') @api.constrains('type_id', 'date_start', 'date_end', 'company_id')
def _validate_range(self): def _validate_range(self):
for this in self: for this in self:
@ -95,3 +140,29 @@ class DateRange(models.Model):
self.ensure_one() self.ensure_one()
return [(field_name, '>=', self.date_start), return [(field_name, '>=', self.date_start),
(field_name, '<=', self.date_end)] (field_name, '<=', self.date_end)]
@api.multi
@api.onchange('company_id', 'type_id', 'date_start', 'date_end')
def onchange_type_id(self):
"""The type_id and the dates determine the choices for parent."""
domain = []
if self.company_id:
domain.append(('company_id', '=', self.company_id.id))
if self.parent_type_id:
domain.append(('type_id', '=', self.parent_type_id.id))
if self.date_start:
domain.append('|')
domain.append(('date_start', '<=', self.date_start))
domain.append(('date_start', '=', False))
if self.date_end:
domain.append('|')
domain.append(('date_end', '>=', self.date_end))
domain.append(('date_end', '=', False))
if domain:
# If user did not select a parent already, autoselect the last
# (ordered by date_start) or only parent that applies.
if self.type_id and self.date_start and not self.parent_id:
possible_parent = self.search(
domain, limit=1, order='date_start desc')
self.parent_id = possible_parent # can be empty!
return {'domain': {'parent_id': domain}}

15
date_range/models/date_range_type.py

@ -26,6 +26,9 @@ class DateRangeType(models.Model):
comodel_name='res.company', string='Company', index=1, comodel_name='res.company', string='Company', index=1,
default=_default_company) default=_default_company)
date_range_ids = fields.One2many('date.range', 'type_id', string='Ranges') date_range_ids = fields.One2many('date.range', 'type_id', string='Ranges')
parent_type_id = fields.Many2one(
comodel_name='date.range.type',
index=1)
_sql_constraints = [ _sql_constraints = [
('date_range_type_uniq', 'unique (name,company_id)', ('date_range_type_uniq', 'unique (name,company_id)',
@ -44,3 +47,15 @@ class DateRangeType(models.Model):
_('You cannot change the company, as this ' _('You cannot change the company, as this '
'Date Range Type is assigned to Date Range ' 'Date Range Type is assigned to Date Range '
'(%s).') % (rec.date_range_ids.name_get()[0][1])) '(%s).') % (rec.date_range_ids.name_get()[0][1]))
@api.constrains('parent_type_id')
def _validate_parent_type_id(self):
for record in self:
parent = record
while parent:
if not parent.parent_type_id:
break
if parent.parent_type_id == record:
raise ValidationError(
_("A type can not have itself as parent or child"))
parent = parent.parent_type_id

1
date_range/readme/CONTRIBUTORS.rst

@ -2,3 +2,4 @@
* Alexis de Lattre <alexis.delattre@akretion.com> * Alexis de Lattre <alexis.delattre@akretion.com>
* Miquel Raïch <miquel.raich@eficent.com> * Miquel Raïch <miquel.raich@eficent.com>
* Andrea Stirpe <a.stirpe@onestein.nl> * Andrea Stirpe <a.stirpe@onestein.nl>
* Nikos Tsirintanis <ntsirintanis@therp.nl>

6
date_range/readme/HISTORY.rst

@ -1,3 +1,9 @@
10.0.3.0.0 (2019-11-20)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Added hierarchical feature for date_range.
(`#1380 <https://github.com/OCA/server-tools/pull/1380>`_)
10.0.2.0.1 (2018-11-19) 10.0.2.0.1 (2018-11-19)
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

139
date_range/tests/test_date_range.py

@ -129,3 +129,142 @@ class DateRangeTest(TransactionCase):
'type_id': self.typeB.id, 'type_id': self.typeB.id,
'company_id': self.company_2.id, 'company_id': self.company_2.id,
}) })
def test_parent_id(self):
date_range_type = self.env['date.range.type']
parent = self.date_range.create({
'name': 'FS2018',
'date_start': '2018-01-01',
'date_end': '2018-12-31',
'type_id': self.type.id,
})
type_block = date_range_type.create({
'name': 'FS2018-type_block',
'company_id': False,
'allow_overlap': False,
})
# Check here all three validation errors thrown
# when child range is not a subrange of parent
with self.assertRaises(ValidationError) as cm, self.env.cr.savepoint():
self.date_range.create({
'name': 'FS2018-period1',
'date_start': '2018-06-06',
'date_end': '2019-01-02',
'type_id': type_block.id,
'parent_id': parent.id,
})
self.assertEqual(
cm.exception.name,
'FS2018-period1 not a subrange of FS2018: '
'End date 2019-01-02 of FS2018-period1 must be '
'smaller than or equal to end date 2018-12-31 of FS2018'
)
with self.assertRaises(ValidationError) as cm, self.env.cr.savepoint():
self.date_range.create({
'name': 'FS2018-period1',
'date_start': '2017-06-06',
'date_end': '2018-01-02',
'type_id': type_block.id,
'parent_id': parent.id,
})
self.assertEqual(
cm.exception.name,
'FS2018-period1 not a subrange of FS2018: '
'Start date 2017-06-06 of FS2018-period1 must be '
'greater than or equal to start date 2018-01-01 of FS2018'
)
with self.assertRaises(ValidationError) as cm, self.env.cr.savepoint():
self.date_range.create({
'name': 'FS2018-period1',
'date_start': '2017-06-06',
'date_end': '2019-01-02',
'type_id': type_block.id,
'parent_id': parent.id,
})
self.assertEqual(
cm.exception.name,
'FS2018-period1 not a subrange of FS2018: '
'FS2018-period1 range not in 2018-01-01 - 2018-12-31'
)
self.date_range.create({
'name': 'FS2018-period1',
'date_start': '2018-01-01',
'date_end': '2018-04-30',
'type_id': type_block.id,
'parent_id': parent.id,
})
# Check here that a validation error is thrown
# when child periods overlap
with self.assertRaises(ValidationError) as cm, self.env.cr.savepoint():
self.date_range.create({
'name': 'FS2018-period2',
'date_start': '2018-02-01',
'date_end': '2018-03-15',
'type_id': type_block.id,
'parent_id': parent.id,
})
self.assertEqual(
cm.exception.name,
'FS2018-period2 overlaps FS2018-period1'
)
def test_parent_type_id(self):
"""Check domain and constraint between parent and child types"""
date_range_type = self.env['date.range.type']
# First create a parent type
parent_type = date_range_type.create({
'name': 'FS2018_parent_type',
'parent_type_id': False,
})
# catch here the validation error when assigning
# a child as its own parent, or vise-versa (predestination)
with self.assertRaises(ValidationError)as cm, self.env.cr.savepoint():
parent_type.write({
'parent_type_id': parent_type.id,
})
self.assertEqual(
cm.exception.name,
'A type can not have itself as parent or child'
)
# Then, add a child type
child_type = date_range_type.create({
'name': 'FS2018_child_type',
'parent_type_id': parent_type.id,
})
# Now create a parent range
parent_range = self.date_range.create({
'name': 'FS2018',
'date_start': '2018-01-01',
'date_end': '2018-12-31',
'type_id': parent_type.id,
})
# and two child ranges
child_range1 = self.date_range.create({
'name': 'FS2018-child1',
'date_start': '2018-01-01',
'date_end': '2018-04-30',
'type_id': child_type.id,
'parent_id': parent_range.id,
})
child_range2 = self.date_range.create({
'name': 'FS2018-child2',
'date_start': '2018-05-01',
'date_end': '2018-06-30',
'type_id': child_type.id,
'parent_id': parent_range.id,
})
# and check how parent_type_id behaves
self.assertEqual(parent_type, child_range1.parent_type_id)
self.assertEqual(
child_range1.parent_type_id,
child_range2.parent_type_id
)
# Ensure here that parent and children are of different type
self.assertNotEqual(
parent_range.type_id,
child_range1.type_id
)
self.assertEqual(
parent_range.type_id,
child_range2.parent_id.type_id
)

38
date_range/tests/test_date_range_generator.py

@ -72,3 +72,41 @@ class DateRangeGeneratorTest(TransactionCase):
'count': 4, 'count': 4,
'company_id': self.company_2.id, 'company_id': self.company_2.id,
}) })
def test_generator_partner_id_domain(self):
"""Check here domain returned for partner_id
in both date.range and date.range.generator"""
date_range = self.env['date.range']
generator = self.env['date.range.generator']
date_type = self.env['date.range.type']
month_type = date_type.create({
'name': 'month type'
})
day_type = date_type.create({
'name': 'day type',
'parent_type_id': month_type.id,
})
month_range = date_range.create({
'name': 'month range',
'type_id': month_type.id,
'date_start': '01-01-2050',
'date_end': '02-01-2050',
})
# now trigger onchange in generator,
# which would also trigger onchange in date_range
values = {
'date_start': month_range.date_start,
'type_id': day_type.id,
}
on_change = generator._onchange_spec()
domain = generator.onchange(
values,
['type_id', 'date_start'],
on_change,
)
# check that with this search domain,
# only the month_range record is returned.
self.assertEqual(
date_range.search(domain['domain']['parent_id']),
month_range,
)

12
date_range/views/date_range_view.xml

@ -10,6 +10,11 @@
<field name="date_start"/> <field name="date_start"/>
<field name="date_end"/> <field name="date_end"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="parent_type_id" invisible="1"/>
<field
name="parent_id"
options="{'no_create': True}"
/>
<field name="active"/> <field name="active"/>
</tree> </tree>
</field> </field>
@ -25,6 +30,11 @@
<field name="date_start"/> <field name="date_start"/>
<field name="date_end"/> <field name="date_end"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="parent_type_id" invisible="1"/>
<field
name="parent_id"
options="{'no_create': True}"
/>
<field name="active"/> <field name="active"/>
</group> </group>
</form> </form>
@ -38,6 +48,7 @@
<field name="name"/> <field name="name"/>
<field name="allow_overlap"/> <field name="allow_overlap"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="parent_type_id"/>
<field name="active"/> <field name="active"/>
</tree> </tree>
</field> </field>
@ -51,6 +62,7 @@
<field name="name"/> <field name="name"/>
<field name="allow_overlap"/> <field name="allow_overlap"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="parent_type_id"/>
<field name="active"/> <field name="active"/>
</group> </group>
</form> </form>

22
date_range/wizard/date_range_generator.py

@ -38,6 +38,8 @@ class DateRangeGenerator(models.TransientModel):
duration_count = fields.Integer('Duration', required=True) duration_count = fields.Integer('Duration', required=True)
count = fields.Integer( count = fields.Integer(
string="Number of ranges to generate", required=True) string="Number of ranges to generate", required=True)
parent_id = fields.Many2one(
comodel_name='date.range', string="Parent", index=1)
@api.multi @api.multi
def _compute_date_ranges(self): def _compute_date_ranges(self):
@ -60,7 +62,8 @@ class DateRangeGenerator(models.TransientModel):
'date_start': date_start, 'date_start': date_start,
'date_end': date_end, 'date_end': date_end,
'type_id': self.type_id.id, 'type_id': self.type_id.id,
'company_id': self.company_id.id})
'company_id': self.company_id.id,
'parent_id': self.parent_id.id})
return date_ranges return date_ranges
@api.onchange('company_id') @api.onchange('company_id')
@ -88,3 +91,20 @@ class DateRangeGenerator(models.TransientModel):
self.env['date.range'].create(dr) self.env['date.range'].create(dr)
return self.env['ir.actions.act_window'].for_xml_id( return self.env['ir.actions.act_window'].for_xml_id(
module='date_range', xml_id='date_range_action') module='date_range', xml_id='date_range_action')
@api.multi
@api.onchange('type_id', 'date_start')
def onchange_type_id(self):
self.ensure_one()
date_range = self.env['date.range']
values = {
'date_start': self.date_start,
'type_id': self.type_id.id,
}
on_change = date_range._onchange_spec()
domain = date_range.onchange(
values,
['type_id', 'date_start'],
on_change,
)
return domain

1
date_range/wizard/date_range_generator.xml

@ -15,6 +15,7 @@
</div> </div>
<field name="date_start"/> <field name="date_start"/>
<field name="count"/> <field name="count"/>
<field name="parent_id" options="{'no_create': True}"/>
<field groups="base.group_multi_company" <field groups="base.group_multi_company"
name="company_id" options="{'no_create': True}"/> name="company_id" options="{'no_create': True}"/>
</group> </group>

Loading…
Cancel
Save