CreateContract

This commit is contained in:
2026-05-20 14:00:32 +02:00
parent 83612eeea3
commit 072960307a
3 changed files with 164 additions and 33 deletions

View File

@@ -4386,17 +4386,31 @@ class ContractDetail(ModelView):
return getattr(line.purchase.currency if lqt.lot_p else line.sale.currency, 'id', None)
@classmethod
def default_unit(cls):
lqt = cls._get_lqt_from_context()
if not lqt:
return
line = lqt.lot_p.line if lqt.lot_p else lqt.lot_s.sale_line
if getattr(line, 'enable_linked_currency', False):
return line.linked_unit.id
return getattr(line.unit, 'id', None)
@classmethod
def default_tol_min(cls):
def default_unit(cls):
lqt = cls._get_lqt_from_context()
if not lqt:
return
line = lqt.lot_p.line if lqt.lot_p else lqt.lot_s.sale_line
if getattr(line, 'enable_linked_currency', False):
return line.linked_unit.id
return getattr(line.unit, 'id', None)
@classmethod
def default_currency_unit(cls):
lqt = cls._get_lqt_from_context()
if not lqt:
return
line = lqt.lot_p.line if lqt.lot_p else lqt.lot_s.sale_line
if getattr(line, 'enable_linked_currency', False):
return "0_%s" % line.linked_unit.id
contract = line.purchase if lqt.lot_p else line.sale
currency = getattr(contract, 'currency', None)
unit = getattr(line, 'unit', None)
if currency and unit:
return "%s_%s" % (currency.id, unit.id)
@classmethod
def default_tol_min(cls):
lqt = cls._get_lqt_from_context()
return getattr(lqt.lot_p.line.purchase if lqt.lot_p else lqt.lot_s.sale_line.sale, 'tol_min', None) if lqt else None

View File

@@ -46,16 +46,19 @@ class ContractFactory:
contract = Purchase() if type_ == 'Purchase' else Sale()
# ---------- CONTRACT ----------
parts = c.currency_unit.split("_")
parts = cls._get_currency_unit_parts(c, base_contract)
contract.currency = int(parts[0]) or 1
contract.party = c.party
contract.crop = cls._get_crop(c, base_contract)
contract.tol_min = cls._get_tolerance(c, 'tol_min')
contract.tol_max = cls._get_tolerance(c, 'tol_max')
contract.payment_term = c.payment_term
contract.payment_term = cls._get_payment_term(
c, base_contract, type_)
contract.reference = c.reference
contract.from_location = c.from_location
contract.to_location = c.to_location
from_location, to_location = cls._get_locations(
c, base_contract, type_)
contract.from_location = from_location
contract.to_location = to_location
context = Transaction().context
contract.company = context.get('company') if context else None
if type_ == 'Purchase':
@@ -63,18 +66,18 @@ class ContractFactory:
else:
contract.sale_date = Date.today()
cls._apply_locations(contract, base_contract, type_)
cls._apply_party_data(contract, c.party, type_)
cls._apply_payment_term(contract, c.party, type_)
cls._apply_party_data(contract, c.party, base_contract, type_)
if type_ == 'Sale':
contract.product_origin = getattr(base_contract, 'product_origin', None)
contract.incoterm = c.incoterm
contract.incoterm = cls._get_contract_value(
c, base_contract, 'incoterm')
if c.party.addresses:
contract.invoice_address = c.party.addresses[0]
addresses = getattr(c.party, 'addresses', None)
if addresses:
contract.invoice_address = addresses[0]
if type_ == 'Sale':
contract.shipment_address = c.party.addresses[0]
contract.shipment_address = addresses[0]
contract.save()
@@ -147,19 +150,74 @@ class ContractFactory:
return to_location, None
return None, None
@staticmethod
def _apply_party_data(contract, party, type_):
if party.wb:
contract.wb = party.wb
if party.association:
contract.association = party.association
@classmethod
def _get_locations(cls, contract_detail, base, type_):
mirror_from, mirror_to = cls._get_mirror_locations(base, type_)
return (
getattr(contract_detail, 'from_location', None)
or mirror_from
or getattr(base, 'from_location', None),
getattr(contract_detail, 'to_location', None)
or mirror_to
or getattr(base, 'to_location', None),
)
@classmethod
def _apply_party_data(cls, contract, party, base, type_):
Contract = Pool().get(
'purchase.purchase' if type_ == 'Purchase' else 'sale.sale')
contract.wb = cls._get_header_value(
'wb', party, base, Contract.default_wb)
contract.association = cls._get_header_value(
'association', party, base, Contract.default_association)
contract.certif = cls._get_header_value(
'certif', party, base, Contract.default_certif)
@staticmethod
def _apply_payment_term(contract, party, type_):
if type_ == 'Purchase' and party.supplier_payment_term:
contract.payment_term = party.supplier_payment_term
elif type_ == 'Sale' and party.customer_payment_term:
contract.payment_term = party.customer_payment_term
def _get_header_value(field, party, base, default_getter=None):
value = getattr(party, field, None)
if value:
return value
value = getattr(base, field, None)
if value:
return value
if default_getter:
return default_getter()
@staticmethod
def _get_payment_term(contract_detail, base, type_):
value = getattr(contract_detail, 'payment_term', None)
if value:
return value
party = getattr(contract_detail, 'party', None)
field = (
'supplier_payment_term'
if type_ == 'Purchase' else 'customer_payment_term')
value = getattr(party, field, None)
if value:
return value
return getattr(base, 'payment_term', None)
@staticmethod
def _get_contract_value(contract_detail, base, field):
return (
getattr(contract_detail, field, None)
or getattr(base, field, None))
@staticmethod
def _get_currency_unit_parts(contract_detail, base):
value = getattr(contract_detail, 'currency_unit', None)
if value:
return value.split("_")
currency = (
getattr(contract_detail, 'currency', None)
or getattr(base, 'currency', None))
unit = getattr(contract_detail, 'unit', None)
currency_id = getattr(currency, 'id', currency)
unit_id = getattr(unit, 'id', unit)
if currency_id and unit_id:
return [str(currency_id), str(unit_id)]
return ["1", str(unit_id or 1)]
@staticmethod
def _apply_price(line, c, parts):

View File

@@ -2786,6 +2786,65 @@ class PurchaseTradeTestCase(ModuleTestCase):
ContractFactory._get_crop(contract_detail, base_contract),
crop)
def test_contract_factory_locations_fallback_to_source_contract(self):
'create contracts keeps required locations when wizard line is empty'
source_from = Mock(type='supplier')
source_to = Mock(type='storage')
contract_detail = Mock(from_location=None, to_location=None)
base_contract = Mock(
from_location=source_from, to_location=source_to)
from_location, to_location = ContractFactory._get_locations(
contract_detail, base_contract, 'Sale')
self.assertEqual(from_location, source_to)
self.assertEqual(to_location, source_to)
def test_contract_factory_required_header_fields_fallback_to_defaults(self):
'create contracts fills required header fields outside the normal form'
contract = Mock()
party = Mock(wb=None, association=None, certif=None)
base_contract = Mock(wb=None, association=None, certif=None)
Contract = Mock()
Contract.default_wb.return_value = 1
Contract.default_association.return_value = 2
Contract.default_certif.return_value = 3
with patch('trytond.modules.purchase_trade.service.Pool') as PoolMock:
PoolMock.return_value.get.return_value = Contract
ContractFactory._apply_party_data(
contract, party, base_contract, 'Purchase')
self.assertEqual(contract.wb, 1)
self.assertEqual(contract.association, 2)
self.assertEqual(contract.certif, 3)
def test_contract_factory_incoterm_fallback_to_source_contract(self):
'create contracts keeps required incoterm when wizard line is empty'
incoterm = Mock()
contract_detail = Mock(incoterm=None)
base_contract = Mock(incoterm=incoterm)
self.assertEqual(
ContractFactory._get_contract_value(
contract_detail, base_contract, 'incoterm'),
incoterm)
def test_contract_factory_currency_unit_fallback_to_hidden_defaults(self):
'create contracts can price even when currency/unit selector is empty'
contract_detail = Mock(
currency_unit=None,
currency=Mock(id=7),
unit=Mock(id=9),
)
base_contract = Mock(currency=Mock(id=3))
self.assertEqual(
ContractFactory._get_currency_unit_parts(
contract_detail, base_contract),
['7', '9'])
def test_contract_factory_delivery_dates_fallback_to_period(self):
'hidden delivery dates are still derived from delivery period'
period = Mock(beg_date='2026-05-01', end_date='2026-05-31')