diff --git a/modules/account/account.py b/modules/account/account.py
index 27c2310222..6964ef77f5 100644
--- a/modules/account/account.py
+++ b/modules/account/account.py
@@ -214,8 +214,8 @@ class Type(
ModelSQL, ModelView):
__name__ = 'account.account.type'
- parent = fields.Many2One('account.account.type', 'Parent',
- ondelete="RESTRICT",
+ parent = fields.Many2One(
+ 'account.account.type', 'Parent', ondelete='CASCADE',
states={
'readonly': (Bool(Eval('template', -1))
& ~Eval('template_override', False)),
@@ -370,13 +370,6 @@ class Type(
default.setdefault('template', None)
return super().copy(types, default=default)
- @classmethod
- def delete(cls, types):
- types = cls.search([
- ('parent', 'child_of', [t.id for t in types]),
- ])
- super().delete(types)
-
def update_type(self, template2type=None):
'''
Update recursively types based on template.
@@ -1280,21 +1273,6 @@ class Account(
cls._rebuild_tree('parent', None, 0)
return new_accounts
- @classmethod
- def delete(cls, accounts):
- MoveLine = Pool().get('account.move.line')
- childs = cls.search([
- ('parent', 'child_of', [a.id for a in accounts]),
- ])
- lines = MoveLine.search([
- ('account', 'in', [a.id for a in childs]),
- ])
- if lines:
- raise AccessError(
- gettext('account.msg_delete_account_with_move_lines',
- account=lines[0].account.rec_name))
- super().delete(accounts)
-
def update_account(self, template2account=None, template2type=None):
'''
Update recursively accounts based on template.
diff --git a/modules/account/company.py b/modules/account/company.py
index b4a2dc2f87..88ac83dfe0 100644
--- a/modules/account/company.py
+++ b/modules/account/company.py
@@ -133,20 +133,17 @@ class Company(metaclass=PoolMeta):
return [('id', 'in', query)]
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, companies, values=None, external=False):
pool = Pool()
Move = pool.get('account.move')
- transaction = Transaction()
- if transaction.user and transaction.check_access:
- actions = iter(args)
- for companies, values in zip(actions, actions):
- if 'currency' in values:
- moves = Move.search([
- ('company', 'in', [c.id for c in companies]),
- ],
- limit=1, order=[])
- if moves:
- raise AccessError(gettext(
- 'account.msg_company_change_currency'))
-
- super().write(*args)
+ super().check_modification(
+ mode, companies, values=values, external=external)
+ if mode == 'write':
+ if 'currency' in values:
+ moves = Move.search([
+ ('company', 'in', [c.id for c in companies]),
+ ],
+ limit=1, order=[])
+ if moves:
+ raise AccessError(gettext(
+ 'account.msg_company_change_currency'))
diff --git a/modules/account/fiscalyear.py b/modules/account/fiscalyear.py
index 9edb656d7b..438b842800 100644
--- a/modules/account/fiscalyear.py
+++ b/modules/account/fiscalyear.py
@@ -204,38 +204,28 @@ class FiscalYear(Workflow, ModelSQL, ModelView):
Period.check_fiscalyear_dates(periods, field_names={'fiscalyear'})
@classmethod
- def create(cls, vlist):
- fiscalyears = super().create(vlist)
- cls._find_cache.clear()
- return fiscalyears
-
- @classmethod
- def write(cls, *args):
+ def check_modification(
+ cls, mode, fiscalyears, values=None, external=False):
pool = Pool()
Move = pool.get('account.move')
- actions = iter(args)
- for fiscalyears, values in zip(actions, actions):
- if values.get('move_sequence'):
- for fiscalyear in fiscalyears:
- if (fiscalyear.move_sequence
- and fiscalyear.move_sequence.id
- != values['move_sequence']):
+ super().check_modification(
+ mode, fiscalyears, values=values, external=external)
+ if mode == 'write' and 'move_sequence' in values:
+ for fiscalyear in fiscalyears:
+ if sequence := fiscalyear.move_sequence:
+ if sequence.id != values['move_sequence']:
if Move.search([
('period.fiscalyear', '=', fiscalyear.id),
('state', '=', 'posted'),
- ]):
+ ], limit=1):
raise AccessError(
gettext('account.'
'msg_change_fiscalyear_move_sequence',
fiscalyear=fiscalyear.rec_name))
- super().write(*args)
- cls._find_cache.clear()
@classmethod
- def delete(cls, fiscalyears):
- Period = Pool().get('account.period')
- Period.delete([p for f in fiscalyears for p in f.periods])
- super().delete(fiscalyears)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._find_cache.clear()
@classmethod
diff --git a/modules/account/journal.py b/modules/account/journal.py
index ce099950cb..133f93c367 100644
--- a/modules/account/journal.py
+++ b/modules/account/journal.py
@@ -147,23 +147,22 @@ class Journal(
return journal
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, journals, values=None, external=False):
pool = Pool()
Move = pool.get('account.move')
- actions = iter(args)
- for journals, values in zip(actions, actions):
- if 'type' in values:
- for sub_journals in grouped_slice(journals):
- moves = Move.search([
- ('journal', 'in', [j.id for j in sub_journals]),
- ('state', '=', 'posted')
- ], order=[], limit=1)
- if moves:
- move, = moves
- raise AccessError(gettext(
- 'account.msg_journal_account_moves',
- journal=move.journal.rec_name))
- super().write(*args)
+ super().check_modification(
+ mode, journals, values=values, external=external)
+ if mode == 'write' and 'type' in values:
+ for sub_journals in grouped_slice(journals):
+ moves = Move.search([
+ ('journal', 'in', [j.id for j in sub_journals]),
+ ('state', '=', 'posted')
+ ], order=[], limit=1)
+ if moves:
+ move, = moves
+ raise AccessError(gettext(
+ 'account.msg_journal_account_moves',
+ journal=move.journal.rec_name))
class JournalCashContext(ModelView):
@@ -275,39 +274,26 @@ class JournalPeriod(Workflow, ModelSQL, ModelView):
journal_period=period.rec_name))
@classmethod
- def create(cls, vlist):
- Period = Pool().get('account.period')
- for vals in vlist:
- if vals.get('period'):
- period = Period(vals['period'])
- if period.state != 'open':
- raise AccessError(
- gettext('account'
- '.msg_create_journal_period_closed_period',
- period=period.rec_name))
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- for journal_periods, values in zip(actions, actions):
- if (values != {'state': 'closed'}
- and values != {'state': 'open'}):
- cls._check(journal_periods)
- if values.get('state') == 'open':
- for journal_period in journal_periods:
- if journal_period.period.state != 'open':
- raise AccessError(
- gettext('account'
- '.msg_open_journal_period_closed_period',
- journal_period=journal_period.rec_name,
- period=journal_period.period.rec_name))
- super().write(*args)
-
- @classmethod
- def delete(cls, periods):
- cls._check(periods)
- super().delete(periods)
+ def check_modification(cls, mode, records, values=None, external=False):
+ super().check_modification(
+ mode, records, values=values, external=external)
+ if mode == 'create':
+ for record in records:
+ if record.period.state != 'open':
+ raise AccessError(gettext(
+ 'account.msg_create_journal_period_closed_period',
+ period=record.period.rec_name))
+ elif mode in {'write', 'delete'}:
+ if values != {'state': 'closed'} and values != {'state': 'open'}:
+ cls._check(records)
+ if mode == 'write' and values.get('state') == 'open':
+ for record in records:
+ if record.period.state != 'open':
+ raise AccessError(gettext(
+ 'account.'
+ 'msg_open_journal_period_closed_period',
+ journal_period=record.rec_name,
+ period=record.period.rec_name))
@classmethod
@ModelView.button
diff --git a/modules/account/message.xml b/modules/account/message.xml
index fa4c43fef3..e2ebcb0ad4 100644
--- a/modules/account/message.xml
+++ b/modules/account/message.xml
@@ -95,9 +95,6 @@ this repository contains the full copyright notices and license terms. -->
Account can have only one of the debit or credit type set.
-
- You cannot delete account "%(account)s" because it has move lines.
-
To set a second currency for account "%(account)s", it must not have a type "payable", "revenue", "receivable" nor "expense".
diff --git a/modules/account/move.py b/modules/account/move.py
index d70cbb8b5e..8e1114cdc4 100644
--- a/modules/account/move.py
+++ b/modules/account/move.py
@@ -132,7 +132,7 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView):
super().__setup__()
t = cls.__table__()
cls.create_date.select = True
- cls._check_modify_exclude = ['lines']
+ cls._check_modify_exclude = {'lines'}
cls._order.insert(0, ('date', 'DESC'))
cls._order.insert(1, ('number', 'DESC'))
cls._buttons.update({
@@ -269,40 +269,17 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView):
return [(None, '')] + [(m, get_name(m)) for m in models]
@classmethod
- def check_modify(cls, moves):
- 'Check posted moves for modifications.'
- for move in moves:
- if move.state == 'posted':
- raise AccessError(
- gettext('account.msg_modify_posted_moved',
- move=move.rec_name))
-
- def get_rec_name(self, name):
- if self.number:
- return self.number
- else:
- return f'({self.id})'
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for moves, values in zip(actions, actions):
- keys = list(values.keys())
- for key in cls._check_modify_exclude:
- if key in keys:
- keys.remove(key)
- if len(keys):
- cls.check_modify(moves)
- args.extend((moves, values))
- super().write(*args)
-
- @classmethod
- def delete(cls, moves):
- MoveLine = Pool().get('account.move.line')
- cls.check_modify(moves)
- MoveLine.delete([l for m in moves for l in m.lines])
- super().delete(moves)
+ def check_modification(cls, mode, moves, values=None, external=False):
+ super().check_modification(
+ mode, moves, values=values, external=external)
+ if (mode == 'delete'
+ or (mode == 'write'
+ and values.keys() - cls._check_modify_exclude)):
+ for move in moves:
+ if move.state == 'posted':
+ raise AccessError(gettext(
+ 'account.msg_modify_posted_moved',
+ move=move.rec_name))
@classmethod
def copy(cls, moves, default=None):
@@ -591,20 +568,49 @@ class Reconciliation(ModelSQL, ModelView):
return Transaction().context.get('company')
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('account.configuration')
- configuration = Configuration(1)
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'reconciliation_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
- vlist = [x.copy() for x in vlist]
- default_company = cls.default_company()
- for vals in vlist:
- if 'number' not in vals:
- vals['number'] = configuration.get_multivalue(
- 'reconciliation_sequence',
- company=vals.get('company', default_company)).get()
+ @classmethod
+ def check_modification(
+ cls, mode, reconciliations, values=None, external=False):
+ pool = Pool()
+ Warning = pool.get('res.user.warning')
- return super().create(vlist)
+ super().check_modification(
+ mode, reconciliations, values=values, external=external)
+
+ if mode == 'delete':
+ for reconciliation in reconciliations:
+ if reconciliation.delegate_to:
+ key = Warning.format('delete.delegated', [reconciliation])
+ if Warning.check(key):
+ raise ReconciliationDeleteWarning(key, gettext(
+ 'account.msg_reconciliation_delete_delegated',
+ reconciliation=reconciliation.rec_name,
+ line=reconciliation.delegate_to.rec_name,
+ move=reconciliation.delegate_to.move.rec_name))
+ for line in reconciliation.lines:
+ if line.move.journal.type == 'write-off':
+ key = Warning.format(
+ 'delete.write-off', [reconciliation])
+ if Warning.check(key):
+ raise ReconciliationDeleteWarning(key, gettext(
+ 'account.'
+ 'msg_reconciliation_delete_write_off',
+ reconciliation=reconciliation.rec_name,
+ line=line.rec_name,
+ move=line.move.rec_name))
@classmethod
def write(cls, moves, values, *args):
@@ -615,31 +621,6 @@ class Reconciliation(ModelSQL, ModelView):
super().validate(reconciliations)
cls.check_lines(reconciliations)
- @classmethod
- def delete(cls, reconciliations):
- pool = Pool()
- Warning = pool.get('res.user.warning')
- for reconciliation in reconciliations:
- if reconciliation.delegate_to:
- key = Warning.format('delete.delegated', [reconciliation])
- if Warning.check(key):
- raise ReconciliationDeleteWarning(key,
- gettext('account.msg_reconciliation_delete_delegated',
- reconciliation=reconciliation.rec_name,
- line=reconciliation.delegate_to.rec_name,
- move=reconciliation.delegate_to.move.rec_name))
- for line in reconciliation.lines:
- if line.move.journal.type == 'write-off':
- key = Warning.format('delete.write-off', [reconciliation])
- if Warning.check(key):
- raise ReconciliationDeleteWarning(key,
- gettext(
- 'account.msg_reconciliation_delete_write_off',
- reconciliation=reconciliation.rec_name,
- line=line.rec_name,
- move=line.move.rec_name))
- super().delete(reconciliations)
-
@classmethod
def check_lines(cls, reconciliations):
Lang = Pool().get('ir.lang')
@@ -1441,36 +1422,50 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
}])
@classmethod
- def check_modify(cls, lines, modified_fields=None):
- '''
- Check if the lines can be modified
- '''
- if (modified_fields is not None
- and modified_fields <= cls._check_modify_exclude):
- return
- journal_period_done = []
- for line in lines:
- if line.move.state == 'posted':
- raise AccessError(
- gettext('account.msg_modify_line_posted_move',
- line=line.rec_name,
- move=line.move.rec_name))
- journal_period = (line.journal.id, line.period.id)
- if journal_period not in journal_period_done:
- cls.check_journal_period_modify(line.period,
- line.journal)
- journal_period_done.append(journal_period)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ pool = Pool()
+ Move = pool.get('account.move')
+
+ super().check_modification(
+ mode, lines, values=values, external=external)
+
+ if (mode in {'create', 'delete'}
+ or (mode == 'write'
+ and values.keys() - cls._check_modify_exclude)):
+ journal_period_done = set()
+ for line in lines:
+ if line.move.state == 'posted':
+ raise AccessError(gettext(
+ 'account.msg_modify_line_posted_move',
+ line=line.rec_name,
+ move=line.move.rec_name))
+ journal_period = (line.journal.id, line.period.id)
+ if journal_period not in journal_period_done:
+ cls.check_journal_period_modify(
+ line.period, line.journal)
+ journal_period_done.add(journal_period)
+
+ if (mode == 'delete'
+ or (mode == 'write'
+ and values.keys() & cls._reconciliation_modify_disallow)):
+ for line in lines:
+ if line.reconciliation:
+ raise AccessError(gettext(
+ 'account.msg_modify_line_reconciled',
+ line=line.rec_name))
+
+ if mode in {'create', 'write'}:
+ moves = Move.browse({l.move for l in lines})
+ Move.validate_move(moves)
@classmethod
- def check_reconciliation(cls, lines, modified_fields=None):
- if (modified_fields is not None
- and not modified_fields & cls._reconciliation_modify_disallow):
- return
- for line in lines:
- if line.reconciliation:
- raise AccessError(
- gettext('account.msg_modify_line_reconciled',
- line=line.rec_name))
+ def on_delete(cls, lines):
+ pool = Pool()
+ Move = pool.get('account.move')
+ callback = super().on_delete(lines)
+ moves = Move.browse({l.move for l in lines})
+ callback.append(lambda: Move.validate_move(moves))
+ return callback
@classmethod
def view_attributes(cls):
@@ -1489,35 +1484,6 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
return {ModelData.get_id(
'account', 'move_line_view_list_payable_receivable')}
- @classmethod
- def delete(cls, lines):
- Move = Pool().get('account.move')
- cls.check_modify(lines)
- cls.check_reconciliation(lines)
- moves = [x.move for x in lines]
- super().delete(lines)
- Move.validate_move(moves)
-
- @classmethod
- def write(cls, *args):
- Move = Pool().get('account.move')
-
- actions = iter(args)
- args = []
- moves = []
- all_lines = []
- for lines, values in zip(actions, actions):
- cls.check_modify(lines, set(values.keys()))
- cls.check_reconciliation(lines, set(values.keys()))
- moves.extend((x.move for x in lines))
- all_lines.extend(lines)
- args.extend((lines, values))
-
- super().write(*args)
-
- Transaction().timestamp = {}
- Move.validate_move(list(set(l.move for l in all_lines) | set(moves)))
-
@classmethod
def create(cls, vlist):
pool = Pool()
@@ -1550,16 +1516,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
# prevent default value for field with set_move_field
for fname in move_fields(move_name=False):
vals.setdefault(fname, None)
- lines = super().create(vlist)
- period_and_journals = set((line.period, line.journal)
- for line in lines)
- for period, journal in period_and_journals:
- cls.check_journal_period_modify(period, journal)
- # Re-browse for cache alignment
- moves = Move.browse(list(set(line.move for line in lines)))
- Move.check_modify(moves)
- Move.validate_move(moves)
- return lines
+ return super().create(vlist)
@classmethod
def copy(cls, lines, default=None):
diff --git a/modules/account/period.py b/modules/account/period.py
index a3b463ca70..f38d4fe6cc 100644
--- a/modules/account/period.py
+++ b/modules/account/period.py
@@ -30,7 +30,8 @@ class Period(Workflow, ModelSQL, ModelView):
end_date = fields.Date('Ending Date', required=True, states=_STATES,
domain=[('end_date', '>=', Eval('start_date', None))])
fiscalyear = fields.Many2One(
- 'account.fiscalyear', "Fiscal Year", required=True, states=_STATES)
+ 'account.fiscalyear', "Fiscal Year", required=True, ondelete='CASCADE',
+ states=_STATES)
state = fields.Selection([
('open', 'Open'),
('closed', 'Closed'),
@@ -320,26 +321,29 @@ class Period(Workflow, ModelSQL, ModelView):
order=order, count=count, query=query)
@classmethod
- def create(cls, vlist):
- FiscalYear = Pool().get('account.fiscalyear')
- vlist = [x.copy() for x in vlist]
- for vals in vlist:
- if vals.get('fiscalyear'):
- fiscalyear = FiscalYear(vals['fiscalyear'])
- if fiscalyear.state != 'open':
- raise AccessError(
- gettext('account.msg_create_period_closed_fiscalyear',
- fiscalyear=fiscalyear.rec_name))
- periods = super().create(vlist)
- cls._find_cache.clear()
- return periods
+ def preprocess_values(cls, mode, values):
+ pool = Pool()
+ FiscalYear = pool.get('account.fiscalyear')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and values.get('fiscalyear') is not None:
+ fiscalyear = FiscalYear(values['fiscalyear'])
+ if values.get('move_sequence') is None:
+ values['move_sequence'] = fiscalyear.move_sequence.id
+ return values
@classmethod
- def write(cls, *args):
- Move = Pool().get('account.move')
- actions = iter(args)
- args = []
- for periods, values in zip(actions, actions):
+ def check_modification(cls, mode, periods, values=None, external=False):
+ pool = Pool()
+ Move = pool.get('account.move')
+ super().check_modification(
+ mode, periods, values=values, external=external)
+ if mode == 'create':
+ for period in periods:
+ if period.fiscalyear.state != 'open':
+ raise AccessError(
+ gettext('account.msg_create_period_closed_fiscalyear',
+ fiscalyear=period.fiscalyear.rec_name))
+ elif mode == 'write':
if values.get('state') == 'open':
for period in periods:
if period.fiscalyear.state != 'open':
@@ -348,26 +352,22 @@ class Period(Workflow, ModelSQL, ModelView):
'account.msg_open_period_closed_fiscalyear',
period=period.rec_name,
fiscalyear=period.fiscalyear.rec_name))
- if values.get('move_sequence'):
+ if 'move_sequence' in values:
for period in periods:
- if (period.move_sequence
- and period.move_sequence.id
- != values['move_sequence']):
- if Move.search([
- ('period', '=', period.id),
- ('state', '=', 'posted'),
- ]):
- raise AccessError(
- gettext('account'
- '.msg_change_period_move_sequence',
- period=period.rec_name))
- args.extend((periods, values))
- super().write(*args)
- cls._find_cache.clear()
+ if sequence := period.move_sequence:
+ if sequence.id != values['move_sequence']:
+ if Move.search([
+ ('period', '=', period.id),
+ ('state', '=', 'posted'),
+ ], limit=1, order=[]):
+ raise AccessError(gettext(
+ 'account.'
+ 'msg_change_period_move_sequence',
+ period=period.rec_name))
@classmethod
- def delete(cls, periods):
- super().delete(periods)
+ def on_modification(cls, mode, periods, field_names=None):
+ super().on_modification(mode, periods, field_names=field_names)
cls._find_cache.clear()
@classmethod
diff --git a/modules/account/tax.py b/modules/account/tax.py
index db4aa7a04f..0dce46ad8e 100644
--- a/modules/account/tax.py
+++ b/modules/account/tax.py
@@ -132,7 +132,8 @@ class TaxCode(
code = fields.Char('Code', states=_states)
company = fields.Many2One('company.company', 'Company', required=True)
parent = fields.Many2One(
- 'account.tax.code', "Parent", states=_states,
+ 'account.tax.code', "Parent", ondelete='CASCADE',
+ states=_states,
domain=[
('company', '=', Eval('company', -1)),
])
@@ -243,13 +244,6 @@ class TaxCode(
default.setdefault('template', None)
return super().copy(codes, default=default)
- @classmethod
- def delete(cls, codes):
- codes = cls.search([
- ('parent', 'child_of', [c.id for c in codes]),
- ])
- super().delete(codes)
-
@classmethod
def update_tax_code(cls, company_id, template2tax_code=None):
'''
@@ -1407,34 +1401,19 @@ class TaxLine(ModelSQL, ModelView):
def search_rec_name(cls, name, clause):
return [('tax',) + tuple(clause[1:])]
- @classmethod
- def create(cls, vlist):
- lines = super().create(vlist)
- cls.check_modify(lines)
- return lines
-
- @classmethod
- def write(cls, *args):
- lines = sum(args[0:None:2], [])
- cls.check_modify(lines)
- super().write(*args)
-
- @classmethod
- def delete(cls, lines):
- cls.check_modify(lines)
- super().delete(lines)
-
@property
def period_checked(self):
return self.move_line.period
@classmethod
- def check_modify(cls, lines):
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
for line in lines:
period = line.period_checked
if period and period.state != 'open':
- raise AccessError(
- gettext('account.msg_modify_tax_line_closed_period',
+ raise AccessError(gettext(
+ 'account.msg_modify_tax_line_closed_period',
period=period.rec_name))
diff --git a/modules/account_asset/asset.py b/modules/account_asset/asset.py
index d067898a3f..39db924d3a 100644
--- a/modules/account_asset/asset.py
+++ b/modules/account_asset/asset.py
@@ -712,13 +712,15 @@ class Asset(Workflow, ModelSQL, ModelView):
return super().copy(assets, default=default)
@classmethod
- def delete(cls, assets):
- for asset in assets:
- if asset.state != 'draft':
- raise AccessError(
- gettext('account_asset.msg_asset_delete_draft',
- asset=asset.rec_name))
- return super().delete(assets)
+ def check_modification(cls, mode, assets, values=None, external=False):
+ super().check_modification(
+ mode, assets, values=values, external=external)
+ if mode == 'delete':
+ for asset in assets:
+ if asset.state != 'draft':
+ raise AccessError(gettext(
+ 'account_asset.msg_asset_delete_draft',
+ asset=asset.rec_name))
class AssetLine(ModelSQL, ModelView):
diff --git a/modules/account_consolidation/account.py b/modules/account_consolidation/account.py
index 282f501ad7..32ad2df1f1 100644
--- a/modules/account_consolidation/account.py
+++ b/modules/account_consolidation/account.py
@@ -57,7 +57,7 @@ class Move(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
- cls._check_modify_exclude.append('consolidation_company')
+ cls._check_modify_exclude.add('consolidation_company')
class MoveLine(metaclass=PoolMeta):
diff --git a/modules/account_es_sii/account.py b/modules/account_es_sii/account.py
index 269dd47a32..f072a388f6 100644
--- a/modules/account_es_sii/account.py
+++ b/modules/account_es_sii/account.py
@@ -229,17 +229,16 @@ class Period(metaclass=PoolMeta):
help="Check to create SII records for the invoices in the period.")
@classmethod
- def write(cls, *args):
- actions = iter(args)
- to_check = []
- for periods, values in zip(actions, actions):
- if 'es_sii_send_invoices' in values:
- for period in periods:
- if (period.es_sii_send_invoices
- != values['es_sii_send_invoices']):
- to_check.append(period)
- cls.check_es_sii_posted_invoices(to_check)
- super().write(*args)
+ def check_modification(cls, mode, periods, values=None, external=False):
+ super().check_modification(
+ mode, periods, values=values, external=external)
+ if mode == 'write' and 'es_sii_send_invoices' in values:
+ to_check = []
+ for period in periods:
+ if (period.es_sii_send_invoices
+ != values['es_sii_send_invoices']):
+ to_check.append(period)
+ cls.check_es_sii_posted_invoices(to_check)
@classmethod
def check_es_sii_posted_invoices(cls, periods):
@@ -248,11 +247,11 @@ class Period(metaclass=PoolMeta):
for sub_ids in grouped_slice(list(map(int, periods))):
invoices = Invoice.search([
('move.period', 'in', sub_ids),
- ], limit=1)
+ ], limit=1, order=[])
if invoices:
invoice, = invoices
- raise ESSIIPostedInvoicesError(
- gettext('account_es_sii.msg_es_sii_posted_invoices',
+ raise ESSIIPostedInvoicesError(gettext(
+ 'account_es_sii.msg_es_sii_posted_invoices',
period=invoice.move.period.rec_name))
@@ -352,13 +351,15 @@ class InvoiceSII(ModelSQL, ModelView):
return [('invoice.rec_name',) + tuple(clause[1:])]
@classmethod
- def delete(cls, records):
- for record in records:
- if record.csv:
- raise AccessError(
- gettext('account_es_sii.msg_es_sii_invoice_delete_sent',
- invoice=record.invoice.rec_name))
- super().delete(records)
+ def check_modification(cls, mode, invoices, values=None, external=False):
+ super().check_modification(
+ mode, invoices, values=values, external=external)
+ if mode == 'delete':
+ for invoice in invoices:
+ if invoice.csv:
+ raise AccessError(gettext(
+ 'account_es_sii.msg_es_sii_invoice_delete_sent',
+ invoice=invoice.rec_name))
@property
def endpoint(self):
diff --git a/modules/account_export/account.py b/modules/account_export/account.py
index 29943e57b3..21d9ef6e6a 100644
--- a/modules/account_export/account.py
+++ b/modules/account_export/account.py
@@ -60,7 +60,7 @@ class Move(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
- cls._check_modify_exclude.append('export')
+ cls._check_modify_exclude.add('export')
@classmethod
def copy(cls, moves, default=None):
@@ -209,11 +209,13 @@ class MoveExport(Workflow, ModelSQL, ModelView):
return super().copy(exports, default=default)
@classmethod
- def delete(cls, exports):
- for export in exports:
- if export.state != 'draft':
- raise AccessError(
- gettext(
- 'account_export.msg_account_move_export_delete_draft',
- export=export.rec_name))
- super().delete(exports)
+ def check_modification(cls, mode, exports, values=None, external=False):
+ super().check_modification(
+ mode, exports, values=values, external=external)
+ if mode == 'delete':
+ for export in exports:
+ if export.state != 'draft':
+ raise AccessError(gettext(
+ 'account_export.'
+ 'msg_account_move_export_delete_draft',
+ export=export.rec_name))
diff --git a/modules/account_fr_chorus/account.py b/modules/account_fr_chorus/account.py
index e2e73dc96a..b18374720d 100644
--- a/modules/account_fr_chorus/account.py
+++ b/modules/account_fr_chorus/account.py
@@ -163,11 +163,12 @@ class CredentialChorus(ModelSQL, CompanyValueMixin):
return resp.json()
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, records, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for records, values in zip(actions, actions):
+ super().check_modification(
+ mode, records, values=values, external=external)
+ if mode == 'write' and external:
for record in records:
for field in [
'chorus_piste_client_id', 'chorus_piste_client_secret',
@@ -182,7 +183,6 @@ class CredentialChorus(ModelSQL, CompanyValueMixin):
warning_name,
gettext('account_fr_chorus'
'.msg_chorus_credential_modified'))
- return super().write(*args)
class Invoice(metaclass=PoolMeta):
@@ -300,6 +300,9 @@ class InvoiceChorus(
table, _ = tables[None]
return [CharLength(table.number), table.number]
+ def get_rec_name(self, name):
+ return self.invoice.rec_name
+
@classmethod
def validate(cls, records):
super().validate(records)
@@ -316,13 +319,15 @@ class InvoiceChorus(
address=address.rec_name))
@classmethod
- def delete(cls, records):
- for record in records:
- if record.number:
- raise AccessError(
- gettext('account_fr_chorus.msg_invoice_delete_sent',
- invoice=record.invoice.rec_name))
- super().delete(records)
+ def check_modification(cls, mode, invoices, values=None, external=False):
+ super().check_modification(
+ mode, invoices, values=values, external=external)
+ if mode == 'delete':
+ for invoice in invoices:
+ if invoice.number:
+ raise AccessError(gettext(
+ 'account_fr_chorus.msg_invoice_delete_sent',
+ invoice=invoice.rec_name))
def _send_context(self):
return {
diff --git a/modules/account_invoice/account.py b/modules/account_invoice/account.py
index da1b61889b..56a76e13d7 100644
--- a/modules/account_invoice/account.py
+++ b/modules/account_invoice/account.py
@@ -253,27 +253,18 @@ class Reconciliation(metaclass=PoolMeta):
__name__ = 'account.move.reconciliation'
@classmethod
- def create(cls, vlist):
- Invoice = Pool().get('account.invoice')
+ def on_modification(cls, mode, reconciliations, field_names=None):
+ pool = Pool()
+ Invoice = pool.get('account.invoice')
transaction = Transaction()
context = transaction.context
- reconciliations = super().create(vlist)
+
+ super().on_modification(mode, reconciliations, field_names=field_names)
+
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Invoice.__queue__.process(
list(_invoices_to_process(reconciliations)))
- return reconciliations
-
- @classmethod
- def delete(cls, reconciliations):
- Invoice = Pool().get('account.invoice')
- transaction = Transaction()
- context = transaction.context
- invoices_to_process = _invoices_to_process(reconciliations)
- super().delete(reconciliations)
- with transaction.set_context(
- queue_batch=context.get('queue_batch', True)):
- Invoice.__queue__.process(list(invoices_to_process))
class RenewFiscalYear(metaclass=PoolMeta):
diff --git a/modules/account_invoice/invoice.py b/modules/account_invoice/invoice.py
index 79237b9c22..c882f148f7 100644
--- a/modules/account_invoice/invoice.py
+++ b/modules/account_invoice/invoice.py
@@ -1404,17 +1404,6 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
or (self.state == 'cancelled'
and (self.move or self.cancel_move or self.number)))
- @classmethod
- def check_modify(cls, invoices):
- '''
- Check if the invoices can be modified
- '''
- for invoice in invoices:
- if not invoice.is_modifiable:
- raise AccessError(
- gettext('account_invoice.msg_invoice_modify',
- invoice=invoice.rec_name))
-
def get_rec_name(self, name):
items = []
if self.number:
@@ -1456,39 +1445,34 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
]
@classmethod
- def delete(cls, invoices):
- cls.check_modify(invoices)
- # Cancel before delete
- cls.cancel(invoices)
- for invoice in invoices:
- if invoice.state != 'cancelled':
- raise AccessError(
- gettext('account_invoice.msg_invoice_delete_cancel',
- invoice=invoice.rec_name))
- if invoice.number:
- raise AccessError(
- gettext('account_invoice.msg_invoice_delete_numbered',
- invoice=invoice.rec_name))
- super().delete(invoices)
+ def check_modification(cls, mode, invoices, values=None, external=False):
+ super().check_modification(
+ mode, invoices, values=values, external=external)
+ if (mode == 'delete'
+ or (mode == 'write'
+ and values.keys() - cls._check_modify_exclude)):
+ for invoice in invoices:
+ if not invoice.is_modifiable:
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_modify',
+ invoice=invoice.rec_name))
+ if mode == 'delete':
+ for invoice in invoices:
+ if invoice.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_delete_cancel',
+ invoice=invoice.rec_name))
+ if invoice.number:
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_delete_numbered',
+ invoice=invoice.rec_name))
@classmethod
- def create(cls, vlist):
- invoices = super().create(vlist)
- cls.update_taxes(invoices)
- return invoices
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- all_invoices = []
- for invoices, values in zip(actions, actions):
- if set(values) - cls._check_modify_exclude:
- cls.check_modify(invoices)
- all_invoices += invoices
- update_tax = [i for i in all_invoices if i.state == 'draft']
- super().write(*args)
- if update_tax:
- cls.update_taxes(update_tax)
+ def on_modification(cls, mode, invoices, field_names=None):
+ super().on_modification(mode, invoices, field_names=field_names)
+ if mode != 'delete':
+ if draft_invoices := [i for i in invoices if i.state == 'draft']:
+ cls.update_taxes(draft_invoices)
@classmethod
def copy(cls, invoices, default=None):
@@ -2804,15 +2788,22 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
]
@classmethod
- def check_modify(cls, lines, fields=None):
- '''
- Check if the lines can be modified
- '''
- if fields is None or fields - cls._check_modify_exclude:
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'create':
+ for line in lines:
+ if line.invoice.state != 'draft':
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_line_create_draft',
+ invoice=line.invoice.rec_name))
+ elif (mode == 'delete'
+ or (mode == 'write'
+ and values.keys() - cls._check_modify_exclude)):
for line in lines:
if line.invoice and not line.invoice.is_modifiable:
- raise AccessError(
- gettext('account_invoice.msg_invoice_line_modify',
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_line_modify',
line=line.rec_name,
invoice=line.invoice.rec_name))
@@ -2822,30 +2813,6 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
('/form//field[@name="note"]|/form//field[@name="description"]',
'spell', Eval('party_lang'))]
- @classmethod
- def delete(cls, lines):
- cls.check_modify(lines)
- super().delete(lines)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- for lines, values in zip(actions, actions):
- cls.check_modify(lines, set(values))
- super().write(*args)
-
- @classmethod
- def create(cls, vlist):
- pool = Pool()
- Invoice = pool.get('account.invoice')
- invoice_ids = filter(None, {v.get('invoice') for v in vlist})
- for invoice in Invoice.browse(list(invoice_ids)):
- if invoice.state != 'draft':
- raise AccessError(
- gettext('account_invoice.msg_invoice_line_create_draft',
- invoice=invoice.rec_name))
- return super().create(vlist)
-
@classmethod
def copy(cls, lines, default=None):
if default is None:
@@ -3134,15 +3101,22 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
return (account_id, tax_id, (getattr(self, 'base', 0) or 0) >= 0)
@classmethod
- def check_modify(cls, taxes, fields=None):
- '''
- Check if the taxes can be modified
- '''
- if fields is None or fields - cls._check_modify_exclude:
+ def check_modification(cls, mode, taxes, values=None, external=False):
+ super().check_modification(
+ mode, taxes, values=values, external=external)
+ if mode == 'create':
+ for tax in taxes:
+ if tax.invoice.state != 'draft':
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_line_create_draft',
+ invoice=tax.invoice.rec_name))
+ elif (mode == 'delete'
+ or (mode == 'write'
+ and values.keys() - cls._check_modify_exclude)):
for tax in taxes:
if not tax.invoice.is_modifiable:
- raise AccessError(
- gettext('account_invoice.msg_invoice_tax_modify',
+ raise AccessError(gettext(
+ 'account_invoice.msg_invoice_tax_modify',
tax=tax.rec_name,
invoice=tax.invoice.rec_name))
@@ -3154,30 +3128,6 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
i += 1
return 0
- @classmethod
- def delete(cls, taxes):
- cls.check_modify(taxes)
- super().delete(taxes)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- for taxes, values in zip(actions, actions):
- cls.check_modify(taxes, set(values))
- super().write(*args)
-
- @classmethod
- def create(cls, vlist):
- pool = Pool()
- Invoice = pool.get('account.invoice')
- invoice_ids = filter(None, {v.get('invoice') for v in vlist})
- for invoice in Invoice.browse(list(invoice_ids)):
- if invoice.state != 'draft':
- raise AccessError(
- gettext('account_invoice.msg_invoice_tax_create_draft',
- invoice=invoice.rec_name))
- return super().create(vlist)
-
def get_move_lines(self):
'''
Return a list of move lines instances for invoice tax
diff --git a/modules/account_invoice_defer/account.py b/modules/account_invoice_defer/account.py
index f37b48c22a..decef1ffb0 100644
--- a/modules/account_invoice_defer/account.py
+++ b/modules/account_invoice_defer/account.py
@@ -240,14 +240,16 @@ class InvoiceDeferred(Workflow, ModelSQL, ModelView):
and deferral.moves[-1].period.end_date >= deferral.end_date)
@classmethod
- def delete(cls, deferrals):
- for deferral in deferrals:
- if deferral.state != 'draft':
- raise AccessError(
- gettext('account_invoice_defer'
- '.msg_invoice_deferred_delete_draft',
- deferral=deferral.rec_name))
- return super().delete(deferrals)
+ def check_modification(cls, mode, deferrals, values=None, external=False):
+ super().check_modification(
+ mode, deferrals, values=values, external=external)
+ if mode == 'delete':
+ for deferral in deferrals:
+ if deferral.state != 'draft':
+ raise AccessError(gettext(
+ 'account_invoice_defer'
+ '.msg_invoice_deferred_delete_draft',
+ deferral=deferral.rec_name))
@classmethod
def defer_amount(cls, deferrals):
diff --git a/modules/account_invoice_stock/account.py b/modules/account_invoice_stock/account.py
index 64fc63d7d6..e9e32c220d 100644
--- a/modules/account_invoice_stock/account.py
+++ b/modules/account_invoice_stock/account.py
@@ -115,18 +115,18 @@ class InvoiceLine(metaclass=PoolMeta):
return ', '.join(sorted(shipments))
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, lines, field_names=None):
pool = Pool()
Move = pool.get('stock.move')
transaction = Transaction()
context = transaction.context
- super().write(*args)
- lines = sum(args[0:None:2], [])
- moves = sum((l.stock_moves for l in lines), ())
- if moves:
- with transaction.set_context(
- queue_batch=context.get('queue_batch', True)):
- Move.__queue__.update_unit_price(moves)
+ super().on_modification(mode, lines, field_names=field_names)
+ if mode == 'write':
+ moves = sum((l.stock_moves for l in lines), ())
+ if moves:
+ with transaction.set_context(
+ queue_batch=context.get('queue_batch', True)):
+ Move.__queue__.update_unit_price(moves)
@classmethod
def copy(cls, lines, default=None):
diff --git a/modules/account_payment/payment.py b/modules/account_payment/payment.py
index 029850d871..b95484219c 100644
--- a/modules/account_payment/payment.py
+++ b/modules/account_payment/payment.py
@@ -141,19 +141,18 @@ class Group(ModelSQL, ModelView):
pass
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Config = pool.get('account.configuration')
-
- vlist = [v.copy() for v in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'payment_group_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ Configuration = pool.get('account.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ configuration = Configuration(1)
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ if sequence := configuration.get_multivalue(
+ 'payment_group_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, groups, default=None):
@@ -610,53 +609,58 @@ class Payment(Workflow, ModelSQL, ModelView):
return super().copy(payments, default=default)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Line = pool.get('account.move.line')
- Config = pool.get('account.configuration')
-
- vlist = [v.copy() for v in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'payment_sequence',
- company=values.get('company', default_company)).get()
- payments = super().create(vlist)
- lines = {p.line for p in payments if p.line}
- if lines:
- Line.set_payment_amount(list(lines))
- return payments
+ Configuration = pool.get('account.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ configuration = Configuration(1)
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ if sequence := configuration.get_multivalue(
+ 'payment_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
- def write(cls, *args):
- pool = Pool()
- Line = pool.get('account.move.line')
- actions = iter(args)
- lines = set()
- for payments, values in zip(actions, actions):
- if values.keys() & {'line', 'amount', 'state'}:
- lines.update(p.line for p in payments if p.line)
- if line := values.get('line'):
- lines.add(Line(line))
- super().write(*args)
- if lines:
- Line.set_payment_amount(list(lines))
+ def check_modification(cls, mode, payments, values=None, external=False):
+ super().check_modification(
+ mode, payments, values=values, external=external)
+ if mode == 'delete':
+ for payment in payments:
+ if payment.state != 'draft':
+ raise AccessError(gettext(
+ 'account_payment.msg_payment_delete_draft',
+ payment=payment.rec_name))
@classmethod
- def delete(cls, payments):
+ def on_modification(cls, mode, payments, field_names=None):
pool = Pool()
Line = pool.get('account.move.line')
- for payment in payments:
- if payment.state != 'draft':
- raise AccessError(
- gettext('account_payment.msg_payment_delete_draft',
- payment=payment.rec_name))
- lines = {p.line for p in payments if p.line}
- super().delete(payments)
- if lines:
- Line.set_payment_amount(list(lines))
+ super().on_modification(mode, payments, field_names=field_names)
+ if mode in {'create', 'write'}:
+ if not field_names or 'line' in field_names:
+ if lines := Line.browse({p.line for p in payments if p.line}):
+ Line.set_payment_amount(lines)
+
+ @classmethod
+ def on_write(cls, payments, values):
+ pool = Pool()
+ Line = pool.get('account.move.line')
+ callback = super().on_write(payments, values)
+ if values.keys() & {'line', 'amount', 'state'}:
+ if lines := Line.browse({p.line for p in payments if p.line}):
+ callback.append(lambda: Line.set_payment_amount(lines))
+ return callback
+
+ @classmethod
+ def on_delete(cls, payments):
+ pool = Pool()
+ Line = pool.get('account.move.line')
+ callback = super().on_delete(payments)
+ if lines := Line.browse({p.line for p in payments if p.line}):
+ callback.append(lambda: Line.set_payment_amount(lines))
+ return callback
@classmethod
@ModelView.button
diff --git a/modules/account_payment_braintree/account.py b/modules/account_payment_braintree/account.py
index 532c5f670f..65908e139c 100644
--- a/modules/account_payment_braintree/account.py
+++ b/modules/account_payment_braintree/account.py
@@ -909,20 +909,23 @@ class PaymentBraintreeAccount(ModelSQL, ModelView):
return bool(payments)
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, accounts, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for accounts, values in zip(actions, actions):
- if ({'environment', 'merchant_id', 'public_key', 'private_key'}
- & values.keys()):
- warning_name = Warning.format('braintree_key', accounts)
- if Warning.check(warning_name):
- raise BraintreeAccountWarning(
- warning_name,
- gettext('account_payment_braintree'
- '.msg_braintree_key_modified'))
- return super().write(*args)
+
+ super().check_modification(
+ mode, accounts, values=values, external=external)
+
+ if (mode == 'write'
+ and external
+ and values.keys()
+ & {'environment', 'merchant_id', 'public_key', 'private_key'}):
+ warning_name = Warning.format('braintree_key', accounts)
+ if Warning.check(warning_name):
+ raise BraintreeAccountWarning(
+ warning_name,
+ gettext('account_payment_braintree'
+ '.msg_braintree_key_modified'))
class PaymentBraintreeCustomer(
@@ -992,9 +995,11 @@ class PaymentBraintreeCustomer(
@classmethod
def delete(cls, customers):
+ ids, on_delete = cls._before_delete(customers)
cls.write(customers, {
'active': False,
})
+ cls._after_delete(ids, on_delete)
@classmethod
def copy(cls, customers, default=None):
diff --git a/modules/account_payment_braintree/party.py b/modules/account_payment_braintree/party.py
index 6c3f6ed4dc..7baf55ddf5 100644
--- a/modules/account_payment_braintree/party.py
+++ b/modules/account_payment_braintree/party.py
@@ -21,26 +21,29 @@ class Party(metaclass=PoolMeta):
return parties
@classmethod
- def write(cls, *args):
+ def on_write(cls, parties, values):
pool = Pool()
Customer = pool.get('account.payment.braintree.customer')
transaction = Transaction()
context = transaction.context
- parties = sum(args[0:None:2], [])
+ callback = super().on_write(parties, values)
+
customers = sum((p.braintree_customers for p in parties), ())
- customer2params = {c: c._customer_parameters() for c in customers}
+ if customers:
+ customer2params = {c: c._customer_parameters() for c in customers}
- super().write(*args)
-
- to_update = []
- for customer, params in customer2params.items():
- if customer._customer_parameters() != params:
- to_update.append(customer)
- if to_update:
- with Transaction().set_context(
- queue_batch=context.get('queue_batch', True)):
- Customer.__queue__.braintree_update(to_update)
+ def update():
+ to_update = []
+ for customer, params in customer2params.items():
+ if customer._customer_parameters() != params:
+ to_update.append(customer)
+ if to_update:
+ with transaction.set_context(
+ queue_batch=context.get('queue_batch', True)):
+ Customer.__queue__.braintree_update(to_update)
+ callback.append(update)
+ return callback
class PartyReceptionDirectDebit(
diff --git a/modules/account_payment_clearing/account.py b/modules/account_payment_clearing/account.py
index 9787dd80f5..65d8b23230 100644
--- a/modules/account_payment_clearing/account.py
+++ b/modules/account_payment_clearing/account.py
@@ -40,18 +40,9 @@ class MoveReconciliation(metaclass=PoolMeta):
__name__ = 'account.move.reconciliation'
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, reconciliations, field_names=None):
pool = Pool()
Payment = pool.get('account.payment')
- reconciliations = super().create(vlist)
+ super().on_modification(mode, reconciliations, field_names=field_names)
Payment.__queue__.update_reconciled(
list(_payments_to_update(reconciliations)))
- return reconciliations
-
- @classmethod
- def delete(cls, reconciliations):
- pool = Pool()
- Payment = pool.get('account.payment')
- payments = _payments_to_update(reconciliations)
- super().delete(reconciliations)
- Payment.__queue__.update_reconciled(list(payments))
diff --git a/modules/account_payment_clearing/payment.py b/modules/account_payment_clearing/payment.py
index fa041724a9..f694628a55 100644
--- a/modules/account_payment_clearing/payment.py
+++ b/modules/account_payment_clearing/payment.py
@@ -76,7 +76,7 @@ def cancel_clearing_move(func):
Line = pool.get('account.move.line')
Reconciliation = pool.get('account.move.reconciliation')
- func(cls, payments, *args, **kwargs)
+ result = func(cls, payments, *args, **kwargs)
to_delete = []
to_reconcile = defaultdict(lambda: defaultdict(list))
@@ -110,6 +110,7 @@ def cancel_clearing_move(func):
for lines in to_reconcile[party].values():
Line.reconcile(lines)
cls.update_reconciled(payments)
+ return result
return wrapper
diff --git a/modules/account_payment_sepa/payment.py b/modules/account_payment_sepa/payment.py
index 8b382acc03..4e538d2881 100644
--- a/modules/account_payment_sepa/payment.py
+++ b/modules/account_payment_sepa/payment.py
@@ -639,36 +639,32 @@ class Mandate(Workflow, ModelSQL, ModelView):
]
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('account.configuration')
-
- config = Configuration(1)
- vlist = [v.copy() for v in vlist]
- default_company = cls.default_company()
- for values in vlist:
- if not values.get('identification'):
- sequence = config.get_multivalue(
- 'sepa_mandate_sequence',
- company=values.get('company', default_company))
- if sequence:
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('identification'):
+ configuration = Configuration(1)
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ if sequence := configuration.get_multivalue(
+ 'sepa_mandate_sequence', company=company_id):
values['identification'] = sequence.get()
- # Prevent raising false unique constraint
- if values.get('identification') == '':
- values['identification'] = None
- return super().create(vlist)
+ if values.get('identification') == '':
+ values['identification'] = None
+ return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for mandates, values in zip(actions, actions):
- # Prevent raising false unique constraint
- if values.get('identification') == '':
- values = values.copy()
- values['identification'] = None
- args.extend((mandates, values))
- super().write(*args)
+ def check_modification(cls, mode, mandates, values=None, external=False):
+ super().check_modification(
+ mode, mandates, values=values, external=external)
+ if mode == 'delete':
+ for mandate in mandates:
+ if mandate.state not in {'draft', 'cancelled'}:
+ raise AccessError(gettext(
+ 'account_payment_sepa'
+ '.msg_mandate_delete_draft_cancelled',
+ mandate=mandate.rec_name))
@classmethod
def copy(cls, mandates, default=None):
@@ -767,16 +763,6 @@ class Mandate(Workflow, ModelSQL, ModelView):
# TODO must be automaticaly cancelled 13 months after last collection
pass
- @classmethod
- def delete(cls, mandates):
- for mandate in mandates:
- if mandate.state not in ('draft', 'cancelled'):
- raise AccessError(
- gettext('account_payment_sepa'
- '.msg_mandate_delete_draft_cancelled',
- mandate=mandate.rec_name))
- super().delete(mandates)
-
class MandateReport(CompanyReport):
__name__ = 'account.payment.sepa.mandate'
diff --git a/modules/account_payment_stripe/party.py b/modules/account_payment_stripe/party.py
index 9a34f40c30..ee4d1f1f7e 100644
--- a/modules/account_payment_stripe/party.py
+++ b/modules/account_payment_stripe/party.py
@@ -21,26 +21,29 @@ class Party(metaclass=PoolMeta):
return parties
@classmethod
- def write(cls, *args):
+ def on_write(cls, parties, values):
pool = Pool()
Customer = pool.get('account.payment.stripe.customer')
transaction = Transaction()
context = transaction.context
- parties = sum(args[0:None:2], [])
+ callback = super().on_write(parties, values)
+
customers = sum((p.stripe_customers for p in parties), ())
- customer2params = {c: c._customer_parameters() for c in customers}
+ if customers:
+ customer2params = {c: c._customer_parameters() for c in customers}
- super().write(*args)
-
- to_update = []
- for customer, params in customer2params.items():
- if customer._customer_parameters() != params:
- to_update.append(customer)
- if to_update:
- with transaction.set_context(
- queue_batch=context.get('queue_batch', True)):
- Customer.__queue__.stripe_update(to_update)
+ def update():
+ to_update = []
+ for customer, params in customer2params.items():
+ if customer._customer_parameters() != params:
+ to_update.append(customer)
+ if to_update:
+ with transaction.set_context(
+ queue_batch=context.get('queue_batch', True)):
+ Customer.__queue__.stripe_update(to_update)
+ callback.append(update)
+ return callback
class PartyReceptionDirectDebit(
diff --git a/modules/account_payment_stripe/payment.py b/modules/account_payment_stripe/payment.py
index f782e5eb01..22dc10da35 100644
--- a/modules/account_payment_stripe/payment.py
+++ b/modules/account_payment_stripe/payment.py
@@ -324,14 +324,15 @@ class Payment(StripeCustomerMethodMixin, CheckoutMixin, metaclass=PoolMeta):
payment=payment.rec_name))
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- # Ensure to get a different key for each record
- # default methods are called only once
- values.setdefault('stripe_idempotency_key',
- cls.default_stripe_idempotency_key())
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
+ if 'stripe_idempotency_key' not in values:
+ # Ensure to get a different key for each record
+ # default methods are called only once
+ values['stripe_idempotency_key'] = (
+ cls.default_stripe_idempotency_key())
+ return values
@classmethod
def copy(cls, payments, default=None):
@@ -839,13 +840,15 @@ class Refund(Workflow, ModelSQL, ModelView):
return 'draft'
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- values.setdefault(
- 'stripe_idempotency_key',
- cls.default_stripe_idempotency_key())
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
+ if 'stripe_idempotency_key' not in values:
+ # Ensure to get a different key for each record
+ # default methods are called only once
+ values['stripe_idempotency_key'] = (
+ cls.default_stripe_idempotency_key())
+ return values
@classmethod
def copy(cls, refunds, default=None):
@@ -1394,19 +1397,22 @@ class Account(ModelSQL, ModelView):
cls.save(accounts)
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, accounts, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for accounts, values in zip(actions, actions):
- if {'secret_key', 'publishable_key'} & values.keys():
- warning_name = Warning.format('stripe_key', accounts)
- if Warning.check(warning_name):
- raise StripeAccountWarning(
- warning_name,
- gettext('account_payment_stripe'
- '.msg_stripe_key_modified'))
- return super().write(*args)
+
+ super().check_modification(
+ mode, accounts, values=values, external=external)
+
+ if (mode == 'write'
+ and external
+ and values.keys() & {'secret_key', 'publishable_key'}):
+ warning_name = Warning.format('stripe_key', accounts)
+ if Warning.check(warning_name):
+ raise StripeAccountWarning(
+ warning_name,
+ gettext('account_payment_stripe'
+ '.msg_stripe_key_modified'))
class Customer(CheckoutMixin, DeactivableMixin, ModelSQL, ModelView):
@@ -1505,16 +1511,19 @@ class Customer(CheckoutMixin, DeactivableMixin, ModelSQL, ModelView):
return self.stripe_customer_id if self.stripe_customer_id else name
@classmethod
- def write(cls, *args, **kwargs):
- super().write(*args, **kwargs)
- cls._sources_cache.clear()
- cls._payment_methods_cache.clear()
+ def on_modification(cls, mode, customers, field_names=None):
+ super().on_modification(mode, customers, field_names=field_names)
+ if mode == 'write':
+ cls._sources_cache.clear()
+ cls._payment_methods_cache.clear()
@classmethod
def delete(cls, customers):
+ ids, on_delete = cls._before_delete(customers)
cls.write(customers, {
'active': False,
})
+ cls._after_delete(ids, on_delete)
@classmethod
def copy(cls, customers, default=None):
diff --git a/modules/account_statement/statement.py b/modules/account_statement/statement.py
index 12f26e8c60..a47a5e4f70 100644
--- a/modules/account_statement/statement.py
+++ b/modules/account_statement/statement.py
@@ -368,15 +368,15 @@ class Statement(Workflow, ModelSQL, ModelView):
]
@classmethod
- def delete(cls, statements):
- # Cancel before delete
- cls.cancel(statements)
- for statement in statements:
- if statement.state != 'cancelled':
- raise AccessError(
- gettext('account_statement.msg_statement_delete_cancel',
- statement=statement.rec_name))
- super().delete(statements)
+ def check_modification(cls, mode, statements, values=None, external=False):
+ super().check_modification(
+ mode, statements, values=values, external=external)
+ if mode == 'delete':
+ for statement in statements:
+ if statement.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'account_statement.msg_statement_delete_cancel',
+ statement=statement.rec_name))
@classmethod
@ModelView.button
@@ -963,16 +963,17 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView):
return [('statement.rec_name',) + tuple(clause[1:])]
@classmethod
- def delete(cls, lines):
- for line in lines:
- if line.statement_state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext(
- 'account_statement.'
- 'msg_statement_line_delete_cancel_draft',
- line=line.rec_name,
- sale=line.statement.rec_name))
- super().delete(lines)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'delete':
+ for line in lines:
+ if line.statement_state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'account_statement.'
+ 'msg_statement_line_delete_cancel_draft',
+ line=line.rec_name,
+ sale=line.statement.rec_name))
@classmethod
def copy(cls, lines, default=None):
@@ -1214,16 +1215,17 @@ class Origin(origin_mixin(_states), ModelSQL, ModelView):
return [('id', 'in', query)]
@classmethod
- def delete(cls, origins):
- for origin in origins:
- if origin.statement_state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext(
- 'account_statement.'
- 'msg_statement_origin_delete_cancel_draft',
- origin=origin.rec_name,
- sale=origin.statement.rec_name))
- super().delete(origins)
+ def check_modification(cls, mode, origins, values=None, external=False):
+ super().check_modification(
+ mode, origins, values=values, external=external)
+ if mode == 'delete':
+ for origin in origins:
+ if origin.statement_state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'account_statement.'
+ 'msg_statement_origin_delete_cancel_draft',
+ origin=origin.rec_name,
+ sale=origin.statement.rec_name))
@classmethod
def copy(cls, origins, default=None):
diff --git a/modules/account_stock_anglo_saxon/stock.py b/modules/account_stock_anglo_saxon/stock.py
index dd2029a6ce..b012c4fd4d 100644
--- a/modules/account_stock_anglo_saxon/stock.py
+++ b/modules/account_stock_anglo_saxon/stock.py
@@ -201,11 +201,14 @@ class Move(metaclass=PoolMeta):
super().cancel(moves)
@classmethod
- def delete(cls, moves):
- for move in moves:
- if move.in_anglo_saxon_quantity or move.out_anglo_saxon_quantity:
- raise AccessError(
- gettext('account_stock_anglo_saxon'
- '.msg_move_delete_anglo_saxon',
- move=move.rec_name))
- super().delete(moves)
+ def check_modification(cls, mode, moves, values=None, external=False):
+ super().check_modification(
+ mode, moves, values=values, external=external)
+ if mode == 'delete':
+ for move in moves:
+ if (move.in_anglo_saxon_quantity
+ or move.out_anglo_saxon_quantity):
+ raise AccessError(gettext(
+ 'account_stock_anglo_saxon'
+ '.msg_move_delete_anglo_saxon',
+ move=move.rec_name))
diff --git a/modules/account_stock_eu/account_stock_eu.py b/modules/account_stock_eu/account_stock_eu.py
index 81a68398e8..3011d70bc4 100644
--- a/modules/account_stock_eu/account_stock_eu.py
+++ b/modules/account_stock_eu/account_stock_eu.py
@@ -60,19 +60,8 @@ class IntrastatTransaction(DeactivableMixin, ModelSQL, ModelView):
return transaction
@classmethod
- def create(cls, vlist):
- transactions = super().create(vlist)
- cls._code_cache.clear()
- return transactions
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._code_cache.clear()
-
- @classmethod
- def delete(cls, transactions):
- super().delete(transactions)
+ def on_modification(cls, mode, transactions, field_names=None):
+ super().on_modification(mode, transactions, field_names=field_names)
cls._code_cache.clear()
@@ -212,19 +201,8 @@ class IntrastatDeclaration(Workflow, ModelSQL, ModelView):
cls.close(declarations)
@classmethod
- def create(cls, vlist):
- declarations = super().create(vlist)
- cls._get_cache.clear()
- return declarations
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._get_cache.clear()
-
- @classmethod
- def delete(cls, declarations):
- super().delete(declarations)
+ def on_modification(cls, mode, declarations, field_names=None):
+ super().on_modification(mode, declarations, field_names=field_names)
cls._get_cache.clear()
diff --git a/modules/account_stock_eu/stock.py b/modules/account_stock_eu/stock.py
index 78f9e7d6c1..869263243d 100644
--- a/modules/account_stock_eu/stock.py
+++ b/modules/account_stock_eu/stock.py
@@ -10,7 +10,7 @@ from trytond.model import Index, ModelView, Workflow, fields
from trytond.modules.account.exceptions import FiscalYearNotFoundError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
-from trytond.transaction import Transaction, without_check_access
+from trytond.transaction import Transaction
from .exceptions import CounterPartyNotFound, CountryNotFound
@@ -163,19 +163,13 @@ class Move(metaclass=PoolMeta):
return self.intrastat_from_country
@classmethod
- @without_check_access
- def _reopen_intrastat(cls, *args):
+ def _reopen_intrastat(cls, moves):
pool = Pool()
IntrastatDeclaration = pool.get(
'account.stock.eu.intrastat.declaration')
- declarations = set()
- actions = iter(args)
- for moves, values in zip(actions, actions):
- moves = cls.browse(moves)
- if any(k.startswith('intrastat_') for k in values.keys()):
- declarations.update(
- m.intrastat_declaration for m in moves
- if m.intrastat_declaration)
+ declarations = {
+ m.intrastat_declaration for m in moves
+ if m.intrastat_declaration}
if declarations:
IntrastatDeclaration.open(
IntrastatDeclaration.browse(declarations))
@@ -205,10 +199,12 @@ class Move(metaclass=PoolMeta):
return values
@classmethod
- def write(cls, *args):
- cls._reopen_intrastat(*args)
- super().write(*args)
- cls._reopen_intrastat(*args)
+ def on_write(cls, moves, values):
+ callback = super().on_write(moves, values)
+ callback.append(lambda: cls._reopen_intrastat(moves))
+ if any(f.startswith('intrastat_') for f in values):
+ cls._reopen_intrastat(moves)
+ return callback
@classmethod
def copy(cls, moves, default=None):
diff --git a/modules/account_stock_landed_cost/account.py b/modules/account_stock_landed_cost/account.py
index 7ca73a9654..518f19b8b1 100644
--- a/modules/account_stock_landed_cost/account.py
+++ b/modules/account_stock_landed_cost/account.py
@@ -410,29 +410,31 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
cls.save(landed_costs)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Config = pool.get('account.configuration')
-
- vlist = [v.copy() for v in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'landed_cost_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ values = super().preprocess_values(mode, values)
+ if values.get('number') is None:
+ config = Config(1)
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ if sequence := config.get_multivalue(
+ 'landed_cost_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
- def delete(cls, landed_costs):
- for landed_cost in landed_costs:
- if landed_cost.state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext('account_stock_landed_cost'
- '.msg_landed_cost_delete_cancel',
- landed_cost=landed_cost.rec_name))
- super().delete(landed_costs)
+ def check_modification(
+ cls, mode, landed_costs, values=None, external=False):
+ super().check_modification(
+ mode, landed_costs, values=values, external=external)
+ if mode == 'delete':
+ for landed_cost in landed_costs:
+ if landed_cost.state not in {'cancelled', 'draft'}:
+ raise AccessError(
+ gettext('account_stock_landed_cost'
+ '.msg_landed_cost_delete_cancel',
+ landed_cost=landed_cost.rec_name))
@classmethod
def copy(cls, landed_costs, default=None):
diff --git a/modules/account_stock_shipment_cost/account.py b/modules/account_stock_shipment_cost/account.py
index 24365afe22..0a7eb120e4 100644
--- a/modules/account_stock_shipment_cost/account.py
+++ b/modules/account_stock_shipment_cost/account.py
@@ -321,29 +321,31 @@ class ShipmentCost(Workflow, ModelSQL, ModelView):
})
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Config = pool.get('account.configuration')
-
- vlist = [v.copy() for v in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'shipment_cost_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ values = super().preprocess_values(mode, values)
+ if values.get('number') is None:
+ config = Config(1)
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ if sequence := config.get_multivalue(
+ 'shipment_cost_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
- def delete(cls, shipment_costs):
- for shipment_cost in shipment_costs:
- if shipment_cost.state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext('account_stock_shipment_cost'
- '.msg_shipment_cost_delete_cancel',
- shipment_cost=shipment_cost.rec_name))
- super().delete(shipment_costs)
+ def check_modification(
+ cls, mode, shipment_costs, values=None, external=False):
+ super().check_modification(
+ mode, shipment_costs, values=values, external=external)
+ if mode == 'delete':
+ for shipment_cost in shipment_costs:
+ if shipment_cost.state not in {'cancelled', 'draft'}:
+ raise AccessError(
+ gettext('account_stock_shipment_cost'
+ '.msg_shipment_cost_delete_cancel',
+ shipment_cost=shipment_cost.rec_name))
class ShipmentCost_Shipment(ModelSQL):
diff --git a/modules/account_tax_cash/account.py b/modules/account_tax_cash/account.py
index 4b382ed276..3a0613d0ac 100644
--- a/modules/account_tax_cash/account.py
+++ b/modules/account_tax_cash/account.py
@@ -283,16 +283,10 @@ class Invoice(metaclass=PoolMeta):
return ratio
@classmethod
- def write(cls, *args):
- super().write(*args)
-
- invoices = []
- actions = iter(args)
- for records, values in zip(actions, actions):
- if 'payment_lines' in values:
- invoices.extend(records)
- invoices = cls.browse(invoices)
- cls._update_tax_cash_basis(invoices)
+ def on_modification(cls, mode, invoices, field_names=None):
+ super().on_modification(mode, invoices, field_names=field_names)
+ if mode == 'write' and 'payment_lines' in field_names:
+ cls._update_tax_cash_basis(invoices)
@classmethod
def process(cls, invoices):
diff --git a/modules/analytic_account/account.py b/modules/analytic_account/account.py
index 8197b1c679..29b7cbb8c8 100644
--- a/modules/analytic_account/account.py
+++ b/modules/analytic_account/account.py
@@ -305,24 +305,25 @@ class Account(
return result
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, accounts, values=None, external=False):
pool = Pool()
Entry = pool.get('analytic.account.entry')
- actions = iter(args)
- for records, values in zip(actions, actions):
- if 'root' in values:
- for sub_records in grouped_slice(records):
- entries = Entry.search([
- ('account', 'in', list(map(int, sub_records))),
- ],
- limit=1, order=[])
- if entries:
- entry, = entries
- raise AccessError(gettext(
- 'analytic_account'
- '.msg_analytic_account_root_change',
- account=entry.account.rec_name))
- super().write(*args)
+
+ super().check_modification(
+ mode, accounts, values=values, external=external)
+
+ if mode == 'write' and 'root' in values:
+ for sub_records in grouped_slice(accounts):
+ entries = Entry.search([
+ ('account', 'in', list(map(int, sub_records))),
+ ],
+ limit=1, order=[])
+ if entries:
+ entry, = entries
+ raise AccessError(gettext(
+ 'analytic_account'
+ '.msg_analytic_account_root_change',
+ account=entry.account.rec_name))
class AccountContext(ModelView):
diff --git a/modules/analytic_account/line.py b/modules/analytic_account/line.py
index d0e2533a99..942ccf880f 100644
--- a/modules/analytic_account/line.py
+++ b/modules/analytic_account/line.py
@@ -105,33 +105,27 @@ class Line(ModelSQL, ModelView):
return clause
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, lines, field_names=None):
pool = Pool()
MoveLine = pool.get('account.move.line')
- lines = super().create(vlist)
- move_lines = [l.move_line for l in lines]
- MoveLine.set_analytic_state(move_lines)
- MoveLine.save(move_lines)
- return lines
+ super().on_modification(mode, lines, field_names=field_names)
+ if mode in {'create', 'write'}:
+ move_lines = MoveLine.browse({l.move_line for l in lines})
+ MoveLine.set_analytic_state(move_lines)
+ MoveLine.save(move_lines)
@classmethod
- def write(cls, *args):
+ def on_delete(cls, lines):
pool = Pool()
MoveLine = pool.get('account.move.line')
- super().write(*args)
- lines = sum(args[0:None:2], [])
- move_lines = [l.move_line for l in lines]
- MoveLine.set_analytic_state(move_lines)
- MoveLine.save(move_lines)
-
- @classmethod
- def delete(cls, lines):
- pool = Pool()
- MoveLine = pool.get('account.move.line')
- move_lines = [l.move_line for l in lines]
- super().delete(lines)
- MoveLine.set_analytic_state(move_lines)
- MoveLine.save(move_lines)
+ callback = super().on_delete(lines)
+ move_lines = MoveLine.browse({l.move_line for l in lines})
+ if move_lines:
+ def set_state():
+ MoveLine.set_analytic_state(move_lines)
+ MoveLine.save(move_lines)
+ callback.append(set_state)
+ return callback
class Move(metaclass=PoolMeta):
diff --git a/modules/attendance/attendance.py b/modules/attendance/attendance.py
index ec7f88ed81..5752a54cc3 100644
--- a/modules/attendance/attendance.py
+++ b/modules/attendance/attendance.py
@@ -98,9 +98,11 @@ class Line(ModelSQL, ModelView):
return values
@classmethod
- def delete(cls, records):
- cls.check_closed_period(records, msg='delete')
- super().delete(records)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'delete':
+ cls.check_closed_period(lines, msg='delete')
@classmethod
def validate_fields(cls, records, field_names):
diff --git a/modules/bank/bank.py b/modules/bank/bank.py
index 794c5bae12..0d57eef79b 100644
--- a/modules/bank/bank.py
+++ b/modules/bank/bank.py
@@ -156,15 +156,15 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
bic=iban.bic))
@classmethod
- def create(cls, vlist):
- accounts = super().create(vlist)
- for account in accounts:
- if not account.bank:
- bank = account.guess_bank()
- if bank:
- account.bank = bank
- cls.save(accounts)
- return accounts
+ def on_modification(cls, mode, accounts, field_names=None):
+ super().on_modification(mode, accounts, field_names=field_names)
+ if mode == 'create':
+ for account in accounts:
+ if not account.bank:
+ bank = account.guess_bank()
+ if bank:
+ account.bank = bank
+ cls.save(accounts)
def guess_bank(self):
pool = Pool()
diff --git a/modules/carrier/carrier.py b/modules/carrier/carrier.py
index 2078ea1ec3..e6dca07fa7 100644
--- a/modules/carrier/carrier.py
+++ b/modules/carrier/carrier.py
@@ -66,19 +66,12 @@ class Carrier(DeactivableMixin, ModelSQL, ModelView):
return None, None
@classmethod
- def create(cls, *args, **kwargs):
+ def on_modification(cls, mode, carriers, field_names=None):
pool = Pool()
CarrierSelection = pool.get('carrier.selection')
- carriers = super().create(*args, **kwargs)
- CarrierSelection._get_carriers_cache.clear()
- return carriers
-
- @classmethod
- def delete(cls, *args, **kwargs):
- pool = Pool()
- CarrierSelection = pool.get('carrier.selection')
- super().delete(*args, **kwargs)
- CarrierSelection._get_carriers_cache.clear()
+ super().on_modification(mode, carriers, field_names=field_names)
+ if mode in {'create', 'delete'}:
+ CarrierSelection._get_carriers_cache.clear()
class Selection(sequence_ordered(), MatchMixin, ModelSQL, ModelView):
@@ -97,22 +90,6 @@ class Selection(sequence_ordered(), MatchMixin, ModelSQL, ModelView):
carrier = fields.Many2One('carrier', 'Carrier', required=True,
ondelete='CASCADE', help="The selected carrier.")
- @classmethod
- def create(cls, *args, **kwargs):
- selections = super().create(*args, **kwargs)
- cls._get_carriers_cache.clear()
- return selections
-
- @classmethod
- def write(cls, *args, **kwargs):
- super().write(*args, **kwargs)
- cls._get_carriers_cache.clear()
-
- @classmethod
- def delete(cls, *args, **kwargs):
- super().delete(*args, **kwargs)
- cls._get_carriers_cache.clear()
-
@classmethod
def get_carriers(cls, pattern):
pool = Pool()
@@ -136,3 +113,8 @@ class Selection(sequence_ordered(), MatchMixin, ModelSQL, ModelView):
cls._get_carriers_cache.set(key, list(map(int, carriers)))
return carriers
+
+ @classmethod
+ def on_modification(cls, mode, selections, field_names=None):
+ super().on_modification(mode, selections, field_names=field_names)
+ cls._get_carriers_cache.clear()
diff --git a/modules/carrier_carriage/stock.py b/modules/carrier_carriage/stock.py
index d665217745..3e7d78f9bc 100644
--- a/modules/carrier_carriage/stock.py
+++ b/modules/carrier_carriage/stock.py
@@ -83,13 +83,15 @@ class Carriage(sequence_ordered(), ShipmentCostMixin, ModelSQL, ModelView):
return f'{self.carrier.rec_name} @ {self.shipment.rec_name}'
@classmethod
- def delete(cls, carriages):
- for carriage in carriages:
- if carriage.shipment_cost_readonly:
- raise AccessError(
- gettext('carrier_carriages.msg_shipment_carriage_delete',
- carriage=carriage.rec_name))
- super().delete(carriages)
+ def check_modification(cls, mode, carriages, values=None, external=False):
+ super().check_modification(
+ mode, carriages, values=values, external=external)
+ if mode == 'delete':
+ for carriage in carriages:
+ if carriage.shipment_cost_readonly:
+ raise AccessError(gettext(
+ 'carrier_carriages.msg_shipment_carriage_delete',
+ carriage=carriage.rec_name))
class _ShipmentMixin:
@@ -127,12 +129,14 @@ class _ShipmentMixin:
return cost
@classmethod
- def delete(cls, shipments):
+ def on_delete(cls, shipments):
pool = Pool()
Carriage = pool.get('stock.shipment.carriage')
+ callback = super().on_delete(shipments)
carriages = sum((s.carriages for s in shipments), ())
- super().delete(shipments)
- Carriage.delete(carriages)
+ if carriages:
+ callback.append(lambda: Carriage.delete(carriages))
+ return callback
@classmethod
def copy(cls, shipments, default=None):
diff --git a/modules/company/company.py b/modules/company/company.py
index 69eb8c2333..d792ae204b 100644
--- a/modules/company/company.py
+++ b/modules/company/company.py
@@ -99,27 +99,21 @@ class Company(ModelSQL, ModelView):
return [('party.rec_name',) + tuple(clause[1:])]
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if logo := values.get('logo'):
- values['logo'] = cls._logo_convert(logo)
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if logo := values.get('logo'):
+ values['logo'] = cls._logo_convert(logo)
+ return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for companies, values in zip(actions, actions):
- if logo := values.get('logo'):
- values = values.copy()
- values['logo'] = cls._logo_convert(logo)
- args.append(companies)
- args.append(values)
- super().write(*args)
- cls._logo_clear_cache(sum(args[0:None:2], []))
- # Restart the cache on the domain_get method
- Pool().get('ir.rule')._domain_get_cache.clear()
+ def on_modification(cls, mode, companies, field_names=None):
+ pool = Pool()
+ Rule = pool.get('ir.rule')
+ super().on_modification(mode, companies, field_names=field_names)
+ if mode == 'write':
+ if not field_names or 'logo' in field_names:
+ cls._logo_clear_cache(companies)
+ Rule._domain_get_cache.clear()
@classmethod
def _logo_convert(cls, image, **_params):
diff --git a/modules/company/res.py b/modules/company/res.py
index 44257ece6c..3cb4e90cb0 100644
--- a/modules/company/res.py
+++ b/modules/company/res.py
@@ -18,25 +18,10 @@ class UserCompany(ModelSQL):
ondelete='CASCADE', required=True)
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
User = pool.get('res.user')
- records = super().create(vlist)
- User._get_companies_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- pool = Pool()
- User = pool.get('res.user')
- super().write(*args)
- User._get_companies_cache.clear()
-
- @classmethod
- def delete(cls, records):
- pool = Pool()
- User = pool.get('res.user')
- super().delete(records)
+ super().on_modification(mode, records, field_names=field_names)
User._get_companies_cache.clear()
@@ -48,25 +33,10 @@ class UserEmployee(ModelSQL):
'company.employee', "Employee", ondelete='CASCADE', required=True)
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
User = pool.get('res.user')
- records = super().create(vlist)
- User._get_employees_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- pool = Pool()
- User = pool.get('res.user')
- super().write(*args)
- User._get_employees_cache.clear()
-
- @classmethod
- def delete(cls, records):
- pool = Pool()
- User = pool.get('res.user')
- super().delete(records)
+ super().on_modification(mode, records, field_names=field_names)
User._get_employees_cache.clear()
@@ -277,7 +247,8 @@ class User(metaclass=PoolMeta):
return result
@classmethod
- def write(cls, *args):
- super().write(*args)
- cls._get_companies_cache.clear()
- cls._get_employees_cache.clear()
+ def on_modification(cls, mode, users, field_names=None):
+ super().on_modification(mode, users, field_names=field_names)
+ if mode == 'write':
+ cls._get_companies_cache.clear()
+ cls._get_employees_cache.clear()
diff --git a/modules/country/country.py b/modules/country/country.py
index 82a4e98991..e2719c6d66 100644
--- a/modules/country/country.py
+++ b/modules/country/country.py
@@ -215,25 +215,12 @@ class Country(DeactivableMixin, ModelSQL, ModelView):
return self in organization.countries
@classmethod
- def create(cls, vlist):
- vlist = [x.copy() for x in vlist]
- for vals in vlist:
- for code in {'code', 'code3', 'code_numeric'}:
- if code in vals and vals[code]:
- vals[code] = vals[code].upper()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for countries, values in zip(actions, actions):
- for code in {'code', 'code3', 'code_numeric'}:
- if values.get(code):
- values = values.copy()
- values[code] = values[code].upper()
- args.extend((countries, values))
- super().write(*args)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ for code in {'code', 'code3', 'code_numeric'}:
+ if values.get(code):
+ values[code] = values[code].upper()
+ return values
class Subdivision(DeactivableMixin, ModelSQL, ModelView):
@@ -465,23 +452,11 @@ class Subdivision(DeactivableMixin, ModelSQL, ModelView):
return Operator(column, value)
@classmethod
- def create(cls, vlist):
- vlist = [x.copy() for x in vlist]
- for vals in vlist:
- if 'code' in vals and vals['code']:
- vals['code'] = vals['code'].upper()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for subdivisions, values in zip(actions, actions):
- if values.get('code'):
- values = values.copy()
- values['code'] = values['code'].upper()
- args.extend((subdivisions, values))
- super().write(*args)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if values.get('code'):
+ values['code'] = values['code'].upper()
+ return values
class PostalCode(ModelSQL, ModelView):
diff --git a/modules/currency_rs/currency.py b/modules/currency_rs/currency.py
index 3e874b63b4..53c4f80a8b 100644
--- a/modules/currency_rs/currency.py
+++ b/modules/currency_rs/currency.py
@@ -100,16 +100,19 @@ class Cron(metaclass=PoolMeta):
if r.Date == date.strftime('%d.%m.%Y')}
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, crons, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for crons, values in zip(actions, actions):
- if ({'rs_username', 'rs_password', 'rs_license_id'}
- & values.keys()):
- warning_name = Warning.format('rs_credential', crons)
- if Warning.check(warning_name):
- raise RSCredentialWarning(
- warning_name,
- gettext('currency_rs.msg_rs_credential_modified'))
- return super().write(*args)
+
+ super().check_modification(
+ mode, crons, values=values, external=external)
+
+ if (mode == 'write'
+ and external
+ and values.keys() & {
+ 'rs_username', 'rs_password', 'rs_license_id'}):
+ warning_name = Warning.format('rs_credential', crons)
+ if Warning.check(warning_name):
+ raise RSCredentialWarning(
+ warning_name,
+ gettext('currency_rs.msg_rs_credential_modified'))
diff --git a/modules/customs/product.py b/modules/customs/product.py
index 3362decb94..95a651fc35 100644
--- a/modules/customs/product.py
+++ b/modules/customs/product.py
@@ -76,18 +76,24 @@ class Category(metaclass=PoolMeta):
]
@classmethod
- def delete(cls, categories):
+ def on_delete(cls, categories):
pool = Pool()
Product_TariffCode = pool.get('product-customs.tariff.code')
+
+ callback = super().on_delete(categories)
+
+ product_tariffcodes = set()
products = [str(t) for t in categories]
-
- super().delete(categories)
-
for products in grouped_slice(products):
- product_tariffcodes = Product_TariffCode.search([
- 'product', 'in', list(products),
- ])
- Product_TariffCode.delete(product_tariffcodes)
+ product_tariffcodes.update(Product_TariffCode.search([
+ 'product', 'in', list(products),
+ ]))
+ if product_tariffcodes:
+ product_tariffcodes = Product_TariffCode.browse(
+ product_tariffcodes)
+ callback.append(
+ lambda: Product_TariffCode.delete(product_tariffcodes))
+ return callback
class Template(metaclass=PoolMeta):
@@ -145,18 +151,24 @@ class Template(metaclass=PoolMeta):
]
@classmethod
- def delete(cls, templates):
+ def on_delete(cls, templates):
pool = Pool()
Product_TariffCode = pool.get('product-customs.tariff.code')
+
+ callback = super().on_delete(templates)
+
+ product_tariffcodes = set()
products = [str(t) for t in templates]
-
- super().delete(templates)
-
for products in grouped_slice(products):
- product_tariffcodes = Product_TariffCode.search([
- 'product', 'in', list(products),
- ])
- Product_TariffCode.delete(product_tariffcodes)
+ product_tariffcodes.update(Product_TariffCode.search([
+ 'product', 'in', list(products),
+ ]))
+ if product_tariffcodes:
+ product_tariffcodes = Product_TariffCode.browse(
+ product_tariffcodes)
+ callback.append(
+ lambda: Product_TariffCode.delete(product_tariffcodes))
+ return callback
class Product_TariffCode(sequence_ordered(), ModelSQL, ModelView):
diff --git a/modules/dashboard/res.py b/modules/dashboard/res.py
index b2187bfa86..06388a8623 100644
--- a/modules/dashboard/res.py
+++ b/modules/dashboard/res.py
@@ -29,12 +29,11 @@ class User(metaclass=PoolMeta):
return 'square'
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, users, field_names=None):
pool = Pool()
View = pool.get('ir.ui.view')
- super().write(*args)
- for values in args[1:None:2]:
- if values.keys() & {'dashboard_layout', 'dashboard_actions'}:
- View._view_get_cache.clear()
- ModelView._fields_view_get_cache.clear()
- break
+ super().on_modification(mode, users, field_names=field_names)
+ if (mode == 'write'
+ and field_names & {'dashboard_layout', 'dashboard_actions'}):
+ View._view_get_cache.clear()
+ ModelView._fields_view_get_cache.clear()
diff --git a/modules/document_incoming_ocr/document.py b/modules/document_incoming_ocr/document.py
index 3299c7127d..fcd0cd14ce 100644
--- a/modules/document_incoming_ocr/document.py
+++ b/modules/document_incoming_ocr/document.py
@@ -478,17 +478,6 @@ class IncomingOCRService(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
getattr(self, f'_send_feedback_{self.type}')(document)
@classmethod
- def create(cls, *args, **kwargs):
- services = super().create(*args, **kwargs)
- cls._get_service_cache.clear()
- return services
-
- @classmethod
- def write(cls, *args, **kwargs):
- super().write(*args, **kwargs)
- cls._get_service_cache.clear()
-
- @classmethod
- def delete(cls, *args, **kwargs):
- super().delete(*args, **kwargs)
+ def on_modification(cls, mode, services, field_names=None):
+ super().on_modification(mode, services, field_names=field_names)
cls._get_service_cache.clear()
diff --git a/modules/document_incoming_ocr_typless/document.py b/modules/document_incoming_ocr_typless/document.py
index adf6e1f853..3dadfd9c7b 100644
--- a/modules/document_incoming_ocr_typless/document.py
+++ b/modules/document_incoming_ocr_typless/document.py
@@ -322,16 +322,17 @@ class IncomingOCRService(metaclass=PoolMeta):
return str(line.base)
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, services, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for services, values in zip(actions, actions):
- if 'typless_api_key' in values.keys():
- warning_name = Warning.format('typless_credential', services)
- if Warning.check(warning_name):
- raise TyplessCredentialWarning(
- warning_name,
- gettext('document_incoming_ocr_typless'
- '.msg_typless_credential_modified'))
- return super().write(*args)
+
+ super().check_modification(
+ mode, services, values=values, external=external)
+
+ if mode == 'write' and external and 'typless_api_key' in values:
+ warning_name = Warning.format('typless_credential', services)
+ if Warning.check(warning_name):
+ raise TyplessCredentialWarning(
+ warning_name,
+ gettext('document_incoming_ocr_typless'
+ '.msg_typless_credential_modified'))
diff --git a/modules/incoterm/incoterm.py b/modules/incoterm/incoterm.py
index 843f3b9df7..e5e8ce78e2 100644
--- a/modules/incoterm/incoterm.py
+++ b/modules/incoterm/incoterm.py
@@ -70,23 +70,8 @@ class Incoterm_Company(ModelSQL):
company = fields.Many2One('company.company', "Company", required=True)
@classmethod
- def create(cls, *args, **kwargs):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
Incoterm = pool.get('incoterm.incoterm')
- records = super().create(*args, **kwargs)
- Incoterm._get_incoterms_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args, **kwargs):
- pool = Pool()
- Incoterm = pool.get('incoterm.incoterm')
- super().write(*args, **kwargs)
- Incoterm._get_incoterms_cache.clear()
-
- @classmethod
- def delete(cls, *args, **kwargs):
- pool = Pool()
- Incoterm = pool.get('incoterm.incoterm')
- super().delete(*args, **kwargs)
+ super().on_modification(mode, records, field_names=field_names)
Incoterm._get_incoterms_cache.clear()
diff --git a/modules/ldap_authentication/res.py b/modules/ldap_authentication/res.py
index fbe35dc1fd..32612083f5 100644
--- a/modules/ldap_authentication/res.py
+++ b/modules/ldap_authentication/res.py
@@ -12,7 +12,6 @@ from trytond.exceptions import LoginException
from trytond.i18n import gettext
from trytond.model.exceptions import AccessError
from trytond.pool import PoolMeta
-from trytond.transaction import without_check_access
logger = logging.getLogger(__name__)
section = 'ldap_authentication'
@@ -100,7 +99,6 @@ class User(metaclass=PoolMeta):
for e in result]
@classmethod
- @without_check_access
def _check_passwd_ldap_user(cls, logins):
find = False
try:
@@ -119,23 +117,18 @@ class User(metaclass=PoolMeta):
user=login))
@classmethod
- def create(cls, vlist):
- tocheck = []
- for values in vlist:
- if values.get('password') and 'login' in values:
- tocheck.append(values['login'])
- if tocheck:
- cls._check_passwd_ldap_user(tocheck)
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and values.get('password') and 'login' in values:
+ cls._check_passwd_ldap_user([values['login']])
+ return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for users, values in zip(actions, actions):
- if values.get('password'):
- logins = [x.login for x in users]
- cls._check_passwd_ldap_user(logins)
- super().write(*args)
+ def check_modification(cls, mode, users, values=None, external=False):
+ super().check_modification(
+ mode, users, values=values, external=external)
+ if mode == 'write' and values.get('password'):
+ cls._check_passwd_ldap_user([u.login for u in users])
@classmethod
def _login_ldap(cls, login, parameters):
diff --git a/modules/marketing_automation/marketing_automation.py b/modules/marketing_automation/marketing_automation.py
index b13f059409..02b47a79d8 100644
--- a/modules/marketing_automation/marketing_automation.py
+++ b/modules/marketing_automation/marketing_automation.py
@@ -852,13 +852,13 @@ class Record(ModelSQL, ModelView):
return '(%s)' % self.id
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
# Ensure to get a different uuid for each record
# default methods are called only once
values.setdefault('uuid', cls.default_uuid())
- return super().create(vlist)
+ return values
class RecordActivity(Workflow, ModelSQL, ModelView):
diff --git a/modules/marketing_campaign/marketing.py b/modules/marketing_campaign/marketing.py
index db95577106..324ff5038a 100644
--- a/modules/marketing_campaign/marketing.py
+++ b/modules/marketing_campaign/marketing.py
@@ -31,23 +31,11 @@ class Parameter(DeactivableMixin, ModelSQL, ModelView):
return self.name.title()
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if values.get('name'):
- values['name'] = values['name'].lower()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for parameters, values in zip(actions, actions):
- if values.get('name'):
- values = values.copy()
- values['name'] = values['name'].lower()
- args.extend((parameters, values))
- super().write(*args)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if values.get('name'):
+ values['name'] = values['name'].lower()
+ return values
@classmethod
def from_name(cls, name, create=True):
diff --git a/modules/marketing_email/marketing.py b/modules/marketing_email/marketing.py
index 0fb1965f53..b585735b89 100644
--- a/modules/marketing_email/marketing.py
+++ b/modules/marketing_email/marketing.py
@@ -113,29 +113,15 @@ class Email(DeactivableMixin, ModelSQL, ModelView):
return result
@classmethod
- def _format_email(cls, records):
- for record in records:
- email = normalize_email(record.email).lower()
- if email != record.email:
- record.email = email
- cls.save(records)
-
- @classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
# Ensure to get a different token for each record
# default methods are called only once
values.setdefault('email_token', cls.default_email_token())
- records = super().create(vlist)
- cls._format_email(records)
- return records
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- records = sum(args[0:None:2], [])
- cls._format_email(records)
+ if values.get('email'):
+ values['email'] = normalize_email(values['email']).lower()
+ return values
@classmethod
def validate_fields(cls, records, fields_names):
diff --git a/modules/party/address.py b/modules/party/address.py
index 4553bcd59b..31afa8a5e5 100644
--- a/modules/party/address.py
+++ b/modules/party/address.py
@@ -225,16 +225,15 @@ class Address(
]
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for addresses, values in zip(actions, actions):
- if 'party' in values:
- for address in addresses:
- if address.party.id != values['party']:
- raise AccessError(
- gettext('party.msg_address_change_party',
- address=address.rec_name))
- super().write(*args)
+ def check_modification(cls, mode, addresses, values=None, external=False):
+ super().check_modification(
+ mode, addresses, values=values, external=external)
+ if mode == 'write' and 'party' in values:
+ for address in addresses:
+ if address.party.id != values['party']:
+ raise AccessError(gettext(
+ 'party.msg_address_change_party',
+ address=address.rec_name))
@fields.depends('subdivision', 'country')
def on_change_country(self):
@@ -299,19 +298,8 @@ ${subdivision}
${COUNTRY}"""
@classmethod
- def create(cls, *args, **kwargs):
- records = super().create(*args, **kwargs)
- cls._get_format_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args, **kwargs):
- super().write(*args, **kwargs)
- cls._get_format_cache.clear()
-
- @classmethod
- def delete(cls, *args, **kwargs):
- super().delete(*args, **kwargs)
+ def on_modification(cls, mode, formats, field_names=None):
+ super().on_modification(mode, formats, field_names=field_names)
cls._get_format_cache.clear()
@classmethod
@@ -386,22 +374,6 @@ class SubdivisionType(DeactivableMixin, ModelSQL, ModelView):
selection = Subdivision.fields_get(['type'])['type']['selection']
return [(k, v) for k, v in selection if k is not None]
- @classmethod
- def create(cls, *args, **kwargs):
- records = super().create(*args, **kwargs)
- cls._get_types_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args, **kwargs):
- super().write(*args, **kwargs)
- cls._get_types_cache.clear()
-
- @classmethod
- def delete(cls, *args, **kwargs):
- super().delete(*args, **kwargs)
- cls._get_types_cache.clear()
-
@classmethod
def get_types(cls, country):
key = country.code if country else None
@@ -420,3 +392,8 @@ class SubdivisionType(DeactivableMixin, ModelSQL, ModelView):
cls._get_types_cache.set(key, types)
return types
+
+ @classmethod
+ def on_modification(cls, mode, types, field_names=None):
+ super().on_modification(mode, types, field_names=field_names)
+ cls._get_types_cache.clear()
diff --git a/modules/party/configuration.py b/modules/party/configuration.py
index fc95571d90..8e7c3b480e 100644
--- a/modules/party/configuration.py
+++ b/modules/party/configuration.py
@@ -64,19 +64,8 @@ class Configuration(ModelSingleton, ModelSQL, ModelView, MultiValueMixin):
return selection
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
- ModelView._fields_view_get_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- ModelView._fields_view_get_cache.clear()
-
- @classmethod
- def delete(cls, records):
- super().delete(records)
+ def on_modification(cls, mode, configurations, field_names=None):
+ super().on_modification(mode, configurations, field_names=field_names)
ModelView._fields_view_get_cache.clear()
@classmethod
diff --git a/modules/party/contact_mechanism.py b/modules/party/contact_mechanism.py
index f6a74b9493..a1a6511dce 100644
--- a/modules/party/contact_mechanism.py
+++ b/modules/party/contact_mechanism.py
@@ -285,20 +285,15 @@ class ContactMechanism(
return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- all_mechanisms = []
- for mechanisms, values in zip(actions, actions):
- all_mechanisms.extend(mechanisms)
- if 'party' in values:
- for mechanism in mechanisms:
- if mechanism.party.id != values['party']:
- raise AccessError(
- gettext('party'
- '.msg_contact_mechanism_change_party') % {
- 'contact': mechanism.rec_name,
- })
- super().write(*args)
+ def check_modification(cls, mode, mechanisms, values=None, external=False):
+ super().check_modification(
+ mode, mechanisms, values=values, external=external)
+ if mode == 'write' and 'party' in values:
+ for mechanism in mechanisms:
+ if mechanism.party.id != values['party']:
+ raise AccessError(gettext(
+ 'party.msg_contact_mechanism_change_party',
+ contact=mechanism.rec_name))
@classmethod
def validate_fields(cls, mechanisms, field_names):
diff --git a/modules/party/party.py b/modules/party/party.py
index b42973c313..5f09c076a6 100644
--- a/modules/party/party.py
+++ b/modules/party/party.py
@@ -286,19 +286,14 @@ class Party(
return config.get_multivalue('party_sequence', **pattern)
@classmethod
- def create(cls, vlist):
- vlist = [x.copy() for x in vlist]
- missing_code = []
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
if not values.get('code'):
- missing_code.append(values)
+ if sequence := cls._code_sequence():
+ values['code'] = sequence.get()
values.setdefault('addresses', None)
- if missing_code:
- if sequence := cls._code_sequence():
- for values, code in zip(
- missing_code, sequence.get_many(len(missing_code))):
- values['code'] = code
- return super().create(vlist)
+ return values
@classmethod
def copy(cls, parties, default=None):
diff --git a/modules/party_relationship/party.py b/modules/party_relationship/party.py
index 3e7d67c76e..19887421a2 100644
--- a/modules/party_relationship/party.py
+++ b/modules/party_relationship/party.py
@@ -195,8 +195,9 @@ class RelationAll(Relation, ModelView):
def create(cls, vlist):
pool = Pool()
Relation = pool.get('party.relation')
+ vlist = cls._before_create(vlist)
relations = Relation.create(vlist)
- return cls.browse([r.id * 2 for r in relations])
+ return cls.browse(cls._after_create([r.id * 2 for r in relations]))
@classmethod
def write(cls, *args):
@@ -204,23 +205,19 @@ class RelationAll(Relation, ModelView):
Relation = pool.get('party.relation')
RelationType = pool.get('party.relation.type')
- all_records = sum(args[0:None:2], [])
+ ids, field_names, on_write, trigger_eligibles, *args = (
+ cls._before_write(*args))
- # Increase transaction counter
- Transaction().counter += 1
-
- # Clean local cache
- for record in all_records:
- for record_id in (record.id, record.reverse_id):
- record._local_cache.pop(record_id, None)
-
- # Clean cursor cache
+ relations = cls.browse(ids)
+ # Clean local cache of reverse
+ for relation in relations:
+ relation._local_cache.pop(relation.reverse_id, None)
+ # Clean cursor cache of reverse
for cache in Transaction().cache.values():
if cls.__name__ in cache:
cache_cls = cache[cls.__name__]
- for record in all_records:
- for record_id in (record.id, record.reverse_id):
- cache_cls.pop(record_id, None)
+ for relation in relations:
+ cache_cls.pop(relation.reverse_id, None)
actions = iter(args)
args = []
@@ -247,25 +244,23 @@ class RelationAll(Relation, ModelView):
(cls.convert_instances(reverse_relations), reverse_values))
Relation.write(*args)
+ cls._after_write(ids, field_names, on_write, trigger_eligibles)
+
@classmethod
def delete(cls, relations):
pool = Pool()
Relation = pool.get('party.relation')
+ ids, on_delete = cls._before_delete(relations)
- # Increase transaction counter
- Transaction().counter += 1
-
- # Clean cursor cache
+ # Clean cursor cache of reverse
for cache in list(Transaction().cache.values()):
- for cache in (cache,
- list(cache.get('_language_cache', {}).values())):
- if cls.__name__ in cache:
- cache_cls = cache[cls.__name__]
- for record in relations:
- for record_id in (record.id, record.reverse_id):
- cache_cls.pop(record_id, None)
+ if cls.__name__ in cache:
+ cache_cls = cache[cls.__name__]
+ for relation in relations:
+ cache_cls.pop(relation.reverse_id, None)
Relation.delete(cls.convert_instances(relations))
+ cls._after_delete(ids, on_delete)
class Party(metaclass=PoolMeta):
diff --git a/modules/product/category.py b/modules/product/category.py
index b70d938956..420962785a 100644
--- a/modules/product/category.py
+++ b/modules/product/category.py
@@ -97,15 +97,12 @@ class Category(tree(separator=' / '), ModelSQL, ModelView):
return config.category_sequence
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- missing_code = [v for v in vlist if not v.get('code')]
- if missing_code:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('code'):
if sequence := cls._code_sequence():
- for values, code in zip(
- missing_code, sequence.get_many(len(missing_code))):
- values['code'] = code
- return super().create(vlist)
+ values['code'] = sequence.get()
+ return values
@classmethod
def copy(cls, categories, default=None):
diff --git a/modules/product/product.py b/modules/product/product.py
index a8da7ececa..231f6743ce 100644
--- a/modules/product/product.py
+++ b/modules/product/product.py
@@ -239,33 +239,23 @@ class Template(
return config.template_sequence
@classmethod
- def create(cls, vlist):
- pool = Pool()
- Product = pool.get('product.product')
- vlist = [v.copy() for v in vlist]
- missing_code = []
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
values.setdefault('products', None)
if not values.get('code'):
- missing_code.append(values)
- if missing_code:
- if sequence := cls._code_sequence():
- for values, code in zip(
- missing_code, sequence.get_many(len(missing_code))):
- values['code'] = code
- templates = super().create(vlist)
- products = sum((t.products for t in templates), ())
- Product.sync_code(products)
- return templates
+ if sequence := cls._code_sequence():
+ values['code'] = sequence.get()
+ return values
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, templates, field_names=None):
pool = Pool()
Product = pool.get('product.product')
- super().write(*args)
- templates = sum(args[0:None:2], [])
- products = sum((t.products for t in templates), ())
- Product.sync_code(products)
+ super().on_modification(mode, templates, field_names=field_names)
+ if mode in {'create', 'write'}:
+ products = sum((t.products for t in templates), ())
+ Product.sync_code(products)
@classmethod
def copy(cls, templates, default=None):
@@ -673,23 +663,18 @@ class Product(
return config.product_sequence
@classmethod
- def create(cls, vlist):
- vlist = [x.copy() for x in vlist]
- missing_code = [v for v in vlist if not v.get('suffix_code')]
- if missing_code:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('suffix_code'):
if sequence := cls._code_sequence():
- for values, code in zip(
- missing_code, sequence.get_many(len(missing_code))):
- values['suffix_code'] = code
- products = super().create(vlist)
- cls.sync_code(products)
- return products
+ values['suffix_code'] = sequence.get()
+ return values
@classmethod
- def write(cls, *args):
- super().write(*args)
- products = sum(args[0:None:2], [])
- cls.sync_code(products)
+ def on_modification(cls, mode, products, field_names=None):
+ super().on_modification(mode, products, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls.sync_code(products)
@classmethod
def copy(cls, products, default=None):
diff --git a/modules/product/uom.py b/modules/product/uom.py
index ecb597ef68..dba6752b27 100644
--- a/modules/product/uom.py
+++ b/modules/product/uom.py
@@ -10,7 +10,6 @@ from trytond.model import (
Check, DeactivableMixin, DigitsMixin, ModelSQL, ModelView, SymbolMixin,
fields)
from trytond.pyson import Eval, If
-from trytond.transaction import Transaction
from .exceptions import UOMAccessError, UOMValidationError
@@ -150,30 +149,30 @@ class Uom(SymbolMixin, DigitsMixin, DeactivableMixin, ModelSQL, ModelView):
uom=uom.rec_name))
@classmethod
- def write(cls, *args):
- if Transaction().user == 0:
- super().write(*args)
- return
-
- all_uoms = sum(args[0:None:2], [])
- old_uom = {
- uom.id: (uom.factor, uom.rate, uom.category) for uom in all_uoms}
- old_digits = {uom.id: uom.digits for uom in all_uoms}
-
- super().write(*args)
-
- for uom in all_uoms:
- for i, field in enumerate(['factor', 'rate', 'category']):
- if getattr(uom, field) != old_uom[uom.id][i]:
- raise UOMAccessError(
- gettext('product.msg_uom_modify_%s' % field,
- uom=uom.rec_name),
- gettext('product.msg_uom_modify_options'))
- if uom.digits < old_digits[uom.id]:
- raise UOMAccessError(
- gettext('product.msg_uom_decrease_digits',
- uom=uom.rec_name),
- gettext('product.msg_uom_modify_options'))
+ def check_modification(cls, mode, uoms, values=None, external=False):
+ super().check_modification(
+ mode, uoms, values=values, external=external)
+ if (mode == 'write'
+ and values.keys() & {'factor', 'rate', 'category', 'digits'}):
+ for uom in uoms:
+ for field_name in values.keys() & {'factor', 'rate'}:
+ if values[field_name] != getattr(uom, field_name):
+ raise UOMAccessError(gettext(
+ 'product.msg_uom_modify_%s' % field_name,
+ uom=uom.rec_name),
+ gettext('product.msg_uom_modify_options'))
+ if 'category' in values:
+ if values['category'] != uom.category.id:
+ raise UOMAccessError(gettext(
+ 'product.msg_uom_modify_category',
+ uom=uom.rec_name),
+ gettext('product.msg_uom_modify_options'))
+ if 'digits' in values:
+ if values['digits'] < uom.digits:
+ raise UOMAccessError(gettext(
+ 'product.msg_uom_decrease_digits',
+ uom=uom.rec_name),
+ gettext('product.msg_uom_modify_options'))
@property
def accurate_field(self):
diff --git a/modules/product_cost_fifo/move.py b/modules/product_cost_fifo/move.py
index b8c3d0888b..0c86144600 100644
--- a/modules/product_cost_fifo/move.py
+++ b/modules/product_cost_fifo/move.py
@@ -155,13 +155,15 @@ class Move(metaclass=PoolMeta):
super().cancel(moves)
@classmethod
- def delete(cls, moves):
- for move in moves:
- if move.fifo_quantity:
- raise AccessError(
- gettext('product_cost_fifo.msg_move_delete_fifo',
- move=move.rec_name))
- super().delete(moves)
+ def check_modification(cls, mode, moves, values=None, external=False):
+ super().check_modification(
+ mode, moves, values=values, external=external)
+ if mode == 'delete':
+ for move in moves:
+ if move.fifo_quantity:
+ raise AccessError(gettext(
+ 'product_cost_fifo.msg_move_delete_fifo',
+ move=move.rec_name))
@classmethod
def copy(cls, moves, default=None):
diff --git a/modules/product_image/product.py b/modules/product_image/product.py
index 37c5f5df8a..21b3fbf83e 100644
--- a/modules/product_image/product.py
+++ b/modules/product_image/product.py
@@ -123,25 +123,17 @@ class ImageMixin(_ImageMixin):
return set()
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if values.get('image'):
- values['image'] = cls.convert(values['image'])
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if values.get('image'):
+ values['image'] = cls.convert(values['image'])
+ return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for images, values in zip(actions, actions):
- if values.get('image'):
- values = values.copy()
- values['image'] = cls.convert(values['image'])
- args.append(images)
- args.append(values)
- super().write(*args)
- cls.clear_cache(sum(args[0:None:2], []))
+ def on_modification(cls, mode, images, field_names=None):
+ super().on_modification(mode, images, field_names=field_names)
+ if mode == 'write':
+ cls.clear_cache(images)
@classmethod
def _round_size(cls, size):
diff --git a/modules/product_price_list_cache/product.py b/modules/product_price_list_cache/product.py
index 462398e7a0..7ed9ac3425 100644
--- a/modules/product_price_list_cache/product.py
+++ b/modules/product_price_list_cache/product.py
@@ -180,16 +180,6 @@ class PriceListCache(ModelSQL):
cache.product, [p.id for p in sub_products])))
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_cache.clear()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- cls._get_cache.clear()
- return super().write(*args)
-
- @classmethod
- def delete(cls, records):
- cls._get_cache.clear()
- return super().delete(records)
diff --git a/modules/production/bom.py b/modules/production/bom.py
index 918c65fa76..ab4c8ce7cf 100644
--- a/modules/production/bom.py
+++ b/modules/production/bom.py
@@ -94,15 +94,12 @@ class BOM(DeactivableMixin, ModelSQL, ModelView):
return config.bom_sequence
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- missing_code = [v for v in vlist if not v.get('code')]
- if missing_code:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('code'):
if sequence := cls._code_sequence():
- for values, code in zip(
- missing_code, sequence.get_many(len(missing_code))):
- values['code'] = code
- return super().create(vlist)
+ values['code'] = sequence.get()
+ return values
@classmethod
def copy(cls, records, default=None):
@@ -205,27 +202,28 @@ class BOMOutput(BOMInput):
return self.unit.floor(self.quantity * factor)
@classmethod
- def delete(cls, outputs):
+ def on_delete(cls, outputs):
pool = Pool()
ProductBOM = pool.get('product.product-production.bom')
+
+ callback = super().on_delete(outputs)
+
bom_products = [b for o in outputs for b in o.product.boms]
- super().delete(outputs)
# Validate that output_products domain on bom is still valid
- ProductBOM._validate(bom_products, ['bom'])
+ callback.append(lambda: ProductBOM._validate(bom_products, ['bom']))
+ return callback
@classmethod
- def write(cls, *args):
+ def on_write(cls, outputs, values):
pool = Pool()
ProductBOM = pool.get('product.product-production.bom')
- actions = iter(args)
- bom_products = []
- for outputs, values in zip(actions, actions):
- if 'product' in values:
- bom_products.extend(
- [b for o in outputs for b in o.product.boms])
- super().write(*args)
+
+ callback = super().on_write(outputs, values)
+
+ bom_products = [b for o in outputs for b in o.product.boms]
# Validate that output_products domain on bom is still valid
- ProductBOM._validate(bom_products, ['bom'])
+ callback.append(lambda: ProductBOM._validate(bom_products, ['bom']))
+ return callback
class BOMTree(ModelView):
diff --git a/modules/production/production.py b/modules/production/production.py
index 481f451468..e87629af6b 100644
--- a/modules/production/production.py
+++ b/modules/production/production.py
@@ -650,15 +650,18 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
cls.save(productions)
@classmethod
- def create(cls, vlist):
- productions = super().create(vlist)
- cls._set_move_planned_date(productions)
- return productions
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
+ def on_modification(cls, mode, productions, field_names=None):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ super().on_modification(mode, productions, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls._set_move_planned_date(productions)
+ elif mode == 'delete':
+ moves = []
+ for production in productions:
+ moves.extend(production.inputs)
+ moves.extend(production.outputs)
+ Move.delete(moves)
@classmethod
def copy(cls, productions, default=None):
diff --git a/modules/production/stock.py b/modules/production/stock.py
index 926420c463..d2327647cf 100644
--- a/modules/production/stock.py
+++ b/modules/production/stock.py
@@ -125,19 +125,12 @@ class Move(metaclass=PoolMeta):
super().set_effective_date()
@classmethod
- def write(cls, *args):
- super().write(*args)
- cost_price_update = []
- actions = iter(args)
- for moves, values in zip(actions, actions):
- for move in moves:
- if (move.state == 'done'
- and move.production_input
- and 'cost_price' in values):
- cost_price_update.append(move)
- if cost_price_update:
+ def on_modification(cls, mode, moves, field_names=None):
+ super().on_modification(mode, moves, field_names=field_names)
+ if mode == 'write' and 'cost_price' in field_names:
cls.write(
- cost_price_update, {'production_cost_price_updated': True})
+ [m for m in moves if m.state == 'done' and m.production_input],
+ {'production_cost_price_updated': True})
class ProductQuantitiesByWarehouseMove(metaclass=PoolMeta):
diff --git a/modules/production_work/work.py b/modules/production_work/work.py
index 1c18fe6116..a4cab6c7d8 100644
--- a/modules/production_work/work.py
+++ b/modules/production_work/work.py
@@ -264,25 +264,21 @@ class Work(sequence_ordered(), ModelSQL, ModelView):
return costs
@classmethod
- def create(cls, values):
- works = super().create(values)
- cls.set_state(works)
- return works
+ def on_modification(cls, mode, works, field_names=None):
+ super().on_modification(mode, works, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls.set_state(works)
@classmethod
- def write(cls, *args):
- super().write(*args)
- works = sum(args[0:None:2], [])
- cls.set_state(works)
-
- @classmethod
- def delete(cls, works):
- for work in works:
- if work.state not in {'request', 'draft'}:
- raise AccessError(
- gettext('production_work.msg_delete_request',
- work=work.rec_name))
- super().delete(works)
+ def check_modification(cls, mode, works, values=None, external=False):
+ super().check_modification(
+ mode, works, values=values, external=external)
+ if mode == 'delete':
+ for work in works:
+ if work.state not in {'request', 'draft'}:
+ raise AccessError(
+ gettext('production_work.msg_delete_request',
+ work=work.rec_name))
def set_work_state(func):
@@ -290,8 +286,9 @@ def set_work_state(func):
def wrapper(cls, cycles):
pool = Pool()
Work = pool.get('production.work')
- func(cls, cycles)
+ result = func(cls, cycles)
Work.set_state(Work.browse({c.work.id for c in cycles}))
+ return result
return wrapper
diff --git a/modules/production_work_timesheet/work.py b/modules/production_work_timesheet/work.py
index ec06cbb4da..d9bd6ca7bd 100644
--- a/modules/production_work_timesheet/work.py
+++ b/modules/production_work_timesheet/work.py
@@ -58,28 +58,19 @@ class Work(metaclass=PoolMeta):
})
@classmethod
- def create(cls, vlist):
- works = super().create(vlist)
- cls._set_timesheet_work(works)
- return works
+ def on_modification(cls, mode, works, field_names=None):
+ super().on_modification(mode, works, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls._set_timesheet_work(works)
@classmethod
- def write(cls, *args):
- super().write(*args)
- works = sum(args[0:None:2], [])
- cls._set_timesheet_work(works)
-
- @classmethod
- def delete(cls, works):
+ def on_delete(cls, works):
pool = Pool()
TimesheetWork = pool.get('timesheet.work')
-
+ callback = super().on_delete(works)
timesheet_works = [w for pw in works for w in pw.timesheet_works]
-
- super().delete(works)
-
- if timesheet_works:
- TimesheetWork.delete(timesheet_works)
+ callback.append(lambda: TimesheetWork.delete(timesheet_works))
+ return callback
@classmethod
def _set_timesheet_work(cls, works):
diff --git a/modules/project/work.py b/modules/project/work.py
index 45b4790cbd..51c3489652 100644
--- a/modules/project/work.py
+++ b/modules/project/work.py
@@ -11,7 +11,7 @@ from trytond.model import (
sum_tree, tree)
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If, PYSONEncoder, TimeDelta
-from trytond.transaction import Transaction, without_check_access
+from trytond.transaction import Transaction
from .exceptions import WorkProgressValidationError
@@ -68,22 +68,10 @@ class WorkStatus(DeactivableMixin, sequence_ordered(), ModelSQL, ModelView):
return status
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_default_status_cache.clear()
cls._get_window_domains_cache.clear()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._get_default_status_cache.clear()
- cls._get_window_domains_cache.clear()
-
- @classmethod
- def delete(cls, status):
- cls._get_default_status_cache.clear()
- cls._get_window_domains_cache.clear()
- super().delete(status)
@classmethod
def get_window_domains(cls, action):
@@ -428,18 +416,16 @@ class Work(sequence_ordered(), tree(separator='\\'), ModelSQL, ModelView):
return new_works
@classmethod
- def delete(cls, project_works):
- TimesheetWork = Pool().get('timesheet.work')
-
- # Get the timesheet works linked to the project works
- timesheet_works = [
- w for pw in project_works for w in pw.timesheet_works]
-
- super().delete(project_works)
-
+ def on_delete(cls, project_works):
+ pool = Pool()
+ TimesheetWork = pool.get('timesheet.work')
+ callback = super().on_delete(project_works)
+ timesheet_works = {
+ w for pw in project_works for w in pw.timesheet_works}
if timesheet_works:
- with without_check_access():
- TimesheetWork.delete(timesheet_works)
+ timesheet_works = TimesheetWork.browse(timesheet_works)
+ callback.append(lambda: TimesheetWork.delete(timesheet_works))
+ return callback
@classmethod
def search_global(cls, text):
diff --git a/modules/project_invoice/invoice.py b/modules/project_invoice/invoice.py
index e2bfa1722a..314a4975ad 100644
--- a/modules/project_invoice/invoice.py
+++ b/modules/project_invoice/invoice.py
@@ -11,7 +11,7 @@ from trytond.modules.account_invoice.exceptions import (
InvoiceLineValidationError)
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice, reduce_ids
-from trytond.transaction import Transaction, without_check_access
+from trytond.transaction import Transaction
class InvoiceLine(metaclass=PoolMeta):
@@ -115,19 +115,3 @@ class InvoiceLine(metaclass=PoolMeta):
default.setdefault('project_invoice_works')
default.setdefault('project_invoice_progresses')
return super().copy(lines, default=default)
-
- @classmethod
- def delete(cls, lines):
- pool = Pool()
- WorkInvoicedProgress = pool.get('project.work.invoiced_progress')
-
- progress = []
- for sub_ids in grouped_slice([l.id for l in lines]):
- progress += WorkInvoicedProgress.search([
- ('invoice_line', 'in', sub_ids),
- ])
- if progress:
- with without_check_access():
- WorkInvoicedProgress.delete(progress)
-
- super().delete(lines)
diff --git a/modules/project_invoice/project.py b/modules/project_invoice/project.py
index f6fda068a2..eca2165fae 100644
--- a/modules/project_invoice/project.py
+++ b/modules/project_invoice/project.py
@@ -426,25 +426,22 @@ class Work(Effort, Progress, Timesheet, metaclass=PoolMeta):
return super().copy(records, default=default)
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for works, values in zip(actions, actions):
+ def check_modification(cls, mode, works, values=None, external=False):
+ super().check_modification(
+ mode, works, values=values, external=external)
+ if mode == 'write':
if ('effort_duration' in values
and any(w.invoice_line for w in works)):
work = next((w for w in works if w.invoice_line))
raise AccessError(gettext(
'project_invoice.msg_invoiced_work_modify_effort',
work=work.rec_name))
- super().write(*args)
-
- @classmethod
- def delete(cls, works):
- if any(w.invoice_line for w in works):
- work = next((w for w in works if w.invoice_line))
- raise AccessError(gettext(
- 'project_invoice.msg_invoiced_work_delete',
- work=work.rec_name))
- super().delete(works)
+ elif mode == 'delete':
+ if any(w.invoice_line for w in works):
+ work = next((w for w in works if w.invoice_line))
+ raise AccessError(gettext(
+ 'project_invoice.msg_invoiced_work_delete',
+ work=work.rec_name))
@classmethod
def get_invoice_methods(cls):
diff --git a/modules/project_invoice/timesheet.py b/modules/project_invoice/timesheet.py
index c020c0b1b9..6f60d481be 100644
--- a/modules/project_invoice/timesheet.py
+++ b/modules/project_invoice/timesheet.py
@@ -21,10 +21,11 @@ class Line(metaclass=PoolMeta):
return super().copy(records, default=default)
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for lines, values in zip(actions, actions):
- if (('duration' in values or 'work' in values)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'write':
+ if (values.keys() & {'duration', 'work'}
and any(l.invoice_line for l in lines)):
line = next(l for l in lines if l.invoice_line)
if 'duration' in values:
@@ -34,13 +35,9 @@ class Line(metaclass=PoolMeta):
raise AccessError(gettext(
f'project_invoice.{msg}',
line=line.rec_name))
- super().write(*args)
-
- @classmethod
- def delete(cls, lines):
- if any(r.invoice_line for r in lines):
- line = next((l for l in lines if l.invoice_line))
- raise AccessError(gettext(
- 'project_invoice.msg_invoiced_timesheet_line_delete',
- line=line.rec_name))
- super().delete(lines)
+ elif mode == 'delete':
+ if any(r.invoice_line for r in lines):
+ line = next((l for l in lines if l.invoice_line))
+ raise AccessError(gettext(
+ 'project_invoice.msg_invoiced_timesheet_line_delete',
+ line=line.rec_name))
diff --git a/modules/project_plan/allocation.py b/modules/project_plan/allocation.py
index 21fab6508e..11df998b74 100644
--- a/modules/project_plan/allocation.py
+++ b/modules/project_plan/allocation.py
@@ -1,7 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import ModelSQL, ModelView, fields
-from trytond.pool import Pool
class Allocation(ModelSQL, ModelView):
@@ -26,34 +25,21 @@ class Allocation(ModelSQL, ModelView):
return [('employee.rec_name',) + tuple(clause[1:])]
@classmethod
- def write(cls, *args):
- Work = Pool().get('project.work')
- super().write(*args)
-
- works = Work.search([
- ('allocations', 'in',
- [a.id for allocations in args[::2] for a in allocations]),
- ])
-
- for work in works:
- work.reset_leveling()
- for work in works:
- work.compute_dates()
+ def on_modification(cls, mode, allocations, field_names=None):
+ super().on_modification(mode, allocations, field_names=field_names)
+ if mode in {'create', 'write'}:
+ for allocation in allocations:
+ allocation.work.reset_leveling()
+ allocation.work.compute_dates()
@classmethod
- def create(cls, vlist):
- allocations = super().create(vlist)
- for allocation in allocations:
- allocation.work.reset_leveling()
- allocation.work.compute_dates()
- return allocations
-
- @classmethod
- def delete(cls, allocations):
+ def on_delete(cls, allocations):
+ callback = super().on_delete(allocations)
works = [a.work for a in allocations]
- super().delete(allocations)
- for work in works:
- work.reset_leveling()
- for work in works:
- work.compute_dates()
+ def replan():
+ for work in works:
+ work.reset_leveling()
+ work.compute_dates()
+ callback.append(replan)
+ return callback
diff --git a/modules/project_plan/work.py b/modules/project_plan/work.py
index 0c90c15e8a..dcaaf7863d 100644
--- a/modules/project_plan/work.py
+++ b/modules/project_plan/work.py
@@ -6,7 +6,7 @@ from functools import reduce
from heapq import heappop, heappush
from trytond.model import ModelSQL, fields, tree
-from trytond.pool import Pool, PoolMeta
+from trytond.pool import PoolMeta
from trytond.pyson import Eval
from trytond.wizard import StateTransition, Wizard
@@ -550,41 +550,33 @@ class Work(tree(parent='successors'), metaclass=PoolMeta):
self.parent.compute_dates()
@classmethod
- def write(cls, *args):
- super().write(*args)
-
- actions = iter(args)
- for works, values in zip(actions, actions):
- if 'effort' in values:
- for work in works:
+ def on_modification(cls, mode, works, field_names=None):
+ super().on_modification(mode, works, field_names=field_names)
+ if mode in {'create', 'write'}:
+ for work in works:
+ if not field_names or 'effort' in field_names:
work.reset_leveling()
- fields = ('constraint_start_time', 'constraint_finish_time',
- 'effort')
- if reduce(lambda x, y: x or y in values, fields, False):
- for work in works:
+ if not field_names or field_names & {
+ 'constraint_start_time', 'constraint_finish_time',
+ 'effort'}:
work.compute_dates()
@classmethod
- def create(cls, vlist):
- works = super().create(vlist)
- for work in works:
- work.reset_leveling()
- work.compute_dates()
- return works
-
- @classmethod
- def delete(cls, works):
+ def on_delete(cls, works):
+ callback = super().on_delete(works)
to_update = set()
for work in works:
if work.parent and work.parent not in works:
to_update.add(work.parent)
- to_update.update(c for c in work.parent.children
- if c not in works)
- super().delete(works)
+ to_update.update(
+ c for c in work.parent.children if c not in works)
- for work in to_update:
- work.reset_leveling()
- work.compute_dates()
+ def replan():
+ for work in to_update:
+ work.reset_leveling()
+ work.compute_dates()
+ callback.append(replan)
+ return callback
class PredecessorSuccessor(ModelSQL):
@@ -595,49 +587,35 @@ class PredecessorSuccessor(ModelSQL):
'project.work', "Successor", ondelete='CASCADE', required=True)
@classmethod
- def write(cls, *args):
- Work = Pool().get('project.work')
- super().write(*args)
-
- work_ids = [v for values in args[1::2]
- for k, v in values.values()
- if k in ('predecessor', 'successor')]
- works = Work.browse(work_ids)
- for work in works:
- work.reset_leveling()
- for work in works:
- work.compute_dates()
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if mode == 'create':
+ if not field_names or field_names & {
+ 'predecessor', 'successor', 'parent'}:
+ for record in records:
+ record.predecessor.reset_leveling()
+ record.successor.reset_leveling()
+ if record.predecessor.parent:
+ record.predecessor.parent.compute_dates()
@classmethod
- def delete(cls, pred_succs):
+ def on_delete(cls, records):
+ callback = super().on_delete(records)
works = set()
parents = set()
- for pred_succ in pred_succs:
- works.update((pred_succ.predecessor,
- pred_succ.successor))
+ for record in records:
+ works.add(record.predecessor)
+ works.add(record.successor)
+ if record.predecessor.parent:
+ parents.add(record.predecessor.parent)
- if pred_succ.predecessor.parent:
- parents.add(pred_succ.predecessor.parent)
-
- super().delete(pred_succs)
-
- for work in works:
- work.reset_leveling()
-
- for parent in parents:
- parent.compute_dates()
-
- @classmethod
- def create(cls, vlist):
- pred_succs = super().create(vlist)
-
- for pred_succ in pred_succs:
- pred_succ.predecessor.reset_leveling()
- pred_succ.successor.reset_leveling()
-
- if pred_succ.predecessor.parent:
- pred_succ.predecessor.parent.compute_dates()
- return pred_succs
+ def replan():
+ for work in works:
+ work.reset_leveling()
+ for parent in parents:
+ parent.compute_dates()
+ callback.append(replan)
+ return callback
class Leveling(Wizard):
diff --git a/modules/purchase/invoice.py b/modules/purchase/invoice.py
index 82ec2bd644..9fd260a359 100644
--- a/modules/purchase/invoice.py
+++ b/modules/purchase/invoice.py
@@ -21,11 +21,12 @@ def process_purchase(func):
with without_check_access():
purchases = set(
p for i in cls.browse(invoices) for p in i.purchases)
- func(cls, invoices)
+ result = func(cls, invoices)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
+ return result
return wrapper
@@ -68,8 +69,8 @@ class Invoice(metaclass=PoolMeta):
@classmethod
@process_purchase
- def delete(cls, invoices):
- super().delete(invoices)
+ def on_delete(cls, invoices):
+ return super().on_delete(invoices)
@classmethod
@ModelView.button
@@ -160,7 +161,7 @@ class InvoiceLine(metaclass=PoolMeta):
return models
@classmethod
- def delete(cls, lines):
+ def on_delete(cls, lines):
pool = Pool()
Purchase = pool.get('purchase.purchase')
transaction = Transaction()
@@ -169,8 +170,8 @@ class InvoiceLine(metaclass=PoolMeta):
invoices = (l.invoice for l in cls.browse(lines)
if l.type == 'line' and l.invoice)
purchases = set(p for i in invoices for p in i.purchases)
- super().delete(lines)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
+ return super().on_delete(lines)
diff --git a/modules/purchase/product.py b/modules/purchase/product.py
index 6e3dabb4e8..366d2bffc4 100644
--- a/modules/purchase/product.py
+++ b/modules/purchase/product.py
@@ -65,13 +65,14 @@ class Template(metaclass=PoolMeta):
yield product_supplier
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, templates, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for templates, values in zip(actions, actions):
- if not values.get("purchase_uom"):
- continue
+
+ super().check_modification(
+ mode, templates, values=values, external=external)
+
+ if mode == 'write' and values.get("purchase_uom"):
for template in templates:
if not template.purchase_uom:
continue
@@ -84,7 +85,6 @@ class Template(metaclass=PoolMeta):
if Warning.check(name):
raise PurchaseUOMWarning(
name, gettext('purchase.msg_change_purchase_uom'))
- super().write(*args)
@classmethod
def copy(cls, templates, default=None):
diff --git a/modules/purchase/purchase.py b/modules/purchase/purchase.py
index e58b7b4afc..b9b25912bc 100644
--- a/modules/purchase/purchase.py
+++ b/modules/purchase/purchase.py
@@ -895,15 +895,15 @@ class Purchase(
if l.moves_progress is not None))))
@classmethod
- def delete(cls, purchases):
- # Cancel before delete
- cls.cancel(purchases)
- for purchase in purchases:
- if purchase.state != 'cancelled':
- raise AccessError(
- gettext('purchase.msg_purchase_delete_cancel',
- purchase=purchase.rec_name))
- super().delete(purchases)
+ def check_modification(cls, mode, purchases, values=None, external=False):
+ super().check_modification(
+ mode, purchases, values=values, external=external)
+ if mode == 'delete':
+ for purchase in purchases:
+ if purchase.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'purchase.msg_purchase_delete_cancel',
+ purchase=purchase.rec_name))
@classmethod
@ModelView.button
@@ -2105,26 +2105,22 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
]
@classmethod
- def create(cls, vlist):
- pool = Pool()
- Purchase = pool.get('purchase.purchase')
- purchase_ids = filter(None, {v.get('purchase') for v in vlist})
- for purchase in Purchase.browse(list(purchase_ids)):
- if purchase.state != 'draft':
- raise AccessError(
- gettext('purchase.msg_purchase_line_create_draft',
- purchase=purchase.rec_name))
- return super().create(vlist)
-
- @classmethod
- def delete(cls, lines):
- for line in lines:
- if line.purchase_state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext('purchase.msg_purchase_line_delete_cancel_draft',
- line=line.rec_name,
- purchase=line.purchase.rec_name))
- super().delete(lines)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'create':
+ for line in lines:
+ if line.purchase.state != 'draft':
+ raise AccessError(gettext(
+ 'purchase.msg_purchase_line_create_draft',
+ purchase=line.purchase.rec_name))
+ elif mode == 'delete':
+ for line in lines:
+ if line.purchase_state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'purchase.msg_purchase_line_delete_cancel_draft',
+ line=line.rec_name,
+ purchase=line.purchase.rec_name))
@classmethod
def copy(cls, lines, default=None):
diff --git a/modules/purchase/stock.py b/modules/purchase/stock.py
index 4991eb206f..e38a404473 100644
--- a/modules/purchase/stock.py
+++ b/modules/purchase/stock.py
@@ -22,11 +22,12 @@ def process_purchase(moves_field):
with without_check_access():
purchases = set(m.purchase for s in cls.browse(shipments)
for m in getattr(s, moves_field) if m.purchase)
- func(cls, shipments)
+ result = func(cls, shipments)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
+ return result
return wrapper
return _process_purchase
@@ -124,11 +125,11 @@ def process_purchase_move(without_shipment=False):
if without_shipment:
p_moves = [m for m in p_moves if not m.shipment]
purchases = set(m.purchase for m in p_moves if m.purchase)
- func(cls, moves)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
+ return func(cls, moves)
return wrapper
return _process_purchase_move
@@ -250,8 +251,8 @@ class Move(metaclass=PoolMeta):
@classmethod
@process_purchase_move()
- def delete(cls, moves):
- super().delete(moves)
+ def on_delete(cls, moves):
+ return super().on_delete(moves)
class Location(metaclass=PoolMeta):
diff --git a/modules/purchase_request/purchase.py b/modules/purchase_request/purchase.py
index b5d0ae6fd8..ec85e1faac 100644
--- a/modules/purchase_request/purchase.py
+++ b/modules/purchase_request/purchase.py
@@ -19,13 +19,14 @@ def process_request(func):
def wrapper(cls, purchases):
pool = Pool()
Request = pool.get('purchase.request')
- func(cls, purchases)
+ result = func(cls, purchases)
with without_check_access():
requests = [
r for p in cls.browse(purchases)
for l in p.lines
for r in l.requests]
Request.update_state(requests)
+ return result
return wrapper
@@ -33,19 +34,16 @@ class Purchase(metaclass=PoolMeta):
__name__ = 'purchase.purchase'
@classmethod
- def delete(cls, purchases):
- cls.check_delete_purchase_request(purchases)
- super().delete(purchases)
-
- @classmethod
- @without_check_access
- def check_delete_purchase_request(cls, purchases):
- for purchase in purchases:
- for line in purchase.lines:
- if line.requests:
- raise AccessError(
- gettext('purchase_request.msg_purchase_delete_request',
- purchase=purchase.rec_name))
+ def check_modification(cls, mode, purchases, values=None, external=False):
+ super().check_modification(
+ mode, purchases, values=values, external=external)
+ if mode == 'delete':
+ for purchase in purchases:
+ for line in purchase.lines:
+ if line.requests:
+ raise AccessError(gettext(
+ 'purchase_request.msg_purchase_delete_request',
+ purchase=purchase.rec_name))
@classmethod
@ModelView.button
@@ -103,11 +101,12 @@ class Line(metaclass=PoolMeta):
return super().copy(lines, default=default)
@classmethod
- def delete(cls, lines):
+ def on_delete(cls, lines):
pool = Pool()
Request = pool.get('purchase.request')
- with without_check_access():
- requests = [r for l in cls.browse(lines) for r in l.requests]
- super().delete(lines)
- with without_check_access():
- Request.update_state(requests)
+ callback = super().on_delete(lines)
+ requests = {r for l in lines for r in l.requests}
+ if requests:
+ requests = Request.browse(requests)
+ callback.append(lambda: Request.update_state(requests))
+ return callback
diff --git a/modules/purchase_request/purchase_request.py b/modules/purchase_request/purchase_request.py
index 8012094f53..44f0977b34 100644
--- a/modules/purchase_request/purchase_request.py
+++ b/modules/purchase_request/purchase_request.py
@@ -268,21 +268,19 @@ class PurchaseRequest(ModelSQL, ModelView):
]
@classmethod
- def create(cls, vlist):
- transaction = Transaction()
- if transaction.user and transaction.check_access:
- raise AccessError(
- gettext('purchase_request.msg_request_no_create'))
- return super().create(vlist)
-
- @classmethod
- def delete(cls, requests):
- for request in requests:
- if request.purchase_line:
+ def check_modification(cls, mode, requests, values=None, external=False):
+ super().check_modification(
+ mode, requests, values=values, external=external)
+ if mode == 'create':
+ if external:
raise AccessError(
- gettext('purchase_request.msg_request_delete_purchased',
- request=request.rec_name))
- super().delete(requests)
+ gettext('purchase_request.msg_request_no_create'))
+ elif mode == 'delete':
+ for request in requests:
+ if request.purchase_line:
+ raise AccessError(gettext(
+ 'purchase_request.msg_request_delete_purchased',
+ request=request.rec_name))
@classmethod
def copy(cls, requests, default=None):
diff --git a/modules/purchase_request_quotation/purchase.py b/modules/purchase_request_quotation/purchase.py
index d7289145c7..9af3862c04 100644
--- a/modules/purchase_request_quotation/purchase.py
+++ b/modules/purchase_request_quotation/purchase.py
@@ -27,9 +27,10 @@ def process_request(func):
def wrapper(cls, quotations):
pool = Pool()
Request = pool.get('purchase.request')
- func(cls, quotations)
+ result = func(cls, quotations)
requests = [l.request for q in quotations for l in q.lines]
Request.update_state(requests)
+ return result
return wrapper
@@ -411,12 +412,15 @@ class QuotationLine(ModelSQL, ModelView):
return domain
@classmethod
- def delete(cls, quotationlines):
+ def on_delete(cls, lines):
pool = Pool()
Request = pool.get('purchase.request')
- requests = [l.request for l in quotationlines]
- super().delete(quotationlines)
- Request.update_state(requests)
+ callback = super().on_delete(lines)
+ requests = {l.request for l in lines}
+ if requests:
+ requests = Request.browse(requests)
+ callback.append(lambda: Request.update_state(requests))
+ return callback
def get_product(self, name):
if self.request and self.request.product:
diff --git a/modules/purchase_requisition/purchase.py b/modules/purchase_requisition/purchase.py
index 7eb47a2607..b8273d7491 100644
--- a/modules/purchase_requisition/purchase.py
+++ b/modules/purchase_requisition/purchase.py
@@ -283,30 +283,31 @@ class PurchaseRequisition(Workflow, ModelSQL, ModelView):
]
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Config = pool.get('purchase.configuration')
-
- config = Config(1)
- vlist = [v.copy() for v in vlist]
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'purchase_requisition_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ Configuration = pool.get('purchase.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'purchase_requisition_sequence',
+ company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
- def delete(cls, requisitions):
- # Cancel before delete
- cls.cancel(requisitions)
- for requisition in requisitions:
- if requisition.state != 'cancelled':
- raise AccessError(
- gettext('purchase_requisition.msg_delete_cancel',
- requisition=requisition.rec_name))
- super().delete(requisitions)
+ def check_modification(
+ cls, mode, requisitions, values=None, external=False):
+ super().check_modification(
+ mode, requisitions, values=values, external=external)
+ if mode == 'delete':
+ for requisition in requisitions:
+ if requisition.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'purchase_requisition.msg_delete_cancel',
+ requisition=requisition.rec_name))
def check_for_waiting(self):
if not self.warehouse:
diff --git a/modules/quality/quality.py b/modules/quality/quality.py
index eb699dd27f..550818b5de 100644
--- a/modules/quality/quality.py
+++ b/modules/quality/quality.py
@@ -563,19 +563,30 @@ class Inspection(Workflow, ModelSQL, ModelView):
origin=self)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('quality.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'inspection_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
- config = Configuration(1)
- default_company = cls.default_company()
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if not values.get('number'):
- values['number'] = config.get_multivalue(
- 'inspection_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ @classmethod
+ def check_modification(
+ cls, mode, inspections, values=None, external=False):
+ super().check_modification(
+ mode, inspections, values=values, external=external)
+ if mode == 'delete':
+ for inspection in inspections:
+ if inspection.state != 'pending':
+ raise AccessError(gettext(
+ 'quality.msg_inspection_delete_non_pending',
+ inspection=inspection.rec_name))
@classmethod
def copy(cls, inspections, default=None):
@@ -587,15 +598,6 @@ class Inspection(Workflow, ModelSQL, ModelView):
default.setdefault('failed_by')
return super().copy(inspections, default=default)
- @classmethod
- def delete(cls, inspections):
- for inspection in inspections:
- if inspection.state != 'pending':
- raise AccessError(
- gettext('quality.msg_inspection_delete_non_pending',
- inspection=inspection.rec_name))
- super().delete(inspections)
-
@classmethod
def get_from_control(cls, control, origin=None):
return cls(control=control, origin=origin)
@@ -721,19 +723,18 @@ class Alert(Workflow, ModelSQL, ModelView):
pass
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('quality.configuration')
-
- config = Configuration(1)
- default_company = cls.default_company()
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if not values.get('number'):
- values['number'] = config.get_multivalue(
- 'alert_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'alert_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, inspections, default=None):
diff --git a/modules/sale/invoice.py b/modules/sale/invoice.py
index c649ff02e7..d3fe395218 100644
--- a/modules/sale/invoice.py
+++ b/modules/sale/invoice.py
@@ -19,11 +19,12 @@ def process_sale(func):
context = transaction.context
with without_check_access():
sales = set(s for i in cls.browse(invoices) for s in i.sales)
- func(cls, invoices)
+ result = func(cls, invoices)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return result
return wrapper
@@ -77,8 +78,8 @@ class Invoice(metaclass=PoolMeta):
@classmethod
@process_sale
- def delete(cls, invoices):
- super().delete(invoices)
+ def on_delete(cls, invoices):
+ return super().on_delete(invoices)
@classmethod
@process_sale
@@ -158,7 +159,7 @@ class Line(metaclass=PoolMeta):
return models
@classmethod
- def delete(cls, lines):
+ def on_delete(cls, lines):
pool = Pool()
Sale = pool.get('sale.sale')
transaction = Transaction()
@@ -167,8 +168,8 @@ class Line(metaclass=PoolMeta):
invoices = (l.invoice for l in cls.browse(lines)
if l.type == 'line' and l.invoice)
sales = set(s for i in invoices for s in i.sales)
- super().delete(lines)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return super().on_delete(lines)
diff --git a/modules/sale/sale.py b/modules/sale/sale.py
index 8b932361e8..a913de616e 100644
--- a/modules/sale/sale.py
+++ b/modules/sale/sale.py
@@ -1096,15 +1096,15 @@ class Sale(
if l.moves_progress is not None))))
@classmethod
- def delete(cls, sales):
- # Cancel before delete
- cls.cancel(sales)
- for sale in sales:
- if sale.state != 'cancelled':
- raise AccessError(
- gettext('sale.msg_sale_delete_cancel',
- sale=sale.rec_name))
- super().delete(sales)
+ def check_modification(cls, mode, sales, values=None, external=False):
+ super().check_modification(
+ mode, sales, values=values, external=external)
+ if mode == 'delete':
+ for sale in sales:
+ if sale.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'sale.msg_sale_delete_cancel',
+ sale=sale.rec_name))
@classmethod
@ModelView.button
@@ -2240,26 +2240,22 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
'spell', Eval('_parent_sale', {}).get('party_lang'))]
@classmethod
- def create(cls, vlist):
- pool = Pool()
- Sale = pool.get('sale.sale')
- sale_ids = filter(None, {v.get('sale') for v in vlist})
- for sale in Sale.browse(list(sale_ids)):
- if sale.state != 'draft':
- raise AccessError(
- gettext('sale.msg_sale_line_create_draft',
- sale=sale.rec_name))
- return super().create(vlist)
-
- @classmethod
- def delete(cls, lines):
- for line in lines:
- if line.sale_state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext('sale.msg_sale_line_delete_cancel_draft',
- line=line.rec_name,
- sale=line.sale.rec_name))
- super().delete(lines)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'create':
+ for line in lines:
+ if line.sale.state != 'draft':
+ raise AccessError(gettext(
+ 'sale.msg_sale_line_create_draft',
+ sale=line.sale.rec_name))
+ elif mode == 'delete':
+ for line in lines:
+ if line.sale_state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'sale.msg_sale_line_delete_cancel_draft',
+ line=line.rec_name,
+ sale=line.sale.rec_name))
@classmethod
def copy(cls, lines, default=None):
diff --git a/modules/sale/stock.py b/modules/sale/stock.py
index b30e5031aa..c7b38af286 100644
--- a/modules/sale/stock.py
+++ b/modules/sale/stock.py
@@ -22,11 +22,12 @@ def process_sale(moves_field):
with without_check_access():
sales = set(m.sale for s in cls.browse(shipments)
for m in getattr(s, moves_field) if m.sale)
- func(cls, shipments)
+ result = func(cls, shipments)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return result
return wrapper
return _process_sale
@@ -112,11 +113,12 @@ def process_sale_move(func):
context = transaction.context
with without_check_access():
sales = set(m.sale for m in cls.browse(moves) if m.sale)
- func(cls, moves)
+ result = func(cls, moves)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return result
return wrapper
@@ -230,5 +232,5 @@ class Move(metaclass=PoolMeta):
@classmethod
@process_sale_move
- def delete(cls, moves):
- super().delete(moves)
+ def on_delete(cls, moves):
+ return super().on_delete(moves)
diff --git a/modules/sale_complaint/complaint.py b/modules/sale_complaint/complaint.py
index 55580bcc28..a9cb31f9a1 100644
--- a/modules/sale_complaint/complaint.py
+++ b/modules/sale_complaint/complaint.py
@@ -279,19 +279,29 @@ class Complaint(Workflow, ModelSQL, ModelView):
]
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('sale.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'complaint_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
- config = Configuration(1)
- vlist = [v.copy() for v in vlist]
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'complaint_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ @classmethod
+ def check_modification(cls, mode, complaints, values=None, external=False):
+ super().check_modification(
+ mode, complaints, values=values, external=external)
+ if mode == 'delete':
+ for complaint in complaints:
+ if complaint.state != 'draft':
+ raise AccessError(gettext(
+ 'sale_complaint.msg_complaint_delete_draft',
+ complaint=complaint.rec_name))
@classmethod
def copy(cls, complaints, default=None):
@@ -306,15 +316,6 @@ class Complaint(Workflow, ModelSQL, ModelView):
default.setdefault('cancelled_by')
return super().copy(complaints, default=default)
- @classmethod
- def delete(cls, complaints):
- for complaint in complaints:
- if complaint.state != 'draft':
- raise AccessError(
- gettext('sale_complaint.msg_complaint_delete_draft',
- complaint=complaint.rec_name))
- super().delete(complaints)
-
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@@ -721,13 +722,15 @@ class Action(ModelSQL, ModelView):
return credit_note
@classmethod
- def delete(cls, actions):
- for action in actions:
- if action.result:
- raise AccessError(
- gettext('sale_complaint.msg_action_delete_result',
- action=action.rec_name))
- super().delete(actions)
+ def check_modification(cls, mode, actions, values=None, external=False):
+ super().check_modification(
+ mode, actions, values=values, external=external)
+ if mode == 'delete':
+ for action in actions:
+ if action.result:
+ raise AccessError(gettext(
+ 'sale_complaint.msg_action_delete_result',
+ action=action.rec_name))
class _Action_Line:
diff --git a/modules/sale_opportunity/opportunity.py b/modules/sale_opportunity/opportunity.py
index 4c55b75b97..750d349829 100644
--- a/modules/sale_opportunity/opportunity.py
+++ b/modules/sale_opportunity/opportunity.py
@@ -300,19 +300,30 @@ class SaleOpportunity(
}
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Config = pool.get('sale.configuration')
+ Configuration = pool.get('sale.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'sale_opportunity_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
- config = Config(1)
- vlist = [x.copy() for x in vlist]
- default_company = cls.default_company()
- for vals in vlist:
- if vals.get('number') is None:
- vals['number'] = config.get_multivalue(
- 'sale_opportunity_sequence',
- company=vals.get('company', default_company)).get()
- return super().create(vlist)
+ @classmethod
+ def check_modification(
+ cls, mode, opportunities, values=None, external=False):
+ super().check_modification(
+ mode, opportunities, values=values, external=external)
+ if mode == 'delete':
+ for opportunity in opportunities:
+ if opportunity.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'sale_opportunity.msg_opportunity_delete_cancel',
+ opportunity=opportunity.rec_name))
@classmethod
def copy(cls, opportunities, default=None):
@@ -371,17 +382,6 @@ class SaleOpportunity(
sale.lines = sale_lines
return sale
- @classmethod
- def delete(cls, opportunities):
- # Cancel before delete
- cls.cancel(opportunities)
- for opportunity in opportunities:
- if opportunity.state != 'cancelled':
- raise AccessError(
- gettext('sale_opportunity.msg_opportunity_delete_cancel',
- opportunity=opportunity.rec_name))
- super().delete(opportunities)
-
@classmethod
@ModelView.button
@Workflow.transition('lead')
diff --git a/modules/sale_opportunity/sale.py b/modules/sale_opportunity/sale.py
index 273a8ce45f..c025db69d9 100644
--- a/modules/sale_opportunity/sale.py
+++ b/modules/sale_opportunity/sale.py
@@ -17,9 +17,10 @@ def process_opportunity(func):
opportunities = Opportunity.browse(
set(s.origin for s in cls.browse(sales)
if isinstance(s.origin, Opportunity)))
- func(cls, sales)
+ result = func(cls, sales)
with without_check_access():
Opportunity.process(opportunities)
+ return result
return wrapper
@@ -31,28 +32,24 @@ class Sale(metaclass=PoolMeta):
return super()._get_origin() + ['sale.opportunity']
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, sales, values=None, external=False):
pool = Pool()
Opportunity = pool.get('sale.opportunity')
- actions = iter(args)
- for sales, values in zip(actions, actions):
- if 'origin' in values:
- origin = values['origin']
- if origin and not isinstance(origin, str):
- origin = '%s,%s' % tuple(origin)
- for sale in sales:
- if (isinstance(sale.origin, Opportunity)
- and str(sale.origin) != origin):
- raise AccessError(gettext(
- 'sale_opportunity'
- '.msg_modify_origin_opportunity',
- sale=sale.rec_name))
- super().write(*args)
- @classmethod
- @process_opportunity
- def delete(cls, sales):
- super().delete(sales)
+ super().check_modification(
+ mode, sales, values=values, external=external)
+
+ if mode == 'write' and 'origin' in values:
+ origin = values['origin']
+ if origin and not isinstance(origin, str):
+ origin = '%s,%s' % tuple(origin)
+ for sale in sales:
+ if (isinstance(sale.origin, Opportunity)
+ and str(sale.origin) != origin):
+ raise AccessError(gettext(
+ 'sale_opportunity'
+ '.msg_modify_origin_opportunity',
+ sale=sale.rec_name))
@classmethod
@process_opportunity
diff --git a/modules/sale_payment/account.py b/modules/sale_payment/account.py
index 74513a7635..cfe633b688 100644
--- a/modules/sale_payment/account.py
+++ b/modules/sale_payment/account.py
@@ -70,18 +70,20 @@ class Payment(metaclass=PoolMeta):
self.currency = currency
@classmethod
- def create(cls, vlist):
- payments = super().create(vlist)
- cls.trigger_authorized([p for p in payments if p.is_authorized])
- return payments
+ def on_modification(cls, mode, payments, field_names=None):
+ super().on_modification(mode, payments, field_names=field_names)
+ if mode == 'create':
+ cls.trigger_authorized([p for p in payments if p.is_authorized])
@classmethod
- def write(cls, *args):
- payments = sum(args[0:None:2], [])
- unauthorized = {p for p in payments if not p.is_authorized}
- super().write(*args)
- authorized = {p for p in payments if p.is_authorized}
- cls.trigger_authorized(cls.browse(unauthorized & authorized))
+ def on_write(cls, payments, values):
+ callback = super().on_write(payments, values)
+ if unauthorized := {p for p in payments if not p.is_authorized}:
+ def trigger():
+ authorized = {p for p in payments if p.is_authorized}
+ cls.trigger_authorized(cls.browse(unauthorized & authorized))
+ callback.append(trigger)
+ return callback
@property
def is_authorized(self): # TODO: move to account_payment
diff --git a/modules/sale_point/sale.py b/modules/sale_point/sale.py
index a899565396..14be9265e8 100644
--- a/modules/sale_point/sale.py
+++ b/modules/sale_point/sale.py
@@ -72,24 +72,24 @@ class POS(ModelSQL, ModelView):
return True
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, points, values=None, external=False):
pool = Pool()
Sale = pool.get('sale.point.sale')
- transaction = Transaction()
- if transaction.user and transaction.check_access:
- actions = iter(args)
- for points, values in zip(actions, actions):
- if 'tax_included' in values:
- for sub_points in grouped_slice(points):
- if Sale.search([
- ('point', 'in',
- list(map(int, sub_points))),
- ],
- limit=1, order=[]):
- raise AccessError(gettext(
- 'sale_point.msg_point_change_tax_included')
- )
- super().write(*args)
+
+ super().check_modification(
+ mode, points, values=values, external=external)
+
+ if mode == 'write':
+ if 'tax_included' in values:
+ for sub_points in grouped_slice(points):
+ if Sale.search([
+ ('point', 'in',
+ list(map(int, sub_points))),
+ ],
+ limit=1, order=[]):
+ raise AccessError(gettext(
+ 'sale_point.'
+ 'msg_point_change_tax_included'))
class POSSale(Workflow, ModelSQL, ModelView, TaxableMixin):
@@ -276,13 +276,15 @@ class POSSale(Workflow, ModelSQL, ModelView, TaxableMixin):
return super().copy(sales, default=default)
@classmethod
- def delete(cls, sales):
- for sale in sales:
- if sale.number:
- raise AccessError(
- gettext('sale_point.msg_sale_delete_numbered',
- sale=sale.rec_name))
- super().delete(sales)
+ def check_modification(cls, mode, sales, values=None, external=False):
+ super().check_modification(
+ mode, sales, values=values, external=external)
+ if mode == 'delete':
+ for sale in sales:
+ if sale.number:
+ raise AccessError(gettext(
+ 'sale_point.msg_sale_delete_numbered',
+ sale=sale.rec_name))
@classmethod
def validate(cls, sales):
@@ -909,14 +911,21 @@ class POSCashSession(Workflow, ModelSQL, ModelView):
session.currency)))
@classmethod
- def delete(cls, sessions):
- cls.open(sessions)
- for session in sessions:
- if session.state != 'open':
- raise AccessError(
- gettext('sale_point.msg_cash_session_delete_open',
- session=session.rec_name))
- super().delete(sessions)
+ def on_modification(cls, mode, sessions, field_names=None):
+ super().on_modification(mode, sessions, field_names=field_names)
+ if mode == 'delete':
+ cls.open(sessions)
+
+ @classmethod
+ def check_modification(cls, mode, sessions, values=None, external=False):
+ super().check_modification(
+ mode, sessions, values=values, external=external)
+ if mode == 'delete':
+ for session in sessions:
+ if session.state != 'open':
+ raise AccessError(gettext(
+ 'sale_point.msg_cash_session_delete_open',
+ session=session.rec_name))
@classmethod
@ModelView.button
@@ -1054,34 +1063,36 @@ class POSPayment(ModelSQL, ModelView):
return self.method.cash
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Method = pool.get('sale.point.payment.method')
Sale = pool.get('sale.point.sale')
Session = pool.get('sale.point.cash.session')
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
if values.get('method') and values.get('sale'):
method = Method(values['method'])
if method.cash:
sale = Sale(values['sale'])
values['session'] = Session.get_current(sale.point)
- return super().create(vlist)
+ return values
@classmethod
- def delete(cls, payments):
- for payment in payments:
- if payment.sale.state != 'open':
- raise AccessError(
- gettext('sale_point.msg_payment_delete_sale_open',
- payment=payment.rec_name,
- sale=payment.sale.rec_name))
- if payment.session and payment.session.state != 'open':
- raise AccessError(
- gettext('sale_point.msg_payment_delete_session_open',
- payment=payment.rec_name,
- session=payment.session.rec_name))
- super().delete(payments)
+ def check_modification(cls, mode, payments, values=None, external=False):
+ super().check_modification(
+ mode, payments, values=values, external=external)
+ if mode == 'delete':
+ for payment in payments:
+ if payment.sale.state != 'open':
+ raise AccessError(gettext(
+ 'sale_point.msg_payment_delete_sale_open',
+ payment=payment.rec_name,
+ sale=payment.sale.rec_name))
+ if payment.session and payment.session.state != 'open':
+ raise AccessError(gettext(
+ 'sale_point.msg_payment_delete_session_open',
+ payment=payment.rec_name,
+ session=payment.session.rec_name))
def get_account_move_lines(self):
"Return account move lines"
@@ -1258,18 +1269,20 @@ class POSCashTransfer(Workflow, ModelSQL, ModelView):
return self.point.company.currency if self.point else None
@classmethod
- def delete(cls, transfers):
- for transfer in transfers:
- if transfer.state == 'posted':
- raise AccessError(
- gettext('sale_point.msg_transfer_delete_posted',
- transfer=transfer.rec_name))
- if transfer.session.state != 'open':
- raise AccessError(
- gettext('sale_point.msg_transfer_delete_session_open',
- transfer=transfer.rec_name,
- session=transfer.session.rec_name))
- super().delete(transfers)
+ def check_modification(cls, mode, transfers, values=None, external=False):
+ super().check_modification(
+ mode, transfers, values=values, external=external)
+ if mode == 'delete':
+ for transfer in transfers:
+ if transfer.state == 'posted':
+ raise AccessError(gettext(
+ 'sale_point.msg_transfer_delete_posted',
+ transfer=transfer.rec_name))
+ if transfer.session.state != 'open':
+ raise AccessError(gettext(
+ 'sale_point.msg_transfer_delete_session_open',
+ transfer=transfer.rec_name,
+ session=transfer.session.rec_name))
@classmethod
@ModelView.button
@@ -1320,16 +1333,16 @@ class POSCashTransfer(Workflow, ModelSQL, ModelView):
return move
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Point = pool.get('sale.point')
Session = pool.get('sale.point.cash.session')
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if values.get('point') and not values.get('session'):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
+ if values.get('point') is not None and not values.get('session'):
point = Point(values['point'])
values['session'] = Session.get_current(point)
- return super().create(vlist)
+ return values
class POSCashTransferType(ModelSQL, ModelView):
diff --git a/modules/sale_promotion_coupon/sale.py b/modules/sale_promotion_coupon/sale.py
index 8fa2a0bf20..242efea6aa 100644
--- a/modules/sale_promotion_coupon/sale.py
+++ b/modules/sale_promotion_coupon/sale.py
@@ -45,16 +45,12 @@ class Promotion(metaclass=PoolMeta):
return super().match(pattern)
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, promotions, field_names=None):
pool = Pool()
Number = pool.get('sale.promotion.coupon.number')
- super().write(*args)
- actions = iter(args)
- numbers = []
- for promotions, values in zip(actions, actions):
- if {'start_date', 'end_date'} & values.keys():
- numbers.extend(cls._update_coupon_number_dates(promotions))
- Number.save(numbers)
+ super().on_modification(mode, promotions, field_names=field_names)
+ if mode == 'write' and field_names & {'start_date', 'end_date'}:
+ Number.save(list(cls._update_coupon_number_dates(promotions)))
@classmethod
def _update_coupon_number_dates(cls, promotions):
diff --git a/modules/sale_subscription/subscription.py b/modules/sale_subscription/subscription.py
index 382b45c87d..77dac71e41 100644
--- a/modules/sale_subscription/subscription.py
+++ b/modules/sale_subscription/subscription.py
@@ -871,25 +871,17 @@ class LineConsumption(ModelSQL, ModelView):
return super().copy(consumptions, default=default)
@classmethod
- def write(cls, *args):
- for consumptions in args[::2]:
+ def check_modification(
+ cls, mode, consumptions, values=None, external=False):
+ super().check_modification(
+ mode, consumptions, values=values, external=external)
+ if mode in {'write', 'delete'}:
for consumption in consumptions:
if consumption.invoice_line:
- raise AccessError(
- gettext('sale_subscription'
- '.msg_consumption_modify_invoiced',
+ raise AccessError(gettext(
+ 'sale_subscription.'
+ 'msg_consumption_modify_invoiced',
consumption=consumption.rec_name))
- super().write(*args)
-
- @classmethod
- def delete(cls, consumptions):
- for consumption in consumptions:
- if consumption.invoice_line:
- raise AccessError(
- gettext('sale_subscription'
- '.msg_consumption_modify_invoiced',
- consumption=consumption.rec_name))
- super().delete(consumptions)
@classmethod
def get_invoice_lines(cls, consumptions, invoice):
diff --git a/modules/sale_supply/purchase.py b/modules/sale_supply/purchase.py
index 2b7d11ed37..9b793b8816 100644
--- a/modules/sale_supply/purchase.py
+++ b/modules/sale_supply/purchase.py
@@ -38,7 +38,7 @@ class Request(metaclass=PoolMeta):
return super().copy(requests, default=default)
@classmethod
- def delete(cls, requests):
+ def on_delete(cls, requests):
pool = Pool()
Sale = pool.get('sale.sale')
SaleLine = pool.get('sale.line')
@@ -54,9 +54,8 @@ class Request(metaclass=PoolMeta):
'purchase_request': None,
})
- super().delete(requests)
-
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return super().on_delete(requests)
diff --git a/modules/sale_supply_drop_shipment/message.xml b/modules/sale_supply_drop_shipment/message.xml
index e77aa6d7fd..2f7e145735 100644
--- a/modules/sale_supply_drop_shipment/message.xml
+++ b/modules/sale_supply_drop_shipment/message.xml
@@ -9,9 +9,6 @@ this repository contains the full copyright notices and license terms. -->
You cannot reset move "%(move)s" to draft because it was generated by a sale or a purchase.
-
- To delete drop shipment "%(shipment)s" you must cancel it.
-
To split move from drop shipment, you must use the split wizard.
diff --git a/modules/sale_supply_drop_shipment/stock.py b/modules/sale_supply_drop_shipment/stock.py
index b579b50f07..ec9ac35b3d 100644
--- a/modules/sale_supply_drop_shipment/stock.py
+++ b/modules/sale_supply_drop_shipment/stock.py
@@ -243,25 +243,18 @@ class ShipmentDrop(
Move.write(*to_write)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Config = pool.get('stock.configuration')
-
- vlist = [x.copy() for x in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- values['number'] = config.get_multivalue(
- 'shipment_drop_sequence',
- company=values.get('company', default_company)).get()
- shipments = super().create(vlist)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
+ Configuration = pool.get('stock.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'shipment_drop_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, shipments, default=None):
@@ -274,22 +267,6 @@ class ShipmentDrop(
default.setdefault('customer_moves', None)
return super().copy(shipments, default=default)
- @classmethod
- def delete(cls, shipments):
- pool = Pool()
- Move = pool.get('stock.move')
-
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('sale_supply_drop_shipment'
- '.msg_drop_shipment_delete_cancel',
- shipment=shipment.rec_name,
- ))
- Move.delete([m for s in shipments for m in s.supplier_moves])
- super().delete(shipments)
-
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
diff --git a/modules/sale_supply_production/production.py b/modules/sale_supply_production/production.py
index 1d59d7eb9e..8966526207 100644
--- a/modules/sale_supply_production/production.py
+++ b/modules/sale_supply_production/production.py
@@ -26,11 +26,12 @@ def process_sale_supply(func):
sales.update([s.id for s in Sale.search([
('lines.productions', 'in', ids),
])])
- func(cls, productions)
+ result = func(cls, productions)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
+ return result
return wrapper
@@ -43,8 +44,8 @@ class Production(metaclass=PoolMeta):
@classmethod
@process_sale_supply
- def delete(cls, productions):
- super().delete(productions)
+ def on_delete(cls, productions):
+ return super().on_delete(productions)
@classmethod
@ModelView.button
diff --git a/modules/stock/inventory.py b/modules/stock/inventory.py
index e2d833d8ac..fb8fddfac8 100644
--- a/modules/stock/inventory.py
+++ b/modules/stock/inventory.py
@@ -147,17 +147,6 @@ class Inventory(Workflow, ModelSQL, ModelView):
('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
]
- @classmethod
- def delete(cls, inventories):
- # Cancel before delete
- cls.cancel(inventories)
- for inventory in inventories:
- if inventory.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_inventory_delete_cancel',
- inventory=inventory.rec_name))
- super().delete(inventories)
-
@classmethod
@ModelView.button
@Workflow.transition('done')
@@ -215,26 +204,36 @@ class Inventory(Workflow, ModelSQL, ModelView):
Line.cancel_move([l for i in inventories for l in i.lines])
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Configuration = pool.get('stock.configuration')
- config = Configuration(1)
- vlist = [x.copy() for x in vlist]
- default_company = cls.default_company()
- for values in vlist:
- if values.get('number') is None:
- values['number'] = config.get_multivalue(
- 'inventory_sequence',
- company=values.get('company', default_company)).get()
- inventories = super().create(vlist)
- cls.complete_lines(inventories, fill=False)
- return inventories
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'inventory_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
- def write(cls, *args):
- super().write(*args)
- inventories = cls.browse(set(sum(args[::2], [])))
- cls.complete_lines(inventories, fill=False)
+ def on_modification(cls, mode, inventories, field_names=None):
+ super().on_modification(mode, inventories, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls.complete_lines(inventories, fill=False)
+
+ @classmethod
+ def check_modification(
+ cls, mode, inventories, values=None, external=False):
+ super().check_modification(
+ mode, inventories, values=values, external=external)
+ if mode == 'delete':
+ for inventory in inventories:
+ if inventory.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'stock.msg_inventory_delete_cancel',
+ inventory=inventory.rec_name))
@classmethod
def copy(cls, inventories, default=None):
@@ -249,9 +248,7 @@ class Inventory(Workflow, ModelSQL, ModelView):
default.setdefault('lines.moves', None)
default.setdefault('number', None)
- new_inventories = super().copy(inventories, default=default)
- cls.complete_lines(new_inventories, fill=False)
- return new_inventories
+ return super().copy(inventories, default=default)
@staticmethod
def grouping():
@@ -514,14 +511,16 @@ class InventoryLine(ModelSQL, ModelView):
self.expected_quantity = quantity
@classmethod
- def delete(cls, lines):
- for line in lines:
- if line.inventory_state not in {'cancelled', 'draft'}:
- raise AccessError(
- gettext('stock.msg_inventory_line_delete_cancel',
- line=line.rec_name,
- inventory=line.inventory.rec_name))
- super().delete(lines)
+ def check_modification(cls, mode, lines, values=None, external=False):
+ super().check_modification(
+ mode, lines, values=values, external=external)
+ if mode == 'delete':
+ for line in lines:
+ if line.inventory_state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'stock.msg_inventory_line_delete_cancel',
+ line=line.rec_name,
+ inventory=line.inventory.rec_name))
class Count(Wizard):
diff --git a/modules/stock/location.py b/modules/stock/location.py
index 10738d8230..158e39a11e 100644
--- a/modules/stock/location.py
+++ b/modules/stock/location.py
@@ -545,43 +545,23 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
cls.save(to_save)
@classmethod
- def create(cls, vlist):
- locations = super().create(vlist)
- cls._set_warehouse_parent(locations)
+ def on_modification(cls, mode, locations, field_names=None):
+ super().on_modification(mode, locations, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls._set_warehouse_parent(locations)
cls._default_warehouse_cache.clear()
- return locations
@classmethod
- def write(cls, *args):
- super().write(*args)
- locations = sum(args[::2], [])
- cls._set_warehouse_parent(locations)
- cls._default_warehouse_cache.clear()
-
- ids = [l.id for l in locations]
- warehouses = cls.search([
- ('type', '=', 'warehouse'),
- ['OR',
- ('storage_location', 'in', ids),
- ('input_location', 'in', ids),
- ('output_location', 'in', ids),
- ]])
-
- fields = ('storage_location', 'input_location', 'output_location')
- wh2childs = {}
- for warehouse in warehouses:
- in_out_sto = (getattr(warehouse, f).id for f in fields)
- for location in locations:
- if location.id not in in_out_sto:
- continue
- childs = wh2childs.setdefault(warehouse.id, cls.search([
- ('parent', 'child_of', warehouse.id),
- ]))
- if location not in childs:
- raise LocationValidationError(
- gettext('stock.msg_location_child_of_warehouse',
- location=location.rec_name,
- warehouse=warehouse.rec_name))
+ def check_modification(cls, mode, locations, values=None, external=False):
+ super().check_modification(
+ mode, locations, values=values, external=external)
+ if mode == 'write' and 'parent' in values:
+ warehouses = cls.search([
+ ('type', '=', 'warehouse'),
+ ])
+ cls.validate_fields(
+ warehouses,
+ {'storage_location', 'input_location', 'output_location'})
@classmethod
def delete(cls, locations):
@@ -594,7 +574,6 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
location.storage_location,
]))
super().delete(locations + extra_locations)
- cls._default_warehouse_cache.clear()
@classmethod
def copy(cls, locations, default=None):
diff --git a/modules/stock/message.xml b/modules/stock/message.xml
index 8f56de2d64..2bb2dde0d1 100644
--- a/modules/stock/message.xml
+++ b/modules/stock/message.xml
@@ -26,9 +26,6 @@ You must use the "Modify Cost Price" wizard.
You cannot change the type of location "%(location)s" to "%(type)s"
because the type does not support moves and location has existing moves.
-
- To set location "%(location)s" as storage/input/output of warehouse "%(warehouse)s", it must be in the warehouse.
-
To inactivate location "%(location)s", you must empty it.
diff --git a/modules/stock/move.py b/modules/stock/move.py
index 133d8922b3..e8617e54f0 100644
--- a/modules/stock/move.py
+++ b/modules/stock/move.py
@@ -3,7 +3,7 @@
import copy
import datetime
import operator
-from collections import OrderedDict, defaultdict
+from collections import defaultdict
from decimal import Decimal
from itertools import groupby
@@ -991,72 +991,52 @@ class Move(Workflow, ModelSQL, ModelView):
return values
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Product = pool.get('product.product')
context = Transaction().context
-
- vlist = [x.copy() for x in vlist]
- # Use ordered dict to optimize cache alignment
- products = OrderedDict()
- for vals in vlist:
- if vals.get('product') is not None:
- products[vals['product']] = None
- id2product = {p.id: p for p in Product.browse(products.keys())}
- for vals in vlist:
- assert vals.get('state', cls.default_state()
- ) in ['draft', 'staging']
- if vals.get('product') is not None:
- product = id2product[int(vals['product'])]
- if context.get('_product_replacement', True):
- vals['product'] = product.replacement.id
- moves = super().create(vlist)
- cls.check_period_closed(moves)
- return moves
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
+ assert values.get('state', cls.default_state()) in {
+ 'draft', 'staging'}
+ if context.get('_product_replacement', True):
+ product_id = values.get('product')
+ if product_id is not None:
+ product = Product(product_id)
+ values['product'] = product.replacement.id
+ elif mode == 'write':
+ if {'unit_price', 'currency'} & values.keys():
+ values['unit_price_updated'] = True
+ return values
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for moves, values in zip(actions, actions):
- vals_set = set(values)
- if cls._deny_modify_assigned & vals_set:
+ def check_modification(cls, mode, moves, values=None, external=False):
+ super().check_modification(
+ mode, moves, values=values, external=external)
+ if mode == 'create':
+ cls.check_period_closed(moves)
+ elif mode == 'write':
+ if values.keys() - cls._allow_modify_closed_period:
+ cls.check_period_closed(moves)
+ if values.keys() & cls._deny_modify_assigned:
for move in moves:
if move.state == 'assigned':
- raise AccessError(
- gettext('stock.msg_move_modify_assigned',
+ raise AccessError(gettext(
+ 'stock.msg_move_modify_assigned',
move=move.rec_name))
- if cls._deny_modify_done_cancel & vals_set:
+ if values.keys() & cls._deny_modify_done_cancel:
for move in moves:
- if move.state in ('done', 'cancelled'):
- raise AccessError(
- gettext('stock.msg_move_modify_%s' % move.state,
+ if move.state in {'done', 'cancelled'}:
+ raise AccessError(gettext(
+ 'stock.msg_move_modify_%s' % move.state,
move=move.rec_name))
-
- super().write(*args)
-
- unit_price_update = []
- actions = iter(args)
- for moves, values in zip(actions, actions):
- if any(f not in cls._allow_modify_closed_period for f in values):
- cls.check_period_closed(moves)
+ elif mode == 'delete':
for move in moves:
- if (move.state == 'done'
- and ('unit_price' in values
- or 'currency' in values)):
- unit_price_update.append(move)
-
- if unit_price_update:
- cls.write(unit_price_update, {'unit_price_updated': True})
-
- @classmethod
- def delete(cls, moves):
- for move in moves:
- if (move.state not in {'staging', 'draft', 'cancelled'}
- and move.quantity):
- raise AccessError(
- gettext('stock.msg_move_delete_draft_cancel',
- move=move.rec_name))
- super().delete(moves)
+ if (move.state not in {'staging', 'draft', 'cancelled'}
+ and move.quantity):
+ raise AccessError(gettext(
+ 'stock.msg_move_delete_draft_cancel',
+ move=move.rec_name))
@staticmethod
def check_origin_types():
diff --git a/modules/stock/product.py b/modules/stock/product.py
index 7cf3e96715..3a27febeb5 100644
--- a/modules/stock/product.py
+++ b/modules/stock/product.py
@@ -1,7 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
-import functools
from collections import defaultdict
from copy import deepcopy
from decimal import Decimal
@@ -29,124 +28,73 @@ from .move import StockMixin
from .shipment import ShipmentAssignMixin
-def check_no_move(func):
-
- def find_moves(cls, records):
- pool = Pool()
- Move = pool.get('stock.move')
- if cls.__name__ == 'product.template':
- field = 'product.template'
- else:
- field = 'product'
- for sub_records in grouped_slice(records):
- moves = Move.search([
- (field, 'in', list(map(int, sub_records))),
- ],
- limit=1, order=[])
- if moves:
- return moves
- return False
-
- @functools.wraps(func)
- def decorator(cls, *args):
- pool = Pool()
- Template = pool.get('product.template')
- transaction = Transaction()
- if transaction.user and transaction.check_access:
- actions = iter(args)
- for records, values in zip(actions, actions):
- for field, msg in Template._modify_no_move:
- if field in values:
- if find_moves(cls, records):
- raise AccessError(gettext(msg))
- # No moves for those records
- break
-
- if not values.get('template'):
- continue
- template = Template(values['template'])
- for record in records:
- for field, msg in Template._modify_no_move:
- if isinstance(
- getattr(Template, field), fields.Function):
- continue
- if getattr(record, field) != getattr(template, field):
- if find_moves(cls, [record]):
- raise AccessError(gettext(msg))
- # No moves for this record
- break
- func(cls, *args)
- return decorator
+def _find_moves(cls, records):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ if cls.__name__ == 'product.template':
+ field = 'product.template'
+ else:
+ field = 'product'
+ for sub_records in grouped_slice(records):
+ moves = Move.search([
+ (field, 'in', list(map(int, sub_records))),
+ ],
+ limit=1, order=[])
+ if moves:
+ return moves
+ return False
-def check_no_stock_if_inactive(func):
+def _check_no_stock(cls, records):
+ pool = Pool()
+ Company = pool.get('company.company')
+ Location = pool.get('stock.location')
+ Product = pool.get('product.product')
+ Warning = pool.get('res.user.warning')
- def get_product_locations(
- company, location_ids, sub_products, grouping=('product',)):
- pool = Pool()
- Product = pool.get('product.product')
-
- product2locations = defaultdict(list)
- product_ids = list(map(int, sub_products))
+ def get_record_locations(company, location_ids, records, grouping):
+ record2locations = defaultdict(list)
+ record_ids = list(map(int, records))
with Transaction().set_context(company=company.id):
quantities = Product.products_by_location(
location_ids, with_childs=True,
- grouping=grouping, grouping_filter=(product_ids,))
+ grouping=grouping, grouping_filter=(record_ids,))
for key, quantity in quantities.items():
location_id, product_id, = key
if quantity:
- product2locations[product_id].append(location_id)
- return product2locations
+ record2locations[product_id].append(location_id)
+ return record2locations
- def raise_warning(cls, company, product2locations):
- pool = Pool()
- Location = pool.get('stock.location')
- Warning = pool.get('res.user.warning')
-
- for product_id, location_ids in product2locations.items():
- product = cls(product_id)
+ def raise_warning(cls, company, record2locations):
+ for record_id, location_ids in record2locations.items():
+ record = cls(record_id)
locations = ','.join(
l.rec_name for l in Location.browse(location_ids[:5]))
if len(location_ids) > 5:
locations += '...'
warning_name = Warning.format(
- 'deactivate_product_with_stock', [product])
+ 'deactivate_product_with_stock', [record])
if Warning.check(warning_name):
raise ProductStockWarning(warning_name,
gettext(
'stock.msg_product_location_quantity',
- product=product.rec_name,
+ product=record.rec_name,
company=company.rec_name,
locations=locations),
gettext('stock.msg_product_location_quantity_description'))
- @functools.wraps(func)
- def decorator(cls, *args):
- pool = Pool()
- Company = pool.get('company.company')
- Location = pool.get('stock.location')
+ if cls.__name__ == 'product.template':
+ grouping = ('product.template',)
+ else:
+ grouping = ('product',)
- if cls.__name__ == 'product.template':
- grouping = ('product.template',)
- else:
- grouping = ('product',)
-
- to_check = []
- actions = iter(args)
- for products, values in zip(actions, actions):
- if not values.get('active', True):
- to_check.extend(products)
- if to_check:
- with without_check_access():
- locations = Location.search([('type', '=', 'storage')])
- location_ids = list(map(int, locations))
- for company in Company.search([]):
- for sub_products in grouped_slice(to_check):
- product2locations = get_product_locations(
- company, location_ids, sub_products, grouping)
- raise_warning(cls, company, product2locations)
- return func(cls, *args)
- return decorator
+ locations = Location.search([('type', '=', 'storage')])
+ location_ids = list(map(int, locations))
+ for company in Company.search([]):
+ for sub_records in grouped_slice(records):
+ record2locations = get_record_locations(
+ company, location_ids, sub_records, grouping)
+ raise_warning(cls, company, record2locations)
class Template(metaclass=PoolMeta):
@@ -185,10 +133,19 @@ class Template(metaclass=PoolMeta):
]
@classmethod
- @check_no_move
- @check_no_stock_if_inactive
- def write(cls, *args):
- super().write(*args)
+ def check_modification(cls, mode, templates, values=None, external=False):
+ super().check_modification(
+ mode, templates, values=values, external=external)
+ if mode == 'write' and external:
+ for field_name, msg in cls._modify_no_move:
+ if field_name in values:
+ if _find_moves(cls, templates):
+ raise AccessError(gettext(msg))
+ # No moves for those records
+ break
+
+ if not values.get('active', True):
+ _check_no_stock(cls, templates)
@classmethod
def recompute_cost_price(cls, templates, start=None):
@@ -262,10 +219,36 @@ class Product(StockMixin, object, metaclass=PoolMeta):
return cost_values
@classmethod
- @check_no_move
- @check_no_stock_if_inactive
- def write(cls, *args):
- super().write(*args)
+ def check_modification(cls, mode, products, values=None, external=False):
+ pool = Pool()
+ Template = pool.get('product.template')
+ super().check_modification(
+ mode, products, values=values, external=external)
+ if mode == 'write' and external:
+ for field_name, msg in Template._modify_no_move:
+ if field_name in values:
+ if _find_moves(cls, products):
+ raise AccessError(gettext(msg))
+ # No moves for those records
+ break
+
+ if values.get('template'):
+ template = Template(values['template'])
+ for product in products:
+ for field_name, msg in Template._modify_no_move:
+ if isinstance(
+ getattr(Template, field_name),
+ fields.Function):
+ continue
+ if (getattr(product, field_name)
+ != getattr(template, field_name)):
+ if _find_moves(cls, [product]):
+ raise AccessError(gettext(msg))
+ # No moves for this record
+ break
+
+ if not values.get('active', True):
+ _check_no_stock(cls, products)
def can_be_deactivated(self):
pool = Pool()
diff --git a/modules/stock/shipment.py b/modules/stock/shipment.py
index 75c669f927..c1141c03cd 100644
--- a/modules/stock/shipment.py
+++ b/modules/stock/shipment.py
@@ -148,6 +148,34 @@ class ShipmentMixin:
default.setdefault('number')
return super().copy(shipments, default=default)
+ @classmethod
+ def on_modification(cls, mode, shipments, field_names=None):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ super().on_modification(mode, shipments, field_names=field_names)
+ if (mode in {'create', 'write'}
+ and (not field_names or 'planned_date' in field_names)):
+ cls._set_move_planned_date(shipments)
+ elif mode == 'delete':
+ if hasattr(cls, 'moves'):
+ moves = [m for s in shipments for m in s.moves]
+ Move.delete(moves)
+
+ @classmethod
+ def check_modification(cls, mode, shipments, values=None, external=False):
+ super().check_modification(
+ mode, shipments, values=values, external=external)
+ if mode == 'delete':
+ for shipment in shipments:
+ if shipment.state not in {'cancelled', 'request', 'draft'}:
+ raise AccessError(gettext(
+ 'stock.msg_shipment_delete_cancel',
+ shipment=shipment.rec_name))
+
+ @classmethod
+ def _set_move_planned_date(cls, shipments):
+ raise NotImplementedError
+
class ShipmentAssignMixin(ShipmentMixin):
__slots__ = ()
@@ -620,16 +648,18 @@ class ShipmentIn(
(m.origin_name for m in self.incoming_moves))))
@classmethod
- def create(cls, vlist):
- shipments = super().create(vlist)
- cls.set_number(shipments)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
+ def preprocess_values(cls, mode, values):
+ pool = Pool()
+ Configuration = pool.get('stock.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'shipment_in_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, shipments, default=None):
@@ -682,19 +712,6 @@ class ShipmentIn(
shipment.moves = moves
cls.save(shipments)
- @classmethod
- def delete(cls, shipments):
- Move = Pool().get('stock.move')
- # Cancel before delete
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_shipment_delete_cancel',
- shipment=shipment.rec_name))
- Move.delete([m for s in shipments for m in s.moves])
- super().delete(shipments)
-
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@@ -955,30 +972,6 @@ class ShipmentInReturn(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
return ', '.join(set(filter(None,
(m.origin_name for m in self.moves))))
- @classmethod
- def create(cls, vlist):
- shipments = super().create(vlist)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
-
- @classmethod
- def delete(cls, shipments):
- Move = Pool().get('stock.move')
- # Cancel before delete
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_shipment_delete_cancel',
- shipment=shipment.rec_name))
- Move.delete([m for s in shipments for m in s.moves])
- super().delete(shipments)
-
@classmethod
def copy(cls, shipments, default=None):
if default is None:
@@ -1738,17 +1731,6 @@ class ShipmentOut(
if to_write:
Move.write(*to_write)
- @classmethod
- def create(cls, vlist):
- shipments = super().create(vlist)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
-
@classmethod
def copy(cls, shipments, default=None):
if default is None:
@@ -1761,19 +1743,6 @@ class ShipmentOut(
default.setdefault('done_by', None)
return super().copy(shipments, default=default)
- @classmethod
- def delete(cls, shipments):
- Move = Pool().get('stock.move')
- # Cancel before delete
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_shipment_delete_cancel',
- shipment=shipment.rec_name))
- Move.delete([m for s in shipments for m in s.moves])
- super().delete(shipments)
-
@classmethod
@ModelView.button_action('stock.wizard_shipment_out_assign')
def assign_wizard(cls, shipments):
@@ -2123,16 +2092,18 @@ class ShipmentOutReturn(
(m.origin_name for m in self.incoming_moves))))
@classmethod
- def create(cls, vlist):
- shipments = super().create(vlist)
- cls.set_number(shipments)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
+ def preprocess_values(cls, mode, values):
+ pool = Pool()
+ Configuration = pool.get('stock.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'shipment_out_return_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, shipments, default=None):
@@ -2144,19 +2115,6 @@ class ShipmentOutReturn(
default.setdefault('done_by', None)
return super().copy(shipments, default=default)
- @classmethod
- def delete(cls, shipments):
- Move = Pool().get('stock.move')
- # Cance before delete
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_shipment_delete_cancel',
- shipment=shipment.rec_name))
- Move.delete([m for s in shipments for m in s.moves])
- super().delete(shipments)
-
@classmethod
@ModelView.button
@Workflow.transition('draft')
@@ -2611,30 +2569,6 @@ class ShipmentInternal(
'moves': value,
})
- @classmethod
- def create(cls, vlist):
- shipments = super().create(vlist)
- cls._set_move_planned_date(shipments)
- return shipments
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._set_move_planned_date(sum(args[::2], []))
-
- @classmethod
- def delete(cls, shipments):
- Move = Pool().get('stock.move')
- # Cancel before delete
- cls.cancel(shipments)
- for shipment in shipments:
- if shipment.state != 'cancelled':
- raise AccessError(
- gettext('stock.msg_shipment_delete_cancel',
- shipment=shipment.rec_name))
- Move.delete([m for s in shipments for m in s.moves])
- super().delete(shipments)
-
@classmethod
def copy(cls, shipments, default=None):
def shipment_field(data, name):
diff --git a/modules/stock/tests/test_module.py b/modules/stock/tests/test_module.py
index 989bb75918..eb9713bd5d 100644
--- a/modules/stock/tests/test_module.py
+++ b/modules/stock/tests/test_module.py
@@ -16,7 +16,7 @@ from trytond.modules.stock.exceptions import (
ProductStockWarning)
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
-from trytond.transaction import Transaction
+from trytond.transaction import Transaction, check_access
class StockTestCase(
@@ -1609,11 +1609,12 @@ class StockTestCase(
'currency': company.currency.id,
'company': company.id,
}])
- with self.assertRaises(ProductStockWarning):
- Product.write(products, {'active': False})
+ with check_access():
+ with self.assertRaises(ProductStockWarning):
+ Product.write(products, {'active': False})
- with self.assertRaises(ProductStockWarning):
- Template.write([template], {'active': False})
+ with self.assertRaises(ProductStockWarning):
+ Template.write([template], {'active': False})
Move.create([{
'product': product.id,
@@ -1634,8 +1635,9 @@ class StockTestCase(
'currency': company.currency.id,
'company': company.id,
}])
- Template.write([template], {'active': False})
- Product.write(products, {'active': False})
+ with check_access():
+ Template.write([template], {'active': False})
+ Product.write(products, {'active': False})
@with_transaction()
def test_inactive_products_one_with_stock(self):
@@ -1670,11 +1672,12 @@ class StockTestCase(
'currency': company.currency.id,
'company': company.id,
}])
- with self.assertRaises(ProductStockWarning):
- Product.write(products, {'active': False})
+ with check_access():
+ with self.assertRaises(ProductStockWarning):
+ Product.write(products, {'active': False})
- with self.assertRaises(ProductStockWarning):
- Template.write([template], {'active': False})
+ with self.assertRaises(ProductStockWarning):
+ Template.write([template], {'active': False})
Move.create([{
'product': product.id,
@@ -1686,8 +1689,9 @@ class StockTestCase(
'currency': company.currency.id,
'company': company.id,
}])
- Template.write([template], {'active': False})
- Product.write(products, {'active': False})
+ with check_access():
+ Template.write([template], {'active': False})
+ Product.write(products, {'active': False})
@with_transaction()
def test_location_inactive_with_consumable_product(self):
diff --git a/modules/stock_forecast/forecast.py b/modules/stock_forecast/forecast.py
index 779ea2e753..38c3bd4836 100644
--- a/modules/stock_forecast/forecast.py
+++ b/modules/stock_forecast/forecast.py
@@ -182,15 +182,15 @@ class Forecast(Workflow, ModelSQL, ModelView):
]
@classmethod
- def delete(cls, forecasts):
- # Cancel before delete
- cls.cancel(forecasts)
- for forecast in forecasts:
- if forecast.state != 'cancelled':
- raise AccessError(
- gettext('stock_forecast.msg_forecast_delete_cancel',
- forecast=forecast.rec_name))
- super().delete(forecasts)
+ def check_modification(cls, mode, forecasts, values=None, external=False):
+ super().check_modification(
+ mode, forecasts, values=values, external=external)
+ if mode == 'delete':
+ for forecast in forecasts:
+ if forecast.state not in {'cancelled', 'draft'}:
+ raise AccessError(gettext(
+ 'stock_forecast.msg_forecast_delete_cancel',
+ forecast=forecast.rec_name))
@classmethod
@ModelView.button
diff --git a/modules/stock_location_move/stock.py b/modules/stock_location_move/stock.py
index 9253262231..783108d76f 100644
--- a/modules/stock_location_move/stock.py
+++ b/modules/stock_location_move/stock.py
@@ -226,13 +226,14 @@ def deactivate_empty_location(func):
def wrapper(cls, shipments, *args, **kwargs):
pool = Pool()
Location = pool.get('stock.location')
- func(cls, shipments, *args, **kwargs)
+ result = func(cls, shipments, *args, **kwargs)
locations = set()
for shipment in shipments:
locations.update(
move.from_location for move in shipment.moves
if move.from_location.movable)
Location.deactivate_empty(list(locations))
+ return result
return wrapper
diff --git a/modules/stock_lot/stock.py b/modules/stock_lot/stock.py
index 1d67e7713e..74acbdd905 100644
--- a/modules/stock_lot/stock.py
+++ b/modules/stock_lot/stock.py
@@ -3,7 +3,6 @@
import datetime
from collections import defaultdict
from copy import copy
-from functools import wraps
from sql import Cast, Column, Null
from sql.conditionals import Case
@@ -24,37 +23,6 @@ from trytond.transaction import Transaction
from trytond.wizard import Button, StateTransition, StateView, Wizard
-def check_no_move(func):
- def find_moves(cls, records, state=None):
- pool = Pool()
- Move = pool.get('stock.move')
- for sub_records in grouped_slice(records):
- domain = [
- ('lot', 'in', [r.id for r in sub_records])
- ]
- if state:
- domain.append(('state', '=', state))
- moves = Move.search(domain, limit=1, order=[])
- if moves:
- return True
- return False
-
- @wraps(func)
- def decorator(cls, *args):
- transaction = Transaction()
- if transaction.user and transaction.check_access:
- actions = iter(args)
- for records, values in zip(actions, actions):
- for field, state, error in cls._modify_no_move:
- if field in values:
- if find_moves(cls, records, state):
- raise AccessError(gettext(error))
- # No moves for those records
- break
- func(cls, *args)
- return decorator
-
-
class LotMixin:
__slots__ = ()
number = fields.Char(
@@ -144,26 +112,46 @@ class Lot(DeactivableMixin, ModelSQL, ModelView, LotMixin, StockMixin):
return super().copy(lots, default=default)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Product = pool.get('product.product')
- vlist = [v.copy() for v in vlist]
- missing_number = defaultdict(list)
- for values in vlist:
- if not values.get('number') and values.get('product') is not None:
- product = Product(values['product'])
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ product_id = values.get('product')
+ if product_id is not None:
+ product = Product(product_id)
if product.lot_sequence:
- missing_number[product.lot_sequence].append(values)
-
- for sequence, values in missing_number.items():
- for vals, number in zip(values, sequence.get_many(len(values))):
- vals['number'] = number
- return super().create(vlist)
+ values['number'] = product.lot_sequence.get()
+ return values
@classmethod
- @check_no_move
- def write(cls, *args):
- super().write(*args)
+ def check_modification(cls, mode, lots, values=None, external=False):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ transaction = Transaction()
+
+ def find_moves(cls, state=None):
+ for sub_records in grouped_slice(lots):
+ domain = [
+ ('lot', 'in', [r.id for r in sub_records])
+ ]
+ if state:
+ domain.append(('state', '=', state))
+ moves = Move.search(domain, limit=1, order=[])
+ if moves:
+ return True
+ return False
+
+ super().check_modification(
+ mode, lots, values=values, external=external)
+ if mode == 'write':
+ if transaction.user and transaction.check_access:
+ for field, state, error in cls._modify_no_move:
+ if field in values:
+ if find_moves(state):
+ raise AccessError(gettext(error))
+ # No moves
+ break
@classmethod
@ModelView.button_action('stock_lot.act_lot_trace_upward_relate')
diff --git a/modules/stock_lot_sled/stock.py b/modules/stock_lot_sled/stock.py
index 569a1f2bac..3d4bffaf9d 100644
--- a/modules/stock_lot_sled/stock.py
+++ b/modules/stock_lot_sled/stock.py
@@ -104,14 +104,13 @@ class Lot(LotSledMixin, metaclass=PoolMeta):
__name__ = 'stock.lot'
@classmethod
- def write(cls, *args):
- super().write(*args)
-
- actions = iter(args)
- for lots, values in zip(actions, actions):
- if any(f in ['shelf_life_expiration_date', 'expiration_date']
- for f in values):
- cls.check_sled_period_closed(lots)
+ def check_modification(cls, mode, lots, values=None, external=False):
+ super().check_modification(
+ mode, lots, values=values, external=external)
+ if (mode == 'write'
+ and values.keys() & {
+ 'shelf_life_expiration_date', 'expiration_date'}):
+ cls.check_sled_period_closed(lots)
@classmethod
def check_sled_period_closed(cls, lots):
diff --git a/modules/stock_package/stock.py b/modules/stock_package/stock.py
index 438e9c5947..1d2e062d12 100644
--- a/modules/stock_package/stock.py
+++ b/modules/stock_package/stock.py
@@ -315,18 +315,18 @@ class Package(tree(), MeasurementsMixin, ModelSQL, ModelView):
children_volume, self.packaging_volume_uom)))
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
- Config = pool.get('stock.configuration')
-
- vlist = [v.copy() for v in vlist]
- config = Config(1)
- default_company = cls.default_company()
- for values in vlist:
- values['number'] = config.get_multivalue(
- 'package_sequence',
- company=values.get('company', default_company)).get()
- return super().create(vlist)
+ Configuration = pool.get('stock.configuration')
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('number'):
+ company_id = values.get('company', cls.default_company())
+ if company_id is not None:
+ configuration = Configuration(1)
+ if sequence := configuration.get_multivalue(
+ 'package_sequence', company=company_id):
+ values['number'] = sequence.get()
+ return values
@classmethod
def copy(cls, packages, default=None):
diff --git a/modules/stock_package_shipping_dpd/carrier.py b/modules/stock_package_shipping_dpd/carrier.py
index 70b08d898d..a607cade9c 100644
--- a/modules/stock_package_shipping_dpd/carrier.py
+++ b/modules/stock_package_shipping_dpd/carrier.py
@@ -52,19 +52,21 @@ class CredentialDPD(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
self.save()
@classmethod
- def write(cls, *args):
+ def check_modification(
+ cls, mode, credentials, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for credentials, values in zip(actions, actions):
- if {'user_id', 'password', 'server'} & values.keys():
- warning_name = Warning.format('dpd_credential', credentials)
- if Warning.check(warning_name):
- raise DPDCredentialWarning(
- warning_name,
- gettext('stock_package_shipping_dpd'
- '.msg_dpd_credential_modified'))
- return super().write(*args)
+ super().check_modification(
+ mode, credentials, values=values, external=external)
+ if (mode == 'write'
+ and external
+ and values.keys() & {'user_id', 'password', 'server'}):
+ warning_name = Warning.format('dpd_credential', credentials)
+ if Warning.check(warning_name):
+ raise DPDCredentialWarning(
+ warning_name,
+ gettext('stock_package_shipping_dpd'
+ '.msg_dpd_credential_modified'))
class Carrier(metaclass=PoolMeta):
diff --git a/modules/stock_package_shipping_mygls/carrier.py b/modules/stock_package_shipping_mygls/carrier.py
index 03265a9803..10fd219b2f 100644
--- a/modules/stock_package_shipping_mygls/carrier.py
+++ b/modules/stock_package_shipping_mygls/carrier.py
@@ -34,20 +34,22 @@ class CredentialMyGLS(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
return 'testing'
@classmethod
- def write(cls, *args):
+ def check_modification(
+ cls, mode, credentials, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for credentials, values in zip(actions, actions):
- if ({'server', 'username', 'password', 'client_number'}
- & values.keys()):
- warning_name = Warning.format('mygls_credential', credentials)
- if Warning.check(warning_name):
- raise MyGLSCredentialWarning(
- warning_name,
- gettext('stock_package_shipping_mygls'
- '.msg_mygls_credential_modified'))
- return super().write(*args)
+ super().check_modification(
+ mode, credentials, values=values, external=external)
+ if (mode == 'write'
+ and external
+ and values.keys() & {
+ 'server', 'username', 'password', 'client_number'}):
+ warning_name = Warning.format('mygls_credential', credentials)
+ if Warning.check(warning_name):
+ raise MyGLSCredentialWarning(
+ warning_name,
+ gettext('stock_package_shipping_mygls'
+ '.msg_mygls_credential_modified'))
class Carrier(metaclass=PoolMeta):
diff --git a/modules/stock_package_shipping_sendcloud/carrier.py b/modules/stock_package_shipping_sendcloud/carrier.py
index 74c9042c44..4e4e5c7f1b 100644
--- a/modules/stock_package_shipping_sendcloud/carrier.py
+++ b/modules/stock_package_shipping_sendcloud/carrier.py
@@ -184,20 +184,22 @@ class CredentialSendcloud(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
return response.content
@classmethod
- def write(cls, *args):
+ def check_modification(
+ cls, mode, credentials, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for credentials, values in zip(actions, actions):
- if {'public_key', 'secret_key'} & values.keys():
- warning_name = Warning.format(
- 'sendcloud_credential', credentials)
- if Warning.check(warning_name):
- raise SendcloudCredentialWarning(
- warning_name,
- gettext('stock_package_shipping_sendcloud'
- '.msg_sendcloud_credential_modified'))
- return super().write(*args)
+ super().check_modification(
+ mode, credentials, values=values, external=external)
+ if (mode == 'write'
+ and external
+ and values.keys() & {'public_key', 'secret_key'}):
+ warning_name = Warning.format(
+ 'sendcloud_credential', credentials)
+ if Warning.check(warning_name):
+ raise SendcloudCredentialWarning(
+ warning_name,
+ gettext('stock_package_shipping_sendcloud'
+ '.msg_sendcloud_credential_modified'))
class SendcloudAddress(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
diff --git a/modules/stock_package_shipping_ups/carrier.py b/modules/stock_package_shipping_ups/carrier.py
index e17de0088b..3fd4835093 100644
--- a/modules/stock_package_shipping_ups/carrier.py
+++ b/modules/stock_package_shipping_ups/carrier.py
@@ -64,20 +64,22 @@ class CredentialUPS(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
return 'https://wwwcie.ups.com/api/shipments/v2403/ship'
@classmethod
- def write(cls, *args):
+ def check_modification(
+ cls, mode, credentials, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for credentials, values in zip(actions, actions):
- if ({'client_id', 'client_secret', 'account_number'}
- & values.keys()):
- warning_name = Warning.format('dpd_credential', credentials)
- if Warning.check(warning_name):
- raise UPSCredentialWarning(
- warning_name,
- gettext('stock_package_shipping_ups'
- '.msg_ups_credential_modified'))
- return super().write(*args)
+ super().check_modification(
+ mode, credentials, values=values, external=external)
+ if (mode == 'write'
+ and external
+ and values.keys() & {
+ 'client_id', 'client_secret', 'account_number'}):
+ warning_name = Warning.format('dpd_credential', credentials)
+ if Warning.check(warning_name):
+ raise UPSCredentialWarning(
+ warning_name,
+ gettext('stock_package_shipping_ups'
+ '.msg_ups_credential_modified'))
class Carrier(metaclass=PoolMeta):
diff --git a/modules/timesheet_cost/company.py b/modules/timesheet_cost/company.py
index 7f657eac08..97a379cad3 100644
--- a/modules/timesheet_cost/company.py
+++ b/modules/timesheet_cost/company.py
@@ -106,20 +106,8 @@ class EmployeeCostPrice(ModelSQL, ModelView):
return str(self.date)
@classmethod
- def delete(cls, prices):
- Employee = Pool().get('company.employee')
- super().delete(prices)
- Employee._cost_prices_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- Employee = Pool().get('company.employee')
- prices = super().create(vlist)
- Employee._cost_prices_cache.clear()
- return prices
-
- @classmethod
- def write(cls, *args):
- Employee = Pool().get('company.employee')
- super().write(*args)
+ def on_modification(cls, mode, prices, field_names=None):
+ pool = Pool()
+ Employee = pool.get('company.employee')
+ super().on_modification(mode, prices, field_names=field_names)
Employee._cost_prices_cache.clear()
diff --git a/modules/timesheet_cost/timesheet.py b/modules/timesheet_cost/timesheet.py
index 5db69c5a16..215060ab2e 100644
--- a/modules/timesheet_cost/timesheet.py
+++ b/modules/timesheet_cost/timesheet.py
@@ -19,13 +19,12 @@ class Line(metaclass=PoolMeta):
return 0
@classmethod
- def create(cls, vlist):
- # XXX Remove cost_price because proteus set it as default value
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
+ # XXX Remove cost_price because proteus set it as default value
values.pop('cost_price', None)
- lines = super().create(vlist)
- return lines
+ return values
@classmethod
def compute_fields(self, field_names=None):
diff --git a/modules/user_role/res.py b/modules/user_role/res.py
index 4a1bb7e03f..2a840e6afd 100644
--- a/modules/user_role/res.py
+++ b/modules/user_role/res.py
@@ -13,16 +13,15 @@ class Role(ModelSQL, ModelView):
groups = fields.Many2Many('res.role-res.group', 'role', 'group', "Groups")
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, roles, field_names=None):
pool = Pool()
User = pool.get('res.user')
- roles = sum(args[0:None:2], [])
- super().write(*args)
-
- users = User.search([
- ('roles.role', 'in', [r.id for r in roles]),
- ])
- User.sync_roles(users)
+ super().on_modification(mode, roles, field_names=field_names)
+ if mode == 'write':
+ users = User.search([
+ ('roles.role', 'in', [r.id for r in roles]),
+ ])
+ User.sync_roles(users)
class RoleGroup(ModelSQL):
@@ -48,16 +47,10 @@ class User(metaclass=PoolMeta):
cls._context_fields.append('roles')
@classmethod
- def create(cls, vlist):
- users = super().create(vlist)
- cls.sync_roles(users)
- return users
-
- @classmethod
- def write(cls, *args):
- users = sum(args[0:None:2], [])
- super().write(*args)
- cls.sync_roles(users)
+ def on_modification(cls, mode, users, field_names=None):
+ super().on_modification(mode, users, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls.sync_roles(users)
@classmethod
def sync_roles(cls, users=None, date=None):
diff --git a/modules/web_shop/web.py b/modules/web_shop/web.py
index 72ea25291a..84c841fdcc 100644
--- a/modules/web_shop/web.py
+++ b/modules/web_shop/web.py
@@ -35,12 +35,13 @@ class Inactivate(ModelSQL, DeactivableMixin):
domain, active_test=active_test, tables=tables)
@classmethod
- def delete(cls, records):
- cls.copy([r for r in records if r.active and r.shop.to_sync],
- default={
- 'active': False,
- })
- super().delete(records)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if mode == 'delete':
+ cls.copy([r for r in records if r.active and r.shop.to_sync],
+ default={
+ 'active': False,
+ })
class Shop(DeactivableMixin, ModelSQL, ModelView):
@@ -157,14 +158,10 @@ class Shop(DeactivableMixin, ModelSQL, ModelView):
return super().copy(shops, default=default)
@classmethod
- def write(cls, *args):
- cls._name_cache.clear()
- super().write(*args)
-
- @classmethod
- def delete(cls, shops):
- cls._name_cache.clear()
- super().delete(shops)
+ def on_modification(cls, mode, shops, field_names=None):
+ super().on_modification(mode, shops, field_names=field_names)
+ if mode == 'delete' or (mode == 'write' and 'name' in field_names):
+ cls._name_cache.clear()
@property
def to_sync(self):
diff --git a/modules/web_shop_shopify/common.py b/modules/web_shop_shopify/common.py
index c493496f7b..b0d80bac2d 100644
--- a/modules/web_shop_shopify/common.py
+++ b/modules/web_shop_shopify/common.py
@@ -77,15 +77,13 @@ class IdentifiersUpdateMixin:
raise NotImplementedError
@classmethod
- def write(cls, *args):
- actions = iter(args)
- to_update = set()
- for records, values in zip(actions, actions):
- if values.keys() & cls._shopify_fields:
- to_update.update(records)
- if to_update:
- cls.set_shopify_to_update(cls.browse(list(to_update)))
- super().write(*args)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if mode in {'create', 'delete'}:
+ cls.set_shopify_to_update(records)
+ elif mode == 'write':
+ if field_names & cls._shopify_fields:
+ cls.set_shopify_to_update(records)
class IdentifiersMixin(IdentifiersUpdateMixin):
@@ -148,11 +146,13 @@ class IdentifiersMixin(IdentifiersUpdateMixin):
return super().copy(records, default=default)
@classmethod
- def delete(cls, records):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
Identifier = pool.get('web.shop.shopify_identifier')
- Identifier.delete(sum((r.shopify_identifiers for r in records), ()))
- super().delete(records)
+ super().on_modification(mode, records, field_names=field_names)
+ if mode == 'delete':
+ Identifier.delete(
+ sum((r.shopify_identifiers for r in records), ()))
@classmethod
def get_shopify_identifier_to_update(cls, records):
diff --git a/modules/web_shop_shopify/product.py b/modules/web_shop_shopify/product.py
index 21add964dc..c2d9d660ec 100644
--- a/modules/web_shop_shopify/product.py
+++ b/modules/web_shop_shopify/product.py
@@ -50,17 +50,6 @@ class TemplateCategory(IdentifiersUpdateMixin, metaclass=PoolMeta):
super().__setup__()
cls._shopify_fields.update(['template', 'category'])
- @classmethod
- def create(cls, vlist):
- records = super().create(vlist)
- cls.set_shopify_to_update(records)
- return records
-
- @classmethod
- def delete(cls, records):
- cls.set_shopify_to_update(records)
- super().delete(records)
-
@classmethod
def get_shopify_identifier_to_update(cls, records):
return sum((list(r.template.shopify_identifiers) for r in records), [])
@@ -280,17 +269,16 @@ class Product(IdentifiersMixin, metaclass=PoolMeta):
factor=self.shopify_uom_factor, rate=self.shopify_uom_rate)
@classmethod
- def write(cls, *args):
- actions = iter(args)
- for products, values in zip(actions, actions):
- if 'template' in values:
- for product in products:
- if (product.template.id != values.get('template')
- and product.shopify_identifiers):
- raise AccessError(gettext(
- 'web_shop_shopify.msg_product_change_template',
- product=product.rec_name))
- super().write(*args)
+ def check_modification(cls, mode, products, values=None, external=False):
+ super().check_modification(
+ mode, products, values=values, external=external)
+ if mode == 'write' and 'template' in values:
+ for product in products:
+ if (product.template.id != values.get('template')
+ and product.shopify_identifiers):
+ raise AccessError(gettext(
+ 'web_shop_shopify.msg_product_change_template',
+ product=product.rec_name))
class ShopifyInventoryItem(IdentifiersMixin, ModelSQL, ModelView):
@@ -376,17 +364,6 @@ class Product_TariffCode(IdentifiersUpdateMixin, metaclass=PoolMeta):
super().__setup__()
cls._shopify_fields.update(['product', 'tariff_code'])
- @classmethod
- def create(cls, vlist):
- identifiers = super().create(vlist)
- cls.set_shopify_to_update(identifiers)
- return identifiers
-
- @classmethod
- def delete(cls, records):
- cls.set_shopify_to_update(records)
- super().delete(records)
-
@classmethod
def get_shopify_identifier_to_update(cls, records):
pool = Pool()
@@ -417,17 +394,6 @@ class ProductIdentifier(IdentifiersUpdateMixin, metaclass=PoolMeta):
super().__setup__()
cls._shopify_fields.update(['product', 'code'])
- @classmethod
- def create(cls, vlist):
- identifiers = super().create(vlist)
- cls.set_shopify_to_update(identifiers)
- return identifiers
-
- @classmethod
- def delete(cls, identifiers):
- cls.set_shopify_to_update(identifiers)
- super().delete(identifiers)
-
@classmethod
def get_shopify_identifier_to_update(cls, identifiers):
return sum((
@@ -549,28 +515,17 @@ class Image(IdentifiersMixin, metaclass=PoolMeta):
cls._shopify_fields.update(['template', 'product', 'attributes'])
@classmethod
- def create(cls, vlist):
- images = super().create(vlist)
- cls.set_shopify_to_update(images)
- return images
-
- @classmethod
- def write(cls, *args):
+ def on_write(cls, images, values):
pool = Pool()
Identifier = pool.get('web.shop.shopify_identifier')
- actions = iter(args)
- to_delete = []
- for images, values in zip(actions, actions):
- if values.keys() & {'image', 'template', 'web_shop'}:
- for image in images:
- to_delete.extend(image.shopify_identifiers)
- super().write(*args)
- Identifier.delete(to_delete)
-
- @classmethod
- def delete(cls, images):
- cls.set_shopify_to_update(images)
- super().delete(images)
+ callback = super().on_write(images, values)
+ if values.keys() & {'image', 'template', 'web_shop'}:
+ to_delete = []
+ for image in images:
+ to_delete.extend(image.shopify_identifiers)
+ if to_delete:
+ callback.append(lambda: Identifier.delete(to_delete))
+ return callback
@classmethod
def get_shopify_identifier_to_update(cls, images):
diff --git a/modules/web_shop_shopify/web.py b/modules/web_shop_shopify/web.py
index ab81f293f5..3194de0b6e 100644
--- a/modules/web_shop_shopify/web.py
+++ b/modules/web_shop_shopify/web.py
@@ -582,21 +582,22 @@ class Shop(metaclass=PoolMeta):
Sale.__queue__.process(to_update.keys())
@classmethod
- def write(cls, *args):
+ def check_modification(cls, mode, shops, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
- actions = iter(args)
- for shops, values in zip(actions, actions):
- if ({'shopify_url', 'shopify_password',
- 'shopify_webhook_shared_secret'}
- & values.keys()):
- warning_name = Warning.format('shopify_credential', shops)
- if Warning.check(warning_name):
- raise ShopifyCredentialWarning(
- warning_name,
- gettext('web_shop_shopify'
- '.msg_shopidy_credential_modified'))
- return super().write(*args)
+ super().check_modification(
+ mode, shops, values=values, external=external)
+ if (mode == 'write'
+ and external
+ and values.keys() & {
+ 'shopify_url', 'shopify_password',
+ 'shopify_webhook_shared_secret'}):
+ warning_name = Warning.format('shopify_credential', shops)
+ if Warning.check(warning_name):
+ raise ShopifyCredentialWarning(
+ warning_name,
+ gettext('web_shop_shopify'
+ '.msg_shopidy_credential_modified'))
class ShopShopifyIdentifier(IdentifierMixin, ModelSQL, ModelView):
diff --git a/modules/web_user/user.py b/modules/web_user/user.py
index 0e4b91ed13..13104d20bd 100644
--- a/modules/web_user/user.py
+++ b/modules/web_user/user.py
@@ -144,14 +144,6 @@ class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
if not self.email and self.party:
self.email = self.party.email
- @classmethod
- def _format_email(cls, users):
- for user in users:
- email = normalize_email(user.email).lower()
- if email != user.email:
- user.email = email
- cls.save(users)
-
@classmethod
def copy(cls, users, default=None):
default = default.copy() if default is not None else {}
@@ -160,16 +152,11 @@ class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
return super().copy(users, default=default)
@classmethod
- def create(cls, vlist):
- users = super().create(vlist)
- cls._format_email(users)
- return users
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- users = sum(args[0:None:2], [])
- cls._format_email(users)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if values.get('email'):
+ values['email'] = normalize_email(values['email']).lower()
+ return values
@classmethod
def validate_fields(cls, users, fields_names):
@@ -468,13 +455,13 @@ class UserSession(ModelSQL):
cls.write(sessions, {})
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create':
# Ensure to get a different key for each record
# default methods are called only once
values.setdefault('key', cls.default_key())
- return super().create(vlist)
+ return values
class EmailValidation(Report):
diff --git a/trytond/trytond/ir/action.py b/trytond/trytond/ir/action.py
index 2822fdc262..4984ebee85 100644
--- a/trytond/trytond/ir/action.py
+++ b/trytond/trytond/ir/action.py
@@ -89,10 +89,11 @@ class Action(DeactivableMixin, ModelSQL, ModelView):
return None
@classmethod
- def write(cls, actions, values, *args):
+ def on_modification(cls, mode, actions, field_names=None):
pool = Pool()
- super().write(actions, values, *args)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
+ ActionKeyword = pool.get('ir.action.keyword')
+ super().on_modification(mode, actions, field_names=field_names)
+ ActionKeyword._get_keyword_cache.clear()
@classmethod
@inactive_records
@@ -159,36 +160,20 @@ class ActionGroup(ModelSQL):
'res.group', "Group", ondelete='CASCADE', required=True)
@classmethod
- def create(cls, vlist):
+ def preprocess_values(cls, mode, values):
pool = Pool()
Action = pool.get('ir.action')
- vlist = [x.copy() for x in vlist]
- for vals in vlist:
- if vals.get('action'):
- vals['action'] = Action.get_action_id(vals['action'])
- Action._groups_cache.clear()
- return super().create(vlist)
+ values = super().preprocess_values(mode, values)
+ if values.get('action') is not None:
+ values['action'] = Action.get_action_id(values['action'])
+ return values
@classmethod
- def write(cls, records, values, *args):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
Action = pool.get('ir.action')
- actions = iter((records, values) + args)
- args = []
- for records, values in zip(actions, actions):
- if values.get('action'):
- values = values.copy()
- values['action'] = Action.get_action_id(values['action'])
- args.extend((records, values))
+ super().on_modification(mode, records, field_names=field_names)
Action._groups_cache.clear()
- super().write(*args)
-
- @classmethod
- def delete(cls, records):
- pool = Pool()
- Action = pool.get('ir.action')
- Action._groups_cache.clear()
- super().delete(records)
class ActionKeyword(ModelSQL, ModelView):
@@ -251,14 +236,20 @@ class ActionKeyword(ModelSQL, ModelView):
gettext('ir.msg_action_wrong_wizard_model',
name=action_wizard.rec_name))
- @staticmethod
- def _convert_vals(vals):
- vals = vals.copy()
+ @classmethod
+ def preprocess_values(cls, mode, values):
pool = Pool()
Action = pool.get('ir.action')
- if 'action' in vals:
- vals['action'] = Action.get_action_id(vals['action'])
- return vals
+ values = super().preprocess_values(mode, values)
+ if values.get('action') is not None:
+ values['action'] = Action.get_action_id(values['action'])
+ return values
+
+ @classmethod
+ def on_modification(cls, mode, keywords, field_names=None):
+ super().on_modification(mode, keywords, field_names=field_names)
+ ModelView._view_toolbar_get_cache.clear()
+ cls._get_keyword_cache.clear()
@staticmethod
def models_get():
@@ -273,31 +264,6 @@ class ActionKeyword(ModelSQL, ModelView):
def search_groups(cls, name, clause):
return [('action.' + clause[0],) + tuple(clause[1:])]
- @classmethod
- def delete(cls, keywords):
- ModelView._view_toolbar_get_cache.clear()
- cls._get_keyword_cache.clear()
- super().delete(keywords)
-
- @classmethod
- def create(cls, vlist):
- ModelView._view_toolbar_get_cache.clear()
- cls._get_keyword_cache.clear()
- new_vlist = []
- for vals in vlist:
- new_vlist.append(cls._convert_vals(vals))
- return super().create(new_vlist)
-
- @classmethod
- def write(cls, keywords, values, *args):
- actions = iter((keywords, values) + args)
- args = []
- for keywords, values in zip(actions, actions):
- args.extend((keywords, cls._convert_vals(values)))
- super().write(*args)
- ModelView._view_toolbar_get_cache.clear()
- cls._get_keyword_cache.clear()
-
@classmethod
def get_keyword(cls, keyword, value):
pool = Pool()
@@ -455,10 +421,21 @@ class ActionMixin(ModelSQL):
Action._groups_cache.set(key, list(groups))
return groups
+ @classmethod
+ def on_modification(cls, mode, records, field_names=None):
+ pool = Pool()
+ Action = pool.get('ir.action')
+ ActionKeyword = pool.get('ir.action.keyword')
+ super().on_modification(mode, records, field_names=field_names)
+ ModelView._view_toolbar_get_cache.clear()
+ ActionKeyword._get_keyword_cache.clear()
+ if mode == 'delete':
+ actions = [x.action for x in records]
+ Action.delete(actions)
+
@classmethod
def create(cls, vlist):
pool = Pool()
- ModelView._view_toolbar_get_cache.clear()
Action = pool.get('ir.action')
ir_action = cls.__table__()
new_records = []
@@ -497,23 +474,6 @@ class ActionMixin(ModelSQL):
cls.write(*to_write)
return new_records
- @classmethod
- def write(cls, records, values, *args):
- pool = Pool()
- ActionKeyword = pool.get('ir.action.keyword')
- super().write(records, values, *args)
- ModelView._view_toolbar_get_cache.clear()
- ActionKeyword._get_keyword_cache.clear()
-
- @classmethod
- def delete(cls, records):
- pool = Pool()
- ModelView._view_toolbar_get_cache.clear()
- Action = pool.get('ir.action')
- actions = [x.action for x in records]
- super().delete(records)
- Action.delete(actions)
-
@classmethod
def copy(cls, records, default=None):
pool = Pool()
@@ -757,19 +717,18 @@ class ActionReport(
return super().copy(reports, default=default)
@classmethod
- def write(cls, reports, values, *args):
+ def preprocess_values(cls, mode, values):
context = Transaction().context
- if 'module' in context:
- actions = iter((reports, values) + args)
- args = []
- for reports, values in zip(actions, actions):
- values = values.copy()
- values['module'] = context['module']
- args.extend((reports, values))
- reports, values = args[:2]
- args = args[2:]
- cls._template_cache.clear()
- super().write(reports, values, *args)
+ values = super().preprocess_values(mode, values)
+ if 'module' in context and not values.get('module'):
+ values['module'] = context['module']
+ return values
+
+ @classmethod
+ def on_modification(cls, mode, reports, field_names=None):
+ super().on_modification(mode, reports, field_names=field_names)
+ if mode == 'write':
+ cls._template_cache.clear()
def get_template_cached(self):
return self._template_cache.get(self.id)
@@ -1028,23 +987,11 @@ class ActionActWindowView(
return self.act_window.res_model
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
- windows = super().create(vlist)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
- return windows
-
- @classmethod
- def write(cls, windows, values, *args):
- pool = Pool()
- super().write(windows, values, *args)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
-
- @classmethod
- def delete(cls, windows):
- pool = Pool()
- super().delete(windows)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
+ Keyword = pool.get('ir.action.keyword')
+ super().on_modification(mode, records, field_names=field_names)
+ Keyword._get_keyword_cache.clear()
class ActionActWindowDomain(
@@ -1105,23 +1052,11 @@ class ActionActWindowDomain(
action=action.rec_name)) from exception
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
- domains = super().create(vlist)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
- return domains
-
- @classmethod
- def write(cls, domains, values, *args):
- pool = Pool()
- super().write(domains, values, *args)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
-
- @classmethod
- def delete(cls, domains):
- pool = Pool()
- super().delete(domains)
- pool.get('ir.action.keyword')._get_keyword_cache.clear()
+ Keyword = pool.get('ir.action.keyword')
+ super().on_modification(mode, records, field_names=field_names)
+ Keyword._get_keyword_cache.clear()
class ActionWizard(
diff --git a/trytond/trytond/ir/avatar.py b/trytond/trytond/ir/avatar.py
index 6aa5aa9af5..dc8b6b141d 100644
--- a/trytond/trytond/ir/avatar.py
+++ b/trytond/trytond/ir/avatar.py
@@ -58,17 +58,17 @@ class Avatar(ImageMixin, ResourceMixin, ModelSQL):
return uuid.uuid4().hex
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- values.setdefault('uuid', cls.default_uuid())
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and 'uuid' not in values:
+ values['uuid'] = cls.default_uuid()
+ return values
@classmethod
- def write(cls, *args):
- avatars = sum(args[0:None:2], [])
- super().write(*args)
- cls.clear_cache(avatars)
+ def on_modification(cls, mode, avatars, field_names=None):
+ super().on_modification(mode, avatars, field_names=field_names)
+ if mode == 'write':
+ cls.clear_cache(avatars)
@classmethod
def copy(cls, avatars, default=None):
diff --git a/trytond/trytond/ir/configuration.py b/trytond/trytond/ir/configuration.py
index 2aa880da89..5c3a8f26e1 100644
--- a/trytond/trytond/ir/configuration.py
+++ b/trytond/trytond/ir/configuration.py
@@ -32,17 +32,6 @@ class Configuration(ModelSingleton, ModelSQL):
pass
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
- cls._get_language_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._get_language_cache.clear()
-
- @classmethod
- def delete(cls, records):
- super().delete(records)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_language_cache.clear()
diff --git a/trytond/trytond/ir/email_.py b/trytond/trytond/ir/email_.py
index 281428f471..04a242f8d3 100644
--- a/trytond/trytond/ir/email_.py
+++ b/trytond/trytond/ir/email_.py
@@ -562,20 +562,10 @@ class EmailTemplate(ModelSQL, ModelView):
return record.language
@classmethod
- def create(cls, vlist):
- ModelView._view_toolbar_get_cache.clear()
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- if any({'name', 'model'} & v.keys() for v in args[1:None:2]):
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if not field_names or {'name', 'model'} & set(field_names):
ModelView._view_toolbar_get_cache.clear()
- super().write(*args)
-
- @classmethod
- def delete(cls, records):
- ModelView._view_toolbar_get_cache.clear()
- super().delete(records)
class EmailTemplate_Report(ModelSQL):
diff --git a/trytond/trytond/ir/export.py b/trytond/trytond/ir/export.py
index bd5383f68a..7cc80d85d7 100644
--- a/trytond/trytond/ir/export.py
+++ b/trytond/trytond/ir/export.py
@@ -9,20 +9,11 @@ from trytond.transaction import Transaction
class _ClearCache(ModelSQL):
- @classmethod
- def create(cls, vlist):
- ModelView._view_toolbar_get_cache.clear()
- return super().create(vlist)
@classmethod
- def write(cls, *args):
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
ModelView._view_toolbar_get_cache.clear()
- super().write(*args)
-
- @classmethod
- def delete(cls, records):
- ModelView._view_toolbar_get_cache.clear()
- super().delete(records)
class Export(_ClearCache, ModelSQL, ModelView):
diff --git a/trytond/trytond/ir/lang.py b/trytond/trytond/ir/lang.py
index 3c44fd4dd0..988b3effcf 100644
--- a/trytond/trytond/ir/lang.py
+++ b/trytond/trytond/ir/lang.py
@@ -15,6 +15,7 @@ from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.model import (
Check, DeactivableMixin, ModelSQL, ModelView, Unique, fields)
+from trytond.model.exceptions import AccessError
from trytond.modules import create_graph, load_translations
from trytond.pool import Pool
from trytond.pyson import Eval
@@ -40,7 +41,7 @@ class TranslatableError(UserError):
pass
-class DeleteDefaultError(UserError):
+class DeleteDefaultError(AccessError):
pass
@@ -396,43 +397,34 @@ class Lang(DeactivableMixin, ModelSQL, ModelView):
return res
@classmethod
- def create(cls, vlist):
- pool = Pool()
- Translation = pool.get('ir.translation')
- # Clear cache
- cls._lang_cache.clear()
- languages = super().create(vlist)
- Translation._get_language_cache.clear()
- _parents.clear()
- return languages
-
- @classmethod
- def write(cls, langs, values, *args):
- pool = Pool()
- Translation = pool.get('ir.translation')
- # Clear cache
- cls._lang_cache.clear()
- cls._code_cache.clear()
- super().write(langs, values, *args)
- Translation._get_language_cache.clear()
- _parents.clear()
-
- @classmethod
- def delete(cls, langs):
+ def check_modification(cls, mode, languages, values=None, external=False):
pool = Pool()
Config = pool.get('ir.configuration')
+
+ super().check_modification(
+ mode, languages, values=values, external=external)
+
+ if mode == 'write' and 'code' in values:
+ for language in languages:
+ if (language.code == Config.get_language()
+ and (not values
+ or language.code != values['code'])):
+ raise DeleteDefaultError(
+ gettext('ir.msg_language_delete_default',
+ language=language.rec_name))
+
+ @classmethod
+ def on_modification(cls, mode, languages, field_names=None):
+ pool = Pool()
Translation = pool.get('ir.translation')
- for lang in langs:
- if lang.code == Config.get_language():
- raise DeleteDefaultError(
- gettext('ir.msg_language_delete_default',
- language=lang.rec_name))
- # Clear cache
+
+ super().on_modification(mode, languages, field_names=field_names)
+
cls._lang_cache.clear()
- cls._code_cache.clear()
- super().delete(langs)
Translation._get_language_cache.clear()
_parents.clear()
+ if mode in {'write', 'delete'}:
+ cls._code_cache.clear()
@classmethod
@inactive_records
diff --git a/trytond/trytond/ir/message.py b/trytond/trytond/ir/message.py
index 135b3618a2..d32c6705c1 100644
--- a/trytond/trytond/ir/message.py
+++ b/trytond/trytond/ir/message.py
@@ -91,6 +91,19 @@ class Message(ModelSQL, ModelView):
return translation
language = get_parent(language)
+ @classmethod
+ def on_modification(cls, mode, messages, field_names=None):
+ pool = Pool()
+ Translation = pool.get('ir.translation')
+ super().on_modification(mode, messages, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls._set_translations(messages)
+ if mode in {'write', 'delete'}:
+ cls._message_cache.clear()
+ if mode == 'delete':
+ Translation.delete_ids(
+ cls.__name__, 'model', [m.id for m in messages])
+
@classmethod
def _set_translations(cls, messages):
pool = Pool()
@@ -142,27 +155,6 @@ class Message(ModelSQL, ModelView):
if to_save:
Translation.save(to_save)
- @classmethod
- def create(cls, vlist):
- messages = super().create(vlist)
- cls._set_translations(messages)
- return messages
-
- @classmethod
- def write(cls, messages, values, *args):
- super().write(messages, values, *args)
- cls._set_translations(messages)
- cls._message_cache.clear()
-
- @classmethod
- def delete(cls, messages):
- pool = Pool()
- Translation = pool.get('ir.translation')
- ids = [m.id for m in messages]
- super().delete(messages)
- Translation.delete_ids(cls.__name__, 'model', ids)
- cls._message_cache.clear()
-
@classmethod
def search_rec_name(cls, name, clause):
return [('text',) + tuple(clause[1:])]
diff --git a/trytond/trytond/ir/model.py b/trytond/trytond/ir/model.py
index b2a66b4aab..c50c83974c 100644
--- a/trytond/trytond/ir/model.py
+++ b/trytond/trytond/ir/model.py
@@ -754,24 +754,8 @@ class ModelAccess(
return True
@classmethod
- def write(cls, accesses, values, *args):
- super().write(accesses, values, *args)
- # Restart the cache
- cls._get_access_cache.clear()
- ModelView._fields_view_get_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- # Restart the cache
- cls._get_access_cache.clear()
- ModelView._fields_view_get_cache.clear()
- return res
-
- @classmethod
- def delete(cls, accesses):
- super().delete(accesses)
- # Restart the cache
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_access_cache.clear()
ModelView._fields_view_get_cache.clear()
@@ -947,24 +931,8 @@ class ModelFieldAccess(
return True
@classmethod
- def write(cls, field_accesses, values, *args):
- super().write(field_accesses, values, *args)
- # Restart the cache
- cls._get_access_cache.clear()
- ModelView._fields_view_get_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- # Restart the cache
- cls._get_access_cache.clear()
- ModelView._fields_view_get_cache.clear()
- return res
-
- @classmethod
- def delete(cls, field_accesses):
- super().delete(field_accesses)
- # Restart the cache
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_access_cache.clear()
ModelView._fields_view_get_cache.clear()
@@ -1044,25 +1012,8 @@ class ModelButton(
cls._order.insert(0, ('model', 'ASC'))
@classmethod
- def create(cls, vlist):
- result = super().create(vlist)
- cls._rules_cache.clear()
- cls._reset_cache.clear()
- cls._groups_cache.clear()
- cls._view_attributes_cache.clear()
- return result
-
- @classmethod
- def write(cls, buttons, values, *args):
- super().write(buttons, values, *args)
- cls._rules_cache.clear()
- cls._reset_cache.clear()
- cls._groups_cache.clear()
- cls._view_attributes_cache.clear()
-
- @classmethod
- def delete(cls, buttons):
- super().delete(buttons)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._rules_cache.clear()
cls._reset_cache.clear()
cls._groups_cache.clear()
@@ -1172,26 +1123,11 @@ class ModelButtonGroup(DeactivableMixin, ModelSQL):
'res.group', "Group", ondelete='CASCADE', required=True)
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
- result = super().create(vlist)
- # Restart the cache for get_groups
- pool.get('ir.model.button')._groups_cache.clear()
- return result
-
- @classmethod
- def write(cls, records, values, *args):
- pool = Pool()
- super().write(records, values, *args)
- # Restart the cache for get_groups
- pool.get('ir.model.button')._groups_cache.clear()
-
- @classmethod
- def delete(cls, records):
- pool = Pool()
- super().delete(records)
- # Restart the cache for get_groups
- pool.get('ir.model.button')._groups_cache.clear()
+ Button = pool.get('ir.model.button')
+ super().on_modification(mode, records, field_names=field_names)
+ Button._groups_cache.clear()
class ModelButtonRule(ModelSQL, ModelView):
@@ -1249,28 +1185,10 @@ class ModelButtonRule(ModelSQL, ModelView):
return len(users) >= self.number_user
@classmethod
- def create(cls, vlist):
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
ModelButton = pool.get('ir.model.button')
- result = super().create(vlist)
- # Restart the cache for get_rules
- ModelButton._rules_cache.clear()
- return result
-
- @classmethod
- def write(cls, buttons, values, *args):
- pool = Pool()
- ModelButton = pool.get('ir.model.button')
- super().write(buttons, values, *args)
- # Restart the cache for get_rules
- ModelButton._rules_cache.clear()
-
- @classmethod
- def delete(cls, buttons):
- pool = Pool()
- ModelButton = pool.get('ir.model.button')
- super().delete(buttons)
- # Restart the cache for get_rules
+ super().on_modification(mode, records, field_names=field_names)
ModelButton._rules_cache.clear()
@@ -1430,22 +1348,11 @@ class ModelData(
return False
@classmethod
- def create(cls, *args):
- records = super().create(*args)
- cls._has_model_cache.clear()
- return records
-
- @classmethod
- def write(cls, data, values, *args):
- super().write(data, values, *args)
- # Restart the cache for get_id
- cls._get_id_cache.clear()
- cls._has_model_cache.clear()
-
- @classmethod
- def delete(cls, records):
- super().delete(records)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._has_model_cache.clear()
+ if mode == 'write':
+ cls._get_id_cache.clear()
@classmethod
def has_model(cls, model):
diff --git a/trytond/trytond/ir/module.py b/trytond/trytond/ir/module.py
index 05d3869bcc..9d7bdaa3e8 100644
--- a/trytond/trytond/ir/module.py
+++ b/trytond/trytond/ir/module.py
@@ -123,16 +123,18 @@ class Module(ModelSQL, ModelView):
return child_ids
@classmethod
- def delete(cls, records):
- for module in records:
- if module.state in (
- 'activated',
- 'to upgrade',
- 'to remove',
- 'to activate',
- ):
- raise AccessError(gettext('ir.msg_module_delete_state'))
- return super().delete(records)
+ def check_modification(cls, mode, records, values=None, external=False):
+ super().check_modification(
+ mode, records, values=values, external=external)
+ if mode == 'delete':
+ for module in records:
+ if module.state in (
+ 'activated',
+ 'to upgrade',
+ 'to remove',
+ 'to activate',
+ ):
+ raise AccessError(gettext('ir.msg_module_delete_state'))
@classmethod
def on_written(cls, modules):
diff --git a/trytond/trytond/ir/resource.py b/trytond/trytond/ir/resource.py
index 0762000935..b096251d8a 100644
--- a/trytond/trytond/ir/resource.py
+++ b/trytond/trytond/ir/resource.py
@@ -117,13 +117,13 @@ class ResourceAccessMixin(ModelStorage):
@classmethod
def write(cls, records, values, *args):
- all_records = []
+ all_ids = []
actions = iter((records, values) + args)
for other_records, _ in zip(actions, actions):
- all_records += other_records
- cls.check_access([a.id for a in all_records], mode='write')
+ all_ids.extend(r.id for r in other_records)
+ cls.check_access(all_ids, mode='write')
super().write(records, values, *args)
- cls.check_access(all_records, mode='write')
+ cls.check_access(all_ids, mode='write')
@classmethod
def create(cls, vlist):
diff --git a/trytond/trytond/ir/rule.py b/trytond/trytond/ir/rule.py
index 43071d5f79..5e6ca02cb1 100644
--- a/trytond/trytond/ir/rule.py
+++ b/trytond/trytond/ir/rule.py
@@ -144,23 +144,11 @@ class RuleGroup(
return True
@classmethod
- def delete(cls, groups):
- super().delete(groups)
- # Restart the cache on the domain_get method of ir.rule
- Pool().get('ir.rule')._domain_get_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- # Restart the cache on the domain_get method of ir.rule
- Pool().get('ir.rule')._domain_get_cache.clear()
- return res
-
- @classmethod
- def write(cls, groups, vals, *args):
- super().write(groups, vals, *args)
- # Restart the cache on the domain_get method of ir.rule
- Pool().get('ir.rule')._domain_get_cache.clear()
+ def on_modification(cls, mode, groups, field_names=None):
+ pool = Pool()
+ Rule = pool.get('ir.rule')
+ super().on_modification(mode, groups, field_names=field_names)
+ Rule._domain_get_cache.clear()
class RuleGroup_Group(ModelSQL):
@@ -395,20 +383,6 @@ class Rule(ModelSQL, ModelView):
**Model.__names__()))
@classmethod
- def delete(cls, rules):
- super().delete(rules)
- # Restart the cache on the domain_get method of ir.rule
- cls._domain_get_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- # Restart the cache on the domain_get method of ir.rule
- cls._domain_get_cache.clear()
- return res
-
- @classmethod
- def write(cls, rules, vals, *args):
- super().write(rules, vals, *args)
- # Restart the cache on the domain_get method
+ def on_modification(cls, mode, rules, field_names=None):
+ super().on_modification(mode, rules, field_names=field_names)
cls._domain_get_cache.clear()
diff --git a/trytond/trytond/ir/session.py b/trytond/trytond/ir/session.py
index 850de0429a..fd831ed311 100644
--- a/trytond/trytond/ir/session.py
+++ b/trytond/trytond/ir/session.py
@@ -42,9 +42,9 @@ class Session(ModelSQL):
return token_hex(nbytes)
@classmethod
- def write(cls, *args):
- super().write(*args)
- for sessions in args[0:None:2]:
+ def on_modification(cls, mode, sessions, field_names=None):
+ super().on_modification(mode, sessions, field_names=field_names)
+ if mode == 'write':
for session in sessions:
cls._session_reset_cache.set(session.key, session.write_date)
@@ -148,13 +148,11 @@ class Session(ModelSQL):
cls.delete(sessions)
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- # Ensure to get a different key for each record
- # default methods are called only once
- values.setdefault('key', cls.default_key())
- return super().create(vlist)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and 'key' not in values:
+ values['key'] = cls.default_key()
+ return values
class SessionWizard(ModelSQL):
diff --git a/trytond/trytond/ir/translation.py b/trytond/trytond/ir/translation.py
index ce912c4d0c..7d090fa49f 100644
--- a/trytond/trytond/ir/translation.py
+++ b/trytond/trytond/ir/translation.py
@@ -707,26 +707,16 @@ class Translation(
return super().copy(translations, default=default)
@classmethod
- def delete(cls, translations):
- cls.__clear_cache_for(translations)
- return super().delete(translations)
+ def preprocess_values(cls, mode, values):
+ context = Transaction().context
+ values = super().preprocess_values(mode, values)
+ if mode == 'create' and not values.get('module'):
+ values['module'] = context.get('module')
+ return values
@classmethod
- def create(cls, vlist):
- vlist = [x.copy() for x in vlist]
-
- for vals in vlist:
- if not vals.get('module'):
- if Transaction().context.get('module'):
- vals['module'] = Transaction().context['module']
- translations = super().create(vlist)
- cls.__clear_cache_for(translations)
- return translations
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- translations = sum(args[0:None:2], [])
+ def on_modification(cls, mode, translations, field_names=None):
+ super().on_modification(mode, translations, field_names=field_names)
cls.__clear_cache_for(translations)
@classmethod
diff --git a/trytond/trytond/ir/trigger.py b/trytond/trytond/ir/trigger.py
index 50d7852d11..00c53dd65f 100644
--- a/trytond/trytond/ir/trigger.py
+++ b/trytond/trytond/ir/trigger.py
@@ -278,22 +278,8 @@ class Trigger(DeactivableMixin, ModelSQL, ModelView):
trigger.trigger_action(records)
@classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- # Restart the cache on the get_triggers method of ir.trigger
- cls._get_triggers_cache.clear()
- return res
-
- @classmethod
- def write(cls, triggers, values, *args):
- super().write(triggers, values, *args)
- # Restart the cache on the get_triggers method of ir.trigger
- cls._get_triggers_cache.clear()
-
- @classmethod
- def delete(cls, records):
- super().delete(records)
- # Restart the cache on the get_triggers method of ir.trigger
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._get_triggers_cache.clear()
diff --git a/trytond/trytond/ir/ui/icon.py b/trytond/trytond/ir/ui/icon.py
index a8825a51f1..8a4b71905f 100644
--- a/trytond/trytond/ir/ui/icon.py
+++ b/trytond/trytond/ir/ui/icon.py
@@ -61,17 +61,6 @@ class Icon(
return fp.read()
@classmethod
- def create(cls, vlist):
- icons = super().create(vlist)
- cls._list_icons.clear()
- return icons
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._list_icons.clear()
-
- @classmethod
- def delete(cls, icons):
- super().delete(icons)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._list_icons.clear()
diff --git a/trytond/trytond/ir/ui/view.py b/trytond/trytond/ir/ui/view.py
index 6f3955da7d..858d6b70d0 100644
--- a/trytond/trytond/ir/ui/view.py
+++ b/trytond/trytond/ir/ui/view.py
@@ -266,24 +266,8 @@ class View(
cls.write(views, {'data': value})
@classmethod
- def delete(cls, views):
- super().delete(views)
- # Restart the cache
- cls._view_get_cache.clear()
- ModelView._fields_view_get_cache.clear()
-
- @classmethod
- def create(cls, vlist):
- views = super().create(vlist)
- # Restart the cache
- cls._view_get_cache.clear()
- ModelView._fields_view_get_cache.clear()
- return views
-
- @classmethod
- def write(cls, views, values, *args):
- super().write(views, values, *args)
- # Restart the cache
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._view_get_cache.clear()
ModelView._fields_view_get_cache.clear()
@@ -492,19 +476,8 @@ class ViewTreeWidth(
]
@classmethod
- def delete(cls, records):
- ModelView._fields_view_get_cache.clear()
- super().delete(records)
-
- @classmethod
- def create(cls, vlist):
- res = super().create(vlist)
- ModelView._fields_view_get_cache.clear()
- return res
-
- @classmethod
- def write(cls, records, values, *args):
- super().write(records, values, *args)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
ModelView._fields_view_get_cache.clear()
@classmethod
@@ -602,20 +575,9 @@ class ViewTreeOptional(
view=record.view.rec_name))
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
+ def on_modification(cls, mode, record, field_names=None):
+ super().on_modification(mode, record, field_names=field_names)
ModelView._fields_view_get_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- ModelView._fields_view_get_cache.clear()
-
- @classmethod
- def delete(cls, records):
- ModelView._fields_view_get_cache.clear()
- super().delete(records)
@classmethod
def set_optional(cls, view_id, fields):
diff --git a/trytond/trytond/model/avatar.py b/trytond/trytond/model/avatar.py
index 9e5ba32ce2..26adcc20c5 100644
--- a/trytond/trytond/model/avatar.py
+++ b/trytond/trytond/model/avatar.py
@@ -87,15 +87,9 @@ def avatar_mixin(size=64, default=None):
if default:
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
- cls.generate_avatar(records, field=default)
- return records
-
- @classmethod
- def write(cls, *args):
- records = sum(args[0:None:2], [])
- super().write(*args)
- cls.generate_avatar(records, field=default)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if mode in {'create', 'write'}:
+ cls.generate_avatar(records, field=default)
return AvatarMixin
diff --git a/trytond/trytond/model/dictschema.py b/trytond/trytond/model/dictschema.py
index a72ae49e1c..392dbd603c 100644
--- a/trytond/trytond/model/dictschema.py
+++ b/trytond/trytond/model/dictschema.py
@@ -214,19 +214,8 @@ class DictSchemaMixin(object):
return fields
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
- cls._relation_fields_cache.clear()
- return records
-
- @classmethod
- def write(cls, *args):
- super().write(*args)
- cls._relation_fields_cache.clear()
-
- @classmethod
- def delete(cls, records):
- super().delete(records)
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
cls._relation_fields_cache.clear()
def format(self, value, lang=None):
diff --git a/trytond/trytond/model/digits.py b/trytond/trytond/model/digits.py
index 16c7c3392e..47a29511a3 100644
--- a/trytond/trytond/model/digits.py
+++ b/trytond/trytond/model/digits.py
@@ -29,6 +29,7 @@ class DigitsMixin:
return (None, self.digits)
@classmethod
- def write(cls, *args):
- super().write(*args)
- cls._digits_cache.clear()
+ def on_modification(cls, mode, records, field_names=None):
+ super().on_modification(mode, records, field_names=field_names)
+ if mode == 'write':
+ cls._digits_cache.clear()
diff --git a/trytond/trytond/model/fields/fmany2one.py b/trytond/trytond/model/fields/fmany2one.py
index a730e3e9d8..43caa6a688 100644
--- a/trytond/trytond/model/fields/fmany2one.py
+++ b/trytond/trytond/model/fields/fmany2one.py
@@ -37,23 +37,11 @@ def fmany2one(
on_delete=getattr(cls, name).ondelete)
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
- if name in values:
- values[sources[0]] = convert_value(values.pop(name))
- return super().create(vlist)
-
- @classmethod
- def write(cls, *args):
- actions = iter(args)
- args = []
- for records, values in zip(actions, actions):
- if name in values:
- values = values.copy()
- values[sources[0]] = convert_value(values.pop(name))
- args.extend((records, values))
- super().write(*args)
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if name in values:
+ values[sources[0]] = convert_value(values.pop(name))
+ return values
@classmethod
def getter(cls, records, name):
diff --git a/trytond/trytond/res/group.py b/trytond/trytond/res/group.py
index c2b995808a..f577b47981 100644
--- a/trytond/trytond/res/group.py
+++ b/trytond/trytond/res/group.py
@@ -58,11 +58,11 @@ class Group(DeactivableMixin, tree(), ModelSQL, ModelView):
cls._order.insert(0, ('name', 'ASC'))
@classmethod
- def write(cls, *args):
- super().write(*args)
+ def on_modification(cls, mode, groups, field_names=None):
pool = Pool()
- # Restart the cache for get_groups
- pool.get('res.user')._get_groups_cache.clear()
+ User = pool.get('res.user')
+ super().on_modification(mode, groups, field_names=field_names)
+ User._get_groups_cache.clear()
@classmethod
def copy(cls, groups, default=None):
diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py
index 1197d76919..908f25de2a 100644
--- a/trytond/trytond/res/user.py
+++ b/trytond/trytond/res/user.py
@@ -328,16 +328,36 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
attrgetter('create_uid.id'))))
return result
- @staticmethod
- def _convert_vals(vals):
- vals = vals.copy()
+ @classmethod
+ def preprocess_values(cls, mode, values):
pool = Pool()
Action = pool.get('ir.action')
- if 'menu' in vals:
- vals['menu'] = Action.get_action_id(vals['menu'])
- if vals.get('email'):
- vals['email'] = normalize_email(vals['email'])
- return vals
+ values = super().preprocess_values(mode, values)
+ if values.get('menu') is not None:
+ values['menu'] = Action.get_action_id(values['menu'])
+ if values.get('email'):
+ values['email'] = normalize_email(values['email'])
+ return values
+
+ @classmethod
+ def on_modification(cls, mode, users, field_names=None):
+ pool = Pool()
+ Session = pool.get('ir.session')
+ UserDevice = pool.get('res.user.device')
+
+ super().on_modification(mode, users, field_names=field_names)
+
+ if mode == 'write':
+ if (field_names is None
+ or {'active', 'password'} & set(field_names)):
+ Session.clear(users)
+ UserDevice.clear([u.login for u in users])
+
+ # Clean cursor cache as it could be filled by domain_get
+ for cache in Transaction().cache.values():
+ if cls.__name__ in cache:
+ for user in users:
+ cache[cls.__name__].pop(user.id, None)
@classmethod
def check_xml_record(cls, records, values):
@@ -369,41 +389,6 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
cache[user.id]['password_hash'] = None
return users
- @classmethod
- def create(cls, vlist):
- vlist = [cls._convert_vals(vals) for vals in vlist]
- return super().create(vlist)
-
- @classmethod
- def write(cls, users, values, *args):
- pool = Pool()
- Session = pool.get('ir.session')
- UserDevice = pool.get('res.user.device')
-
- actions = iter((users, values) + args)
- all_users = []
- session_to_clear = []
- users_to_clear = []
- args = []
- for users, values in zip(actions, actions):
- all_users += users
- args.extend((users, cls._convert_vals(values)))
-
- if values.keys() & {'active', 'password'}:
- session_to_clear += users
- users_to_clear += [u.login for u in users]
-
- super().write(*args)
-
- Session.clear(session_to_clear)
- UserDevice.clear(users_to_clear)
-
- # Clean cursor cache as it could be filled by domain_get
- for cache in Transaction().cache.values():
- if cls.__name__ in cache:
- for user in all_users:
- cache[cls.__name__].pop(user.id, None)
-
@classmethod
def delete(cls, users):
raise DeleteError(gettext('res.msg_user_delete_forbidden'))
@@ -911,28 +896,15 @@ class UserAction(ModelSQL):
action = fields.Many2One('ir.action', 'Action', ondelete='CASCADE',
required=True)
- @staticmethod
- def _convert_values(values):
+ @classmethod
+ def preprocess_values(cls, mode, values):
pool = Pool()
Action = pool.get('ir.action')
- values = values.copy()
- if values.get('action'):
+ values = super().preprocess_values(mode, values)
+ if values.get('action') is not None:
values['action'] = Action.get_action_id(values['action'])
return values
- @classmethod
- def create(cls, vlist):
- vlist = [cls._convert_values(values) for values in vlist]
- return super().create(vlist)
-
- @classmethod
- def write(cls, records, values, *args):
- actions = iter((records, values) + args)
- args = []
- for records, values in zip(actions, actions):
- args.extend((records, cls._convert_values(values)))
- super().write(*args)
-
class UserGroup(ModelSQL):
__name__ = 'res.user-res.group'
@@ -942,26 +914,11 @@ class UserGroup(ModelSQL):
required=True)
@classmethod
- def create(cls, vlist):
- records = super().create(vlist)
+ def on_modification(cls, mode, records, field_names=None):
pool = Pool()
- # Restart the cache for get_groups
- pool.get('res.user')._get_groups_cache.clear()
- return records
-
- @classmethod
- def write(cls, groups, values, *args):
- super().write(groups, values, *args)
- pool = Pool()
- # Restart the cache for get_groups
- pool.get('res.user')._get_groups_cache.clear()
-
- @classmethod
- def delete(cls, groups):
- super().delete(groups)
- pool = Pool()
- # Restart the cache for get_groups
- pool.get('res.user')._get_groups_cache.clear()
+ User = pool.get('res.user')
+ super().on_modification(mode, records, field_names=field_names)
+ User._get_groups_cache.clear()
@classmethod
def user_group_all_table(cls):
@@ -1117,14 +1074,13 @@ class UserApplication(Workflow, ModelSQL, ModelView):
return record
@classmethod
- def create(cls, vlist):
- vlist = [v.copy() for v in vlist]
- for values in vlist:
+ def preprocess_values(cls, mode, values):
+ values = super().preprocess_values(mode, values)
+ if 'key' not in values:
# Ensure we get a different key for each record
# default methods are called only once
- values.setdefault('key', cls.default_key())
- applications = super().create(vlist)
- return applications
+ values['key'] = cls.default_key()
+ return values
class EmailResetPassword(Report):