Add Erase Party wizard

issue7169
review37031002
This commit is contained in:
Cédric Krier committed 2018-03-24 00:02:03 +01:00
1 parent 11fbeb58a9
commit 611b5b5fd2
8 files changed
+264 -23

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add Erase Party wizard
* Search party also by contact mechanisms
* Add contact_mechanism_get
* Add name to contact mechanism
+22 -20
View File
@@ -2,30 +2,32 @@
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from .category import *
from .party import *
from .address import *
from .contact_mechanism import *
from .configuration import *
from . import category
from . import party
from . import address
from . import contact_mechanism
from . import configuration
def register():
Pool.register(
Category,
Party,
PartyLang,
PartyCategory,
PartyIdentifier,
CheckVIESResult,
PartyReplaceAsk,
Address,
AddressFormat,
ContactMechanism,
Configuration,
ConfigurationSequence,
ConfigurationLang,
category.Category,
party.Party,
party.PartyLang,
party.PartyCategory,
party.PartyIdentifier,
party.CheckVIESResult,
party.PartyReplaceAsk,
party.PartyEraseAsk,
address.Address,
address.AddressFormat,
contact_mechanism.ContactMechanism,
configuration.Configuration,
configuration.ConfigurationSequence,
configuration.ConfigurationLang,
module='party', type_='model')
Pool.register(
CheckVIES,
PartyReplace,
party.CheckVIES,
party.PartyReplace,
party.PartyErase,
module='party', type_='wizard')
+3
View File
@@ -29,6 +29,9 @@ parties with the VIES web service.
The *Replace* wizard allows to replace duplicate record by the original and
relink all the related documents.
The *Erase* wizard allows to erase all personal data of a party from the system
included the historized data and the resources attached for all the parties
which were replaced by this one.
Address
*******
+140 -3
View File
@@ -2,8 +2,8 @@
# this repository contains the full copyright notices and license terms.
import stdnum.eu.vat as vat
import stdnum.exceptions
from sql import Null, Column
from sql.functions import CharLength
from sql import Null, Column, Cast, Literal
from sql.functions import CharLength, Substring, Position
from trytond.model import (ModelView, ModelSQL, MultiValueMixin, ValueMixin,
fields, Unique, sequence_ordered)
@@ -16,7 +16,8 @@ from trytond.tools.multivalue import migrate_property
__all__ = ['Party', 'PartyLang', 'PartyCategory', 'PartyIdentifier',
'CheckVIESResult', 'CheckVIES',
'PartyReplace', 'PartyReplaceAsk']
'PartyReplace', 'PartyReplaceAsk',
'PartyErase', 'PartyEraseAsk']
VAT_COUNTRIES = [('', '')]
STATES = {
@@ -576,3 +577,139 @@ class PartyReplaceAsk(ModelView):
def on_change_source(self):
if self.source and self.source.replaced_by:
self.destination = self.source.replaced_by
class PartyErase(Wizard):
"Erase Party"
__name__ = 'party.erase'
start_state = 'ask'
ask = StateView('party.erase.ask', 'party.erase_ask_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Erase", 'erase', 'tryton-clear', default=True),
])
erase = StateTransition()
@classmethod
def __setup__(cls):
super(PartyErase, cls).__setup__()
cls._error_messages.update({
'active_party': (
'The party "%(party)s" can not be erased '
'because he is still active.'),
})
def transition_erase(self):
pool = Pool()
Party = pool.get('party.party')
cursor = Transaction().connection.cursor()
def convert_from(table, tables):
right, condition = tables[None]
if table:
table = table.join(right, condition=condition)
else:
table = right
for k, sub_tables in tables.iteritems():
if k is None:
continue
table = convert_from(table, sub_tables)
return table
resources = self.get_resources()
parties = replacing = [self.ask.party]
with Transaction().set_context(active_test=False):
while replacing:
replacing = Party.search([
('replaced_by', 'in', map(int, replacing)),
])
parties += replacing
for party in parties:
self.check_erase(party)
to_erase = self.to_erase(party.id)
for Model, domain, resource, columns, values in to_erase:
assert issubclass(Model, ModelSQL)
assert len(columns) == len(values)
if 'active' in Model._fields:
records = Model.search(domain)
Model.write(records, {'active': False})
tables, where = Model.search_domain(domain, active_test=False)
from_ = convert_from(None, tables)
table, _ = tables[None]
query = from_.select(table.id, where=where)
model_tables = [Model.__table__()]
if Model._history:
model_tables.append(Model.__table_history__())
for table in model_tables:
sql_columns, sql_values = [], []
for column, value in zip(columns, values):
column = Column(table, column)
sql_columns.append(column)
sql_values.append(
value(column) if callable(value) else value)
cursor.execute(*table.update(
sql_columns, sql_values,
where=table.id.in_(query)))
if not resource:
continue
for Resource in resources:
model_tables = [Resource.__table__()]
if Resource._history:
model_tables.append(Resource.__table_history__())
for table in model_tables:
cursor.execute(*table.delete(
where=table.resource.like(
Model.__name__ + ',%')
& Cast(Substring(table.resource,
Position(',', table.resource) +
Literal(1)),
Model.id.sql_type().base).in_(query)))
return 'end'
def check_erase(self, party):
if party.active:
self.raise_user_error('active_party', {
'party': party.rec_name,
})
def to_erase(self, party_id):
pool = Pool()
Party = pool.get('party.party')
Identifier = pool.get('party.identifier')
Address = pool.get('party.address')
ContactMechanism = pool.get('party.contact_mechanism')
return [
(Party, [('id', '=', party_id)], True,
['name'],
[None]),
(Identifier, [('party', '=', party_id)], True,
['type', 'code'],
[None, '****']),
(Address, [('party', '=', party_id)], True,
['name', 'street', 'zip', 'city', 'country', 'subdivision'],
[None, None, None, None, None, None]),
(ContactMechanism, [('party', '=', party_id)], True,
['value', 'name', 'comment'],
[None, None, None]),
]
@classmethod
def get_resources(cls):
pool = Pool()
Attachment = pool.get('ir.attachment')
Note = pool.get('ir.note')
return [Attachment, Note]
class PartyEraseAsk(ModelView):
"Erase Party"
__name__ = 'party.erase.ask'
party = fields.Many2One('party.party', "Party", required=True,
help="The party to be erased.")
@classmethod
def default_party(cls):
context = Transaction().context
if context.get('active_model') == 'party.party':
return context.get('active_id')
+21
View File
@@ -161,5 +161,26 @@ this repository contains the full copyright notices and license terms. -->
<field name="type">form</field>
<field name="name">replace_ask_form</field>
</record>
<record model="ir.action.wizard" id="wizard_erase">
<field name="name">Erase</field>
<field name="wiz_name">party.erase</field>
<field name="model">party.party</field>
</record>
<record model="ir.action-res.group" id="wizard_erase-group_party_admin">
<field name="action" ref="wizard_erase"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.action.keyword" id="wizard_erase_keyword1">
<field name="keyword">form_action</field>
<field name="model">party.party,-1</field>
<field name="action" ref="wizard_erase"/>
</record>
<record model="ir.ui.view" id="erase_ask_view_form">
<field name="model">party.erase.ask</field>
<field name="type">form</field>
<field name="name">erase_ask_form</field>
</record>
</data>
</tryton>
+65
View File
@@ -0,0 +1,65 @@
====================
Party Erase Scenario
====================
Imports::
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules
Install party::
>>> config = activate_modules('party')
Create a party::
>>> Party = Model.get('party.party')
>>> Attachment = Model.get('ir.attachment')
>>> party = Party(name='Pam')
>>> _ = party.identifiers.new(code="Identifier")
>>> _ = party.contact_mechanisms.new(type='other', value="mechanism")
>>> party.save()
>>> address, = party.addresses
>>> address.street = "St sample, 15"
>>> address.city = "City"
>>> address.save()
>>> identifier, = party.identifiers
>>> contact_mechanism, = party.contact_mechanisms
>>> attachment = Attachment()
>>> attachment.resource = party
>>> attachment.name = "Attachment"
>>> attachment.save()
Try erase active party::
>>> erase = Wizard('party.erase', models=[party])
>>> erase.form.party == party
True
>>> erase.execute('erase') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
UserError: ...
Erase inactive party::
>>> party.active = False
>>> party.save()
>>> erase = Wizard('party.erase', models=[party])
>>> erase.form.party == party
True
>>> erase.execute('erase')
Check fields have been erased::
>>> party.name
>>> identifier.reload()
>>> identifier.code
u'****'
>>> address.reload()
>>> address.street
>>> address.city
>>> contact_mechanism.reload()
>>> contact_mechanism.value
>>> Attachment.find()
[]
+5
View File
@@ -423,4 +423,9 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_party_erase.rst',
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="party"/>
<field name="party"/>
</form>