Commit 6e94e1e8 authored by Jérome Perrin's avatar Jérome Perrin

accounting: Improve usage of internal transactions for internal payments

We use internal transactions for invoices and any kind of accounting transactions (in reality, it's not limited to *invoice*), this is a set of changes to make it easier to record payments between two entities with internal transactions.

* We add a *Section Bank Account* field directly on the view ( ![Screenshot_2017-10-04_at_19.36.53](/uploads/4b1f22b5d5e6c1f77a70ea717c2323d9/Screenshot_2017-10-04_at_19.36.53.png)
* Reconfigure  *Causality (Invoices ...)* relation fieds to allow allow every accouting transactions portal type, so also Internal Invoice Transaction ...").  This way, when creating the "payment" internal transaction, we connect it to the "invoice" internal transaction  and grouping reference is set automatically when posting to general ledger.
*  Enable the *Create Payment* action from (which exists on sales and purchase) on normal invoices.

The rest is  small cleanup / fixes described in commit messages.

/reviewed-on !433
parents 3622731e e821619b
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>create_related_payment</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>15.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Create Related Payment</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Invoice_viewCreateRelatedPaymentTransactionDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
<key> <string>text</string> </key> <key> <string>text</string> </key>
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
string:${object_url}/Base_jumpToRelatedObject?base_category=causality&portal_type:list=Payment+Transaction string:${object_url}/Base_jumpToRelatedObject?base_category=causality&portal_type:list=Payment+Transaction&portal_type:list=Internal+Invoice+Transaction
]]></string> </value> ]]></string> </value>
</item> </item>
......
from Products.ERP5Type.Message import translateString from Products.ERP5Type.Message import translateString
from zExceptions import Redirect
portal = context.getPortalObject() portal = context.getPortalObject()
countMessage = portal.portal_activities.countMessage countMessage = portal.portal_activities.countMessage
...@@ -21,10 +20,10 @@ portal.portal_selections.setSelectionParamsFor('accounting_create_related_paymen ...@@ -21,10 +20,10 @@ portal.portal_selections.setSelectionParamsFor('accounting_create_related_paymen
# XXX prevent to call this on the whole module: # XXX prevent to call this on the whole module:
if len(object_list) >= 1000: if len(object_list) >= 1000:
return context.REQUEST.RESPONSE.redirect( return context.Base_redirect(
"%s/view?portal_status_message=%s" % ( form_id,
context.absolute_url(), translateString( keep_items={'portal_status_message': translateString(
'Refusing to process more than 1000 objects, check your selection.'))) 'Refusing to process more than 1000 objects, check your selection.')})
tag = 'payment_creation_%s' % random.randint(0, 1000) tag = 'payment_creation_%s' % random.randint(0, 1000)
activated = 0 activated = 0
...@@ -33,9 +32,11 @@ for obj in object_list: ...@@ -33,9 +32,11 @@ for obj in object_list:
obj = obj.getObject() obj = obj.getObject()
if countMessage(path=obj.getPath(), if countMessage(path=obj.getPath(),
method_id='Invoice_createRelatedPaymentTransaction'): method_id='Invoice_createRelatedPaymentTransaction'):
raise Redirect, "%s/view?portal_status_message=%s" % ( return context.Base_redirect(
context.absolute_url(), translateString( form_id,
'Payment creation already in progress, abandon.')) abort_transaction=True,
keep_items={'portal_status_message': translateString(
'Payment creation already in progress, abandon.')})
obj.activate(tag=tag).Invoice_createRelatedPaymentTransaction( obj.activate(tag=tag).Invoice_createRelatedPaymentTransaction(
node=node, node=node,
payment_mode=payment_mode, payment_mode=payment_mode,
...@@ -44,17 +45,18 @@ for obj in object_list: ...@@ -44,17 +45,18 @@ for obj in object_list:
activated += 1 activated += 1
if not activated: if not activated:
return context.REQUEST.RESPONSE.redirect( return context.Base_redirect(
"%s/view?portal_status_message=%s" % ( form_id,
context.absolute_url(), translateString('No invoice in your selection.'))) keep_items={'portal_status_message': translateString(
'No invoice in your selection.')})
# activate something on the folder # activate something on the folder
context.activate(after_tag=tag).getTitle() context.activate(after_tag=tag).getTitle()
return context.REQUEST.RESPONSE.redirect( return context.Base_redirect(
"%s/view?portal_status_message=%s" % ( form_id,
context.absolute_url(), translateString( keep_items={'portal_status_message': translateString(
'Payments creation for ${activated_invoice_count} on' 'Payments creation for ${activated_invoice_count} on'
' ${total_selection_count} invoices in progress.', ' ${total_selection_count} invoices in progress.',
mapping=dict(activated_invoice_count=activated, mapping={'activated_invoice_count': activated,
total_selection_count=len(object_list))))) 'total_selection_count': len(object_list)})})
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>node, payment_mode, payment, selection_index=None, uids=(), listbox_uid=(),selection_name=\'\', **kw</string> </value> <value> <string>node, payment_mode, payment, selection_index=None, uids=(), listbox_uid=(),selection_name=\'\', form_id=\'\', **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -58,6 +58,22 @@ ...@@ -58,6 +58,22 @@
<key> <string>tales</string> </key> <key> <string>tales</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>allow_creation</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>base_category</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>catalog_index</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>columns</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -66,10 +82,24 @@ ...@@ -66,10 +82,24 @@
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>portal_type</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>proxy_listbox_ids</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>target</string> </key> <key> <string>target</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary> </dictionary>
</value> </value>
</item> </item>
...@@ -139,28 +169,7 @@ ...@@ -139,28 +169,7 @@
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <value>
<list> <list/>
<tuple>
<string>Pay Sheet Transaction</string>
<string>Pay Sheet Transaction</string>
</tuple>
<tuple>
<string>Purchase Invoice Transaction</string>
<string>Purchase Invoice Transaction</string>
</tuple>
<tuple>
<string>Sale Invoice Transaction</string>
<string>Sale Invoice Transaction</string>
</tuple>
<tuple>
<string>Accounting Transaction</string>
<string>Accounting Transaction</string>
</tuple>
<tuple>
<string>Payment Transaction</string>
<string>Payment Transaction</string>
</tuple>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -188,4 +197,17 @@ ...@@ -188,4 +197,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>python: [(x,x) for x in context.getPortalAccountingTransactionTypeList()]</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData> </ZopeData>
...@@ -102,6 +102,7 @@ ...@@ -102,6 +102,7 @@
<value> <value>
<list> <list>
<string>my_destination_section</string> <string>my_destination_section</string>
<string>my_destination_payment</string>
<string>my_destination_reference</string> <string>my_destination_reference</string>
<string>my_title</string> <string>my_title</string>
<string>my_source_section</string> <string>my_source_section</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
<string>items</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_destination_payment</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_destination_payment</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>AccountingTransaction_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Section Bank Account</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</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>python:here.AccountingTransaction_getDestinationPaymentItemList()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -102,6 +102,7 @@ ...@@ -102,6 +102,7 @@
<value> <value>
<list> <list>
<string>my_source_section</string> <string>my_source_section</string>
<string>my_source_payment</string>
<string>my_source_reference</string> <string>my_source_reference</string>
<string>my_title</string> <string>my_title</string>
<string>my_destination_section_title</string> <string>my_destination_section_title</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_source_payment</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_source_payment</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>AccountingTransaction_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -11,7 +11,12 @@ if date is None: ...@@ -11,7 +11,12 @@ if date is None:
date = DateTime() date = DateTime()
portal = context.getPortalObject() portal = context.getPortalObject()
is_source = context.AccountingTransaction_isSourceView() is_source = context.AccountingTransaction_isSourceView()
transaction_portal_type = 'Payment Transaction'
line_portal_type = 'Accounting Transaction Line' line_portal_type = 'Accounting Transaction Line'
if context.getPortalType() == 'Internal Invoice Transaction':
transaction_portal_type = 'Internal Invoice Transaction'
line_portal_type = 'Internal Invoice Transaction Line'
# update selection params, because it'll be used in the selection dialog. # update selection params, because it'll be used in the selection dialog.
portal.portal_selections.setSelectionParamsFor( portal.portal_selections.setSelectionParamsFor(
...@@ -30,13 +35,13 @@ total_payable_price_details = \ ...@@ -30,13 +35,13 @@ total_payable_price_details = \
# if there's nothing more to pay, don't create an empty transaction # if there's nothing more to pay, don't create an empty transaction
if sum(total_payable_price_details.values()) == 0: if sum(total_payable_price_details.values()) == 0:
if not batch_mode: if not batch_mode:
return context.REQUEST.RESPONSE.redirect( return context.Base_redirect(
"%s/view?portal_status_message=%s" % ( form_id,
context.absolute_url(), Base_translateString('Nothing more to pay.'))) keep_items={'portal_status_message': Base_translateString('Nothing more to pay.')})
return None return None
related_payment = portal.accounting_module.newContent( related_payment = portal.accounting_module.newContent(
portal_type="Payment Transaction", portal_type=transaction_portal_type,
title=str(Base_translateString("Payment of ${invoice_title}", title=str(Base_translateString("Payment of ${invoice_title}",
mapping=dict(invoice_title=unicode((context.getReference() or mapping=dict(invoice_title=unicode((context.getReference() or
context.getTitle() or ''), context.getTitle() or ''),
...@@ -55,12 +60,12 @@ related_payment = portal.accounting_module.newContent( ...@@ -55,12 +60,12 @@ related_payment = portal.accounting_module.newContent(
payment_mode=payment_mode, payment_mode=payment_mode,
) )
if is_source: if is_source:
related_payment.edit(destination_payment=context.getDestinationPayment(), related_payment.setDestinationPayment(context.getDestinationPayment())
source_payment=payment) related_payment.setSourcePayment(payment)
mirror_section = context.getDestinationSection() mirror_section = context.getDestinationSection()
else: else:
related_payment.edit(destination_payment=payment, related_payment.setDestinationPayment(payment)
source_payment=context.getSourcePayment()) related_payment.setSourcePayment(context.getSourcePayment())
mirror_section = context.getSourceSection() mirror_section = context.getSourceSection()
bank = related_payment.newContent( bank = related_payment.newContent(
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>node, payment_mode, payment, date=None, plan=False, batch_mode=0, **kw</string> </value> <value> <string>node, payment_mode, payment, date=None, plan=False, batch_mode=0, form_id=\'\', **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -66,6 +66,7 @@ Internal Invoice Transaction | add_internal_invoice_transaction_line ...@@ -66,6 +66,7 @@ Internal Invoice Transaction | add_internal_invoice_transaction_line
Internal Invoice Transaction | convert_destination_price Internal Invoice Transaction | convert_destination_price
Internal Invoice Transaction | convert_source_price Internal Invoice Transaction | convert_source_price
Internal Invoice Transaction | create_new_file Internal Invoice Transaction | create_new_file
Internal Invoice Transaction | create_related_payment
Internal Invoice Transaction | create_reversal Internal Invoice Transaction | create_reversal
Internal Invoice Transaction | destination_asset Internal Invoice Transaction | destination_asset
Internal Invoice Transaction | destination_view Internal Invoice Transaction | destination_view
......
...@@ -5782,6 +5782,47 @@ class TestInternalInvoiceTransaction(AccountingTestCase): ...@@ -5782,6 +5782,47 @@ class TestInternalInvoiceTransaction(AccountingTestCase):
internal_invoice, 'stop_action') internal_invoice, 'stop_action')
self.assertEqual('stopped', internal_invoice.getSimulationState()) self.assertEqual('stopped', internal_invoice.getSimulationState())
def test_internal_invoice_create_related_payment(self):
# 'Create Related Payment' is available on internal invoice transaction
internal_invoice = self.portal.accounting_module.newContent(
portal_type='Internal Invoice Transaction',
title='test invoice',
source_section_value=self.section,
destination_section_value=self.main_section,
start_date=DateTime(2015, 1, 1),
)
line_1, line_2 = internal_invoice.getMovementList()
line_1.edit(
source_value=self.portal.account_module.receivable,
source_debit=100)
line_2.edit(
source_value=self.portal.account_module.goods_sales,
source_credit=100)
self.commit()
self.portal.portal_workflow.doActionFor(
internal_invoice, 'start_action')
self.assertEqual('started', internal_invoice.getSimulationState())
payment_node = self.section.newContent(portal_type='Bank Account')
payment = internal_invoice.Invoice_createRelatedPaymentTransaction(
node=self.account_module.bank.getRelativeUrl(),
payment=payment_node.getRelativeUrl(),
payment_mode='check',
batch_mode=True)
# on internal invoice transaction, we create a payment transaction
self.assertEqual(
'Internal Invoice Transaction',
payment.getPortalType())
self.assertEqual(internal_invoice, payment.getCausalityValue())
self.assertItemsEqual(
[ (self.portal.account_module.bank, 100, 0),
(self.portal.account_module.receivable, 0, 100), ],
[ (line.getSourceValue(), line.getSourceDebit(), line.getSourceCredit())
for line in payment.getMovementList(
portal_type='Internal Invoice Transaction Line')])
class TestAccountingCodingStyle(CodingStyleTestCase, AccountingTestCase): class TestAccountingCodingStyle(CodingStyleTestCase, AccountingTestCase):
"""Runs CodingStyleTestCase checks on erp5_accounting """Runs CodingStyleTestCase checks on erp5_accounting
......
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