Add validate_fields to ModelStorage

issue11303
review386191002
This commit is contained in:
Cédric Krier
2022-04-11 23:24:20 +02:00
parent 50a4e20bbf
commit beb8d6fa3b
5 changed files with 174 additions and 129 deletions
+13 -6
View File
@@ -952,10 +952,10 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView):
table_h.drop_column('kind')
@classmethod
def validate(cls, accounts):
super(Account, cls).validate(accounts)
cls.check_second_currency(accounts)
cls.check_move_domain(accounts)
def validate_fields(cls, accounts, field_names):
super().validate_fields(accounts, field_names)
cls.check_second_currency(accounts, field_names)
cls.check_move_domain(accounts, field_names)
@staticmethod
def default_left():
@@ -1159,9 +1159,14 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView):
setattr(self, field, getattr(self.parent, field))
@classmethod
def check_second_currency(cls, accounts):
def check_second_currency(cls, accounts, field_names=None):
pool = Pool()
Line = pool.get('account.move.line')
if field_names and not (
field_names & (
{'second_currency', 'type'}
| set(cls.deferral.validation_depends))):
return
for account in accounts:
if not account.second_currency:
continue
@@ -1190,9 +1195,11 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView):
account=account.rec_name))
@classmethod
def check_move_domain(cls, accounts):
def check_move_domain(cls, accounts, field_names=None):
pool = Pool()
Line = pool.get('account.move.line')
if field_names and not (field_names & {'closed', 'type'}):
return
accounts = [a for a in accounts if a.closed or not a.type]
for sub_accounts in grouped_slice(accounts):
sub_accounts = list(sub_accounts)
+43 -34
View File
@@ -100,45 +100,54 @@ class FiscalYear(Workflow, ModelSQL, ModelView):
}.get(self.state)
@classmethod
def validate(cls, years):
super(FiscalYear, cls).validate(years)
for year in years:
year.check_dates()
year.check_post_move_sequence()
def validate_fields(cls, fiscalyears, field_names):
super().validate_fields(fiscalyears, field_names)
cls.check_dates(fiscalyears, field_names)
cls.check_post_move_sequence(fiscalyears, field_names)
def check_dates(self):
@classmethod
def check_dates(cls, fiscalyears, field_names=None):
if field_names and not (field_names & {
'start_date', 'end_date', 'company'}):
return
transaction = Transaction()
connection = transaction.connection
self.__class__.lock()
cls.lock()
cursor = connection.cursor()
table = self.__table__()
cursor.execute(*table.select(table.id,
where=(((table.start_date <= self.start_date)
& (table.end_date >= self.start_date))
| ((table.start_date <= self.end_date)
& (table.end_date >= self.end_date))
| ((table.start_date >= self.start_date)
& (table.end_date <= self.end_date)))
& (table.company == self.company.id)
& (table.id != self.id)))
second_id = cursor.fetchone()
if second_id:
second = self.__class__(second_id[0])
raise FiscalYearDatesError(
gettext('account.msg_fiscalyear_overlap',
first=self.rec_name,
second=second.rec_name))
table = cls.__table__()
for year in fiscalyears:
cursor.execute(*table.select(table.id,
where=(((table.start_date <= year.start_date)
& (table.end_date >= year.start_date))
| ((table.start_date <= year.end_date)
& (table.end_date >= year.end_date))
| ((table.start_date >= year.start_date)
& (table.end_date <= year.end_date)))
& (table.company == year.company.id)
& (table.id != year.id)))
second_id = cursor.fetchone()
if second_id:
second = cls(second_id[0])
raise FiscalYearDatesError(
gettext('account.msg_fiscalyear_overlap',
first=year.rec_name,
second=second.rec_name))
def check_post_move_sequence(self):
years = self.search([
('post_move_sequence', '=', self.post_move_sequence.id),
('id', '!=', self.id),
])
if years:
raise FiscalYearSequenceError(
gettext('account.msg_fiscalyear_different_post_move_sequence',
first=self.rec_name,
second=years[0].rec_name))
@classmethod
def check_post_move_sequence(cls, fiscalyears, field_names=None):
if field_names and 'post_move_sequence' not in field_names:
return
for fiscalyear in fiscalyears:
sequence = fiscalyear.post_move_sequence
years = cls.search([
('post_move_sequence', '=', sequence.id),
('id', '!=', fiscalyear.id),
], limit=1)
if years:
raise FiscalYearSequenceError(gettext(
'account.msg_fiscalyear_different_post_move_sequence',
first=fiscalyear.rec_name,
second=years[0].rec_name))
@classmethod
def write(cls, *args):
+30 -24
View File
@@ -219,17 +219,20 @@ class Move(ModelSQL, ModelView):
return [(None, '')] + [(m, get_name(m)) for m in models]
@classmethod
def validate(cls, moves):
super(Move, cls).validate(moves)
for move in moves:
move.check_date()
def validate_fields(cls, moves, field_names):
super().validate_fields(moves, field_names)
cls.check_date(moves, field_names)
def check_date(self):
if (self.date < self.period.start_date
or self.date > self.period.end_date):
raise MoveDatesError(
gettext('account.msg_move_date_outside_period',
move=self.rec_name))
@classmethod
def check_date(cls, moves, field_names=None):
if field_names and not (field_names & {'date', 'period'}):
return
for move in moves:
if (move.date < move.period.start_date
or move.date > move.period.end_date):
raise MoveDatesError(
gettext('account.msg_move_date_outside_period',
move=move.rec_name))
@classmethod
def check_modify(cls, moves):
@@ -1147,22 +1150,25 @@ class Line(MoveLineMixin, ModelSQL, ModelView):
return list(set(l.id for line in lines for l in line.move.lines))
@classmethod
def validate(cls, lines):
def validate_fields(cls, lines, field_names):
super(Line, cls).validate(lines)
for line in lines:
line.check_account()
cls.check_account(lines, field_names)
def check_account(self):
if not self.account.type or self.account.closed:
raise AccessError(
gettext('account.msg_line_closed_account',
account=self.account.rec_name))
if bool(self.party) != bool(self.account.party_required):
error = 'party_set' if self.party else 'party_required'
raise AccessError(
gettext('account.msg_line_%s' % error,
account=self.account.rec_name,
line=self.rec_name))
@classmethod
def check_account(cls, lines, field_names=None):
if field_names and not (field_names & {'account', 'party'}):
return
for line in lines:
if not line.account.type or line.account.closed:
raise AccessError(
gettext('account.msg_line_closed_account',
account=line.account.rec_name))
if bool(line.party) != bool(line.account.party_required):
error = 'party_set' if line.party else 'party_required'
raise AccessError(
gettext('account.msg_line_%s' % error,
account=line.account.rec_name,
line=line.rec_name))
@classmethod
def check_journal_period_modify(cls, period, journal):
+64 -47
View File
@@ -103,59 +103,76 @@ class Period(Workflow, ModelSQL, ModelView):
}.get(self.state)
@classmethod
def validate(cls, periods):
super(Period, cls).validate(periods)
for period in periods:
period.check_dates()
period.check_fiscalyear_dates()
period.check_post_move_sequence()
def validate_fields(cls, periods, field_names):
super().validate_fields(periods, field_names)
cls.check_dates(periods, field_names)
cls.check_fiscalyear_dates(periods, field_names)
cls.check_post_move_sequence(periods, field_names)
def check_dates(self):
if self.type != 'standard':
return True
@classmethod
def check_dates(cls, periods, field_names=None):
if field_names and not (
field_names & {
'start_date', 'end_date', 'fiscalyear', 'type'}):
return
transaction = Transaction()
connection = transaction.connection
self.__class__.lock()
table = self.__table__()
cls.lock()
table = cls.__table__()
cursor = connection.cursor()
cursor.execute(*table.select(table.id,
where=(((table.start_date <= self.start_date)
& (table.end_date >= self.start_date))
| ((table.start_date <= self.end_date)
& (table.end_date >= self.end_date))
| ((table.start_date >= self.start_date)
& (table.end_date <= self.end_date)))
& (table.fiscalyear == self.fiscalyear.id)
& (table.type == 'standard')
& (table.id != self.id)))
period_id = cursor.fetchone()
if period_id:
overlapping_period = self.__class__(period_id[0])
raise PeriodDatesError(
gettext('account.msg_period_overlap',
first=self.rec_name,
second=overlapping_period.rec_name))
for period in periods:
if period.type != 'standard':
continue
cursor.execute(*table.select(table.id,
where=(((table.start_date <= period.start_date)
& (table.end_date >= period.start_date))
| ((table.start_date <= period.end_date)
& (table.end_date >= period.end_date))
| ((table.start_date >= period.start_date)
& (table.end_date <= period.end_date)))
& (table.fiscalyear == period.fiscalyear.id)
& (table.type == 'standard')
& (table.id != period.id)))
period_id = cursor.fetchone()
if period_id:
overlapping_period = cls(period_id[0])
raise PeriodDatesError(
gettext('account.msg_period_overlap',
first=period.rec_name,
second=overlapping_period.rec_name))
def check_fiscalyear_dates(self):
if (self.start_date < self.fiscalyear.start_date
or self.end_date > self.fiscalyear.end_date):
raise PeriodDatesError(
gettext('account.msg_period_fiscalyear_dates',
period=self.rec_name,
fiscalyear=self.fiscalyear.rec_name))
def check_post_move_sequence(self):
if not self.post_move_sequence:
@classmethod
def check_fiscalyear_dates(cls, periods, field_names=None):
if field_names and not (
field_names & {
'start_date', 'end_date', 'fiscalyear'}):
return
periods = self.search([
('post_move_sequence', '=', self.post_move_sequence.id),
('fiscalyear', '!=', self.fiscalyear.id),
])
if periods:
raise PeriodSequenceError(
gettext('account.msg_period_same_sequence',
first=self.rec_name,
second=periods[0].rec_name))
for period in periods:
fiscalyear = period.fiscalyear
if (period.start_date < fiscalyear.start_date
or period.end_date > fiscalyear.end_date):
raise PeriodDatesError(
gettext('account.msg_period_fiscalyear_dates',
period=period.rec_name,
fiscalyear=fiscalyear.rec_name))
@classmethod
def check_post_move_sequence(cls, periods, field_names=None):
if field_names and not (
field_names & {'post_move_sequence', 'fiscalyear'}):
return
for period in periods:
if not period.post_move_sequence:
continue
periods = cls.search([
('post_move_sequence', '=', period.post_move_sequence.id),
('fiscalyear', '!=', period.fiscalyear.id),
])
if periods:
raise PeriodSequenceError(
gettext('account.msg_period_same_sequence',
first=period.rec_name,
second=periods[0].rec_name))
@classmethod
def find(cls, company_id, date=None, exception=True, test_state=True):
+24 -18
View File
@@ -578,16 +578,19 @@ class TaxTemplate(sequence_ordered(), ModelSQL, ModelView, DeactivableMixin):
cls._order.insert(0, ('account', 'ASC'))
@classmethod
def validate(cls, tax_templates):
super(TaxTemplate, cls).validate(tax_templates)
for tax_template in tax_templates:
tax_template.check_update_unit_price()
def validate_fields(cls, tax_templates, field_names):
super().validate_fields(tax_templates, field_names)
cls.check_update_unit_price(tax_templates, field_names)
def check_update_unit_price(self):
if self.update_unit_price and self.parent:
raise AccessError(
gettext('account.msg_tax_update_unit_price_with_parent',
tax=self.rec_name))
@classmethod
def check_update_unit_price(cls, tax_templates, field_names=None):
if field_names and not (field_names & {'update_unit_price', 'parent'}):
return
for tax in tax_templates:
if tax.update_unit_price and tax.parent:
raise AccessError(gettext(
'account.msg_tax_update_unit_price_with_parent',
tax=tax.rec_name))
@staticmethod
def default_type():
@@ -778,16 +781,19 @@ class Tax(sequence_ordered(), ModelSQL, ModelView, DeactivableMixin):
del _states
@classmethod
def validate(cls, taxes):
super(Tax, cls).validate(taxes)
for tax in taxes:
tax.check_update_unit_price()
def validate_fields(cls, taxes, field_names):
super().validate_fields(taxes, field_names)
cls.check_update_unit_price(taxes, field_names)
def check_update_unit_price(self):
if self.parent and self.update_unit_price:
raise AccessError(
gettext('account.msg_tax_update_unit_price_with_parent',
tax=self.rec_name))
@classmethod
def check_update_unit_price(cls, taxes, field_names=None):
if field_names and not (field_names & {'parent', 'update_unit_price'}):
return
for tax in taxes:
if tax.parent and tax.update_unit_price:
raise AccessError(gettext(
'account.msg_tax_update_unit_price_with_parent',
tax=tax.rec_name))
@staticmethod
def default_type():