Duplicate custom
This commit is contained in:
@@ -64,6 +64,66 @@ class TradeCustomDuplicateStart(ModelView):
|
||||
Bool(Eval('create_matched_mirror'))
|
||||
& (Eval('mirror_mode') == 'new_mirror')),
|
||||
})
|
||||
mirror_currency = fields.Many2One('currency.currency', "Mirror Currency",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_payment_term = fields.Many2One(
|
||||
'account.invoice.payment_term', "Mirror Payment Term",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_incoterm = fields.Many2One('incoterm.incoterm', "Mirror Incoterm",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_quantity = fields.Numeric("Mirror Contractual Quantity",
|
||||
digits='unit',
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_unit = fields.Many2One('product.uom', "Mirror Unit",
|
||||
readonly=True,
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_unit_price = fields.Numeric("Mirror Price", digits=(16, 4),
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_price_type = fields.Selection(PRICE_TYPES, "Mirror Price Type",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
'required': Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_clear_fees = fields.Boolean("Mirror Clear Fees",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
mirror_clear_pricing_components = fields.Boolean(
|
||||
"Mirror Clear Pricing Components",
|
||||
states={
|
||||
'invisible': ~Bool(Eval('create_matched_mirror')),
|
||||
})
|
||||
|
||||
@fields.depends('source', 'mirror_mode')
|
||||
def on_change_mirror_mode(self):
|
||||
record = TradeCustomDuplicate._reference_record(self.source)
|
||||
if not record or not self.mirror_mode:
|
||||
return
|
||||
line = TradeCustomDuplicate._trade_line(record)
|
||||
if not line:
|
||||
return
|
||||
defaults = TradeCustomDuplicate._mirror_defaults(
|
||||
record, line, self.mirror_mode, strict=False, as_ids=False)
|
||||
for field, value in defaults.items():
|
||||
setattr(self, field, value)
|
||||
|
||||
|
||||
class TradeCustomDuplicate(Wizard):
|
||||
@@ -101,6 +161,15 @@ class TradeCustomDuplicate(Wizard):
|
||||
def _opposite_type(source_model):
|
||||
return 'Sale' if source_model == 'purchase.purchase' else 'Purchase'
|
||||
|
||||
@staticmethod
|
||||
def _reference_record(reference):
|
||||
if not reference:
|
||||
return None
|
||||
if isinstance(reference, str):
|
||||
model, record_id = reference.split(',', 1)
|
||||
return Pool().get(model)(int(record_id))
|
||||
return reference
|
||||
|
||||
def default_start(self, fields):
|
||||
if len(self.records) != 1:
|
||||
raise UserError("Please select a single purchase or sale.")
|
||||
@@ -113,6 +182,8 @@ class TradeCustomDuplicate(Wizard):
|
||||
matched_record = self._matched_counterpart_record(
|
||||
record, line, strict=False)
|
||||
mirror_mode = 'duplicate_pair' if matched_record else 'new_mirror'
|
||||
mirror_defaults = self._mirror_defaults(
|
||||
record, line, mirror_mode, strict=False)
|
||||
|
||||
return {
|
||||
'source_model': source_model,
|
||||
@@ -132,9 +203,57 @@ class TradeCustomDuplicate(Wizard):
|
||||
'clear_pricing_components': False,
|
||||
'create_matched_mirror': False,
|
||||
'mirror_mode': mirror_mode,
|
||||
'mirror_party': (
|
||||
matched_record.party.id
|
||||
if matched_record and matched_record.party else None),
|
||||
**mirror_defaults,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _field_id(record, field):
|
||||
value = getattr(record, field, None)
|
||||
return value.id if value else None
|
||||
|
||||
@staticmethod
|
||||
def _field_value(record, field, as_ids=True):
|
||||
value = getattr(record, field, None)
|
||||
if as_ids:
|
||||
return value.id if value else None
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _line_quantity_value(line):
|
||||
return (
|
||||
getattr(line, 'quantity_theorical', None)
|
||||
or getattr(line, 'quantity', None))
|
||||
|
||||
@staticmethod
|
||||
def _mirror_defaults(record, line, mirror_mode, strict=True, as_ids=True):
|
||||
mirror_record = record
|
||||
mirror_line = line
|
||||
if mirror_mode == 'duplicate_pair':
|
||||
mirror_line, mirror_record = TradeCustomDuplicate._matched_counterpart(
|
||||
line, record=record, strict=strict)
|
||||
if not mirror_line or not mirror_record:
|
||||
mirror_record = record
|
||||
mirror_line = line
|
||||
return {
|
||||
'mirror_party': TradeCustomDuplicate._field_value(
|
||||
mirror_record, 'party', as_ids=as_ids),
|
||||
'mirror_currency': TradeCustomDuplicate._field_value(
|
||||
mirror_record, 'currency', as_ids=as_ids),
|
||||
'mirror_payment_term': TradeCustomDuplicate._field_value(
|
||||
mirror_record, 'payment_term', as_ids=as_ids),
|
||||
'mirror_incoterm': TradeCustomDuplicate._field_value(
|
||||
mirror_record, 'incoterm', as_ids=as_ids),
|
||||
'mirror_quantity': TradeCustomDuplicate._line_quantity_value(
|
||||
mirror_line),
|
||||
'mirror_unit': (
|
||||
mirror_line.unit.id if (
|
||||
as_ids and getattr(mirror_line, 'unit', None))
|
||||
else getattr(mirror_line, 'unit', None)),
|
||||
'mirror_unit_price': getattr(mirror_line, 'unit_price', None),
|
||||
'mirror_price_type': (
|
||||
getattr(mirror_line, 'price_type', None) or 'priced'),
|
||||
'mirror_clear_fees': False,
|
||||
'mirror_clear_pricing_components': False,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -152,6 +271,40 @@ class TradeCustomDuplicate(Wizard):
|
||||
new_record.save()
|
||||
return Model(new_record.id)
|
||||
|
||||
@staticmethod
|
||||
def _mirror_options(options, fallback_record=None, fallback_line=None):
|
||||
return SimpleNamespace(
|
||||
party=(
|
||||
options.mirror_party
|
||||
or getattr(fallback_record, 'party', None)),
|
||||
currency=(
|
||||
options.mirror_currency
|
||||
or getattr(fallback_record, 'currency', None)),
|
||||
payment_term=(
|
||||
options.mirror_payment_term
|
||||
or getattr(fallback_record, 'payment_term', None)),
|
||||
incoterm=(
|
||||
options.mirror_incoterm
|
||||
or getattr(fallback_record, 'incoterm', None)),
|
||||
quantity=(
|
||||
options.mirror_quantity
|
||||
or TradeCustomDuplicate._line_quantity_value(fallback_line)),
|
||||
unit=(
|
||||
options.mirror_unit
|
||||
or getattr(fallback_line, 'unit', None)),
|
||||
unit_price=(
|
||||
options.mirror_unit_price
|
||||
if options.mirror_unit_price is not None
|
||||
else getattr(fallback_line, 'unit_price', None)),
|
||||
price_type=(
|
||||
options.mirror_price_type
|
||||
or getattr(fallback_line, 'price_type', None)
|
||||
or 'priced'),
|
||||
clear_fees=options.mirror_clear_fees,
|
||||
clear_pricing_components=(
|
||||
options.mirror_clear_pricing_components),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _apply_party_addresses(record, party):
|
||||
addresses = list(getattr(party, 'addresses', None) or [])
|
||||
@@ -482,20 +635,9 @@ class TradeCustomDuplicate(Wizard):
|
||||
|
||||
@staticmethod
|
||||
def _pair_duplicate_options(matched_record, matched_line, options):
|
||||
return SimpleNamespace(
|
||||
party=options.mirror_party or matched_record.party,
|
||||
currency=matched_record.currency or options.currency,
|
||||
payment_term=(
|
||||
matched_record.payment_term or options.payment_term),
|
||||
incoterm=matched_record.incoterm or options.incoterm,
|
||||
quantity=options.quantity,
|
||||
unit_price=getattr(matched_line, 'unit_price', None),
|
||||
price_type=(
|
||||
getattr(matched_line, 'price_type', None)
|
||||
or options.price_type),
|
||||
clear_fees=options.clear_fees,
|
||||
clear_pricing_components=options.clear_pricing_components,
|
||||
)
|
||||
return TradeCustomDuplicate._mirror_options(
|
||||
options, fallback_record=matched_record,
|
||||
fallback_line=matched_line)
|
||||
|
||||
@staticmethod
|
||||
def _match_duplicate_pair_lots(source_line, source_lot, counterpart_line,
|
||||
@@ -552,18 +694,20 @@ class TradeCustomDuplicate(Wizard):
|
||||
if source_lot and counterpart_lot:
|
||||
TradeCustomDuplicate._match_duplicate_pair_lots(
|
||||
new_line, source_lot, new_matched_line, counterpart_lot,
|
||||
options.quantity)
|
||||
mirror_options.quantity)
|
||||
return new_matched_record
|
||||
|
||||
@staticmethod
|
||||
def _create_matched_mirror(record, line, options):
|
||||
mirror_options = TradeCustomDuplicate._mirror_options(
|
||||
options, fallback_record=record, fallback_line=line)
|
||||
source_model = record.__name__
|
||||
mirror_type = TradeCustomDuplicate._opposite_type(source_model)
|
||||
source_lot = TradeCustomDuplicate._first_virtual_lot(line)
|
||||
if not source_lot:
|
||||
raise UserError(
|
||||
"The duplicated line has no virtual lot to match.")
|
||||
if not options.unit:
|
||||
if not mirror_options.unit:
|
||||
raise UserError(
|
||||
"The duplicated line has no unit for mirror contract creation.")
|
||||
|
||||
@@ -574,15 +718,16 @@ class TradeCustomDuplicate(Wizard):
|
||||
record, mirror_type)
|
||||
|
||||
detail = ContractDetail()
|
||||
detail.party = options.mirror_party
|
||||
detail.currency = options.currency
|
||||
detail.currency_unit = "%s_%s" % (options.currency.id, options.unit.id)
|
||||
detail.incoterm = options.incoterm
|
||||
detail.payment_term = options.payment_term
|
||||
detail.quantity = options.quantity
|
||||
detail.unit = options.unit
|
||||
detail.price = options.unit_price
|
||||
detail.price_type = options.price_type
|
||||
detail.party = mirror_options.party
|
||||
detail.currency = mirror_options.currency
|
||||
detail.currency_unit = "%s_%s" % (
|
||||
mirror_options.currency.id, mirror_options.unit.id)
|
||||
detail.incoterm = mirror_options.incoterm
|
||||
detail.payment_term = mirror_options.payment_term
|
||||
detail.quantity = mirror_options.quantity
|
||||
detail.unit = mirror_options.unit
|
||||
detail.price = mirror_options.unit_price
|
||||
detail.price_type = mirror_options.price_type
|
||||
detail.reference = getattr(record, 'reference', None)
|
||||
detail.from_location = from_location
|
||||
detail.to_location = to_location
|
||||
@@ -595,8 +740,8 @@ class TradeCustomDuplicate(Wizard):
|
||||
ct.type = mirror_type
|
||||
ct.matched = True
|
||||
ct.product = line.product
|
||||
ct.unit = line.unit
|
||||
ct.quantity = options.quantity
|
||||
ct.unit = mirror_options.unit
|
||||
ct.quantity = mirror_options.quantity
|
||||
ct.lot = source_lot
|
||||
|
||||
with Transaction().set_context(active_ids=[]):
|
||||
|
||||
@@ -6207,9 +6207,24 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
'custom duplicate can default mirror party from the matched sale'
|
||||
purchase_party = SimpleNamespace(id=1)
|
||||
sale_party = SimpleNamespace(id=2)
|
||||
sale_currency = SimpleNamespace(id=30)
|
||||
sale_payment_term = SimpleNamespace(id=31)
|
||||
sale_incoterm = SimpleNamespace(id=32)
|
||||
sale_unit = SimpleNamespace(id=33)
|
||||
purchase_lot = SimpleNamespace(id=10)
|
||||
sale = SimpleNamespace(party=sale_party)
|
||||
sale_line = SimpleNamespace(id=20, sale=sale)
|
||||
sale = SimpleNamespace(
|
||||
party=sale_party,
|
||||
currency=sale_currency,
|
||||
payment_term=sale_payment_term,
|
||||
incoterm=sale_incoterm)
|
||||
sale_line = SimpleNamespace(
|
||||
id=20,
|
||||
sale=sale,
|
||||
unit=sale_unit,
|
||||
quantity=Decimal('99'),
|
||||
quantity_theorical=Decimal('99'),
|
||||
unit_price=Decimal('52'),
|
||||
price_type='basis')
|
||||
lot_qt = SimpleNamespace(lot_s=SimpleNamespace(sale_line=sale_line))
|
||||
|
||||
class FakeLotQt:
|
||||
@@ -6248,6 +6263,110 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
self.assertEqual(values['mirror_mode'], 'duplicate_pair')
|
||||
self.assertEqual(values['mirror_party'], sale_party.id)
|
||||
self.assertEqual(values['mirror_currency'], sale_currency.id)
|
||||
self.assertEqual(values['mirror_payment_term'], sale_payment_term.id)
|
||||
self.assertEqual(values['mirror_incoterm'], sale_incoterm.id)
|
||||
self.assertEqual(values['mirror_quantity'], Decimal('99'))
|
||||
self.assertEqual(values['mirror_unit'], sale_unit.id)
|
||||
self.assertEqual(values['mirror_unit_price'], Decimal('52'))
|
||||
self.assertEqual(values['mirror_price_type'], 'basis')
|
||||
|
||||
def test_custom_duplicate_defaults_new_mirror_from_source_purchase(self):
|
||||
'new mirror options default from the duplicated source contract'
|
||||
purchase_party = SimpleNamespace(id=1)
|
||||
purchase_currency = SimpleNamespace(id=4)
|
||||
purchase_payment_term = SimpleNamespace(id=5)
|
||||
purchase_incoterm = SimpleNamespace(id=6)
|
||||
unit = SimpleNamespace(id=3)
|
||||
|
||||
class FakeLotQt:
|
||||
@classmethod
|
||||
def search(cls, domain):
|
||||
return []
|
||||
|
||||
pool = Mock()
|
||||
pool.get.side_effect = lambda name: {'lot.qt': FakeLotQt}[name]
|
||||
line = SimpleNamespace(
|
||||
__name__='purchase.line',
|
||||
id=11,
|
||||
type='line',
|
||||
product=Mock(),
|
||||
lots=[],
|
||||
unit=unit,
|
||||
quantity=Decimal('100'),
|
||||
quantity_theorical=Decimal('100'),
|
||||
unit_price=Decimal('25'),
|
||||
price_type='priced',
|
||||
)
|
||||
record = SimpleNamespace(
|
||||
__name__='purchase.purchase',
|
||||
id=12,
|
||||
lines=[line],
|
||||
party=purchase_party,
|
||||
currency=purchase_currency,
|
||||
payment_term=purchase_payment_term,
|
||||
incoterm=purchase_incoterm,
|
||||
btb=None,
|
||||
)
|
||||
wizard = duplicate_module.TradeCustomDuplicate()
|
||||
wizard.records = [record]
|
||||
|
||||
with patch.object(duplicate_module, 'Pool', return_value=pool):
|
||||
values = wizard.default_start(None)
|
||||
|
||||
self.assertEqual(values['mirror_mode'], 'new_mirror')
|
||||
self.assertEqual(values['mirror_party'], purchase_party.id)
|
||||
self.assertEqual(values['mirror_currency'], purchase_currency.id)
|
||||
self.assertEqual(values['mirror_payment_term'], purchase_payment_term.id)
|
||||
self.assertEqual(values['mirror_incoterm'], purchase_incoterm.id)
|
||||
self.assertEqual(values['mirror_quantity'], Decimal('100'))
|
||||
self.assertEqual(values['mirror_unit'], unit.id)
|
||||
self.assertEqual(values['mirror_unit_price'], Decimal('25'))
|
||||
self.assertEqual(values['mirror_price_type'], 'priced')
|
||||
|
||||
def test_custom_duplicate_mirror_mode_change_refreshes_source_defaults(self):
|
||||
'changing mirror mode refreshes mirror fields from the source contract'
|
||||
purchase_party = SimpleNamespace(id=1)
|
||||
purchase_currency = SimpleNamespace(id=4)
|
||||
purchase_payment_term = SimpleNamespace(id=5)
|
||||
purchase_incoterm = SimpleNamespace(id=6)
|
||||
unit = SimpleNamespace(id=3)
|
||||
line = SimpleNamespace(
|
||||
__name__='purchase.line',
|
||||
id=11,
|
||||
type='line',
|
||||
product=Mock(),
|
||||
lots=[],
|
||||
unit=unit,
|
||||
quantity=Decimal('100'),
|
||||
quantity_theorical=Decimal('100'),
|
||||
unit_price=Decimal('25'),
|
||||
price_type='priced',
|
||||
)
|
||||
record = SimpleNamespace(
|
||||
__name__='purchase.purchase',
|
||||
id=12,
|
||||
lines=[line],
|
||||
party=purchase_party,
|
||||
currency=purchase_currency,
|
||||
payment_term=purchase_payment_term,
|
||||
incoterm=purchase_incoterm,
|
||||
)
|
||||
start = duplicate_module.TradeCustomDuplicateStart()
|
||||
start.source = record
|
||||
start.mirror_mode = 'new_mirror'
|
||||
start.mirror_party = SimpleNamespace(id=99)
|
||||
|
||||
start.on_change_mirror_mode()
|
||||
|
||||
self.assertIs(start.mirror_party, purchase_party)
|
||||
self.assertIs(start.mirror_currency, purchase_currency)
|
||||
self.assertIs(start.mirror_payment_term, purchase_payment_term)
|
||||
self.assertIs(start.mirror_incoterm, purchase_incoterm)
|
||||
self.assertEqual(start.mirror_quantity, Decimal('100'))
|
||||
self.assertIs(start.mirror_unit, unit)
|
||||
self.assertEqual(start.mirror_unit_price, Decimal('25'))
|
||||
self.assertEqual(start.mirror_price_type, 'priced')
|
||||
|
||||
def test_custom_duplicate_defaults_mirror_from_back_to_back_sale(self):
|
||||
'custom duplicate can use the back-to-back sale as matched pair'
|
||||
|
||||
@@ -28,6 +28,24 @@
|
||||
<field name="mirror_mode"/>
|
||||
<label name="mirror_party"/>
|
||||
<field name="mirror_party"/>
|
||||
<label name="mirror_payment_term"/>
|
||||
<field name="mirror_payment_term"/>
|
||||
<label name="mirror_incoterm"/>
|
||||
<field name="mirror_incoterm"/>
|
||||
<label name="mirror_currency"/>
|
||||
<field name="mirror_currency"/>
|
||||
<label name="mirror_quantity"/>
|
||||
<field name="mirror_quantity"/>
|
||||
<label name="mirror_unit"/>
|
||||
<field name="mirror_unit"/>
|
||||
<label name="mirror_unit_price"/>
|
||||
<field name="mirror_unit_price"/>
|
||||
<label name="mirror_price_type"/>
|
||||
<field name="mirror_price_type"/>
|
||||
<label name="mirror_clear_fees"/>
|
||||
<field name="mirror_clear_fees"/>
|
||||
<label name="mirror_clear_pricing_components"/>
|
||||
<field name="mirror_clear_pricing_components"/>
|
||||
<field name="source" invisible="1"/>
|
||||
<field name="duplicated_record_id" invisible="1"/>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user