Commit a0f8537b authored by Romain Courteaud's avatar Romain Courteaud

slapos_accounting/erp5: allow accountants to pay deposit/invoices for organisation

- accountants can see Subscription Request to pay deposit
- provide permission to create automated payment transaction
parent c916bd95
......@@ -3,6 +3,8 @@ Base_translateString = portal.Base_translateString
outstanding_deposit_amount = portal.restrictedTraverse(outstanding_deposit_amount)
batch = (dialog_id is None)
# Check that the total_price matches the outstanding amount list
expected_price = context.Entity_getOutstandingDepositAmountList(
section_uid=outstanding_deposit_amount.getSourceSectionUid(),
......@@ -13,19 +15,33 @@ expected_price = context.Entity_getOutstandingDepositAmountList(
precision = outstanding_deposit_amount.getPriceCurrencyValue().getQuantityPrecision()
if round(total_price, precision) != round(expected_price, precision):
if batch:
raise ValueError('Total Amount does not match')
return context.Base_renderForm(dialog_id, Base_translateString('Total Amount does not match'), level='error')
##################################################################
# Trigger creation of the Payment Transaction
def wrapWithShadow(entity, outstanding_amount, payment_mode):
payment_transaction = entity.Entity_createDepositPaymentTransaction(
outstanding_amount,
payment_mode=payment_mode
)
payment_transaction.stop()
return payment_transaction
shadow_person = portal.portal_membership.getAuthenticatedMember().getUserValue()
entity = context
payment_transaction = context.Entity_createDepositPaymentTransaction(
context.Entity_getOutstandingDepositAmountList(
payment_transaction = entity.Person_restrictMethodAsShadowUser(
shadow_document=shadow_person,
callable_object=wrapWithShadow,
argument_list=[entity, context.Entity_getOutstandingDepositAmountList(
section_uid=outstanding_deposit_amount.getSourceSectionUid(),
resource_uid=outstanding_deposit_amount.getPriceCurrencyUid(),
ledger_uid=outstanding_deposit_amount.getLedgerUid(),
group_by_node=False
),
# start_date=date,
payment_mode=payment_mode
)
payment_transaction.stop()
), payment_mode])
if batch:
return payment_transaction
return payment_transaction.Base_redirect()
......@@ -15,16 +15,29 @@ precision = outstanding_amount.getPriceCurrencyValue().getQuantityPrecision()
if round(total_price, precision) != round(expected_price, precision):
return context.Base_renderForm(dialog_id, Base_translateString('Total Amount does not match'), level='error')
##################################################################
# Trigger creation of the Payment Transaction
def wrapWithShadow(entity, outstanding_amount_list, start_date, payment_mode):
payment_transaction = entity.Entity_createPaymentTransaction(
outstanding_amount_list,
start_date=start_date,
payment_mode=payment_mode
)
payment_transaction.stop()
return payment_transaction
shadow_person = portal.portal_membership.getAuthenticatedMember().getUserValue()
entity = context
payment_transaction = context.Entity_createPaymentTransaction(
context.Entity_getOutstandingAmountList(
payment_transaction = entity.Person_restrictMethodAsShadowUser(
shadow_document=shadow_person,
callable_object=wrapWithShadow,
argument_list=[entity, context.Entity_getOutstandingAmountList(
section_uid=outstanding_amount.getSourceSectionUid(),
resource_uid=outstanding_amount.getPriceCurrencyUid(),
ledger_uid=outstanding_amount.getLedgerUid(),
group_by_node=False
),
start_date=date,
payment_mode=payment_mode
)
payment_transaction.stop()
), date, payment_mode])
return payment_transaction.Base_redirect()
......@@ -12,7 +12,7 @@ from DateTime import DateTime
class TestSlapOSManualAccountingScenarioMixin(TestSlapOSVirtualMasterScenarioMixin):
def bootstrapManualAccountingTest(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.tic()
self.logout()
......
<local_roles_item>
<local_roles>
<role id='F-ACCOUNTING*'>
<item>Auditor</item>
</role>
<role id='F-CUSTOMER'>
<item>Auditor</item>
</role>
......@@ -10,6 +13,7 @@
</local_roles>
<local_role_group_ids>
<local_role_group_id id='function'>
<principal id='F-ACCOUNTING*'>Auditor</principal>
<principal id='F-CUSTOMER'>Auditor</principal>
<principal id='F-SALE*'>Auditor</principal>
<principal id='F-SALE*'>Author</principal>
......
......@@ -6,6 +6,12 @@
<multi_property id='category'>function/accounting*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
<role id='Assignee'>
<property id='title'>Shadow Accountant</property>
<property id='condition'>python: (here.getDestinationSection('', portal_type='Organisation') != "") and (here.getLedger("") == "automated")</property>
<multi_property id='categories'>local_role_group/shadow</multi_property>
<multi_property id='category'>role/shadow/person</multi_property>
</role>
<role id='Assignee'>
<property id='title'>Shadow User</property>
<property id='condition'>python: (here.getDestination('', portal_type='Person') != "") and (here.getLedger("") == "automated")</property>
......
<type_roles>
<role id='Auditor'>
<property id='title'>Accounting</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/accounting*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
<role id='Auditor'>
<property id='title'>Member</property>
<multi_property id='categories'>local_role_group/function</multi_property>
......
<type_roles>
<role id='Auditor'>
<property id='title'>Accounting</property>
<multi_property id='categories'>local_role_group/function</multi_property>
<multi_property id='category'>function/accounting*</multi_property>
<multi_property id='base_category'>function</multi_property>
</role>
<role id='Associate'>
<property id='title'>Customer</property>
<property id='condition'>python: context.getDestinationDecision('', portal_type='Person') != ""
......
......@@ -149,7 +149,6 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_simulation/SimulationMovement_testTradeModelSimulationRule',
'slapos_accounting/OpenSaleOrder_archiveIfUnusedItem',
'slapos_accounting/Base_getAccountForUse',
'slapos_accounting/Entity_createDepositAction',
'slapos_accounting/Entity_createPaymentTransactionAction',
'slapos_accounting/Movement_getPriceCalculationOperandDict',
'slapos_accounting/PaymentTransaction_acceptDepositPayment',
......
......@@ -73,7 +73,7 @@ class TestSlapOSAllocationScenarioMixin(TestSlapOSVirtualMasterScenarioMixin):
self.tic()
def bootstrapVirtualMasterTestWithProject(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.tic()
self.logout()
self.login()
......
......@@ -208,6 +208,31 @@ class TestPaymentTransaction(TestSlapOSGroupRoleSecurityMixin):
self.assertRoles(product, person.getUserId(), ['Auditor'])
self.assertRoles(product, self.user_id, ['Owner'])
def test_PaymentTransaction_OrganisationLedger(self):
reference = 'TESTPERSON-%s' % self.generateNewId()
person = self.portal.person_module.newContent(portal_type='Person',
reference=reference)
organisation = self.portal.organisation_module.newContent(
portal_type='Organisation',
title='TESTORGA-%s' % self.generateNewId()
)
product = self.portal.accounting_module.newContent(
portal_type='Payment Transaction')
product.edit(
destination_value=person,
destination_section_value=organisation,
ledger='automated'
)
shadow_user_id = 'SHADOW-%s' % person.getUserId()
self.assertSecurityGroup(product,
['F-ACCOUNTING*', 'R-SHADOW-PERSON', self.user_id, person.getUserId(),
shadow_user_id], False)
self.assertRoles(product, 'F-ACCOUNTING*', ['Auditor'])
self.assertRoles(product, 'R-SHADOW-PERSON', ['Assignee'])
self.assertRoles(product, shadow_user_id, ['Assignee'])
self.assertRoles(product, person.getUserId(), ['Auditor'])
self.assertRoles(product, self.user_id, ['Owner'])
class TestSaleInvoiceTransaction(TestSlapOSGroupRoleSecurityMixin):
def test_SaleInvoiceTransaction_AccountingFunction_LedgerNotAutomated(self):
......@@ -1619,9 +1644,10 @@ class TestSubscriptionRequestModule(TestSlapOSGroupRoleSecurityMixin):
def test_SubscriptionRequestModule(self):
module = self.portal.subscription_request_module
self.assertSecurityGroup(module,
['F-SALE*', 'F-CUSTOMER', module.Base_getOwnerId()], False)
['F-SALE*', 'F-CUSTOMER', 'F-ACCOUNTING*', module.Base_getOwnerId()], False)
self.assertRoles(module, 'F-SALE*', ['Auditor', 'Author'])
self.assertRoles(module, 'F-CUSTOMER', ['Auditor'])
self.assertRoles(module, 'F-ACCOUNTING*', ['Auditor'])
self.assertRoles(module, module.Base_getOwnerId(), ['Owner'])
......@@ -1630,19 +1656,20 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin):
delivery = self.portal.subscription_request_module.newContent(
portal_type='Subscription Request')
self.assertSecurityGroup(delivery,
['F-SALE*', self.user_id], False)
['F-SALE*', 'F-ACCOUNTING*', self.user_id], False)
self.assertRoles(delivery, self.user_id, ['Owner'])
self.assertRoles(delivery, 'F-SALE*', ['Auditor'])
self.assertRoles(delivery, 'F-ACCOUNTING*', ['Auditor'])
def test_SubscriptionRequest_automated_ledger(self):
delivery = self.portal.subscription_request_module.newContent(
portal_type='Subscription Request')
delivery.edit(ledger="automated")
self.assertSecurityGroup(delivery,
['F-SALE*', self.user_id], False)
['F-SALE*', 'F-ACCOUNTING*', self.user_id], False)
self.assertRoles(delivery, self.user_id, ['Owner'])
self.assertRoles(delivery, 'F-SALE*', ['Auditor'])
self.assertRoles(delivery, 'F-ACCOUNTING*', ['Auditor'])
def test_SubscriptionRequest_user(self):
reference = 'TESTPERSON-%s' % self.generateNewId()
......@@ -1652,10 +1679,11 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin):
portal_type='Subscription Request')
delivery.edit(destination_decision_value=person, ledger="automated")
self.assertSecurityGroup(delivery,
['F-SALE*', self.user_id, "SHADOW-%s" % person.getUserId(),
['F-SALE*', 'F-ACCOUNTING*', self.user_id, "SHADOW-%s" % person.getUserId(),
person.getUserId()], False)
self.assertRoles(delivery, self.user_id, ['Owner'])
self.assertRoles(delivery, 'F-SALE*', ['Auditor'])
self.assertRoles(delivery, 'F-ACCOUNTING*', ['Auditor'])
self.assertRoles(delivery, person.getUserId(), ['Associate'])
self.assertRoles(delivery, "SHADOW-%s" % person.getUserId(), ['Auditor'])
......
......@@ -17,7 +17,7 @@ class TestSlapOSSubscriptionChangeRequestScenarioMixin(TestSlapOSVirtualMasterSc
class TestSlapOSSubscriptionChangeRequestScenario(TestSlapOSSubscriptionChangeRequestScenarioMixin):
def test_subscription_change_request_change_instance_destination_without_accounting_scenario(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.tic()
......@@ -185,7 +185,7 @@ class TestSlapOSSubscriptionChangeRequestScenario(TestSlapOSSubscriptionChangeRe
self.checkERP5StateBeforeExit()
def test_subscription_change_request_change_project_destination_section_scenario(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=True)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=True)
self.tic()
......@@ -288,7 +288,7 @@ class TestSlapOSSubscriptionChangeRequestScenario(TestSlapOSSubscriptionChangeRe
def test_subscription_change_request_change_instance_destination_section_scenario(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.tic()
......
......@@ -42,7 +42,7 @@ class TestSlapOSSubscriptionScenario(TestSlapOSSubscriptionScenarioMixin):
subscription request is automatically validated/invalidated if price is 0.
"""
with PinnedDateTime(self, DateTime('2024/01/31')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.logout()
# lets join as slapos administrator, which will manager the project
......@@ -263,7 +263,7 @@ class TestSlapOSSubscriptionScenario(TestSlapOSSubscriptionScenarioMixin):
subscription request is automatically validated/invalidated if price is 0.
"""
with PinnedDateTime(self, DateTime('2024/01/31')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.logout()
# lets join as slapos administrator, which will manager the project
......@@ -362,7 +362,7 @@ class TestSlapOSSubscriptionScenario(TestSlapOSSubscriptionScenarioMixin):
subscription request is automatically validated/invalidated if price is 0.
"""
with PinnedDateTime(self, DateTime('2024/01/31')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.logout()
# lets join as slapos administrator, which will manager the project
......
......@@ -249,7 +249,7 @@ class TestSlapOSVirtualMasterScenarioMixin(DefaultScenarioMixin):
)
sale_supply.validate()
return currency, seller_organisation, seller_bank_account, sale_person
return currency, seller_organisation, seller_bank_account, sale_person, accountant_person
def checkERP5StateBeforeExit(self):
self.logout()
......@@ -284,7 +284,7 @@ class TestSlapOSVirtualMasterScenarioMixin(DefaultScenarioMixin):
return production_manager_person
def bootstrapAccountingTest(self):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.tic()
self.logout()
......@@ -326,7 +326,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_without_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.tic()
......@@ -461,7 +461,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_deposit_with_accounting_scenario(self):
currency, seller_organisation, _, _ = \
currency, seller_organisation, _, _, _ = \
self.bootstrapVirtualMasterTest(is_virtual_master_accountable=True)
self.logout()
......@@ -531,7 +531,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_professional_account_with_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, accountant_person = self.bootstrapVirtualMasterTest()
self.logout()
# lets join as slapos administrator, which will manager the project
......@@ -702,6 +702,8 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
# Pay deposit to validate virtual master + one computer, for the organisation
# For now we cannot rely on user payments
self.logout()
self.login(accountant_person.getUserId())
deposit_amount = 42.0 + 99.0
ledger = self.portal.portal_categories.ledger.automated
......@@ -710,19 +712,18 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
amount = sum([i.total_price for i in outstanding_amount_list])
self.assertEqual(amount, deposit_amount)
payment_transaction = customer_section_organisation.Entity_createDepositPaymentTransaction(
outstanding_amount_list)
# XXX use accountant account couscous
payment_transaction = customer_section_organisation.Entity_createDepositAction(
'wire_transfer', outstanding_amount_list[0].getRelativeUrl(), deposit_amount, None)
self.tic()
self.assertEqual(payment_transaction.getSpecialiseValue().getTradeConditionType(), "deposit")
# payzen/wechat or accountant will only stop the payment
payment_transaction.stop()
self.tic()
assert payment_transaction.receivable.getGroupingReference(None) is not None
outstanding_amount_list = customer_section_organisation.Entity_getOutstandingDepositAmountList(
currency.getUid(), ledger_uid=ledger.getUid())
self.assertEqual(0, sum([i.total_price for i in outstanding_amount_list]))
self.logout()
with PinnedDateTime(self, DateTime('2024/02/17 01:01')):
public_instance_title = 'Public title %s' % self.generateNewId()
......@@ -806,7 +807,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_with_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest()
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest()
self.logout()
# lets join as slapos administrator, which will manager the project
......@@ -1040,7 +1041,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_slave_without_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.web_site = self.portal.web_site_module.slapos_master_panel
......@@ -1196,7 +1197,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_slave_on_same_tree_without_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.web_site = self.portal.web_site_module.slapos_master_panel
......@@ -1314,7 +1315,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_on_remote_tree_without_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.web_site = self.portal.web_site_module.slapos_master_panel
......@@ -1547,7 +1548,7 @@ class TestSlapOSVirtualMasterScenario(TestSlapOSVirtualMasterScenarioMixin):
def test_virtual_master_slave_instance_on_remote_tree_without_accounting_scenario(self):
with PinnedDateTime(self, DateTime('2024/02/17')):
currency, _, _, sale_person = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
currency, _, _, sale_person, _ = self.bootstrapVirtualMasterTest(is_virtual_master_accountable=False)
self.web_site = self.portal.web_site_module.slapos_master_panel
......
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