Commit 42c2a915 authored by Jérome Perrin's avatar Jérome Perrin

configurator_standard: repair accounting generation with default accounts

in 772d8fed (configurator_standard: use specific sale and purchase
business processes, 2020-07-16) we introduced scripts to look up
dynamically accounts to use for expense/income and VAT accounts, from
supply lines, but this accidentally broke the case where no account can be
found on supply lines: in this case, the accounting line was not generated.

Ideally, there should be fallback accounts, to keep the same behavior and
also to prevent generation of incomplete transactions.

Review the scripts, so that the "Dynamic" scripts fallback on the account
defined on the trade model path if not found and introduce new scripts,
where the account is not taken dynamically, but only from the trade model
path, for use on the payable/receivable lines (where we don't want to
lookup the account dynamically, otherwise we'll use same account as for
expense/income)
parent 7058ab6f
Pipeline #14195 failed with stage
in 0 seconds
...@@ -73,6 +73,8 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin): ...@@ -73,6 +73,8 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin):
stepCheckPurchaseTradeCondition stepCheckPurchaseTradeCondition
stepCheckSaleSimulationScenario stepCheckSaleSimulationScenario
stepCheckPurchaseSimulationScenario stepCheckPurchaseSimulationScenario
stepCheckSaleInvoiceAccountFallback
stepCheckPurchaseInvoiceAccountFallback
''' '''
SECURITY_CONFIGURATION_SEQUENCE = """ SECURITY_CONFIGURATION_SEQUENCE = """
...@@ -1255,6 +1257,187 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin): ...@@ -1255,6 +1257,187 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin):
self.portal.portal_workflow.doActionFor(sale_invoice, 'stop_action') self.portal.portal_workflow.doActionFor(sale_invoice, 'stop_action')
self.tic() self.tic()
def stepCheckSaleInvoiceAccountFallback(self, sequence=None, sequence_list=None, **kw):
"""
Non-regression test checking that sales invoice accounting fallback to accounts
defined on businessprocess when no account is set on resources supply lines.
Similar to stepCheckSaleSimulationScenario, but instead of using accounts defined
in the resource supply lines, we don't define accounts there and default accounts
are selected.
"""
portal = self.portal
business_configuration = sequence.get('business_configuration')
self.login()
organisation_list = self.getBusinessConfigurationObjectList(business_configuration, 'Organisation')
self.assertNotEquals(len(organisation_list), 0)
organisation = organisation_list[0]
self.assertEqual('validated', organisation.getValidationState())
configuration_sale_trade_condition = \
self.getBusinessConfigurationObjectList(business_configuration,
'Sale Trade Condition')[0]
self.assertIsNotNone(configuration_sale_trade_condition.getSpecialiseValue())
sales_manager_id, = self._getUserIdList([self.sales_manager_reference])
self._loginAsUser(sales_manager_id)
destination_decision = portal.portal_catalog.getResultValue(
portal_type='Person',
reference=self.sales_manager_reference)
destination_administration = portal.portal_catalog.getResultValue(
portal_type='Person',
reference=self.purchase_manager_reference)
resource = portal.product_module.newContent(
portal_type='Product',
quantity_unit='unit/piece',
individual_variation_base_category='variation',
base_contribution='base_amount/taxable',
default_sale_supply_line_base_price=10,
)
portal.portal_workflow.doActionFor(resource, 'validate_action')
self.tic()
vat_service = portal.service_module.newContent(
portal_type='Service',
title="VAT",
use_value=self.portal.portal_categories.use.trade.tax,
)
portal.portal_workflow.doActionFor(vat_service, 'validate_action')
# make a trade condition for VAT
sale_trade_condition = portal.sale_trade_condition_module.newContent(
portal_type='Sale Trade Condition',
specialise_value=configuration_sale_trade_condition,
)
sale_trade_condition.newContent(
portal_type='Trade Model Line',
base_application_value=self.portal.portal_categories.base_amount.taxable,
price=0.03,
resource_value=vat_service,
trade_phase_value=self.portal.portal_categories.trade_phase.trade.tax,
use_value=self.portal.portal_categories.use.trade.tax
)
portal.portal_workflow.doActionFor(sale_trade_condition, 'validate_action')
client = portal.organisation_module.newContent(
portal_type='Organisation')
portal.portal_workflow.doActionFor(client, 'validate_action')
self.tic()
start_date = stop_date = DateTime("2008/01/02")
order = portal.sale_order_module.newContent(
portal_type='Sale Order',
specialise=(sale_trade_condition.getRelativeUrl(),),
destination_value=client,
destination_section_value=client,
source_value=organisation,
source_section_value=organisation,
destination_decision=destination_decision.getRelativeUrl(),
destination_administration=destination_administration.getRelativeUrl(),
start_date=start_date,
stop_date=stop_date)
self.tic()
# Set the rest through the trade condition.
order.SaleOrder_applySaleTradeCondition()
self.tic()
order.newContent(portal_type='Sale Order Line',
resource=resource.getRelativeUrl(),
quantity=10)
self.tic()
self.assertEqual(order.getTotalPrice(), 100)
self.assertEqual(order.getSimulationState(), 'draft')
self.portal.portal_workflow.doActionFor(order, 'plan_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'planned')
self.portal.portal_workflow.doActionFor(order, 'order_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'ordered')
self.portal.portal_workflow.doActionFor(order, 'confirm_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'confirmed')
causality_list = order.getCausalityRelatedValueList(portal_type='Applied Rule')
self.assertEqual(len(causality_list), 1)
applied_rule = causality_list[0]
self.assertEqual(applied_rule.getPortalType(), 'Applied Rule')
rule = applied_rule.getSpecialiseValue()
self.assertNotEquals(rule, None)
self.assertEqual(rule.getReference(), 'default_order_rule')
self.assertEqual(applied_rule.objectCount(), 1)
simulation_movement = applied_rule.objectValues()[0]
self.assertEqual(simulation_movement.getPortalType(),
'Simulation Movement')
self.assertEqual(simulation_movement.getQuantity(), 10)
self.assertEqual(simulation_movement.getResourceValue(), resource)
self.assertNotEquals(simulation_movement.getCausality(), None)
self.assertEqual(simulation_movement.getDestinationDecisionValue(),
destination_decision)
self.assertEqual(simulation_movement.getDestinationAdministrationValue(),
destination_administration)
self.portal.portal_alarms.packing_list_builder_alarm.activeSense()
self.tic()
sale_packing_list, = order.getCausalityRelatedValueList(portal_type='Sale Packing List')
self.assertEqual(sale_packing_list.getSimulationState(), 'confirmed')
self.assertEqual(sale_packing_list.getCausalityState(), 'solved')
self.assertEqual(sale_packing_list.getDivergenceList(), [])
self.portal.portal_workflow.doActionFor(sale_packing_list, 'start_action')
self.commit()
self.portal.portal_workflow.doActionFor(sale_packing_list, 'stop_action')
self.tic()
self.portal.portal_alarms.invoice_builder_alarm.activeSense()
self.tic()
sale_invoice, = sale_packing_list.getCausalityRelatedValueList(
portal_type='Sale Invoice Transaction')
self.assertEqual(sale_invoice.getSimulationState(), 'confirmed')
self.assertEqual(sale_invoice.getCausalityState(), 'solved')
self.assertEqual(sale_invoice.getDivergenceList(), [])
self.assertEqual(
sorted([
(m.getQuantity(), m.getPrice(), m.getResourceValue())
for m in sale_invoice.getMovementList()
]), [
(10, 10, resource),
(100, .03, vat_service),
])
self.assertEqual(start_date, sale_invoice.getStartDate())
self.assertEqual(stop_date, sale_invoice.getStopDate())
self.assertEqual(client, sale_invoice.getDestinationSectionValue())
self.assertEqual(client, sale_invoice.getDestinationValue())
self.portal.portal_workflow.doActionFor(sale_invoice, 'start_action')
self.tic()
self.portal.portal_alarms.invoice_builder_alarm.activeSense()
self.tic()
self.assertEqual(
sorted(
[
(
m.getSourceDebit(),
m.getSourceCredit(),
m.getSourceValue(portal_type='Account'),
m.getDestinationValue(portal_type='Account'),
) for m in sale_invoice.getMovementList(
portal_type='Sale Invoice Transaction Line')
]), [
(0, 3, self.portal.account_module.coll_vat, None),
(0, 100, self.portal.account_module.sales, None),
(103, 0, self.portal.account_module.receivable, None),
])
def stepCheckPurchaseSimulationScenario(self, sequence): def stepCheckPurchaseSimulationScenario(self, sequence):
""" """
After the configuration we need to make sure that Simulation from After the configuration we need to make sure that Simulation from
...@@ -1514,6 +1697,171 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin): ...@@ -1514,6 +1697,171 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin):
self.portal.portal_workflow.doActionFor(purchase_invoice, 'stop_action') self.portal.portal_workflow.doActionFor(purchase_invoice, 'stop_action')
self.tic() self.tic()
def stepCheckPurchaseInvoiceAccountFallback(self, sequence=None, sequence_list=None, **kw):
"""
Non-regression test checking that purchase invoice accounting fallback to accounts
defined on businessprocess when no account is set on resources supply lines.
Similar to stepCheckPurchaseSimulationScenario, but instead of using accounts defined
in the resource supply lines, we don't define accounts there and default accounts
are selected.
"""
portal = self.portal
business_configuration = sequence.get('business_configuration')
self.login()
organisation_list = self.getBusinessConfigurationObjectList(business_configuration, 'Organisation')
self.assertNotEquals(len(organisation_list), 0)
organisation = organisation_list[0]
self.assertEqual('validated', organisation.getValidationState())
configuration_purchase_trade_condition = \
self.getBusinessConfigurationObjectList(business_configuration,
'Purchase Trade Condition')[0]
self.assertIsNotNone(configuration_purchase_trade_condition.getSpecialiseValue())
purchase_manager_id, = self._getUserIdList([self.purchase_manager_reference])
self._loginAsUser(purchase_manager_id)
destination_decision = portal.portal_catalog.getResultValue(
portal_type='Person',
reference=self.sales_manager_reference)
destination_administration = portal.portal_catalog.getResultValue(
portal_type='Person',
reference=self.purchase_manager_reference)
resource = portal.product_module.newContent(
portal_type='Product',
quantity_unit='unit/piece',
individual_variation_base_category='variation',
base_contribution='base_amount/taxable',
default_purchase_supply_line_base_price=10,
)
portal.portal_workflow.doActionFor(resource, 'validate_action')
self.tic()
vat_service = portal.service_module.newContent(
portal_type='Service',
title="VAT",
use_value=self.portal.portal_categories.use.trade.tax,
)
portal.portal_workflow.doActionFor(vat_service, 'validate_action')
# make a trade condition for VAT
purchase_trade_condition = portal.purchase_trade_condition_module.newContent(
portal_type='Purchase Trade Condition',
specialise_value=configuration_purchase_trade_condition,
)
purchase_trade_condition.newContent(
portal_type='Trade Model Line',
base_application_value=self.portal.portal_categories.base_amount.taxable,
price=0.03,
resource_value=vat_service,
trade_phase_value=self.portal.portal_categories.trade_phase.trade.tax,
use_value=self.portal.portal_categories.use.trade.tax
)
portal.portal_workflow.doActionFor(purchase_trade_condition, 'validate_action')
supplier = portal.organisation_module.newContent(
portal_type='Organisation')
portal.portal_workflow.doActionFor(supplier, 'validate_action')
another_supplier = portal.organisation_module.newContent(
portal_type='Organisation')
portal.portal_workflow.doActionFor(another_supplier, 'validate_action')
self.tic()
start_date = stop_date = DateTime("2008/01/02")
order = portal.purchase_order_module.newContent(
portal_type='Purchase Order',
specialise=(purchase_trade_condition.getRelativeUrl(),),
destination_value=organisation,
destination_section_value=organisation,
source_value=supplier,
source_section_value=supplier,
destination_decision=destination_decision.getRelativeUrl(),
destination_administration=destination_administration.getRelativeUrl(),
start_date=start_date,
stop_date=stop_date)
self.tic()
# Set the rest through the trade condition.
order.PurchaseOrder_applyPurchaseTradeCondition()
self.tic()
order.newContent(portal_type='Purchase Order Line',
resource=resource.getRelativeUrl(),
quantity=10)
self.tic()
self.assertEqual(order.getTotalPrice(), 100)
self.assertEqual(order.getSimulationState(), 'draft')
self.portal.portal_workflow.doActionFor(order, 'plan_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'planned')
self.portal.portal_workflow.doActionFor(order, 'order_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'ordered')
self.portal.portal_workflow.doActionFor(order, 'confirm_action')
self.tic()
self.assertEqual(order.getSimulationState(), 'confirmed')
self.portal.portal_alarms.packing_list_builder_alarm.activeSense()
self.tic()
purchase_packing_list, = order.getCausalityRelatedValueList(portal_type='Purchase Packing List')
self.assertEqual(purchase_packing_list.getSimulationState(), 'confirmed')
self.assertEqual(purchase_packing_list.getCausalityState(), 'solved')
self.assertEqual(purchase_packing_list.getDivergenceList(), [])
self.portal.portal_workflow.doActionFor(
purchase_packing_list, 'start_action')
self.commit()
self.portal.portal_workflow.doActionFor(
purchase_packing_list, 'stop_action')
self.tic()
self.portal.portal_alarms.invoice_builder_alarm.activeSense()
self.tic()
purchase_invoice, = purchase_packing_list.getCausalityRelatedValueList(
portal_type='Purchase Invoice Transaction')
self.assertEqual(purchase_invoice.getSimulationState(), 'confirmed')
self.assertEqual(purchase_invoice.getCausalityState(), 'solved')
self.assertEqual(purchase_invoice.getDivergenceList(), [])
self.assertEqual(
sorted([
(m.getQuantity(), m.getPrice(), m.getResourceValue())
for m in purchase_invoice.getMovementList()
]), [
(10, 10, resource),
(100, 0.03, vat_service),
])
self.assertEqual(start_date, purchase_invoice.getStartDate())
self.assertEqual(stop_date, purchase_invoice.getStopDate())
self.assertEqual(supplier, purchase_invoice.getSourceSectionValue())
self.assertEqual(supplier, purchase_invoice.getSourceValue())
self.portal.portal_workflow.doActionFor(purchase_invoice, 'start_action')
self.tic()
self.portal.portal_alarms.invoice_builder_alarm.activeSense()
self.tic()
self.assertEqual(
sorted(
[
(
m.getDestinationDebit(),
m.getDestinationCredit(),
m.getDestinationValue(portal_type='Account'),
m.getSourceValue(portal_type='Account'),
) for m in purchase_invoice.getMovementList(
portal_type='Purchase Invoice Transaction Line')
]), [
(0, 103, self.portal.account_module.payable, None),
(3, 0, self.portal.account_module.refundable_vat, None),
(100, 0, self.portal.account_module.purchase, None),
])
class TestConsultingConfiguratorWorkflow(StandardConfigurationMixin): class TestConsultingConfiguratorWorkflow(StandardConfigurationMixin):
""" """
......
return [
c for c in [
movement.getDestinationSection(base=True),
movement.getDestinationPayment(base=True),
context.getDestination(base=True)
] if c
]
<?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>movement</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TradeModelPath_getAccountingDestinationCategoryListWithoutAnalytic</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -7,9 +7,10 @@ category_list = [ ...@@ -7,9 +7,10 @@ category_list = [
] if c ] if c
] ]
account = movement.getDestinationAccount() or context.getDestination() account = movement.getDestinationAccount()
if not account: if not account:
# try to find from predicates # try to find from predicates, with fallback on the account
# defined on the trade model path
resource = movement.getResourceValue() resource = movement.getResourceValue()
if resource is not None: if resource is not None:
account = next( account = next(
...@@ -19,8 +20,7 @@ if not account: ...@@ -19,8 +20,7 @@ if not account:
context=movement, context=movement,
portal_type='Purchase Supply Line', portal_type='Purchase Supply Line',
) )
if predicate.getDestinationAccount()), None) if predicate.getDestinationAccount()), context.getDestination())
if account: if account:
category_list.append('destination/%s' % account) category_list.append('destination/%s' % account)
......
...@@ -7,9 +7,10 @@ category_list = [ ...@@ -7,9 +7,10 @@ category_list = [
] if c ] if c
] ]
account = movement.getSourceAccount() or context.getSource() account = movement.getSourceAccount()
if not account: if not account:
# try to find from predicates # try to find from predicates, with fallback on the account
# defined on the trade model path
resource = movement.getResourceValue() resource = movement.getResourceValue()
if resource is not None: if resource is not None:
account = next( account = next(
...@@ -19,7 +20,7 @@ if not account: ...@@ -19,7 +20,7 @@ if not account:
context=movement, context=movement,
portal_type='Sale Supply Line', portal_type='Sale Supply Line',
) )
if predicate.getSourceAccount()), None) if predicate.getSourceAccount()), context.getSource())
if account: if account:
category_list.append('source/%s' % account) category_list.append('source/%s' % account)
......
return [
c for c in [
movement.getSourceSection(base=True),
movement.getSourcePayment(base=True),
context.getSource(base=True),
] if c
]
<?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>movement</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TradeModelPath_getAccountingSourceCategoryListWithoutAnalytic</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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