Commit 4ff78e09 authored by Tomáš Peterka's avatar Tomáš Peterka

[accounting_renderjs_test] All together

parent e1fa7611
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Zuite" module="Products.Zelenium.zuite"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>renderjs_ui_accounting_report_zuite</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_account_statement</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Account Statement</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L894">testAccountingReports.py:TestAccountingReports.testAccountStatement</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=account-statement&report_style=Hal;
now python:DateTime();
today python:DateTime(now.year(), now.month(), now.day());
tomorrow python:today + 1;
yesterday python:today - 1;
offset python:0">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(today.day(), today.month(), today.year())"></td><td>today</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(tomorrow.day(), tomorrow.month(), tomorrow.year())"></td><td>tomorrow</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(yesterday.day(), yesterday.month(), yesterday.year())"></td><td>yesterday</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_page_export.html')]//ul/li</td><td></td></tr>
<tr><td>assertElementPresent</td>
<td>//a[@data-i18n="Account Statement"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Account Statement"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td><td></td></tr>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td>
<td>value=account_module/receivable</td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group/sub1</td></tr>
<tr><td>waitForElementPresent</td>
<td>//input[@name="field_your_at_date"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-${month}-${day}</td></tr>
<!-- field_your_section_category_strict is False by default -->
<!-- field_your_portal_type is good by default -->
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td><td></td></tr>
<!-- we need to zero-out simulation_state to just 'delivered' -->
<tal:block tal:repeat="_ python:range(4)"><!-- There is by default just few simulation states -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="6 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getSpecificReference string:1;
date string:$${yesterday};
mirror_section_title string:Client 1;
Movement_getExplanationTitleAndAnalytics string:Transaction 1 ref1;
debit_price string:100;
credit_price string:0;
running_total_price string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
Movement_getSpecificReference string:2;
date string:$${yesterday};
mirror_section_title string:Client 1;
Movement_getExplanationTitleAndAnalytics string:Transaction 2 ref2;
debit_price string:0;
credit_price string:200;
running_total_price string:-100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[3];
Movement_getSpecificReference string:3;
date string:$${today};
Movement_getExplanationTitleAndAnalytics string:Transaction 3 ref3;
debit_price string:300;
credit_price string:0;
running_total_price string:200">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[4];
Movement_getSpecificReference string:4;
date string:$${today};
Movement_getExplanationTitleAndAnalytics string:Transaction 4 ref4;
debit_price string:400;
credit_price string:0;
running_total_price string:600">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[5];
Movement_getSpecificReference string:5;
date string:$${today};
Movement_getExplanationTitleAndAnalytics string:Transaction 5 ref5;
debit_price string:500;
credit_price string:0;
running_total_price string:1100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[6];
Movement_getSpecificReference string:6;
date string:$${today};
Movement_getExplanationTitleAndAnalytics string:Transaction 6 ref6;
debit_price string:600;
credit_price string:0;
running_total_price string:1700">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_aged_balance</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Aged Balance Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4904">testAccountingReports.py:test_simple_aged_creditor_report_detailed.testOtherPartiesReport</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=aged-balance&report_style=Hal;
now python:DateTime();
last_month python:DateTime(now.year(), now.month(), 1) - 1;
offset python:0">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(last_month.day())"></td><td>last_day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(last_month.month())"></td><td>last_month</td></tr>
<tr><td>store</td><td tal:content="python: last_month.year()"></td><td>last_year</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Aged Balance"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Aged Balance"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_account_type"]//select</td><td></td></tr>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_account_type"]//select</td>
<td>value=account_type/asset/receivable</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-${month}-01</td></tr>
<tr><td>click</td><!-- Check because it is unchecked by default -->
<td>//input[@type="checkbox" and @name="field_your_detailed"]</td><td></td></tr>
<tr><td>type</td>
<td>//textarea[@name="field_your_period_list"]</td>
<td>1
2
3</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td><td></td></tr>
<!-- we need to zero-out simulation_state to just 'delivered' -->
<tal:block tal:repeat="_ python:range(4)"><!-- There is by default just few simulation states -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>store</td>]
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="1 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
<tr><td>store</td>
<td>${table}/tbody/tr[1]</td>
<td>current_row</td></tr>
<tr><td>assertText</td>
<td>${current_row}/td[1]/a</td>
<td>Client 1</td></tr>
<tr><td>assertText</td>
<td>${current_row}/td[2]/a</td>
<td>Sale invoice 2</td></tr>
<!-- skip account_number field -->
<!-- skip invoice_number field -->
<!-- skip transaction_reference field -->
<tr><td>assertText</td>
<td>${current_row}/td[6]/a</td>
<td>${last_day}/${last_month}/${last_year}</td></tr>
<tr><td>assertText</td>
<td>${current_row}/td[7]/a</td>
<td>Sale Invoice Transaction</td></tr>
<tr><td>assertFloat</td>
<td>${current_row}/td[8]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td>300</td></tr><!-- balance -->
<tr><td>assertText</td>
<td>${current_row}/td[9]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td></td></tr><!-- future is empty -->
<tr><td>assertFloat</td>
<td>${current_row}/td[10]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td>300</td></tr><!-- period_1=300 -->
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_balance_sheet</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Balance Sheet Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4563">testAccountingReports.py:TestAccountingReports.testBalanceSheet</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=balance-sheet&report_style=Hal;
offset python:0;
now python:DateTime()">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Balance Sheet"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Balance Sheet"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-12-31</td></tr>
<!-- We cannot easily ensure correctness because the report renders always as
an attachment. Even in unittest we only check validity of such attachment.
-->
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_general_ledger</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head><title>Test Report General Ledger</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L3895">testAccountingReports.py:TestAccountingReports.testGeneralLedger</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=general-ledger&report_style=Hal;
now python:DateTime();
today python:DateTime(now.year(), now.month(), now.day());
tomorrow python:today + 1;
yesterday python:today - 1;
offset python:0">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(today.day(), today.month(), today.year())"></td><td>today</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(tomorrow.day(), tomorrow.month(), tomorrow.year())"></td><td>tomorrow</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}/{:0>#2d}/{:d}'.format(yesterday.day(), yesterday.month(), yesterday.year())"></td><td>yesterday</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="General Ledger"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="General Ledger"]</td><td></td></tr>
<tr><td>waitForElementPresent</td><td>//input[@name="field_your_from_date"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_from_date"]</td>
<td>${year}-1-1</td></tr>
<tr><td>waitForElementPresent</td><td>//input[@name="field_your_at_date"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-12-31</td></tr>
<tr><td>waitForElementPresent</td><td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<!-- All checkboxes are by default turned off -->
<!-- we need to zero-out simulation_state to just 'delivered' -->
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td><td></td></tr>
<tal:block tal:repeat="_ python:range(4)"><!-- There is by default just few simulation states -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<!-- 1. report section is the Bank -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[1]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="A-BANK - Bank (Bank1)"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="5 Records"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getExplanationTitleAndAnalytics string:Transaction 3 ref3;
Movement_getSpecificReference string:3;
date string:$${today};
debit_price string:0;
credit_price string:300;
running_total_price string:-300">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
Movement_getExplanationTitleAndAnalytics string:Transaction 4 ref4;
Movement_getSpecificReference string:4;
date string:$${today};
debit_price string:0;
credit_price string:400;
running_total_price string:-700">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[3];
Movement_getExplanationTitleAndAnalytics string:Transaction 5 ref5;
Movement_getSpecificReference string:5;
date string:$${today};
debit_price string:0;
credit_price string:500;
running_total_price string:-1200">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[4];
Movement_getExplanationTitleAndAnalytics string:Transaction 6 ref6;
Movement_getSpecificReference string:6;
date string:$${today};
debit_price string:0;
credit_price string:600;
running_total_price string:-1800">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[5];
Movement_getExplanationTitleAndAnalytics string:Transaction 8 ref8;
Movement_getSpecificReference string:8;
date string:$${tomorrow};
debit_price string:0;
credit_price string:800;
running_total_price string:-2600">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<!-- 2. report section is Payable - Client 1 -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[2]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="A-PAY - Payable (Client 1)"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="2 Records"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getExplanationTitleAndAnalytics string:Transaction 1 ref1;
Movement_getSpecificReference string:1;
Movement_getMirrorSectionTitle string:Client 1;
date string:$${yesterday};
debit_price string:0;
credit_price string:100;
running_total_price string:-100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
Movement_getExplanationTitleAndAnalytics string:Transaction 2 ref2;
Movement_getSpecificReference string:2;
date string:$${yesterday};
debit_price string:200;
credit_price string:0;
running_total_price string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<!-- 3. report section is A-REC - Receivable (Client 1) -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[3]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="A-REC - Receivable (Client 1)"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="5 Records"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getSpecificReference string:1;
Movement_getExplanationTitleAndAnalytics string:Transaction 1 ref1;
date string:$${yesterday};
debit_price string:100;
credit_price string:0;
running_total_price string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
Movement_getSpecificReference string:2;
Movement_getExplanationTitleAndAnalytics string:Transaction 2 ref2;
date string:$${yesterday};
debit_price string:0;
credit_price string:200;
running_total_price string:-100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[3];
Movement_getSpecificReference string:3;
Movement_getExplanationTitleAndAnalytics string:Transaction 3 ref3;
date string:$${today};
debit_price string:300;
credit_price string:0;
running_total_price string:200">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[4];
Movement_getSpecificReference string:6;
Movement_getExplanationTitleAndAnalytics string:Transaction 6 ref6;
date string:$${today};
debit_price string:600;
credit_price string:0;
running_total_price string:800">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[5];
Movement_getSpecificReference string:8;
Movement_getExplanationTitleAndAnalytics string:Transaction 8 ref8;
date string:$${tomorrow};
debit_price string:800;
credit_price string:0;
running_total_price string:1600">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<!-- 4. report section is A-REC - Receivable (Client 2) -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[4]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="A-REC - Receivable (Client 2)"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="1 Records"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getExplanationTitleAndAnalytics string:Transaction 4 ref4;
Movement_getSpecificReference string:4;
date string:$${today};
debit_price string:400;
credit_price string:0;
running_total_price string:400">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<!-- 5. report section is A-REC - Receivable (John Smith) -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[5]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="A-REC - Receivable (John Smith)"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="1 Records"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getExplanationTitleAndAnalytics string:Transaction 5 ref5;
Movement_getSpecificReference string:5;
date string:$${today};
debit_price string:500;
credit_price string:0;
running_total_price string:500">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_table" />
</tal:block>
<!-- 5. report section is Total -->
<tr><td>store</td>
<td>//div[@class="report_section_list"]/div/div[6]//div[@data-gadget-scope="erp5_form"]</td>
<td>form</td></tr>
<tr><td>waitForElementPresent</td>
<td>${form}//h3[text()="Total"]</td><td></td></tr>
<tr><td>store</td>
<td>${form}//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="1 Records"]</td><td></td></tr>
<tr><td>store</td>
<td>${table}/tbody/tr[1]</td>
<td>current_row</td></tr>
<tr><td>assertFloat</td><!-- debit -->
<td>${current_row}/td[1]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td>2900</td></tr>
<tr><td>assertFloat</td><!-- credit -->
<td>${current_row}/td[2]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td>2900</td></tr>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_invoices_report</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Invoices Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
No test!
</th></tr>
</thead>
<tbody>
<!-- There is no unittest covering this report so no UI test either. -->
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_journal</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Report Journal</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L53">TestAccountingReports.testJournal</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=journal&report_style=Hal;
now python: DateTime()">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Journal"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Journal"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<!-- field_your_at_date is already at now() -->
<!-- field_your_section_category_strict is False by default -->
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_portal_type"]/div/div[1]//select</td><td></td></tr>
<!-- we need to zero-out portal_types to just 'Sale Invoice Transaction' -->
<tal:block tal:repeat="_ python:range(8)"><!-- there is by default many Portal Types -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_portal_type"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_portal_type"]/div/div[1]//select</td>
<td>value=Sale Invoice Transaction</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td><td></td></tr>
<!-- we need to zero-out simulation_state to just 'delivered' -->
<tal:block tal:repeat="_ python:range(4)"><!-- There is by default just few simulation states -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="9 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
date string:$${day}/$${month}/$${year};
title string:First One;
parent_reference string:1;
node_title string:A-REC;
mirror_section_title string:Client 1;
debit string:119.60;
credit string:0">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
specific_reference string:;
title string:;
parent_reference string:;
node_title string:A-COLLECTED-VAT;
mirror_section_title string:Client 1;
debit string:0,
credit string:19.60">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[3];
specific_reference string:;
title string:;
parent_reference string:;
node_title string:A-GS;
mirror_section_title string:Client 1;
debit string:0;
credit string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<!-- Second Transaction -->
<tal:block tal:define="row_selector string:$${table}/tbody/tr[4];
date string:$${day}/$${month}/$${year};
title string:Second One;
parent_reference string:2;
node_title string:A-REC;
mirror_section_title string:Client 2;
debit string:239.20;
credit string:0">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[5];
specific_reference string:;
title string:;
parent_reference string:;
node_title string:A-COLLECTED-VAT;
mirror_section_title string:Client 2;
debit string:0;
credit string:39.20">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[6];
specific_reference string:;
title string:;
parent_reference string:;
node_title string:A-GS;
mirror_section_title string:Client 2;
debit string:0;
credit string:200">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<!-- Third Transaction -->
<tal:block tal:define="row_selector string:$${table}/tbody/tr[7];
date string:$${day}/$${month}/$${year};
title string:Third One;
parent_reference string:3;
node_title string:A-REC;
mirror_section_title string:John Smith;
debit string:358.80;
credit string:0">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[8];
specific_reference string:;
title string:;
parent_reference string:;
node_title string:A-COLLECTED-VAT;
mirror_section_title string:John Smith;
debit string:0;
credit string:58.80">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[9];
specific_reference string:;
title string:Line Title;
parent_reference string:;
node_title string:A-GS;
mirror_section_title string:John Smith;
debit string:0;
credit string:300">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_transactions_table" />
</tal:block>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_profit_and_loss</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Profit and Loss Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4542">testAccountingReports.py:TestAccountingReports.testProfitAndLoss</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=profit-and-loss&report_style=Hal;
offset python:0;
now python:DateTime()">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Profit and Loss"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Profit and Loss"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-12-31</td></tr>
<!-- We cannot easily ensure correctness because the report renders always as
an attachment. Even in unittest we only check validity of such attachment.
-->
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_restricted_skin_selection</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Invoices Report Skin Allowance</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Check that use gets notified if they are using wrong 'Report Style' (UI) Preference.
</th></tr>
</thead>
<!-- Set Report Style to not-allowed value 'View' -->
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=journal&report_style=View;">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<!-- Select random Report and fill in just the necessary values for valid submission. -->
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Journal"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Journal"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<!-- Now we should get somehow notified about wrong skin selection.
At the beginning it was implemented as Form Message because the error is on a hidden field. -->
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="notification"]//button[contains(text(), "skins are allowed for reports")]</td><td></td></tr>
<tr><td>assertElementPresent</td>
<td>//div[@data-gadget-scope="notification"]//button[contains(text(), "skins are allowed for reports")]</td><td></td></tr>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_third_parties</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Third Party (aka Other Party) Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4658">testAccountingReports.py:TestAccountingReports.testOtherPartiesReport</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=other-parties-ledger&report_style=Hal;
offset python:0;
now python:DateTime()">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Third Parties"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Third Parties"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_ledger"]//select</td><td></td></tr>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_ledger"]//select</td>
<td>value=ledger/accounting/general</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-${month}-${day}</td></tr>
<tr><td>click</td><!-- Uncheck because it is checked by default -->
<td>//input[@type="checkbox" and @name="field_your_omit_balanced_accounts"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td><td></td></tr>
<!-- we need to zero-out simulation_state to just 'delivered' -->
<tal:block tal:repeat="_ python:range(4)"><!-- There is by default just few simulation states -->
<!-- we take advantage of dissapearing elements when selecting one empty -->
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=</td></tr>
<tr><td>pause</td>
<td>50</td>
<td></td></tr>
</tal:block>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="2 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
Movement_getExplanationTitle string:Transaction 1;
section_title string:;
Movement_getExplanationTranslatedPortalType string:Accounting Transaction;
Movement_getNodeGapId string:A-REC;
Movement_getExplanationReference string:;
Movement_getSpecificReference string:1;
getTranslatedSimulationStateTitle string:Closed;
debit_price string:100;
credit_price string:0;
running_total_price string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_ledger_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
Movement_getExplanationTitle string:Transaction 2;
section_title string:;
Movement_getExplanationTranslatedPortalType string:Accounting Transaction;
Movement_getNodeGapId string:A-PAY;
Movement_getExplanationReference string:;
Movement_getSpecificReference string:2;
getTranslatedSimulationStateTitle string:Closed;
debit_price string:200;
credit_price string:0;
running_total_price string:300">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_accounts_ledger_table" />
</tal:block>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_trial_balance</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Test Trial Balance Report</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L2326">TestAccountingReports.testJournal</a>
</th></tr>
</thead>
<tbody
tal:define="init_method string:AccountingZuite_initializeAccountingTransactionReportTest?report_name=trial-balance&report_style=Hal;
now python: DateTime()">
<tal:block metal:use-macro="here/AccountingZuite_CommonTemplate/macros/init"/>
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/accounting_module/</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Trial Balance"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Trial Balance"]</td><td></td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.day())"></td><td>day</td></tr>
<tr><td>store</td><td tal:content="python: '{:0>#2d}'.format(now.month())"></td><td>month</td></tr>
<tr><td>store</td><td tal:content="python: now.year()"></td><td>year</td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_from_date"]</td>
<td>${year}-01-01</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
<td>${year}-12-31</td></tr>
<!-- field_your_section_category_strict is False by default -->
<!-- field_your_per_account_class_summary is False by default -->
<!-- field_your_expand_accounts is False by default -->
<tr><td>click</td>
<td>//input[@type="checkbox" and @name="field_your_show_empty_accounts"]</td><td></td></tr>
<tr><td>click</td>
<td>//input[@type="checkbox" and @name="field_your_show_detailed_balance_columns"]</td><td></td></tr>
<!-- let field_your_portal_type its default values -->
<!-- For simulation state only ensure that default values are still "delivered" and "stopped" -->
<tr><td>store</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_multilist.html"]</td>
<td>multi_select</td></tr>
<tr><td>waitForElementPresent</td>
<td>${multi_select}/div[3]//select</td><td></td></tr>
<tr><td>assertValue</td>
<td>${multi_select}/div[1]//select</td>
<td>delivered</td></tr>
<tr><td>assertValue</td>
<td>${multi_select}/div[2]//select</td>
<td>stopped</td></tr>
<tr><td>assertValue</td>
<td>${multi_select}/div[3]//select</td>
<td></td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//tfoot//span[@data-i18n="10 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
<!-- ListBox sorts the result in different order than they are returned by its
List Method. Seems they are sorted on Title alphabetically -->
<tal:block tal:define="row_selector string:$${table}/tbody/tr[1];
node_id string:A-BANK;
node_title string:Bank (Bank1);
credit string:3300;
final_balance string:-3300;
final_credit_balance string:3300;
final_balance_if_credit string:3300">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[2];
node_id string:A-COLLECTED-VAT;
node_title string:Collected VAT 10%">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[3];
node_id string:A-EQUITY;
node_title string:Equity">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[4];
node_id string:A-FIX-ASSETS;
node_title string:Fixed Assets">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[5];
node_id string:A-GP;
node_title string:Goods Purchase">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[6];
node_id string:A-GS;
node_title string:Goods Sales">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[7];
node_id string:A-PAY;
node_title string:Payable;
debit string:200;
credit string:100;
final_balance string:100;
final_debit_balance string:200;
final_credit_balance string:100;
final_balance_if_debit string:100">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[8];
node_id string:A-REC;
node_title string:Receivable;
debit string:3400;
credit string:200;
final_balance string:3200;
final_debit_balance string:3400;
final_credit_balance string:200;
final_balance_if_debit string:3200">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table" />
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[9];
node_title string:Refundable VAT 10%;
node_id string:A-REF-VAT">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table"></tal:block>
</tal:block>
<tal:block tal:define="row_selector string:$${table}/tbody/tr[10];
node_title string:Stocks;
node_id string:A-STOCK">
<tal:block metal:use-macro="here/AccountingZuite_reportMacros/macros/check_row_in_balance_table"></tal:block>
</tal:block>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2011 Nexedi SARL and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
# Rafael Monnerat <rafael@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestRenderJSAccountingReport(ERP5TypeFunctionalTestCase):
foreground = 0
run_only = "renderjs_ui_accounting_report_zuite"
def getBusinessTemplateList(self):
return (
'erp5_accounting_renderjs_ui_test',
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestRenderJSAccountingReport))
return suite
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testFunctionalRJSAccountingReport</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFunctionalRJSAccountingReport</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
Copyright (c) Nexedi SARL
\ No newline at end of file
erp5_accounting_ui_test
erp5_web_renderjs_ui_test
\ No newline at end of file
RenderJS UI tests for Accounting Module.
\ No newline at end of file
portal_tests/renderjs_ui_accounting_report_zuite
portal_tests/renderjs_ui_accounting_report_zuite/**
\ No newline at end of file
test.erp5.testFunctionalRJSAccountingReport
\ No newline at end of file
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_accounting_renderjs_ui_test
\ No newline at end of file
......@@ -14,9 +14,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -27,9 +25,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -40,9 +36,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -54,9 +48,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -82,9 +74,9 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>region/region</string>
<string>group/client</string>
<string>role/client</string>
<string>region/region</string>
</tuple>
</value>
</item>
......
......@@ -14,9 +14,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -27,9 +25,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -40,9 +36,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -54,9 +48,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -82,8 +74,8 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>region/region</string>
<string>role/client</string>
<string>region/region</string>
</tuple>
</value>
</item>
......
......@@ -14,9 +14,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -27,9 +25,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -40,9 +36,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -54,9 +48,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -83,8 +75,8 @@
<value>
<tuple>
<string>price_currency/currency_module/euro</string>
<string>region/region</string>
<string>group/demo_group</string>
<string>region/region</string>
</tuple>
</value>
</item>
......
......@@ -14,9 +14,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -27,9 +25,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -40,9 +36,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -54,9 +48,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......
......@@ -74,9 +74,9 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>region/region</string>
<string>group/vendor</string>
<string>role/supplier</string>
<string>region/region</string>
</tuple>
</value>
</item>
......
......@@ -14,9 +14,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -27,9 +25,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -40,9 +36,7 @@
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -54,9 +48,7 @@
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
......
......@@ -10,6 +10,8 @@
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -19,6 +21,7 @@
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -28,6 +31,7 @@
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -37,6 +41,7 @@
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -46,6 +51,8 @@
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -83,6 +90,10 @@
<key> <string>portal_type</string> </key>
<value> <string>Preference</string> </value>
</item>
<item>
<key> <string>preferred_account_number_method</string> </key>
<value> <string>account_reference</string> </value>
</item>
<item>
<key> <string>preferred_accounting_transaction_at_date</string> </key>
<value>
......@@ -124,6 +135,20 @@
<key> <string>preferred_date_order</string> </key>
<value> <string>ymd</string> </value>
</item>
<item>
<key> <string>preferred_diff_filter_script_id</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>preferred_git_author</string> </key>
<value> <string encoding="cdata"><![CDATA[
Tomas Peterka <tomas.peterka@nexedi.com>
]]></string> </value>
</item>
<item>
<key> <string>preferred_html_style_access_tab</string> </key>
<value> <int>0</int> </value>
......@@ -138,7 +163,7 @@
</item>
<item>
<key> <string>preferred_html_style_developper_mode</string> </key>
<value> <int>0</int> </value>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>preferred_html_style_translator_mode</string> </key>
......@@ -180,6 +205,31 @@
<key> <string>preferred_section_category</string> </key>
<value> <string>group/demo_group</string> </value>
</item>
<item>
<key> <string>preferred_source_code_editor</string> </key>
<value>
<tuple>
<string>ace</string>
</tuple>
</value>
</item>
<item>
<key> <string>preferred_text_editor</string> </key>
<value>
<tuple>
<string>ace</string>
</tuple>
</value>
</item>
<item>
<key> <string>preferred_working_copy</string> </key>
<value>
<tuple>
<string>/srv/slapgrid/slappart26/srv/runner/software/9591320655ac0071f761c5900bf8b60c/parts/erp5/product/ERP5/bootstrap</string>
<string>/srv/slapgrid/slappart26/srv/runner/software/9591320655ac0071f761c5900bf8b60c/parts/erp5/bt5</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <int>3</int> </value>
......
......@@ -13,15 +13,6 @@ business_process = portal.portal_catalog.getResultValue(
'erp5_default_business_process'), # erp5_simulation_test
portal_type='Business Process').getRelativeUrl()
# if the previous test didn't change input data, no need to recreate content
current_script_data_id = '%s_month_count_%s_draft_%s_state_%s_payment_%s_leger_%s' % (
month_count, add_draft_transactions, transaction_state,
add_related_payments, set_ledger, script.getId())
if accounting_module.getProperty('current_content_script',
'') == current_script_data_id:
return "Accounting Transactions Created."
# first, cleanup accounting module
# XXX should be done in an external script / tool, because we have to
# workaround some security checks
......@@ -280,9 +271,6 @@ if add_draft_transactions:
source=getAccountByTitle('Goods Sales'),
quantity=random.randint(300, 400),)
accounting_module.setProperty('current_content_script',
current_script_data_id)
# test depends on this
return "Accounting Transactions Created."
# vim: syntax=python
......@@ -10,13 +10,6 @@ year = 2005
total_receivable_quantity = 0
# if the previous test didn't change input data, no need to recreate content
current_script_data_id = '%s_month_count_%s' % (
month_count, script.getId())
if accounting_module.getProperty('current_content_script',
'') == current_script_data_id:
return "Accounting Transactions Created."
# first, cleanup accounting module
# XXX should be done in an external script / tool, because we have to
# workaround some security checks
......@@ -127,10 +120,6 @@ for month in range(1, month_count + 1):
'immediateReindexObject')),
).AccountingTransactionLine_resetGroupingReference()
accounting_module.setProperty('current_content_script',
current_script_data_id)
# test depends on this
return "Accounting Transactions Created."
# vim: syntax=python
......@@ -7,13 +7,6 @@ from DateTime import DateTime
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
year = 2005
# if the previous test didn't change input data, no need to recreate content
current_script_data_id = '%s_month_count_%s' % (
month_count, script.getId())
if accounting_module.getProperty('current_content_script',
'') == current_script_data_id:
return "Accounting Transactions Created."
# first, cleanup accounting module
# XXX should be done in an external script / tool, because we have to
......@@ -99,8 +92,6 @@ for month in range(1, month_count + 1):
tr.setSourceReference('source_reference')
tr.setDestinationReference('destination_reference')
accounting_module.setProperty('current_content_script',
current_script_data_id)
# test depends on this
return "Accounting Transactions Created."
......
"""Create document of `portal_type` inside `parent` or Accounting Module.
`embedded` is a list of documents to create inside the top-level document
`simulation_state` is known to be a workflow transition variable thus we correctly
use workflows to change the state
"""
portal = context.getPortalObject()
parent = parent or portal.accounting_module
document = parent.newContent(portal_type=portal_type, **kwargs)
for embed in embedded:
script(parent=document, **embed)
if simulation_state == 'planned':
document.plan()
elif simulation_state == 'validated':
document.validate()
elif simulation_state == 'confirmed':
document.confirm()
elif simulation_state in ('stopped', 'delivered'):
document.stop()
if simulation_state == 'delivered':
document.deliver()
return document
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>portal_type, simulation_state=None, parent=None, embedded=(), **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_createDocument</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Create data set for aged balance:
$last_month: Purchase invoice 1 (500)
$last_month: Sale invoice 2 (300)
$next_month: Payment 1 (500)
$this_month + one_day: Payment 2 (300)
"""
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from DateTime import DateTime
portal = context.getPortalObject()
account_module = portal.account_module
organisation_module = portal.organisation_module
now = DateTime()
this_month = DateTime(now.year(), now.month(), 1)
last_month = this_month - 1
next_month = this_month + 32
later_this_month = this_month + 8
def get_object_by_title(portal_type, title):
"""Use portal catalog to search&fetch the object."""
result = portal.portal_catalog(
portal_type=portal_type,
title=SimpleQuery(title=title, comparison_operator='='))
if len(result) == 1:
return result.getObject()
return None
bank1 = get_object_by_title(portal_type='Bank Account', title='Bank1')
if bank1 is None:
bank1 = portal.AccountingZuite_createDocument(
portal_type='Bank Account', title='Bank1', simulation_state='validated',
parent=organisation_module.my_organisation)
purchase1 = portal.AccountingZuite_createDocument(
portal_type='Purchase Invoice Transaction',
title='Purchase invoice 1',
destination_reference='1',
source_reference='no',
reference='ref1',
simulation_state='delivered',
ledger='',
source_section_value=organisation_module.supplier,
start_date=last_month,
embedded=(dict(portal_type='Purchase Invoice Transaction Line',
destination_value=account_module.goods_purchase,
destination_debit=500.0),
dict(portal_type='Purchase Invoice Transaction Line',
destination_value=account_module.payable,
destination_credit=500.0)),
)
sale2 = portal.AccountingZuite_createDocument(
portal_type='Sale Invoice Transaction',
title='Sale invoice 2',
source_reference='2',
destination_reference='no',
reference='ref2',
simulation_state='delivered',
ledger='',
destination_section_value=organisation_module.client_1,
start_date=last_month,
embedded=(dict(portal_type='Sale Invoice Transaction Line',
source_value=account_module.goods_sales,
source_credit=300.0),
dict(portal_type='Sale Invoice Transaction Line',
source_value=account_module.receivable,
source_debit=300.0)),
)
portal.Zuite_waitForActivities()
payment3 = portal.AccountingZuite_createDocument(
portal_type='Payment Transaction',
title='Payment 1',
source_reference='3',
destination_reference='no',
simulation_state='delivered',
ledger='',
causality_value=purchase1,
payment_mode='payment_mode',
destination_section_value=organisation_module.supplier,
start_date=next_month,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.payable,
source_debit=500.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_credit=500.0)),
)
payment4 = portal.AccountingZuite_createDocument(
portal_type='Payment Transaction',
title='Payment 2',
source_reference='4',
destination_reference='4',
simulation_state='delivered',
causality_value=sale2,
payment_mode='payment_mode',
ledger='',
destination_section_value=organisation_module.client_1,
start_date=later_this_month,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_debit=300.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_credit=300.0)),
)
# we should have all receivable and payable lines grouped.
for transaction in [purchase1, sale2, payment3, payment4]:
for line in transaction.getMovementList():
if (line.getSourceValue() in (account_module.receivable,
account_module.payable) or
line.getDestinationValue() in (account_module.receivable,
account_module.payable)):
assert line.getGroupingReference()
assert line.getGroupingDate()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Python Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_createReportAgedBalanceDataset</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Python Script</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Create Dataset for basic Trial Balance Report such as defined at
https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L563
The test operates on 'today' transactions.
"""
from DateTime import DateTime
portal = context.getPortalObject()
person_module = portal.person_module
accounting_module = portal.accounting_module
account_module = portal.account_module
organisation_module = portal.organisation_module
one_hour = 1.0 / 24.0
one_day = 1.0
now = DateTime()
today = DateTime(now.year(), now.month(), now.day()) + 8 * one_hour
yesterday = today - one_day
tomorrow = today + one_day
bank1 = context.AccountingZuite_createDocument(
parent=organisation_module.my_organisation,
portal_type='Bank Account',
title='Bank1',
simulation_state='validated'
)
bank2 = bank1
if two_banks:
bank2 = context.AccountingZuite_createDocument(
parent=organisation_module.my_organisation,
portal_type='Bank Account',
title='Bank2',
simulation_state='validated'
)
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 1',
source_reference='1',
reference='ref1',
simulation_state='delivered',
destination_section_value=organisation_module.client_1,
start_date=yesterday,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=100.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.payable,
source_credit=100.0)),
)
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 2',
source_reference='2',
reference='ref2',
simulation_state='delivered',
destination_section_value=organisation_module.client_1,
start_date=yesterday + one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.payable,
source_debit=200.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_credit=200.0)),
)
# in the period
context.AccountingZuite_createDocument(
portal_type='Payment Transaction',
title='Transaction 3',
source_reference='3',
reference='ref3',
simulation_state='delivered',
source_payment_value=bank1,
destination_section_value=organisation_module.client_1,
start_date=today + one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=300.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_credit=300.0)),
)
context.AccountingZuite_createDocument(
portal_type='Payment Transaction',
title='Transaction 4',
destination_reference='4',
reference='ref4',
simulation_state='delivered',
destination_section_value=organisation_module.my_organisation,
destination_payment_value=bank1,
source_section_value=organisation_module.client_2,
stop_date=today + 2 * one_hour,
start_date=yesterday,
embedded=(dict(portal_type='Accounting Transaction Line',
destination_value=account_module.receivable,
destination_debit=400.0),
dict(portal_type='Accounting Transaction Line',
destination_value=account_module.bank,
destination_credit=400.0)),
)
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 5',
source_reference='5',
reference='ref5',
simulation_state='delivered',
source_payment_value=bank2,
destination_section_value=person_module.john_smith,
start_date=today + 3 * one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=500.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_credit=500.0)),
)
context.AccountingZuite_createDocument(
portal_type='Purchase Invoice Transaction',
title='Transaction 6',
destination_reference='6',
reference='ref6',
simulation_state='delivered',
destination_payment_value=bank2,
source_section_value=organisation_module.client_1,
start_date=yesterday + 2 * one_hour,
stop_date=today + 4 * one_hour,
embedded=(dict(portal_type='Purchase Invoice Transaction Line',
destination_value=account_module.receivable,
destination_debit=600.0),
dict(portal_type='Purchase Invoice Transaction Line',
destination_value=account_module.bank,
destination_credit=600.0)),
)
# another simulation state
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 7',
source_reference='7',
reference='ref7',
simulation_state='stopped',
source_payment_value=bank2,
destination_section_value=organisation_module.client_1,
start_date=today + 5 * one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=700.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_credit=700.0)),
)
# after the period
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 8',
source_reference='8',
reference='ref8',
simulation_state='delivered',
source_payment_value=bank2,
destination_section_value=organisation_module.client_1,
start_date=tomorrow,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=800.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.bank,
source_credit=800.0)),
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>two_banks=False</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_createReportDataset</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from DateTime import DateTime
portal = context.getPortalObject()
accounting_module = portal.accounting_module
account_module = portal.account_module
one_hour = 1.0 / 24.0
today = DateTime(DateTime().Date()) + 8 * one_hour
yesterday = today - 1
tomorrow = today + 1
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
simulation_state='delivered',
start_date=yesterday,
embedded=[
{"portal_type": 'Accounting Transaction Line',
"source_value": account_module.equity,
"source_debit": 100},
{"portal_type": 'Accounting Transaction Line',
"source_value": account_module.stocks,
"source_credit": 100}
]
)
context.AccountingZuite_createDocument(
portal_type='Sale Invoice Transaction',
title='First One',
simulation_state='delivered',
reference='1',
destination_section_value=portal.organisation_module.client_1,
start_date=today,
embedded=[
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.receivable,
"source_debit": 119.60},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.collected_vat,
"source_credit": 19.60},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.goods_sales,
"source_credit": 100.00},
]
)
context.AccountingZuite_createDocument(
portal_type='Sale Invoice Transaction',
title='Second One',
simulation_state='delivered',
reference='2',
destination_section_value=portal.organisation_module.client_2,
start_date=today + one_hour,
# different values of hour minutes, because /for now/ sorting is
# done on date, uid. Sorting on [source|destination]_reference
# would be too heavy, and we just want a sort on date, with a
# stable order (hence the cheap sort on uid)
embedded=[
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.receivable,
"source_debit": 239.20},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.collected_vat,
"source_credit": 39.20},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.goods_sales,
"source_credit": 200.00},
]
)
context.AccountingZuite_createDocument(
portal_type='Sale Invoice Transaction',
title='Third One',
simulation_state='delivered',
reference='3',
destination_section_value=portal.person_module.john_smith,
start_date=today + 2 * one_hour,
embedded=[
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.receivable,
"source_debit": 358.80},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.collected_vat,
"source_credit": 58.80},
{"portal_type": 'Sale Invoice Transaction Line',
"source_value": account_module.goods_sales,
"title": 'Line Title',
"source_credit": 300.00},
]
)
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
simulation_state='delivered',
start_date=today,
embedded=[
{"portal_type": 'Accounting Transaction Line',
"source_value": account_module.equity,
"source_debit": 111},
{"portal_type": 'Accounting Transaction Line',
"source_value": account_module.stocks,
"source_credit": 111},
]
)
context.AccountingZuite_createDocument(
portal_type='Sale Invoice Transaction',
simulation_state='delivered',
destination_section_value=portal.organisation_module.client_2,
start_date=tomorrow,
embedded=[
{"portal_type": "Sale Invoice Transaction Line",
"source_value": account_module.receivable,
"source_debit": 598.00},
{"portal_type": "Sale Invoice Transaction Line",
"source_value": account_module.collected_vat,
"source_credit": 98.00},
{"portal_type": "Sale Invoice Transaction Line",
"source_value": account_module.goods_sales,
"source_credit": 500.00}])
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_createReportJournalDataset</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Create Dataset for basic Trial Balance Report such as defined at
https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4583
The test operates on 'today' transactions.
:param with_ledger: if true then data are prepared for
testOtherPartiesReportLedger unittest rather than testOtherPartiesReport
"""
from DateTime import DateTime
portal = context.getPortalObject()
accounting_module = portal.accounting_module
account_module = portal.account_module
ledger = portal.portal_categories.ledger
organisation_module = portal.organisation_module
person_module = portal.person_module
one_hour = 1.0 / 24.0
now = DateTime()
today = DateTime(now.year(), now.month(), now.day()) + 8 * one_hour
if with_ledger:
extra_kwargs_general = {'ledger': 'accounting/general'}
extra_kwargs_detailed = {'ledger': 'accounting/detailed'}
accounting_ledger = ledger.get('accounting', None) \
or ledger.newContent(portal_type='Category', id='accounting')
if accounting_ledger.get('general', None) is None:
accounting_ledger.newContent(portal_type='Category', id='general')
if accounting_ledger.get('detailed', None) is None:
accounting_ledger.newContent(portal_type='Category', id='detailed')
# necessary for the "ledger" form field to appear and have correct options
accounting_transaction = portal.portal_types.get('Accounting Transaction')
if not accounting_transaction.getLedgerList():
accounting_transaction.setProperty(
"ledger_list", ['accounting/general', 'accounting/detailed'])
else:
extra_kwargs_general = {}
extra_kwargs_detailed = {}
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 1',
source_reference='1',
simulation_state='delivered',
destination_section_value=organisation_module.client_1,
start_date=today + 1 * one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.receivable,
source_debit=100.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.goods_sales,
source_credit=100.0)),
**extra_kwargs_general
)
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 2',
source_reference='2',
simulation_state='delivered',
destination_section_value=organisation_module.client_1,
start_date=today + 2 * one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.payable, source_debit=200.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.goods_sales,
source_credit=200.0)),
**extra_kwargs_general
)
if with_ledger:
context.AccountingZuite_createDocument(
portal_type='Accounting Transaction',
title='Transaction 3',
source_reference='3',
simulation_state='delivered',
destination_section_value=organisation_module.client_1,
start_date=today + 3 * one_hour,
embedded=(dict(portal_type='Accounting Transaction Line',
source_value=account_module.payable, source_debit=400.0),
dict(portal_type='Accounting Transaction Line',
source_value=account_module.goods_sales,
source_credit=400.0)),
**extra_kwargs_detailed
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Python Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>with_ledger=True</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_createReportOtherPartiesDataset</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Python Script</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Create deterministically transactions to test outputs of Reports."""
from DateTime import DateTime
portal = context.getPortalObject()
module = portal.accounting_module
# This option is necessary for RJS interface to render Reports
# If you'd keep XHTML value "View" the RJS will not crash it will
# only refuse to validate Report Form View
portal.AccountingZuite_setAccountReferencePreference(report_style=report_style)
# First, clean up the module
module.manage_delObjects(list(module.objectIds()))
# Create datasets
if report_name == "journal":
module.AccountingZuite_createReportJournalDataset()
elif report_name in ("trial-balance", "general-ledger"):
module.AccountingZuite_createReportDataset()
elif report_name in ("account-statement", "balance-sheet", "profit-and-loss"):
module.AccountingZuite_createReportDataset(two_banks=True)
elif report_name == "other-parties":
module.AccountingZuite_createReportOtherPartiesDataset(with_ledger=False)
elif report_name == "other-parties-ledger":
module.AccountingZuite_createReportOtherPartiesDataset()
elif report_name == "aged-balance":
module.AccountingZuite_createReportAgedBalanceDataset()
else:
raise RuntimeError('Unknown "{}" report - no create*Dataset defined'.format(
report_name))
# test depends on this
return "Accounting Transactions Created."
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>report_name, report_style</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_initializeAccountingTransactionReportTest</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -3,7 +3,5 @@ module = portal.accounting_module
module.manage_delObjects(list(module.objectIds()))
module.setProperty('current_content_script', script.getId())
# test depends on this
return "Accounting Transactions Created."
"""Change the `current_content_script` property on accounting module,
so that the next test knows that we have modified the test data.
XXX Kato: This is seriously wrong because test must not suppose anything
about the other tests - especially that they did not modify the data.
"""
context.getPortalObject().accounting_module.setProperty(
'current_content_script', 'modified')
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingZuite_reportMacros</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<!-- Those macros help to check cell values in tables.
It solves the problem when a column changes its position - then you modify once-here.
Further macros allow to copy&paste code from UnitTest so you are sure you didn't
forget anything.
Usage:
:param row_selector: mandatory XPath selector of processed row - has to be a
full-path selector with possible embedded zope variables
example: tal:define="row_selector string:$${table}/tbody/tr[1]"
will use Zuite's own variable ${table} save by 'store'.
Other parameters copy the names from unittest and are optional. If a parameter
is unspecified then the check is either skipped or replaced with a meaningful
default value (e.g. 0 for numerical variables).
-->
<head>
<title tal:content="template/title">Macros for testing Reports</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body tal:define="offset python:0">
<!-- Following macro checks values in generally used "Transactions Table".
Since all macros are very similar - please refer to the docs at the top. -->
<tal:block metal:define-macro='check_row_in_transactions_table'>
<tr tal:condition="exists:row_selector"><td>store</td>
<td tal:content="string:$row_selector"></td>
<td>current_row</tr>
<tr tal:condition="exists:specific_reference">
<td>assertText</td>
<td>${current_row}/td[1]//a</td>
<td tal:content="string:$specific_reference"></td></tr>
<tr tal:condition="exists:date">
<td>assertText</td>
<td>${current_row}/td[2]//p</td>
<td tal:content="string:$date"></td></tr>
<tr tal:condition="exists:title">
<td>assertText</td>
<td>${current_row}/td[3]//a</td>
<td tal:content="string:$title"></td></tr>
<tr tal:condition="exists:parent_reference">
<td>assertText</td>
<td>${current_row}/td[4]//a</td>
<td tal:content="string:$parent_reference"></td></tr>
<tr tal:condition="exists:node_title">
<td>assertText</td>
<td>${current_row}/td[5]//a</td>
<td tal:content="string:$node_title"></td></tr>
<tr tal:condition="exists:mirror_section_title">
<td>assertText</td>
<td>${current_row}/td[6]//a</td>
<td tal:content="string:$mirror_section_title"></td></tr>
<tr tal:condition="exists:debit">
<td>assertFloat</td>
<td>${current_row}/td[7]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td tal:content="string:$debit"></td></tr>
<tr tal:condition="exists:credit">
<td>assertFloat</td>
<td>${current_row}/td[8]//div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td>
<td tal:content="string:$credit"></td></tr>
</tal:block>
<!-- Following macro checks values in generally used "Accounts Ledger Table"
Since all macros are very similar - please refer to the docs at the top. -->
<tal:block metal:define-macro='check_row_in_accounts_ledger_table'>
<tr tal:condition="exists:row_selector"><td>store</td>
<td tal:content="string:$row_selector"></td>
<td>current_row</tr>
<tr tal:condition="exists:date">
<td>assertText</td><!-- Date -->
<td tal:content="python: '${{current_row}}/td[{:d}]//p'.format(offset + 1)"></td>
<td tal:content="string:$date"></td></tr>
<tr tal:condition="exists:Movement_getExplanationTranslatedPortalType">
<td>assertText</td><!-- Type -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 2)"></td>
<td tal:content="string:$Movement_getExplanationTranslatedPortalType"></td></tr>
<tr tal:condition="exists:Movement_getNodeGapId">
<td>assertText</td><!-- GAP -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 3)"></td>
<td tal:content="string:$Movement_getNodeGapId"></td></tr>
<tr tal:condition="exists:Movement_getExplanationReference">
<td>assertText</td><!-- Invoice No -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 4)"></td>
<td tal:content="string:$Movement_getExplanationReference"></td></tr>
<tr tal:condition="exists:Movement_getExplanationTitle">
<td>assertText</td><!-- Title -->
<td tal:content="python:'${{current_row}}/td[{:d}]//a'.format(offset + 5)"></td>
<td tal:content="string:$Movement_getExplanationTitle"></td></tr>
<tr tal:condition="exists:Movement_getSpecificReference">
<td>assertText</td><!-- Reference -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 6)"></td>
<td tal:content="string:$Movement_getSpecificReference">1</td></tr>
<tr tal:condition="exists:getTranslatedSimulationStateTitle">
<td>assertText</td><!-- State -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 7)"></td>
<td tal:content="string:$getTranslatedSimulationStateTitle"></td></tr>
<tr tal:condition="exists:debit_price">
<td>assertFloat</td>
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 8)"></td>
<td tal:content="string:$debit_price"></td></tr>
<tr tal:condition="exists:credit_price">
<td>assertFloat</td>
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 9)"></td>
<td tal:content="string:$credit_price"></td></tr>
<tr tal:condition="exists:running_total_price">
<td>assertFloat</td><!-- Balance -->
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 10)"></td>
<td tal:content="string:$running_total_price"></td></tr>
</tal:block>
<!-- Following macro checks values in generally used "Accounts Table"
Since all macros are very similar - please refer to the docs at the top. -->
<tal:block metal:define-macro='check_row_in_accounts_table'>
<tr tal:condition="exists:row_selector"><td>store</td>
<td tal:content="string:$row_selector"></td>
<td>current_row</tr>
<tr tal:condition="exists:date">
<td>assertText</td><!-- Date -->
<td tal:content="python: '${{current_row}}/td[{:d}]//p'.format(offset + 1)"></td>
<td tal:content="string:$date"></td></tr>
<tr tal:condition="exists:Movement_getSpecificReference">
<td>assertText</td><!-- Transaction Reference -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 2)"></td>
<td tal:content="string:$Movement_getSpecificReference"></td></tr>
<tr tal:condition="exists:mirror_section_title">
<td>assertText</td><!-- Third Party -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 3)"></td>
<td tal:content="string:$mirror_section_title"></td></tr>
<tr tal:condition="exists:Movement_getExplanationTitleAndAnalytics">
<td>assertText</td><!-- Title Reference -->
<td tal:content="python: '${{current_row}}/td[{:d}]//a'.format(offset + 4)"></td>
<td tal:content="string:$Movement_getExplanationTitleAndAnalytics"></td></tr>
<tr tal:condition="exists:debit_price">
<td>assertFloat</td>
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 5)"></td>
<td tal:content="string:$debit_price"></td></tr>
<tr tal:condition="exists:credit_price">
<td>assertFloat</td>
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 6)"></td>
<td tal:content="string:$credit_price"></td></tr>
<tr tal:condition="exists:running_total_price">
<td>assertFloat</td><!-- Balance -->
<td tal:content="python: '${{current_row}}/td[{:d}]//div[contains(@data-gadget-url, \'gadget_html5_element.html\')]/p'.format(offset + 7)"></td>
<td tal:content="string:$running_total_price"></td></tr>
<!-- Grouping Reference -->
<!-- Grouping Date -->
<!-- State -->
</tal:block>
<!-- Following macro checks values in generally used "Balance Table".
Since all macros are very similar - please refer to the docs at the top. -->
<tal:block metal:define-macro='check_row_in_balance_table'>
<tr><td>store</td><td tal:content="string:$row_selector"></td><td>current_row</td></tr>
<tr><td>store</td><td>div[@data-gadget-url="${renderjs_url}/gadget_html5_element.html"]/p</td><td>float_val</td></tr>
<tr tal:condition="exists:node_id">
<td>assertText</td>
<td>${current_row}/td[1]//a</td>
<td tal:content="string:$node_id"></td></tr><!-- not covered by the unittest -->
<tr tal:condition="exists:node_title">
<td>assertText</td>
<td>${current_row}/td[2]//a</td>
<td tal:content="string:$node_title"></td></tr>
<tr tal:condition="exists:initial_debit_balance">
<td>assertFloat</td>
<td>${current_row}/td[3]//${float_val}</td>
<td tal:condition="exists:initial_debit_balance" tal:content="string:$initial_debit_balance"></td>
<td tal:condition="not:exists:initial_debit_balance">0</td></tr>
<tr tal:condition="exists:initial_credit_balance">
<td>assertFloat</td>
<td>${current_row}/td[4]//${float_val}</td>
<td tal:condition="exists:initial_credit_balance" tal:content="string:$initial_credit_balance"></td>
<td tal:condition="not:exists:initial_credit_balance">0</td></tr>
<tr tal:condition="exists:initial_balance">
<td>assertFloat</td>
<td>${current_row}/td[5]//${float_val}</td>
<td tal:condition="exists:initial_balance" tal:content="string:$initial_balance"></td>
<td tal:condition="not:exists:initial_balance">0</td></tr>
<tr tal:condition="exists:debit">
<td>assertFloat</td>
<td>${current_row}/td[6]//${float_val}</td>
<td tal:condition="exists:debit" tal:content="string:$debit"></td>
<td tal:condition="not:exists:debit">0</td></tr>
<tr tal:condition="exists:credit">
<td>assertFloat</td>
<td>${current_row}/td[7]//${float_val}</td>
<td tal:condition="exists:credit" tal:content="string:$credit"></td>
<td tal:condition="not:exists:credit">0</td></tr>
<tr tal:condition="exists:final_debit_balance">
<td>assertFloat</td>
<td>${current_row}/td[8]//${float_val}</td>
<td tal:condition="exists:final_debit_balance" tal:content="string:$final_debit_balance"></td>
<td tal:condition="not:exists:final_debit_balance">0</td></tr>
<tr tal:condition="exists:final_credit_balance">
<td>assertFloat</td>
<td>${current_row}/td[9]//${float_val}</td>
<td tal:condition="exists:final_credit_balance" tal:content="string:$final_credit_balance"></td>
<td tal:condition="not:exists:final_credit_balance">0</td></tr>
<tr tal:condition="exists:final_balance">
<td>assertFloat</td>
<td>${current_row}/td[10]//${float_val}</td>
<td tal:condition="exists:final_balance" tal:content="string:$final_balance"></td>
<td tal:condition="not:exists:final_balance">0</td></tr>
<tr tal:condition="exists:final_balance_if_debit">
<td>assertFloat</td>
<td>${current_row}/td[11]//${float_val}</td>
<td tal:condition="exists:final_balance_if_debit" tal:content="string:$final_balance_if_debit"></td>
<td tal:condition="not:exists:final_balance_if_debit">0</td></tr>
<tr tal:condition="exists:final_balance_if_credit">
<td>assertFloat</td>
<td>${current_row}/td[12]//${float_val}</td>
<td tal:condition="exists:final_balance_if_credit" tal:content="string:$final_balance_if_credit"></td>
<td tal:condition="not:exists:final_balance_if_credit">0</td></tr>
</tal:block>
</body>
</html>
\ No newline at end of file
preference = context.portal_preferences.accounting_zuite_preference
preference.edit(preferred_account_number_method='account_reference')
preference.edit(
preferred_account_number_method='account_reference',
preferred_report_style=report_style)
return "Preference Set"
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>report_style=\'View\'</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
erp5_invoicing
erp5_accounting
erp5_ui_test
erp5_simulation_test
erp5_simplified_invoicing
erp5_configurator_standard_accounting_template
\ No newline at end of file
......@@ -6,26 +6,25 @@ from Products.ERP5Type.Log import log, DEBUG, INFO, WARNING
# XXX We should not use meta_type properly,
# XXX We need to discuss this problem.(yusei)
def isListBox(field):
if field.meta_type=='ListBox':
return True
elif field.meta_type=='ProxyField':
template_field = field.getRecursiveTemplateField()
if template_field.meta_type=='ListBox':
return True
return False
from Products.Formulator.Errors import FormValidationError
def isFieldType(field, type_name):
if field.meta_type == 'ProxyField':
field = field.getRecursiveTemplateField()
return field.meta_type == type_name
from Products.Formulator.Errors import FormValidationError, ValidationError
from ZTUtils import make_query
request = REQUEST
if REQUEST is None:
request = container.REQUEST
# request.form holds POST data thus containing 'field_' + field.id items
# such as 'field_your_some_field'
request_form = request.form
error_message = ''
translate = context.Base_translateString
# Make this script work alike wether called from another script or by a request
# Make this script work alike whether called from another script or by a request
kw.update(request_form)
# Exceptions for UI
......@@ -63,6 +62,7 @@ if dialog_method == 'Base_editRelation':
listbox_uid=kw.get('listbox_uid', None),
saved_form_data=kw['saved_form_data'])
# Exception for create relation
# Not used in new UI - relation field implemented using JIO calls from JS
if dialog_method == 'Base_createRelation':
return context.Base_createRelation(form_id=kw['form_id'],
selection_name=kw['list_selection_name'],
......@@ -80,6 +80,24 @@ if dialog_method == 'Folder_delete':
selection_name=kw['selection_name'],
md5_object_uid_list=kw['md5_object_uid_list'])
def handle_form_error(form, validation_errors):
"""Return correctly rendered form with all errors assigned to its fields."""
field_errors = form.ErrorFields(validation_errors)
request.set('field_errors', field_errors)
# Make sure editors are pushed back as values into the REQUEST object
for f in form.get_fields():
field_id = f.id
if request.has_key(field_id):
value = request.get(field_id)
if callable(value):
value(request)
if silent_mode:
return context.ERP5Document_getHateoas(form=form, REQUEST=request, mode='form'), 'form'
request.RESPONSE.setStatus(400)
return context.ERP5Document_getHateoas(form=form, REQUEST=request, mode='form')
form = getattr(context, dialog_id)
# form can be a python script that returns the form
......@@ -95,54 +113,54 @@ try:
request.set('editable_mode', 1)
form.validate_all_to_request(request)
request.set('editable_mode', editable_mode)
except FormValidationError, validation_errors:
# Pack errors into the request
field_errors = form.ErrorFields(validation_errors)
request.set('field_errors', field_errors)
# Make sure editors are pushed back as values into the REQUEST object
for f in form.get_fields():
field_id = f.id
if request.has_key(field_id):
value = request.get(field_id)
if callable(value):
value(request)
if silent_mode: return context.ERP5Document_getHateoas(form=form, REQUEST=request, mode='form'), 'form'
request.RESPONSE.setStatus(400)
return context.ERP5Document_getHateoas(form=form, REQUEST=request, mode='form')
# Use REQUEST.redirect if possible. It will not be possible if at least one of these is true :
# * we got an import_file,
# * we got a listbox
# * a value is None or [] or (), because this is not supported by make_query
can_redirect = 1
default_skin = context.getPortalObject().portal_skins.getDefaultSkin()
allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted")
if getattr(getattr(context, dialog_method), 'pt', None) == "report_view" and \
request.get('your_portal_skin', default_skin) not in allowed_styles:
# RJS own validation - only ODS/ODT and Hal* skins work for reports
# Form is OK, it's just this field - style so we return back form-wide error
# for which we don't have support out-of-the-box thus we manually craft it
# XXX TODO: Form-wide validation errors
return handle_form_error(
form,
FormValidationError([
ValidationError(
error_key=None,
field=form.get_field('your_portal_skin'),
error_text=translate(
'Only ODT, ODS, Hal and HalRestricted skins are allowed for reports '\
'in Preferences - User Interface - Report Style'))
], {}))
except FormValidationError as validation_errors:
return handle_form_error(form, validation_errors)
MARKER = [] # A recognisable default value. Use with 'is', not '=='.
listbox_id_list = [] # There should not be more than one listbox - but this give us a way to check.
file_id_list = [] # For uploaded files.
for field in form.get_fields():
k = field.id
v = request.get(k, MARKER)
if v is not MARKER:
if isListBox(field):
listbox_id_list.append(k)
elif can_redirect and (v in (None, [], ()) or hasattr(v, 'read')) : # If we cannot redirect, useless to test it again
can_redirect = 0
# Cleanup my_ and your_ prefixes
splitted = k.split('_', 1)
if len(splitted) == 2 and splitted[0] in ('my', 'your'):
if hasattr(v, 'as_dict'):
# This is an encapsulated editor
# convert it
kw.update(v.as_dict())
field_id = field.id
field_value = request.get(field_id, MARKER)
if field_value is not MARKER:
if isFieldType(field, "ListBox"):
listbox_id_list.append(field_id)
# Cleanup my_ and your_ prefixes if present
if field_id.startswith("my_") or field_id.startswith("your_"):
field_prefix, field_name = field_id.split('_', 1)
if hasattr(field_value, 'as_dict'):
# This is an encapsulated editor - convert it
kw.update(field_value.as_dict())
else:
kw[splitted[1]] = request_form[splitted[1]] = v
kw[field_name] = request_form[field_name] = field_value
else:
kw[k] = request_form[k] = v
kw[field_id] = request_form[field_id] = field_value
if len(listbox_id_list):
can_redirect = 0
# Warn if there are more than one listbox in form ...
if len(listbox_id_list) > 1:
log('Base_callDialogMethod', 'There are %s listboxes in form %s.' % (len(listbox_id_list), form.id))
......@@ -183,11 +201,8 @@ if listbox_uid is not None and kw.has_key('list_selection_name'):
selected_uids = context.portal_selections.updateSelectionCheckedUidList(
kw['list_selection_name'],
listbox_uid, uids)
# Remove unused parameter
clean_kw = {}
for k, v in kw.items() :
if v not in (None, [], ()) :
clean_kw[k] = kw[k]
# Remove empty items
clean_kw = dict((k, v) for k, v in kw.items() if v not in (None, [], ()))
# Handle deferred style, unless we are executing the update action
if dialog_method != update_method and clean_kw.get('deferred_style', 0):
......@@ -195,8 +210,23 @@ if dialog_method != update_method and clean_kw.get('deferred_style', 0):
# XXX Hardcoded Deferred style name
clean_kw['portal_skin'] = 'Deferred'
dialog_form = getattr(context, dialog_method)
page_template = getattr(dialog_form, 'pt', None)
page_template = getattr(getattr(context, dialog_method), 'pt', None)
if page_template == 'report_view':
# Limit Reports in Deferred style to known working styles
if request_form.get('your_portal_skin', None) not in ("ODT", "ODS"):
# RJS own validation - deferred option works here only with ODS/ODT skins
return handle_form_error(
form,
FormValidationError([
ValidationError(
error_key=None,
field=form.get_field('your_deferred_style'),
error_text=translate(
'Deferred reports are possible only with preference '\
'"Report Style" set to "ODT" or "ODS"'))
], {}))
# If the action form has report_view as it's method, it
if page_template != 'report_view':
# use simple wrapper
......@@ -205,23 +235,24 @@ if dialog_method != update_method and clean_kw.get('deferred_style', 0):
request.set('deferred_style_dialog_method', dialog_method)
dialog_method = 'Base_activateSimpleView'
url_params_string = make_query(clean_kw)
# XXX: We always redirect in report mode to make sure portal_skin
# parameter is taken into account by SkinTool.
# If url is too long, we do not redirect to avoid crash.
# XXX: 2000 is an arbitrary value resulted from trial and error.
if (not(can_redirect) or len(url_params_string) > 2000):
# Never redirect in JSON style - do as much as possible here.
# At this point the 'dialog_method' should point to a form (if we are in report)
# if we are not in Deferred mode - then it points to `Base_activateSimpleView`
if True:
if dialog_method != update_method:
# When we are not executing the update action, we have to change the skin
# manually,
if 'portal_skin' in clean_kw:
new_skin_name = clean_kw['portal_skin']
context.log("Changing skin to \"{}\"".format(new_skin_name))
context.getPortalObject().portal_skins.changeSkin(new_skin_name)
request.set('portal_skin', new_skin_name)
deferred_portal_skin = clean_kw.get('deferred_portal_skin')
if deferred_portal_skin:
# has to be either ODS or ODT because only those contain `form_list`
request.set('deferred_portal_skin', deferred_portal_skin)
# and to cleanup formulator's special key in request
# XXX unless we are in Folder_modifyWorkflowStatus which validates again !
......@@ -230,15 +261,28 @@ if (not(can_redirect) or len(url_params_string) > 2000):
if str(key).startswith('field') or str(key).startswith('subfield'):
request.form.pop(key, None)
# If we cannot redirect, then call the form directly.
# now get dialog_method after skin re-selection and dialog_method mingling
dialog_form = getattr(context, dialog_method)
# XXX: this is a hack that should not be needed anymore with the new listbox.
# set the URL in request, so that we can immediatly call method
# that depend on it (eg. Show All). This is really related to
# current ListBox implementation which edit Selection's last_url
# with the content of REQUEST.URL
request.set('URL', '%s/%s' % (context.absolute_url(), dialog_method))
# RJS: If we are in deferred mode - call the form directly and return
# dialog method is now `Base_activateSimpleView` - the only script in
# deferred portal_skins folder
if clean_kw.get('deferred_style', 0):
return dialog_form(**kw) # deferred form should return redirect with a message
# RJS: If skin selection is different than Hal* then ERP5Document_getHateoas
# does not exist and we call form method directly
if clean_kw.get("portal_skin", context.getPortalObject().portal_skins.getDefaultSkin()) not in ("Hal", "HalRestricted"):
return dialog_form(**kw)
dialog_method = getattr(context, dialog_method)
return dialog_method(**clean_kw)
return context.ERP5Document_getHateoas(REQUEST=request, form=dialog_form, mode="form")
# XXX If somebody knows in which case this gets executed ... please tell me.
return getattr(context, dialog_method)(**kw)
......@@ -91,7 +91,10 @@ def editListBox(listbox_field, listbox):
def editMatrixBox(matrixbox_field, matrixbox):
""" Function called to edit a Matrix box
"""Go through every field in matrix and call edit on it.
Most of the code is just a copy&paste from ERP5Form/MatrixBox:render and
should be in (non-yet-existing) MatrixBoxEditor instead of here (XXX TODO).
"""
if matrixbox is None:
return
......@@ -146,7 +149,7 @@ def editMatrixBox(matrixbox_field, matrixbox):
tab_ids = map(lambda x: x[0], tabs)
extra_dimension_category_list_list = [[category for category, label in dimension_list] for dimension_list in extra_dimension_list_list]
# There are 3 cases
# There are 4 cases
# Case 1: we do 1 dimensional matrix
# Case 2: we do 2 dimensional matrix
# Case 3: we do 2 dimensional matrix + tabs
......@@ -170,7 +173,7 @@ def editMatrixBox(matrixbox_field, matrixbox):
matrix_context.setCellRange(base_id=cell_base_id, *matrixbox_cell_range)
for cell_index_tuple, cell_value_dict in matrixbox.items():
# Only update cells which still exist
# after constructing the cell-range we can edit all existing cells
if not matrix_context.hasInRange(*cell_index_tuple, **kd):
return "Cell %s does not exist" % str(cell_index_tuple)
......@@ -204,7 +207,7 @@ MARKER = [] # placeholder for an empty value
message = Base_translateString("Data updated.")
try:
# extract all listbox's object form fields from the request and `edit` the object
# Extract all form fields from the request and call `edit` on them
for field in form.get_fields():
# Dispatch field either to `edit_kwargs` (in case of simple fields) or to `encapsulated_editor_list` in case of editors
field_name = field.id if not field.has_value('alternate_name') else (field.get_value('alternate_name') or field.id)
......@@ -217,13 +220,14 @@ try:
edit_kwargs[field_name[len(field_prefix):]] = field_value if field_value != '' else None
## XXX We need to find a way not to use meta_type.
# Kato: can be done simply by implementing 'Editors' for fields which are here
field_meta_type = field.meta_type
if field_meta_type == 'ProxyField':
field_meta_type = field.getRecursiveTemplateField().meta_type
if(field_meta_type == 'ListBox'):
editListBox(field, request.get(field.id))
if(field_meta_type == 'MatrixBox'):
elif(field_meta_type == 'MatrixBox'):
editMatrixBox(field, request.get(field.id))
# Return parsed values
......@@ -235,6 +239,7 @@ try:
context.edit(REQUEST=request, edit_order=edit_order, **edit_kwargs)
for encapsulated_editor in encapsulated_editor_list:
encapsulated_editor.edit(context)
except ActivityPendingError as e:
message = Base_translateString(str(e))
......
"""Hello. This will be long because this goodness script does almost everything.
In general it always returns a JSON reponse in HATEOAS format specification.
:param REQUEST: HttpRequest holding GET and/or POST data
:param response:
:param view: either "view" or absolute URL of an ERP5 Action
:param mode: {str} help to decide what user wants from us "form" | "search" ...
:param relative_url: an URL of `traversed_document` to operate on (it must have an object_view)
Only in mode == 'search'
:param query:
:param select_list:
:param limit:
:param form_relative_url: {str} relative URL of a form FIELD issuing the search (listbox/relation field...)
Only in mode == 'form'
:param form:
Only in mode == 'traverse'
TBD.
"""
from ZTUtils import make_query
import json
from base64 import urlsafe_b64encode, urlsafe_b64decode
......@@ -8,6 +31,8 @@ import time
from email.Utils import formatdate
import re
from zExceptions import Unauthorized
from Products.ERP5Type.Utils import UpperCase
from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery
if REQUEST is None:
REQUEST = context.REQUEST
......@@ -26,20 +51,59 @@ def byteify(string):
else:
return string
def ensure_serializable(obj):
"""Ensure obj and all sub-objects are JSON serializable."""
if isinstance(obj, dict):
for key in obj:
obj[key] = ensure_serializable(obj[key])
# throw away date's type information and later reconstruct as Zope's DateTime
if isinstance(obj, DateTime):
return obj.ISO()
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
return obj.isoformat()
# we don't check other isinstances - we believe that iterables don't contain unserializable objects
return obj
datetime_iso_re = re.compile(r'^\d{4}-\d{2}-\d{2} |T\d{2}:\d{2}:\d{2}.*$')
time_iso_re = re.compile(r'^(\d{2}):(\d{2}):(\d{2}).*$')
def ensure_deserialized(obj):
"""Deserialize classes serialized by our own `ensure_serializable`.
Method `biteify` must not be called on the result because it would revert out
deserialization by calling __str__ on constructed classes.
"""
if isinstance(obj, dict):
for key in obj:
obj[key] = ensure_deserialized(obj[key])
# seems that default __str__ method is good enough
if isinstance(obj, str):
# Zope's DateTime must be good enough for everyone
if datetime_iso_re.match(obj):
return DateTime(obj)
if time_iso_re.match(obj):
match_obj = time_iso_re.match(obj)
return datetime.time(*tuple(map(int, match_obj.groups())))
return obj
def getProtectedProperty(document, select):
"""getProtectedProperty is a security-aware substitution for builtin `getattr`
It resolves Properties on Products (visible via Zope Formulator), which are
accessible as ordinary attributes as well, by following security rules.
See https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5Form/ListBox.py#L2293
"""
try:
#see https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5Form/ListBox.py#L2293
try:
if "." in select:
select = select[select.rindex('.') + 1:]
except ValueError:
pass
return document.getProperty(select, d=None)
except (ConflictError, RuntimeError):
raise
except:
return None
url_template_dict = {
"form_action": "%(traversed_document_url)s/%(action_id)s",
"traverse_generator": "%(root_url)s/%(script_id)s?mode=traverse" + \
......@@ -75,18 +139,21 @@ url_template_dict = {
default_document_uri_template = url_template_dict["jio_get_template"]
Base_translateString = context.getPortalObject().Base_translateString
def getRealRelativeUrl(document):
return '/'.join(portal.portal_url.getRelativeContentPath(document))
def getFormRelativeUrl(form):
return portal.portal_catalog(
portal_type="ERP5 Form",
portal_type=("ERP5 Form", "ERP5 Report"),
uid=form.getUid(),
id=form.getId(),
limit=1,
select_dict={'relative_url': None}
)[0].relative_url
def getFieldDefault(traversed_document, field, key, value=None):
# REQUEST.get(field.id, field.get_value("default"))
result = traversed_document.Field_getDefaultValue(field, key, value, REQUEST)
......@@ -98,6 +165,9 @@ def getFieldDefault(traversed_document, field, key, value=None):
def renderField(traversed_document, field, form, value=None, meta_type=None, key=None, key_prefix=None, selection_params=None):
"""Extract important field's attributes into `result` dictionary."""
if selection_params is None:
selection_params = {}
if meta_type is None:
meta_type = field.meta_type
if key is None:
......@@ -295,19 +365,23 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"url": field.get_value("gadget_url"),
"sandbox": field.get_value("js_sandbox")
})
try:
result["renderjs_extra"] = json.dumps(dict(field.get_value("renderjs_extra")))
except KeyError:
# Ensure compatibility if the products are not yet up to date
result["renderjs_extra"] = json.dumps({})
return result
if meta_type == "ListBox":
"""Display list of objects with optional search/sort capabilities on columns from catalog."""
"""Display list of objects with optional search/sort capabilities on columns from catalog.
We might be inside a ReportBox which is inside a parent form BUT we still have access to
the original REQUEST with sent POST values from the parent form. We can save those
values into our query method and reconstruct them meanwhile calling asynchronous jio.allDocs.
"""
_translate = Base_translateString
column_list = [(name, _translate(title)) for name, title in field.get_value("columns")]
editable_column_list = [(name, _translate(title)) for name, title in field.get_value("editable_columns")]
# column definition in ListBox own value 'columns' is superseded by dynamic
# column definition from Selection for specific Report ListBoxes; the same for editable_columns
column_list = [(name, _translate(title)) for name, title in (selection_params.get('selection_columns', [])
or field.get_value("columns"))]
editable_column_list = [(name, _translate(title)) for name, title in (selection_params.get('editable_columns', [])
or field.get_value("editable_columns"))]
catalog_column_list = [(name, title)
for name, title in column_list
if sql_catalog.isValidColumn(name)]
......@@ -319,28 +393,79 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# try to get specified sortable columns and fail back to searchable fields
sort_column_list = [(name, _translate(title))
for name, title in field.get_value("sort_columns")
for name, title in (selection_params.get('selection_sort_order', [])
or field.get_value("sort_columns"))
if sql_catalog.isValidColumn(name)] or search_column_list
# portal_type list can be overriden by selection too
# since it can be intentionally empty we don't override with non-empty field value
portal_type_list = selection_params.get("portal_type", field.get_value('portal_types'))
# requirement: get only sortable/searchable columns which are already displayed in listbox
# see https://lab.nexedi.com/nexedi/erp5/blob/HEAD/product/ERP5Form/ListBox.py#L1004
# implemented in javascript in the end
# see https://lab.nexedi.com/nexedi/erp5/blob/master/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js#L163
portal_types = field.get_value('portal_types')
default_params = dict(field.get_value('default_params'))
default_params = dict(field.get_value('default_params')) # default_params is a list of tuples
default_params['ignore_unknown_columns'] = True
if selection_params is not None:
# we abandoned Selections in RJS thus we mix selection query parameters into
# listbox's default parameters
default_params.update(selection_params)
# How to implement pagination?
# default_params.update(REQUEST.form)
lines = field.get_value('lines')
list_method_name = traversed_document.Listbox_getListMethodName(field)
list_method_query_dict = dict(
portal_type=[x[1] for x in portal_types], **default_params
)
# ListBoxes in report view has portal_type defined already in default_params
# in that case we prefer non_empty version
list_method_query_dict = default_params.copy()
if not list_method_query_dict.get("portal_type", []):
list_method_query_dict["portal_type"] = [x for x, _ in portal_type_list]
list_method_custom = None
# Search for non-editable documents - all reports goes here
# Reports have custom search scripts which wants parameters from the form
# thus we introspect such parameters and try to find them in REQUEST
list_method = None
if list_method_name and list_method_name not in ("portal_catalog", "searchFolder", "objectValues"):
# we avoid accessing known protected objects and builtin functions above
try:
list_method = getattr(traversed_document, list_method_name)
except (Unauthorized, AttributeError, ValueError) as error:
# we are touching some specially protected (usually builtin) methods
# which we will not introspect
context.log('ListBox {!s} list_method {} is unavailable because of "{!s}"'.format(
field, list_method_name, error), level=100)
# Put all ListBox's search method params from REQUEST to `default_param_json`
# because old code expects synchronous render thus having all form's values
# still in the request which is not our case because we do asynchronous rendering
if list_method is not None and hasattr(list_method, "ZScriptHTML_tryParams"):
for list_method_param in list_method.ZScriptHTML_tryParams():
if list_method_param in REQUEST and list_method_param not in list_method_query_dict:
list_method_query_dict[list_method_param] = REQUEST.get(list_method_param)
# MIDDLE-DANGEROUS!
# In case of reports (later even exports) substitute None for unknown
# parameters. We suppose Python syntax for parameters!
# What we do here is literally putting every form field from REQUEST
# into search method parameters - this is later put back into REQUEST
# this way we can mimic synchronous rendering when all form field values
# were available in REQUEST. It is obviously wrong behaviour.
for list_method_param in list_method.params().split(","):
if "*" in list_method_param:
continue
if "=" in list_method_param:
continue
# now we have only mandatory parameters
list_method_param = list_method_param.strip()
if list_method_param not in list_method_query_dict:
list_method_query_dict[list_method_param] = None
# Now if the list_method does not specify **kwargs we need to remove
# unwanted parameters like "portal_type" which is everywhere
if "**" not in list_method.params():
_param_key_list = tuple(list_method_query_dict.keys()) # copy the keys
for param_key in _param_key_list:
if param_key not in list_method.params(): # we search in raw string
del list_method_query_dict[param_key] # but it is enough
if (editable_column_list):
list_method_custom = url_template_dict["custom_search_template"] % {
"root_url": site_root.absolute_url(),
......@@ -348,8 +473,11 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"form_relative_url": "%s/%s" % (getFormRelativeUrl(form), field.id),
"list_method": list_method_name,
"default_param_json": urlsafe_b64encode(json.dumps(list_method_query_dict))
"default_param_json": urlsafe_b64encode(
json.dumps(ensure_serializable(list_method_query_dict)))
}
# once we imprint `default_params` into query string of 'list method' we
# don't want them to propagate to the query as well
list_method_query_dict = {}
elif (list_method_name == "portal_catalog"):
pass
......@@ -361,7 +489,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"list_method": list_method_name,
"default_param_json": urlsafe_b64encode(json.dumps(list_method_query_dict))
"default_param_json": urlsafe_b64encode(json.dumps(ensure_serializable(list_method_query_dict)))
}
list_method_query_dict = {}
......@@ -384,7 +512,6 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# line[title] = prop
# line["_relative_url"] = document.getRelativeUrl()
# line_list.append(line)
result.update({
"column_list": column_list,
"search_column_list": search_column_list,
......@@ -392,9 +519,9 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"sort_column_list": sort_column_list,
"editable_column_list": editable_column_list,
"show_anchor": field.get_value("anchor"),
"portal_type": portal_types,
"portal_type": portal_type_list,
"lines": lines,
"default_params": default_params,
"default_params": ensure_serializable(default_params),
"list_method": list_method_name,
"query": url_template_dict["jio_search_template"] % {
"query": make_query({
......@@ -452,6 +579,12 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
def renderForm(traversed_document, form, response_dict, key_prefix=None, selection_params=None):
"""
:param selection_params: holds parameters to construct ERP5Form.Selection instance
for underlaying ListBox - since we do not use selections in RenderJS UI
we mitigate the functionality here by overriding ListBox's own values
for columns, editable columns, and sort with those found in `selection_params`
"""
REQUEST.set('here', traversed_document)
field_errors = REQUEST.get('field_errors', {})
......@@ -531,25 +664,78 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
}
if (form.pt == 'report_view'):
# reports are expected to return list of ReportSection which is a wrapper
# around a form - thus we will need to render those forms
report_item_list = []
report_result_list = []
for field in form.get_fields():
if field.getRecursiveTemplateField().meta_type == 'ReportBox':
# ReportBox.render returns a list of ReportSection classes which are
# just containers for FormId(s) usually containing one ListBox
# and its search/query parameters hidden in `selection_params`
# `path` contains relative_url of intended CONTEXT for underlaying ListBox
report_item_list.extend(field.render())
j = 0
for report_item in report_item_list:
report_context = report_item.getObject(portal)
report_prefix = 'x%s' % j
j += 1
# ERP5 Report document differs from a ERP5 Form in only one thing: it has
# `report_method` attached to it - thus we call it right here
if hasattr(form, 'report_method') and getattr(form, 'report_method', ""):
report_method_name = getattr(form, 'report_method')
report_method = getattr(traversed_document, report_method_name)
report_item_list.extend(report_method())
for report_index, report_item in enumerate(report_item_list):
report_context = report_item.getObject(traversed_document)
report_prefix = 'x%s' % report_index
report_title = report_item.getTitle()
# report_class = "report_title_level_%s" % report_item.getLevel()
report_form = report_item.getFormId()
report_result = {'_links': {}}
renderForm(traversed_document, getattr(report_context, report_item.getFormId()),
report_result, key_prefix=report_prefix,
selection_params=report_item.selection_params)
# some reports save a lot of unserializable data (datetime.datetime) and
# key "portal_type" (don't confuse with "portal_types" in ListBox) into
# report_item.selection_params thus we need to take that into account in
# ListBox field
#
# Selection Params are parameters for embedded ListBox's List Method
# and it must be passed in `default_json_param` field (might contain
# unserializable data types thus we need to take care of that
# In order not to lose information we put all ReportSection attributes
# inside the report selection params
report_form_params = report_item.selection_params.copy() \
if report_item.selection_params is not None \
else {}
if report_item.selection_name:
selection_name = report_prefix + "_" + report_item.selection_name
report_form_params.update(selection_name=selection_name)
# this should load selections with correct values - since it is modifying
# global state in the backend we have nothing more to do here
# I could not find where the code stores params in selection with render
# prefix - maybe it in some `render` method where it should not be
# Of course it is ugly, terrible and should be removed!
selection_tool = context.getPortalObject().portal_selections
selection_tool.getSelectionFor(selection_name, REQUEST)
selection_tool.setSelectionParamsFor(selection_name, report_form_params)
selection_tool.setSelectionColumns(selection_name, report_item.selection_columns)
if report_item.selection_columns:
report_form_params.update(selection_columns=report_item.selection_columns)
if report_item.selection_sort_order:
report_form_params.update(selection_sort_order=report_item.selection_sort_order)
# Report section is just a wrapper around form thus we render it right
# we keep traversed_document because its Portal Type Class should be
# addressable by the user = have actions (object_view) attached to it
# BUT! when Report Section defines `path` that is the new context for
# form rendering and subsequent searches...
renderForm(traversed_document if not report_item.path else report_context,
getattr(report_context, report_item.getFormId()),
report_result,
key_prefix=report_prefix,
selection_params=report_form_params) # used to be only report_item.selection_params
# Report Title is important since there are more section on report page
# but often they render the same form with different data so we need to
# distinguish by the title at least.
report_result['title'] = report_title
report_result_list.append(report_result)
response_dict['report_section_list'] = report_result_list
# XXX form action update, etc
......@@ -593,6 +779,7 @@ def renderRawField(field):
def renderFormDefinition(form, response_dict):
"""Form "definition" is configurable in Zope admin: Form -> Order."""
group_list = []
for group in form.Form_getGroupTitleAndId():
......@@ -700,7 +887,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
return ""
elif (mode == 'root') or (mode == 'traverse'):
elif mode in ('root', 'traverse'):
#################################################
# Raw document
#################################################
......@@ -714,11 +901,15 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# Add a link to the portal type if possible
if not is_portal:
# traversed_document should always have its Portal Type in ERP5 Portal Types
# thus attached actions to it so it is viewable
document_type_name = traversed_document.getPortalType()
document_type = getattr(portal.portal_types, document_type_name, None)
if document_type is not None:
result_dict['_links']['type'] = {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"relative_url": portal.portal_types[traversed_document.getPortalType()]\
.getRelativeUrl(),
"relative_url": document_type.getRelativeUrl(),
"script_id": script.id
},
"name": Base_translateString(traversed_document.getPortalType())
......@@ -834,7 +1025,6 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# renderer_form = traversed_document.restrictedTraverse(form_id, None)
# XXX Proxy field are not correctly handled in traversed_document of web site
renderer_form = getattr(traversed_document, form_id)
# traversed_document.log(form_id)
if (renderer_form is not None):
embedded_dict = {
'_links': {
......@@ -844,12 +1034,24 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
}
}
# Put all query parameters (?reset:int=1&workflow_action=start_action) in request to mimic usual form display
query_param_dict = {}
query_split = embedded_url.split('?', 1)
if len(query_split) == 2:
for query_parameter in query_split[1].split("&"):
query_key, query_value = query_parameter.split("=")
query_key, query_value = query_parameter.split('=')
# often + is used instead of %20 so we replace for space here
query_param_dict[query_key] = query_value.replace("+", " ")
# set URL params into REQUEST (just like it was sent by form)
for query_key, query_value in query_param_dict.items():
REQUEST.set(query_key, query_value)
# unfortunatelly some people use Scripts as targets for Workflow
# transactions - thus we need to check and mitigate
if "Script" in renderer_form.meta_type:
# we suppose that the script takes only what is given in the URL params
return renderer_form(**query_param_dict)
renderForm(traversed_document, renderer_form, embedded_dict)
result_dict['_embedded'] = {
'_view': embedded_dict
......@@ -938,7 +1140,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
else:
traversed_document_portal_type = traversed_document.getPortalType()
if traversed_document_portal_type == "ERP5 Form":
if traversed_document_portal_type in ("ERP5 Form", "ERP5 Report"):
renderFormDefinition(traversed_document, result_dict)
response.setHeader("Cache-Control", "private, max-age=1800")
response.setHeader("Vary", "Cookie,Authorization,Accept-Encoding")
......@@ -966,144 +1168,335 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if action_dict:
result_dict['_actions'] = action_dict
elif mode == 'search':
#################################################
# Portal catalog search
#
# Possible call arguments example:
# form_relative_url: portal_skins/erp5_web/WebSite_view/listbox
# list_method: objectValues (Script providing listing)
# list_method: objectValues (Script providing items)
# default_param_json: <base64 encoded JSON> (Additional search params)
# query: <str> (term for fulltext search)
# select_list: ['int_index', 'id', 'title', ...] (column names to select)
# limit: [15, 16] (begin_index, num_records)
# local_roles: TODO
#
# Default Param JSON contains
# portal_type: list of Portal Types to include (singular form matches the
# catalog column name)
#
# Discussion:
#
# Why you didn't use ListBoxRendererLine?
# > Method 'search' is used for getting related objects as well which are
# > not backed up by a ListBox thus the value resolution would have to be
# > there anyway. It is better to use one code for all in this case.
#################################################
if REQUEST.other['method'] != "GET":
response.setStatus(405)
return ""
# in case we have custom list method
catalog_kw = {}
# hardcoded responses for site and portal objects (which are not Documents!)
# we let the flow to continue because the result of a list_method call can
# be similar - they can in practice return anything
if query == "__root__":
sql_list = [site_root]
search_result_iterable = [site_root]
elif query == "__portal__":
sql_list = [portal]
search_result_iterable = [portal]
else:
# otherwise gather kwargs for list_method and get whatever result it gives
callable_list_method = portal.portal_catalog
if list_method:
callable_list_method = getattr(traversed_document, list_method)
catalog_kw = {
"local_roles": local_roles,
"limit": limit,
"sort_on": () # default is empty tuple
"sort_on": () # default is an empty tuple
}
if default_param_json is not None:
catalog_kw.update(byteify(json.loads(urlsafe_b64decode(default_param_json))))
catalog_kw.update(
ensure_deserialized(
byteify(
json.loads(urlsafe_b64decode(default_param_json)))))
if query:
catalog_kw["full_text"] = query
if sort_on is not None:
if isinstance(sort_on, list):
catalog_kw['sort_on'] = tuple((byteify(sort_col), byteify(sort_order))
for sort_col, sort_order in map(json.loads, sort_on))
def parse_sort_on(raw_string):
"""Turn JSON serialized array into a tuple (col_name, order)."""
sort_col, sort_order = json.loads(raw_string)
sort_col, sort_order = byteify(sort_col), byteify(sort_order)
# JIO keeps sort order as whole word 'ascending' resp. 'descending'
if sort_order.lower().startswith("asc"):
sort_order = "ASC"
elif sort_order.lower().startswith("desc"):
sort_order = "DESC"
else:
sort_col, sort_order = json.loads(sort_on)
catalog_kw['sort_on'] = ((byteify(sort_col), byteify(sort_order)), )
# should raise an ValueError instead
context.log('Wrong sort order "{}" in {}! It must start with "asc" or "desc"'.format(sort_order, form_relative_url),
level=200) # error
return (sort_col, sort_order)
if (list_method is None):
callable_list_method = portal.portal_catalog
if isinstance(sort_on, list):
# sort_on argument is always a list of tuples(col_name, order)
catalog_kw['sort_on'] = list(map(parse_sort_on, sort_on))
else:
callable_list_method = getattr(traversed_document, list_method)
catalog_kw['sort_on'] = [parse_sort_on(sort_on), ]
sql_list = callable_list_method(**catalog_kw)
# Some search scripts impertinently grab their arguments from REQUEST
# instead of being nice and specify them as their input parameters.
#
# We expect that wise and mighty ListBox did copy all form field values
# from its REQUEST into `default_param_json` so we can put them back.
#
# XXX Kato: Seems that current scripts are behaving nicely (using only
# specified input parameters). In case some list_method does not work
# this is the first place to try to uncomment.
#
# for k, v in catalog_kw.items():
# REQUEST.set(k, v)
result_list = [] # returned "content" of the search
search_result_iterable = callable_list_method(**catalog_kw)
# Cast to list if only one element is provided
editable_field_dict = {}
if select_list is None:
select_list = []
elif same_type(select_list, ""):
select_list = [select_list]
if select_list:
if (form_relative_url is not None):
# extract form field definition into `editable_field_dict`
editable_field_dict = {}
if form_relative_url is not None:
listbox_field = portal.restrictedTraverse(form_relative_url)
listbox_field_id = listbox_field.id
# XXX Proxy field are not correctly handled in traversed_document of web site
listbox_form = getattr(traversed_document, listbox_field.aq_parent.id)
for select in select_list:
# See Listbox.py getValueList --> getEditableField & getColumnAliasList method
tmp = select.replace('.', '_')
if listbox_form.has_field("%s_%s" % (listbox_field_id, tmp), include_disabled=1):
editable_field_dict[select] = listbox_form.get_field("%s_%s" % (listbox_field_id, tmp), include_disabled=1)
# In short: there are Form Field definitions which names start with
# matching ListBox name - those are template fields to be rendered in
# cells with actual values defined by row and column
field_name = "{}_{}".format(listbox_field_id, select.replace(".", "_"))
if listbox_form.has_field(field_name, include_disabled=1):
editable_field_dict[select] = listbox_form.get_field(field_name, include_disabled=1)
# handle the case when list-scripts are ignoring `limit` - paginate for them
if limit is not None and isinstance(limit, (tuple, list)):
start, num_items = map(int, limit)
if len(sql_list) <= num_items:
if len(search_result_iterable) <= num_items:
# the limit was most likely taken into account thus we don't need to slice
start, num_items = 0, len(sql_list)
start, num_items = 0, len(search_result_iterable)
else:
start, num_items = 0, len(sql_list)
start, num_items = 0, len(search_result_iterable)
# go through documents and assign values into result_dict._embedded
contents_list = [] # returned "content" of the search
result_dict.update({
'_query': query,
'_local_roles': local_roles,
'_limit': limit,
'_select_list': select_list,
'_embedded': {
'contents': contents_list
}
})
for document_index, sql_document in enumerate(sql_list):
if document_index < start:
# now fill in `contents_list` with actual information
# beware that search_result_iterable can hide anything inside!
for result_index, search_result in enumerate(search_result_iterable):
# skip documents out of `limit`
if result_index < start:
continue
if document_index >= start + num_items:
if result_index >= start + num_items:
break
contents_item = {}
contents_list.append(contents_item)
# Fields, which are used to render results of search, use TALES to obtain
# their other properties. Thus we set up REQUEST.here which is used in TAL
#
# XXX Kato: This might be wrong since search_result is expected in 'cell' and
# 'here' is reserved for a traversed_document
# REQUEST.set('here', search_result)
contents_uid = None
if hasattr(search_result, "getObject"):
# search_result = search_result.getObject()
contents_uid = search_result.uid
# every document indexed in catalog has to have relativeUrl
contents_relative_url = getRealRelativeUrl(search_result)
# get property in secure way from documents
search_property_getter = getProtectedProperty
def search_property_hasser (doc, attr):
"""Brains cannot access Properties - they use permissioned getters."""
try:
document = sql_document.getObject()
except AttributeError:
# XXX ERP5 Site is not an ERP5 document
document = sql_document
document_uid = sql_document.uid
document_result = {
'_links': {
return doc.hasProperty(attr)
except (AttributeError, Unauthorized) as e:
context.log('Cannot state ownership of property "{}" on {!s} because of "{!s}"'.format(
attr, doc, e))
return False
elif hasattr(search_result, "aq_self"):
# Zope products have at least ID thus we work with that
contents_uid = search_result.uid
# either we got a document with relativeUrl or we got product and use ID
contents_relative_url = getRealRelativeUrl(search_result) or search_result.getId()
# documents and products have the same way of accessing properties
search_property_getter = getProtectedProperty
search_property_hasser = lambda doc, attr: doc.hasProperty(attr)
else:
# In case of reports the `search_result` can be list of
# PythonScripts.standard._Object - a reimplementation of plain dictionary
# means we are iterating over plain objects
# list_method must be defined because POPOs can return only that
contents_uid = "{}#{:d}".format(list_method, result_index)
# JIO requires every item to have _links.self.href so it can construct
# links to the document. Here we have a object in RAM (which should
# never happen!) thus we provide temporary UID
contents_relative_url = "{}/{}".format(traversed_document.getRelativeUrl(), contents_uid)
# property getter must be simple __getattr__ implementation
search_property_getter = lambda obj, attr: getattr(obj, attr, None)
search_property_hasser = lambda obj, attr: hasattr(obj, attr)
# _links.self.href is mandatory for JIO so it can create reference to the
# (listbox) item alone
contents_item['_links'] = {
'self': {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
# XXX ERP5 Site is not an ERP5 document
"relative_url": getRealRelativeUrl(document) or document.getId(),
"relative_url": contents_relative_url,
"script_id": script.id
},
},
}
}
# ERP5 stores&send the list of editable elements in a hidden field called
# only database results can be editable so it belongs here
if editable_field_dict:
document_result['listbox_uid:list'] = {
contents_item['listbox_uid:list'] = {
'key': "%s_uid:list" % listbox_field_id,
'value': document_uid
'value': contents_uid
}
# render whole field in contents_item or at least search result value
for select in select_list:
if editable_field_dict.has_key(select):
REQUEST.set('cell', sql_document)
# cell has a Form Field template thus render it using the field
REQUEST.set('cell', search_result)
# if default value is given by evaluating Tales expression then we only
# put "cell" to request (expected by tales) and let the field evaluate
default_field_value = None
if getattr(editable_field_dict[select].tales, "default", "") == "":
# if there is no tales expr (or is empty) we extract the value from search result
default_field_value = search_property_getter(search_result, select)
contents_item[select] = renderField(
traversed_document,
editable_field_dict[select],
listbox_form,
value=default_field_value,
key='field_%s_%s' % (editable_field_dict[select].id, contents_uid))
REQUEST.other.pop('cell', None)
if ('default' in editable_field_dict[select].tales):
tmp_value = None
else:
tmp_value = getProtectedProperty(document, select)
# if the variable does not have a field template we need to find its
# value by resolving value in the correct order. The code is copy&pasted
# from ListBoxRendererLine.getValueList because it is universal
contents_value = None
property_value = renderField(
traversed_document, editable_field_dict[select], form, tmp_value,
key='field_%s_%s' % (editable_field_dict[select].id, document_uid))
REQUEST.other.pop('cell', None)
if not isinstance(select, (str, unicode)) or len(select) == 0:
context.log('There is an invalid column name in {!s}!'.format(select_list), level=200)
continue
if "." in select:
select = select[select.rindex('.') + 1:]
# 1. resolve attribute on a raw object (all wrappers removed) using
# lowest-level secure getattr method given object type
raw_search_result = search_result
if hasattr(search_result, 'aq_base'):
raw_search_result = search_result.aq_base
if search_property_hasser(raw_search_result, select):
contents_value = search_property_getter(raw_search_result, select)
# 2. use the fact that wrappers (brain or acquisition wrapper) use
# permissioned getters
unwrapped_search_result = search_result
if hasattr(search_result, 'aq_self'):
unwrapped_search_result = search_result.aq_self
if contents_value is None:
if not select.startswith('get') and select[0] not in string.ascii_uppercase:
# maybe a hidden getter (variable accessible by a getter)
accessor_name = 'get' + UpperCase(select)
else:
property_value = getProtectedProperty(document, select)
if property_value is not None:
if same_type(property_value, DateTime()):
# or obvious getter (starts with "get" or Capital letter - Script)
accessor_name = select
# again we check on a unwrapped object to avoid acquisition resolution
# which would certainly find something which we don't want
try:
if hasattr(raw_search_result, accessor_name) and callable(getattr(search_result, accessor_name)):
# test on raw object but get the actual accessor using wrapper and acquisition
# do not call it here - it will be done later in generic call part
contents_value = getattr(search_result, accessor_name)
except (AttributeError, KeyError, Unauthorized) as error:
context.log("Could not evaluate {} nor {} on {} with error {!s}".format(
select, accessor_name, search_result, error), level=100) # WARNING
if contents_value is None and search_property_hasser(search_result, select):
# maybe it is just a attribute
contents_value = search_property_getter(search_result, select)
if contents_value is None:
try:
contents_value = getattr(search_result, select, None)
except (Unauthorized, AttributeError, KeyError) as error:
context.log("Cannot resolve {} on {!s} because {!s}".format(
select, raw_search_result, error), level=100)
if callable(contents_value):
has_mandatory_param = False
has_brain_param = False
if hasattr(contents_value, "params"):
has_mandatory_param = any(map(lambda param: '=' not in param and '*' not in param,
contents_value.params().split(","))) \
if contents_value.params() \
else False # because any([]) == True
has_brain_param = "brain" in contents_value.params()
try:
if has_mandatory_param:
contents_value = contents_value(search_result)
elif has_brain_param:
contents_value = contents_value(brain=search_result)
else:
contents_value = contents_value()
except (AttributeError, KeyError, Unauthorized) as error:
context.log("Could not evaluate {} on {} with error {!s}".format(
contents_value, search_result, error), level=100) # WARNING
# make resulting value JSON serializable
if contents_value is not None:
if same_type(contents_value, DateTime()):
# Serialize DateTime
property_value = property_value.rfc822()
elif isinstance(property_value, datetime.date):
property_value = formatdate(time.mktime(property_value.timetuple()))
elif getattr(property_value, 'translate', None) is not None:
property_value = "%s" % property_value
document_result[select] = property_value
result_list.append(document_result)
result_dict['_embedded'] = {"contents": result_list}
result_dict['_query'] = query
result_dict['_local_roles'] = local_roles
result_dict['_limit'] = limit
result_dict['_select_list'] = select_list
contents_value = contents_value.rfc822()
# XXX Kato: what exactly should the later mean?
elif isinstance(contents_value, datetime.date):
contents_value = formatdate(time.mktime(contents_value.timetuple()))
elif hasattr(contents_value, 'translate'):
contents_value = "%s" % contents_value
contents_item[select] = contents_value
# We should cleanup the selection if it exists in catalog params BUT
# we cannot because it requires escalated Permission.'modifyPortal' so
# the correct solution would be to ReportSection.popReport but unfortunately
# we don't have it anymore because we are asynchronous
return result_dict
elif mode == 'form':
#################################################
......@@ -1212,6 +1605,7 @@ hateoas = calculateHateoas(is_portal=temp_is_portal, is_site_root=temp_is_site_r
restricted=restricted, list_method=list_method,
default_param_json=default_param_json,
form_relative_url=form_relative_url)
if hateoas == "":
return hateoas
else:
......
......@@ -9,8 +9,11 @@ from functools import wraps
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
import base64
import DateTime
import StringIO
import json
import re
import urllib
def changeSkin(skin_name):
......@@ -63,18 +66,30 @@ def simulate(script_id, params_string, code_string):
return decorated
return upperWrap
def createIndexedDocument():
"""Create a Foo document inside Foo module and pass it as "document" argument into wrapped function."""
def wipeFolder(folder):
folder.deleteContent(list(folder.objectIds()))
transaction.commit()
def createIndexedDocument(quantity=1):
"""Create `quantity` Foo document(s) in Foo module and pass it as `document(_list)` argument into the wrapped function."""
def decorator(func):
def wrapped(self, *args, **kwargs):
wipeFolder(self.portal.foo_module)
if quantity <= 1:
kwargs.update(document=self._makeDocument())
else:
kwargs.update(document_list=[self._makeDocument() for _ in range(quantity)])
self.portal.portal_caches.clearAllCache()
self.tic()
try:
return func(self, *args, **kwargs)
finally:
wipeFolder(self.portal.foo_module)
self.tic() # unindex
return wrapped
return decorator
def do_fake_request(request_method, headers=None):
def do_fake_request(request_method, headers=None, data=()):
__version__ = "0.1"
if (headers is None):
headers = {}
......@@ -93,7 +108,25 @@ def do_fake_request(request_method, headers=None):
env['GATEWAY_INTERFACE']='CGI/1.1 '
env['SCRIPT_NAME']='Main'
env.update(headers)
return HTTPRequest(StringIO.StringIO(), env, HTTPResponse())
body_stream = StringIO.StringIO()
# for some mysterious reason QUERY_STRING does not get parsed into data fields
if data and request_method.upper() == 'GET':
# see: GET http://www.cgi101.com/book/ch3/text.html
env['QUERY_STRING'] = '&'.join(
'{}={}'.format(urllib.quote_plus(key), urllib.quote(value))
for key, value in data
)
if data and request_method.upper() == 'POST':
# see: POST request body https://tools.ietf.org/html/rfc1866#section-8.2.1
env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
for key, value in data:
body_stream.write('{}={!s}&'.format(
urllib.quote_plus(key), urllib.quote(value)))
return HTTPRequest(body_stream, env, HTTPResponse())
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
......@@ -609,6 +642,42 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_embedded']['_view']['_actions']['put']['method'], 'POST')
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/hal+json"')
@changeSkin('Hal')
def test_getHateoasDocument_listbox_vs_relation_inconsistency(self):
"""Purpose of this test is to point to inconsistencies in search-enabled field rendering.
ListBox gets its Portal Types in `portal_type` as list of tuples whether
Relation Input receives `portal_types` and `translated_portal_types`
"""
document = self._makeDocument()
# Drop editable permission
document.manage_permission('Modify portal content', [], 0)
document.Foo_view.listbox.ListBox_setPropertyList(
field_title = 'Foo Lines',
field_list_method = 'objectValues',
field_portal_types = 'Foo Line | Foo Line',
)
fake_request = do_fake_request("GET")
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
mode="traverse",
relative_url=document.getRelativeUrl(),
view="view")
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/hal+json"
)
result_dict = json.loads(result)
# ListBox rendering of allowed Portal Types
self.assertEqual(result_dict['_embedded']['_view']['listbox']['portal_type'], [['Foo Line', 'Foo Line']])
# Relation Input rendering of allowed Portal Types
self.assertEqual(result_dict['_embedded']['_view']['my_foo_category_title']['portal_types'], ['Category'])
self.assertEqual(result_dict['_embedded']['_view']['my_foo_category_title']['translated_portal_types'], ['Category'])
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
......@@ -682,6 +751,71 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertFalse(result_dict['_embedded']['_view'].has_key('_actions'))
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/hal+json"')
@changeSkin('Hal')
def test_getHateoasDocument_listbox_list_method_params(self):
"""Ensure that `list_method` of ListBox receives specified parameters."""
document = self._makeDocument()
document.manage_permission('Modify portal content', [], 0)
# pass custom list method which expect input arguments
document.Foo_view.listbox.ListBox_setPropertyList(
field_title = 'Foo Lines',
field_list_method = 'Foo_listWithInputParams',
field_portal_types = 'Foo Line | Foo Line',
field_columns = 'id|ID\ntitle|Title\nquantity|Quantity\nstart_date|Date\ncatalog.uid|Uid')
now = DateTime.DateTime()
tomorrow = now + 1
fake_request = do_fake_request("GET", data=(
('start_date', now.ISO()),
('stop_date', tomorrow.ISO()))
)
# I tried to implement the standard way (see `data` param in do_fake_request)
# but for some reason it does not work...so we hack our way around
fake_request.set('start_date', now.ISO())
fake_request.set('stop_date', tomorrow.ISO())
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
mode="traverse",
relative_url=document.getRelativeUrl(),
form=document.restrictedTraverse('portal_skins/erp5_ui_test/Foo_view'),
view="view"
)
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/hal+json"
)
result_dict = json.loads(result)
list_method_template = \
result_dict['_embedded']['_view']['listbox']['list_method_template']
# default_param_json must not be empty because our custom list method
# specifies input parameters - they need to be filled from REQUEST
self.assertIn('default_param_json', list_method_template)
default_param_json = json.loads(
base64.b64decode(
re.search(r'default_param_json=([^\{&]+)',
list_method_template).group(1)))
self.assertIn("start_date", default_param_json)
self.assertEqual(default_param_json["start_date"], now.ISO())
self.assertIn("stop_date", default_param_json)
self.assertEqual(default_param_json["stop_date"], tomorrow.ISO())
# reset listbox properties to defaults
document.Foo_view.listbox.ListBox_setPropertyList(
field_title = 'Foo Lines',
field_list_method = 'objectValues',
field_portal_types = 'Foo Line | Foo Line',
field_stat_method = 'portal_catalog',
field_stat_columns = 'quantity | Foo_statQuantity',
field_editable = 1,
field_columns = 'id|ID\ntitle|Title\nquantity|Quantity\nstart_date|Date\ncatalog.uid|Uid',
field_editable_columns = 'id|ID\ntitle|Title\nquantity|quantity\nstart_date|Date',
field_search_columns = 'id|ID\ntitle|Title\nquantity|Quantity\nstart_date|Date',)
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
......@@ -952,6 +1086,82 @@ class TestERP5Document_getHateoas_mode_search(ERP5HALJSONStyleSkinsMixin):
mode="search",
default_param_json='eyJcdTAwZWEiOiAiXHUwMGU4In0=')
@simulate('Base_getRequestUrl', '*args, **kwargs', 'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs', 'return "application/hal+json"')
@simulate('Test_listObjects', '*args, **kwargs', """
from Products.PythonScripts.standard import Object
return [Object(debit_price=1000.00, credit_price=100.00),
Object(debit_price=10.00, credit_price=0.00)]
""")
@simulate('Test_listProducts', '*args, **kwargs', """
return context.getPortalObject().foo_module.values()
""")
@simulate('Test_listCatalog', '*args, **kwargs', """
return context.getPortalObject().portal_catalog(portal_type='Foo', sort_on=[('id', 'ASC')])
""")
@createIndexedDocument(quantity=2)
@changeSkin('Hal')
def test_getHateoas_exotic_search_results(self, document_list):
"""Test that ingestion of `list_method` result does not fail.
The only limit for the result of `list_method` is that it should be an iterable.
Practically, because we code in python, it can be any object.
"""
fake_request = do_fake_request("GET")
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
mode="search",
local_roles=["Assignor", "Assignee"],
list_method='Test_listObjects',
select_list=['credit_price', 'debit_price']
)
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/hal+json"
)
result_dict = json.loads(result)
self.assertEqual(len(result_dict['_embedded']['contents']), 2)
self.assertEqual(result_dict['_embedded']['contents'][0]['debit_price'], 1000.0)
self.assertEqual(result_dict['_embedded']['contents'][0]['credit_price'], 100.0)
self.assertEqual(result_dict['_embedded']['contents'][1]['debit_price'], 10.0)
self.assertEqual(result_dict['_embedded']['contents'][1]['credit_price'], 0.0)
# Render a Document using Form Field template (only for field 'id')
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
mode="search",
local_roles=["Assignor", "Assignee"],
list_method='Test_listProducts',
select_list=['id'],
form_relative_url='portal_skins/erp5_ui_test/FooModule_viewFooList/listbox'
)
result_dict = json.loads(result)
self.assertEqual(2, len(result_dict['_embedded']['contents']))
self.assertIn("field_listbox", result_dict['_embedded']['contents'][0]['id']['key'])
self.assertEqual("StringField", result_dict['_embedded']['contents'][0]['id']['type'])
self.assertEqual(document_list[0].getId(), result_dict['_embedded']['contents'][0]['id']['default'])
self.assertIn("field_listbox", result_dict['_embedded']['contents'][1]['id']['key'])
self.assertEqual("StringField", result_dict['_embedded']['contents'][1]['id']['type'])
self.assertEqual(document_list[1].getId(), result_dict['_embedded']['contents'][1]['id']['default'])
# Test rendering without form template of attribute, getterm and a script
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
mode="search",
local_roles=["Assignor", "Assignee"],
list_method='Test_listCatalog',
select_list=['title', 'Foo_getLocalTitle', 'getTotalQuantity'] # property, Script, method
)
result_dict = json.loads(result)
self.assertEqual(len(result_dict['_embedded']['contents']), 2)
self.assertEqual(result_dict['_embedded']['contents'][0]['title'].encode('utf-8'), document_list[0].getTitle())
self.assertEqual(result_dict['_embedded']['contents'][0]['Foo_getLocalTitle'], None)
self.assertEqual(result_dict['_embedded']['contents'][0]['getTotalQuantity'], 0)
self.assertEqual(result_dict['_embedded']['contents'][1]['title'].encode('utf-8'), document_list[1].getTitle())
self.assertEqual(result_dict['_embedded']['contents'][1]['Foo_getLocalTitle'], None)
self.assertEqual(result_dict['_embedded']['contents'][1]['getTotalQuantity'], 0)
class TestERP5Document_getHateoas_mode_bulk(ERP5HALJSONStyleSkinsMixin):
@simulate('Base_getRequestHeader', '*args, **kwargs',
......@@ -1092,13 +1302,11 @@ class TestERP5Document_getHateoas_mode_worklist(ERP5HALJSONStyleSkinsMixin):
)
result_dict = json.loads(result)
self.assertEqual(result_dict['_links']['self'], {"href": "http://example.org/bar"})
work_list = [x for x in result_dict['worklist'] if x['name'].startswith('Draft To Validate')]
self.assertEqual(len(work_list), 1)
self.assertTrue(work_list[0]['count'] > 0)
self.assertEqual(work_list[0]['name'], 'Draft To Validate')
self.assertFalse('module' in work_list[0])
self.assertEqual(work_list[0]['href'], 'urn:jio:allDocs?query=portal_type%3A%28%22Bar%22%20OR%20%22Foo%22%29%20AND%20simulation_state%3A%22draft%22')
self.assertEqual(len(result_dict['worklist']), 1)
self.assertTrue(result_dict['worklist'][0]['count'] > 0)
self.assertEqual(result_dict['worklist'][0]['name'], 'Draft To Validate')
self.assertFalse('module' in result_dict['worklist'][0])
self.assertEqual(result_dict['worklist'][0]['href'], 'urn:jio:allDocs?query=portal_type%3A%28%22Bar%22%20OR%20%22Foo%22%29%20AND%20simulation_state%3A%22draft%22')
self.assertEqual(result_dict['_debug'], "worklist")
......@@ -1181,9 +1389,9 @@ return msg"
'return "application/hal+json"')
@simulate('Base_translateString', 'msg, catalog="ui", encoding="utf8", lang="wo", **kw',
code_string)
@createIndexedDocument()
@changeSkin('Hal')
def test_getHateoasWorklist_default_view_translation(self):
# self._makeDocument()
def test_getHateoasWorklist_default_view_translation(self, document):
fake_request = do_fake_request("GET")
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(
REQUEST=fake_request,
......@@ -1195,12 +1403,11 @@ return msg"
)
result_dict = json.loads(result)
self.assertEqual(result_dict['_links']['self'], {"href": "http://example.org/bar"})
work_list = [x for x in result_dict['worklist'] if x['name'].startswith('daiyanzhen')]
self.assertEqual(len(work_list), 1)
self.assertEqual(work_list[0]['name'], 'daiyanzhen')
self.assertTrue(work_list[0]['count'] > 0)
self.assertFalse('module' in work_list[0])
self.assertEqual(work_list[0]['href'], 'urn:jio:allDocs?query=portal_type%3A%28%22Bar%22%20OR%20%22Foo%22%29%20AND%20simulation_state%3A%22draft%22')
self.assertEqual(len(result_dict['worklist']), 1)
self.assertEqual(result_dict['worklist'][0]['name'], 'daiyanzhen')
self.assertTrue(result_dict['worklist'][0]['count'] > 0)
self.assertFalse('module' in result_dict['worklist'][0])
self.assertEqual(result_dict['worklist'][0]['href'], 'urn:jio:allDocs?query=portal_type%3A%28%22Bar%22%20OR%20%22Foo%22%29%20AND%20simulation_state%3A%22draft%22')
self.assertEqual(result_dict['_debug'], "worklist")
......
......@@ -33,9 +33,9 @@ class _ERP5(ERP5TypeTestSuite):
component_re = re.compile(".*/([^/]+)/TestTemplateItem/portal_components"
"/test\.[^.]+\.([^.]+).py$")
for test_path in (
glob('%s/product/*/tests/test*.py' % path) +
glob('%s/bt5/*/TestTemplateItem/test*.py' % path) +
glob('%s/bt5/*/TestTemplateItem/portal_components/test.*.test*.py' % path)):
glob('%s/bt5/erp5_hal_json_style/TestTemplateItem/portal_components/test*.py' % path) +
glob('%s/bt5/erp5_accounting_renderjs_ui_test/TestTemplateItem/portal_components/test*.py' % path) +
glob('%s/bt5/erp5_web_renderjs_ui_test/TestTemplateItem/portal_components/test.*.test*.py' % path)):
component_re_match = component_re.match(test_path)
if component_re_match is not None:
test_case = "%s:%s" % (component_re_match.group(1),
......
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