Add Replace Party wizard
issue5820 review26581003
This commit is contained in:
1 parent
12294c209f
commit
3cd9694f5f
9 files changed
+220
-5
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add Replace Party wizard
|
||||
* Remove streetbis from Address
|
||||
* Add address format per country and language
|
||||
* Restrict subdivision address to first level
|
||||
|
||||
@@ -16,6 +16,7 @@ def register():
|
||||
PartyCategory,
|
||||
PartyIdentifier,
|
||||
CheckVIESResult,
|
||||
PartyReplaceAsk,
|
||||
Address,
|
||||
AddressFormat,
|
||||
ContactMechanism,
|
||||
@@ -23,4 +24,5 @@ def register():
|
||||
module='party', type_='model')
|
||||
Pool.register(
|
||||
CheckVIES,
|
||||
PartyReplace,
|
||||
module='party', type_='wizard')
|
||||
@@ -4,18 +4,19 @@ from itertools import groupby
|
||||
|
||||
import stdnum.eu.vat as vat
|
||||
import stdnum.exceptions
|
||||
from sql import Null
|
||||
from sql import Null, Column
|
||||
from sql.functions import CharLength
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique
|
||||
from trytond.wizard import Wizard, StateTransition, StateView, Button
|
||||
from trytond.pyson import Eval
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond import backend
|
||||
|
||||
__all__ = ['Party', 'PartyCategory', 'PartyIdentifier',
|
||||
'CheckVIESResult', 'CheckVIES']
|
||||
'CheckVIESResult', 'CheckVIES',
|
||||
'PartyReplace', 'PartyReplaceAsk']
|
||||
|
||||
VAT_COUNTRIES = [('', '')]
|
||||
STATES = {
|
||||
@@ -48,7 +49,14 @@ class Party(ModelSQL, ModelView):
|
||||
'Contact Mechanisms', states=STATES, depends=DEPENDS)
|
||||
categories = fields.Many2Many('party.party-party.category',
|
||||
'party', 'category', 'Categories', states=STATES, depends=DEPENDS)
|
||||
active = fields.Boolean('Active', select=True)
|
||||
active = fields.Boolean('Active', select=True, states={
|
||||
'readonly': Bool(Eval('replaced_by')),
|
||||
},
|
||||
depends=['replaced_by'])
|
||||
replaced_by = fields.Many2One('party.party', "Replaced By", readonly=True,
|
||||
states={
|
||||
'invisible': ~Eval('replaced_by'),
|
||||
})
|
||||
full_name = fields.Function(fields.Char('Full Name'), 'get_full_name')
|
||||
phone = fields.Function(fields.Char('Phone'), 'get_mechanism')
|
||||
mobile = fields.Function(fields.Char('Mobile'), 'get_mechanism')
|
||||
@@ -388,3 +396,115 @@ class CheckVIES(Wizard):
|
||||
'parties_succeed': [p.id for p in self.result.parties_succeed],
|
||||
'parties_failed': [p.id for p in self.result.parties_failed],
|
||||
}
|
||||
|
||||
|
||||
class PartyReplace(Wizard):
|
||||
"Replace Party"
|
||||
__name__ = 'party.replace'
|
||||
start_state = 'ask'
|
||||
ask = StateView('party.replace.ask', 'party.replace_ask_view_form', [
|
||||
Button("Cancel", 'end', 'tryton-cancel'),
|
||||
Button("Replace", 'replace', 'tryton-find-replace', default=True),
|
||||
])
|
||||
replace = StateTransition()
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(PartyReplace, cls).__setup__()
|
||||
cls._error_messages.update({
|
||||
'different_name': ("Parties have different names: "
|
||||
"%(source_name)s vs %(destination_name)s."),
|
||||
'different_vat_code': ("Parties have different VAT Code: "
|
||||
"%(source_code)s vs %(destination_code)s."),
|
||||
})
|
||||
|
||||
def check_similarity(self):
|
||||
source = self.ask.source
|
||||
destination = self.ask.destination
|
||||
if source.name != destination.name:
|
||||
key = 'party.replace name %s %s' % (source.id, destination.id)
|
||||
self.raise_user_warning(key, 'different_name', {
|
||||
'source_name': source.name,
|
||||
'destination_name': destination.name,
|
||||
})
|
||||
if source.vat_code != destination.vat_code:
|
||||
key = 'party.replace vat_code %s %s' % (source.id, destination.id)
|
||||
self.raise_user_warning(key, 'different_vat_code', {
|
||||
'source_code': source.vat_code,
|
||||
'destination_code': destination.vat_code,
|
||||
})
|
||||
|
||||
def transition_replace(self):
|
||||
pool = Pool()
|
||||
Address = pool.get('party.address')
|
||||
ContactMechanism = pool.get('party.contact_mechanism')
|
||||
transaction = Transaction()
|
||||
|
||||
self.check_similarity()
|
||||
source = self.ask.source
|
||||
destination = self.ask.destination
|
||||
|
||||
Address.write(list(source.addresses), {
|
||||
'active': False,
|
||||
})
|
||||
ContactMechanism.write(list(source.contact_mechanisms), {
|
||||
'active': False,
|
||||
})
|
||||
source.replaced_by = destination
|
||||
source.active = False
|
||||
source.save()
|
||||
|
||||
cursor = transaction.connection.cursor()
|
||||
for model_name, field_name in self.fields_to_replace():
|
||||
Model = pool.get(model_name)
|
||||
table = Model.__table__()
|
||||
column = Column(table, field_name)
|
||||
where = column == source.id
|
||||
|
||||
if transaction.database.has_returning():
|
||||
returning = [table.id]
|
||||
else:
|
||||
cursor.execute(*table.select(table.id, where=where))
|
||||
ids = [x[0] for x in cursor]
|
||||
returning = None
|
||||
|
||||
cursor.execute(*table.update(
|
||||
[column],
|
||||
[destination.id],
|
||||
where=where,
|
||||
returning=returning))
|
||||
|
||||
if transaction.database.has_returning():
|
||||
ids = [x[0] for x in cursor]
|
||||
|
||||
Model._insert_history(ids)
|
||||
return 'end'
|
||||
|
||||
@classmethod
|
||||
def fields_to_replace(cls):
|
||||
return [
|
||||
('party.address', 'party'),
|
||||
('party.contact_mechanism', 'party'),
|
||||
]
|
||||
|
||||
|
||||
class PartyReplaceAsk(ModelView):
|
||||
"Replace Party"
|
||||
__name__ = 'party.replace.ask'
|
||||
source = fields.Many2One('party.party', "Source", required=True)
|
||||
destination = fields.Many2One('party.party', "Destination", required=True,
|
||||
domain=[
|
||||
('id', '!=', Eval('source', -1)),
|
||||
],
|
||||
depends=['source'])
|
||||
|
||||
@classmethod
|
||||
def default_source(cls):
|
||||
context = Transaction().context
|
||||
if context.get('active_model') == 'party.party':
|
||||
return context.get('active_id')
|
||||
|
||||
@fields.depends('source')
|
||||
def on_change_source(self):
|
||||
if self.source and self.source.replaced_by:
|
||||
self.destination = self.source.replaced_by
|
||||
@@ -133,5 +133,26 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">check_vies_result</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_replace">
|
||||
<field name="name">Replace</field>
|
||||
<field name="wiz_name">party.replace</field>
|
||||
<field name="model">party.party</field>
|
||||
</record>
|
||||
<record model="ir.action-res.group"
|
||||
id="wizard_replace-group_party_admin">
|
||||
<field name="action" ref="wizard_replace"/>
|
||||
<field name="group" ref="group_party_admin"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="wizard_replace_keyword1">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">party.party,-1</field>
|
||||
<field name="action" ref="wizard_replace"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="replace_ask_view_form">
|
||||
<field name="model">party.replace.ask</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">replace_ask_form</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -53,6 +53,12 @@ for dep in info.get('depends', []):
|
||||
requires.append(get_require_version('trytond_%s' % dep))
|
||||
requires.append(get_require_version('trytond'))
|
||||
|
||||
tests_require = [get_require_version('proteus')]
|
||||
dependency_links = []
|
||||
if minor_version % 2:
|
||||
# Add development index for testing with proteus
|
||||
dependency_links.append('https://trydevpi.tryton.org/')
|
||||
|
||||
setup(name=name,
|
||||
version=version,
|
||||
description='Tryton module with parties and addresses',
|
||||
@@ -70,7 +76,7 @@ setup(name=name,
|
||||
package_data={
|
||||
'trytond.modules.party': (info.get('xml', [])
|
||||
+ ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.odt',
|
||||
'icons/*.svg']),
|
||||
'icons/*.svg', 'tests/*.rst']),
|
||||
},
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
@@ -116,5 +122,9 @@ setup(name=name,
|
||||
""",
|
||||
test_suite='tests',
|
||||
test_loader='trytond.test_loader:Loader',
|
||||
tests_require=tests_require,
|
||||
use_2to3=True,
|
||||
convert_2to3_doctests=[
|
||||
'tests/scenario_party_replace.rst',
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
======================
|
||||
Party Replace Scenario
|
||||
======================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from proteus import Model, Wizard
|
||||
>>> from trytond.tests.tools import install_modules
|
||||
|
||||
Install party::
|
||||
|
||||
>>> config = install_modules('party')
|
||||
|
||||
Create a party::
|
||||
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> party1 = Party(name='Pam')
|
||||
>>> party1.save()
|
||||
>>> address1, = party1.addresses
|
||||
|
||||
Create a second party similar party::
|
||||
|
||||
>>> party2 = Party(name='Pam')
|
||||
>>> party2.save()
|
||||
>>> address2, = party2.addresses
|
||||
|
||||
Replace the second by the first party::
|
||||
|
||||
>>> replace = Wizard('party.replace', models=[party2])
|
||||
>>> replace.form.source == party2
|
||||
True
|
||||
>>> replace.form.destination = party1
|
||||
>>> replace.execute('replace')
|
||||
|
||||
>>> party2.reload()
|
||||
>>> bool(party2.active)
|
||||
False
|
||||
|
||||
>>> address2.reload()
|
||||
>>> address2.party == party1
|
||||
True
|
||||
>>> bool(address2.active)
|
||||
False
|
||||
@@ -1,8 +1,12 @@
|
||||
# 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 unittest
|
||||
import doctest
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.tests.test_tryton import doctest_teardown
|
||||
from trytond.tests.test_tryton import doctest_checker
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
@@ -121,4 +125,9 @@ class PartyTestCase(ModuleTestCase):
|
||||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(PartyTestCase))
|
||||
suite.addTests(doctest.DocFileSuite(
|
||||
'scenario_party_replace.rst',
|
||||
tearDown=doctest_teardown, encoding='utf-8',
|
||||
checker=doctest_checker,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
|
||||
return suite
|
||||
@@ -11,6 +11,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="active" xexpand="0" width="25"/>
|
||||
<!-- Add here some checkboxes ! -->
|
||||
</group>
|
||||
<label name="replaced_by"/>
|
||||
<field name="replaced_by"/>
|
||||
<notebook colspan="6">
|
||||
<page string="General" id="general">
|
||||
<field name="addresses" mode="form,tree" colspan="4"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<form>
|
||||
<label name="source" string="Party"/>
|
||||
<field name="source"/>
|
||||
<label name="destination" string="Replace By"/>
|
||||
<field name="destination"/>
|
||||
</form>
|
||||
Reference in new issue
Block a user