Commit 92dae2cf authored by Jérome Perrin's avatar Jérome Perrin

Use payment conditions in "create related payment" action

To ease "manual" (ie. not from payment rule simulation) creation of payments,
update "create relate payment" so that it suggests the payment mode and the
payment date from payment conditions of the invoice's trade condition.

See merge request nexedi/erp5!1369
parents ac15b3c7 b8fdde98
Pipeline #14134 failed with stage
in 0 seconds
portal = context.getPortalObject()
payment_mode_from_trade_condition = None
trade_condition = context.getSpecialiseValue()
if trade_condition is not None:
payment_mode_from_trade_condition = trade_condition.getPaymentConditionPaymentMode()
return payment_mode_from_trade_condition or portal.portal_selections.getSelectionParamsFor(
'accounting_create_related_payment_selection').get('payment_mode_for_related_payment')
...@@ -52,17 +52,9 @@ ...@@ -52,17 +52,9 @@
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_getDueDate</string> </value> <value> <string>Invoice_getCreateRelatedPaymentTransactionDialogDefaultPaymentMode</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
# TODO: this script is not well tested and not fully implemented
# TODO: this is actually PaymentCondition_getDueDate
from DateTime import DateTime from DateTime import DateTime
from datetime import timedelta
import calendar
source = False
if context.getPortalType() in ('Purchase Invoice', 'Purchase Invoice Transaction',):
source = False
elif context.getPortalType() in ('Sale Invoice', 'Sale Invoice Transaction',):
source = True
else: # internal invoices
source = context.AccountingTransaction_isSourceView()
if context.getPortalType() == 'Payment Condition': delivery = context
delivery = context.getParentValue() trade_condition = delivery.getSpecialiseValue(
payment_condition = context portal_type=(
else: 'Purchase Trade Condition',
delivery = context 'Sale Trade Condition',
payment_condition = context.getDefaultPaymentConditionValue() 'Internal Trade Condition',
)
)
if trade_condition is None:
return None
payment_condition = trade_condition.getDefaultPaymentConditionValue()
if payment_condition is None:
return None
# Absolute payment date has priority # Absolute payment date has priority
if payment_condition.getPaymentDate(): if payment_condition.getPaymentDate():
...@@ -18,45 +32,38 @@ def OrderDateGetter(invoice): ...@@ -18,45 +32,38 @@ def OrderDateGetter(invoice):
def getter(): def getter():
packing_list = invoice.getCausalityValue( packing_list = invoice.getCausalityValue(
portal_type=context.getPortalDeliveryTypeList()) portal_type=context.getPortalDeliveryTypeList())
if packing_list: if packing_list is not None:
order = packing_list.getCausalityValue( order = packing_list.getCausalityValue(
portal_type=context.getPortalOrderTypeList()) portal_type=context.getPortalOrderTypeList())
return order.getStartDate() # TODO start or stop ? -> based on source/destination return order.getStartDate()
return getter return getter
def PackingListDateGetter(invoice): def PackingListDateGetter(invoice):
def getter(): def getter():
packing_list = invoice.getCausalityValue( packing_list = invoice.getCausalityValue(
portal_type=context.getPortalDeliveryTypeList()) portal_type=context.getPortalDeliveryTypeList())
if packing_list: if packing_list is not None:
return packing_list.getStartDate() # TODO start or stop ? -> based on source/destination return packing_list.getStopDate()
return getter return getter
case = { date_getter = {
'invoice': delivery.getStartDate, 'invoice': delivery.getStartDate if source else delivery.getStopDate,
'order': OrderDateGetter(delivery), 'order': OrderDateGetter(delivery),
'packing list': PackingListDateGetter(delivery), 'packing_list': PackingListDateGetter(delivery),
} }
due_date = case.get(payment_condition.getTradeDate(), delivery.getStartDate)() due_date = date_getter.get(payment_condition.getTradeDate(), lambda: None)()
due_date += payment_condition.getPaymentTerm(0) if not due_date:
return None
due_date = due_date.asdatetime()
pat = payment_condition.getPaymentAdditionalTerm() due_date += timedelta(days=payment_condition.getPaymentTerm(0))
if payment_condition.getPaymentEndOfMonth(): if payment_condition.getPaymentEndOfMonth():
i = 0 last_day_of_month = calendar.monthrange(due_date.year, due_date.month)[1]
month = due_date.month() due_date = due_date.replace(day=last_day_of_month)
while (month == (due_date + i).month()):
i += 1 due_date += timedelta(days=payment_condition.getPaymentAdditionalTerm(0))
due_date = (due_date + i - 1)
if pat: return DateTime(due_date)
due_date += pat
else:
if pat:
i = 0
month = due_date.month()
while (month == (due_date + i).month()):
i -= 1
due_date = (due_date + i + pat)
return due_date
...@@ -54,11 +54,7 @@ ...@@ -54,11 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>TradeCondition_getDueDate</string> </value> <value> <string>Invoice_getPaymentDueDate</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
<item> <item>
<key> <string>delegated_list</string> </key> <key> <string>delegated_list</string> </key>
<value> <value>
<list/> <list>
<string>default</string>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -50,6 +52,12 @@ ...@@ -50,6 +52,12 @@
<key> <string>tales</string> </key> <key> <string>tales</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -69,6 +77,12 @@ ...@@ -69,6 +77,12 @@
<key> <string>values</string> </key> <key> <string>values</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>default</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string>your_date</string> </value> <value> <string>your_date</string> </value>
...@@ -87,4 +101,17 @@ ...@@ -87,4 +101,17 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/Invoice_getPaymentDueDate</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData> </ZopeData>
...@@ -265,32 +265,20 @@ ...@@ -265,32 +265,20 @@
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle> <pickle>
<tuple> <global name="TALESMethod" module="Products.Formulator.TALESField"/>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>_text</string> </key> <key> <string>_text</string> </key>
<value> <string>python:here.portal_selections.getSelectionParamsFor(\'accounting_create_related_payment_selection\').get(\'payment_mode_for_related_payment\')</string> </value> <value> <string>context/Invoice_getCreateRelatedPaymentTransactionDialogDefaultPaymentMode</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="3" aka="AAAAAAAAAAM="> <record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<tuple> <global name="TALESMethod" module="Products.Formulator.TALESField"/>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
......
packing_list_list = context.getCausalityValueList(portal_type='Sale Packing List')
if len(packing_list_list) > 0:
packing_list = packing_list_list[0]
order = packing_list.getCausalityValue(portal_type='Sale Order')
from DateTime import DateTime
due_date = order.getPaymentConditionPaymentDate( DateTime() )
pat = None #order.getPaymentAdditionalTerm()
else:
due_date = context.getStartDate()
pat = None
due_date += context.getPaymentConditionPaymentTerm(30)
peom = context.getPaymentEndOfMonth()
if peom:
i = 0
month = due_date.month()
while (month == (due_date + i).month()):
i += 1
due_date = (due_date + i - 1)
if pat != None:
due_date += pat
else:
if pat != None:
i = 0
month = due_date.month()
while (month == (due_date + i).month()):
i -= 1
due_date = (due_date + i + pat)
return due_date
...@@ -6171,3 +6171,146 @@ class TestAccountingPeriod(AccountingTestCase): ...@@ -6171,3 +6171,146 @@ class TestAccountingPeriod(AccountingTestCase):
start_date=DateTime('2023/01/01'), start_date=DateTime('2023/01/01'),
stop_date=DateTime('2023/12/31'),) stop_date=DateTime('2023/12/31'),)
self.portal.portal_workflow.doActionFor(third_accounting_period, 'start_action') self.portal.portal_workflow.doActionFor(third_accounting_period, 'start_action')
class TestInvoice_getPaymentTransactionDueDate(AccountingTestCase):
"""Test Invoice_getPaymentTransactionDueDate.
"""
def test_due_date_calculation(self):
"""Test the rule to calculate the due date from payment condition properties.
"""
sale_trade_condition = self.portal.sale_trade_condition_module.newContent(
portal_type='Sale Trade Condition',
)
invoice = self._makeOne(
portal_type='Sale Invoice Transaction',
stop_date=DateTime(1970, 1, 1)
)
self.assertEqual(invoice.Invoice_getPaymentDueDate(), None)
invoice.setSpecialiseValue(sale_trade_condition)
self.assertEqual(invoice.Invoice_getPaymentDueDate(), None)
sale_trade_condition.setPaymentConditionTradeDate('invoice')
for invoice_date, payment_term, payment_end_of_month, payment_additional_term, expected_date in (
(DateTime('2001/01/01'), 10, False, 0, DateTime('2001/01/11')),
(DateTime('2001/01/01'), 10, True, 0, DateTime('2001/01/31')),
(DateTime('2001/01/01'), 10, True, 10, DateTime('2001/02/10')),
(DateTime('2001/01/31'), 10, False, 0, DateTime('2001/02/10')),
(DateTime('2001/01/31'), 10, True, 0, DateTime('2001/02/28')),
(DateTime('2001/01/31'), 10, True, 15, DateTime('2001/03/15')),
# leap year
(DateTime('2004/01/31'), 10, True, 0, DateTime('2004/02/29')),
# this keeps hours/minutes and timezones
(DateTime('2001/01/01 01:02:03 GMT+2'), 10, False, 0, DateTime('2001/01/11 01:02:03 GMT+2')),
# and works well across daylight time switchs
(DateTime('2001/03/31 00:00:00 Europe/Paris'), 10, False, 0, DateTime('2001/04/10 00:00:00 Europe/Paris')),
(DateTime('2001/03/31 00:00:00 Europe/Paris'), 0, False, 10, DateTime('2001/04/10 00:00:00 Europe/Paris')),
(DateTime('2001/10/27 00:00:00 Europe/Paris'), 10, False, 0, DateTime('2001/11/06 00:00:00 Europe/Paris')),
(DateTime('2001/10/27 00:00:00 Europe/Paris'), 0, False, 10, DateTime('2001/11/06 00:00:00 Europe/Paris')),
):
invoice.setStartDate(invoice_date)
sale_trade_condition.setPaymentConditionPaymentTerm(payment_term)
sale_trade_condition.setPaymentConditionPaymentEndOfMonth(payment_end_of_month)
sale_trade_condition.setPaymentConditionPaymentAdditionalTerm(payment_additional_term)
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
expected_date,
"{actual} != {expected_date} for case invoice_date:{invoice_date}, "
"payment_term:{payment_term}, payment_end_of_month:{payment_end_of_month}, "
"payment_additional_term:{payment_additional_term}".format(
actual=invoice.Invoice_getPaymentDueDate(),
**locals()
)
)
def test_sale_invoice_packing_list_or_order(self):
"""Test how the date is selected from invoice, packing list or order, for sales case
"""
sale_trade_condition = self.portal.sale_trade_condition_module.newContent(
portal_type='Sale Trade Condition',
payment_condition_payment_term=10,
)
order = self.portal.sale_order_module.newContent(
portal_type='Sale Order',
start_date=DateTime(2001, 1, 1),
stop_date=DateTime(1970, 1, 1),
)
delivery = self.portal.sale_packing_list_module.newContent(
portal_type='Sale Packing List',
start_date=DateTime(1970, 1, 1),
stop_date=DateTime(2002, 1, 1),
causality_value=order,
)
invoice = self._makeOne(
portal_type='Sale Invoice Transaction',
start_date=DateTime(2003, 1, 1),
stop_date=DateTime(1970, 1, 1),
specialise_value=sale_trade_condition,
)
trade_date = self.portal.portal_categories.trade_date
sale_trade_condition.setPaymentConditionTradeDate(trade_date.invoice.getRelativeUrl())
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2003, 1, 11))
sale_trade_condition.setPaymentConditionTradeDate(trade_date.packing_list.getRelativeUrl())
# no related delivery
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
None)
invoice.setCausalityValue(delivery)
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2002, 1, 11))
sale_trade_condition.setPaymentConditionTradeDate(trade_date.order.getRelativeUrl())
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2001, 1, 11))
def test_purchase_invoice_packing_list_or_order(self):
"""Test how the date is selected from invoice, packing list or order, for purchase case
"""
purchase_trade_condition = self.portal.purchase_trade_condition_module.newContent(
portal_type='Purchase Trade Condition',
payment_condition_payment_term=10,
)
order = self.portal.purchase_order_module.newContent(
portal_type='Purchase Order',
start_date=DateTime(2001, 1, 1),
stop_date=DateTime(1970, 1, 1),
)
delivery = self.portal.purchase_packing_list_module.newContent(
portal_type='Purchase Packing List',
start_date=DateTime(1970, 1, 1),
stop_date=DateTime(2002, 1, 1),
causality_value=order,
)
invoice = self._makeOne(
portal_type='Purchase Invoice Transaction',
start_date=DateTime(2003, 1, 1),
specialise_value=purchase_trade_condition,
)
trade_date = self.portal.portal_categories.trade_date
purchase_trade_condition.setPaymentConditionTradeDate(trade_date.invoice.getRelativeUrl())
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2003, 1, 11))
purchase_trade_condition.setPaymentConditionTradeDate(trade_date.packing_list.getRelativeUrl())
# no related delivery
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
None)
invoice.setCausalityValue(delivery)
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2002, 1, 11))
purchase_trade_condition.setPaymentConditionTradeDate(trade_date.order.getRelativeUrl())
self.assertEqual(
invoice.Invoice_getPaymentDueDate(),
DateTime(2001, 1, 11))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment