Commit 21504241 authored by Rafael Monnerat's avatar Rafael Monnerat

Rework payment transaction generation

See merge request !459
parents 5128c902 48b9b61f
Pipeline #25474 failed with stage
in 0 seconds
......@@ -88,7 +88,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.getSimulationState() in (\'stopped\', )</string> </value>
<value> <string>python: object.getSimulationState() in (\'stopped\', ) and not object.SaleInvoiceTransaction_isLettered()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -3,16 +3,10 @@ portal_membership=portal.portal_membership
user = portal_membership.getAuthenticatedMember().getUserValue()
def wrapWithShadow():
return [i.getCausalityValue(portal_type="Sale Invoice Transaction") for i in context.getPortalObject().portal_catalog(
portal_type="Payment Transaction",
simulation_state="started",
destination_section_uid=user.getUid(),
default_causality_portal_type="Sale Invoice Transaction",
default_causality_simulation_state=("stopped", "delivered"),
)]
def wrapWithShadow(user):
return user.Entity_getOutstandingAmountList()
return user.Person_restrictMethodAsShadowUser(
shadow_document=user,
callable_object=wrapWithShadow,
argument_list=[])
argument_list=[user])
......@@ -42,19 +42,27 @@ else:
elif context.getTotalPrice() == 0:
result = "Free!"
else:
# Check if there is an ongoing SlapOS payment
payment = context.SaleInvoiceTransaction_getSlapOSPaymentRelatedValue()
if payment is None:
result = "Unpaid"
else:
result = "Pay Now"
# Search to know if there are some payment waiting for confirmation
payment = portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
default_causality_uid=context.getUid(),
default_payment_mode_uid=[portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()],
)
if payment is not None:
# Check if mapping exists
external_payment_id = person.Person_restrictMethodAsShadowUser(
shadow_document=person,
callable_object=payment.PaymentTransaction_getExternalPaymentId,
argument_list=[])[0]
if external_payment_id is None:
result = "Pay Now"
if person is not None:
external_payment_id = person.Person_restrictMethodAsShadowUser(
shadow_document=person,
callable_object=payment.PaymentTransaction_getExternalPaymentId,
argument_list=[])[0]
else:
external_payment_id = payment.PaymentTransaction_getExternalPaymentId()
if external_payment_id is not None:
result = "Waiting for payment confirmation"
return result
......@@ -12,7 +12,7 @@ portal.portal_catalog.searchAndActivate(
payment_mode_uid=[
portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()],
method_id='PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped',
method_id='PaymentTransaction_cancelIfSaleInvoiceTransactionIsLettered',
packet_size=1, # just one to minimise errors
activate_kw={'tag': tag},
**kw
......
# Note: can be cached for 24h if needed: the list is not expected to change often, nor to ever reach even 10 entries.
portal = context.getPortalObject()
return portal.account_module.searchFolder(
strict_account_type_uid=portal.portal_categories.account_type.asset.receivable.getUid(),
validation_state='validated',
)
......@@ -52,9 +52,17 @@
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_startPayzenPayment</string> </value>
<value> <string>Base_getReceivableAccountList</string> </value>
</item>
</dictionary>
</pickle>
......
from Products.ERP5Type.Message import translateString
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
if not invoice_list:
raise ValueError('You need to provide at least one Invoice transaction')
# For now consider a single value is passed, in future we intend to create
# a single payment per invoice.
current_invoice = invoice_list[0]
payment_tag = "sale_invoice_transaction_create_payment_%s" % current_invoice.getUid()
if context.REQUEST.get(payment_tag, None) is not None:
raise ValueError('This script was already called twice on the same transaction ')
if current_invoice.SaleInvoiceTransaction_isLettered():
raise ValueError('This invoice is already lettered')
context.serialize()
quantity = 0
for movement in current_invoice.searchFolder(
portal_type='Sale Invoice Transaction Line',
default_source_uid=[i.uid for i in context.Base_getReceivableAccountList()]):
quantity += movement.getQuantity()
if quantity >= 0:
raise ValueError('You cannot generate Payment Transaction for zero or negative amounts.')
current_payment = portal.accounting_module.newContent(
portal_type="Payment Transaction",
causality=current_invoice.getRelativeUrl(),
source_section=current_invoice.getSourceSection(),
destination_section=current_invoice.getDestinationSection(),
resource=current_invoice.getResource(),
price_currency=current_invoice.getResource(),
specialise=current_invoice.getSpecialise(),
payment_mode=current_invoice.getPaymentMode(),
start_date=current_invoice.getStartDate(),
stop_date=current_invoice.getStopDate(),
source_payment='%s/bank_account' % current_invoice.getSourceSection(), # the other place defnied: business process
# Workarround to not create default lines.
created_by_builder=1
)
current_payment.newContent(
portal_type="Accounting Transaction Line",
quantity=-1 * quantity,
source='account_module/receivable',
destination='account_module/payable',
start_date=current_invoice.getStartDate(),
stop_date=current_invoice.getStopDate())
current_payment.newContent(
portal_type="Accounting Transaction Line",
quantity=1 * quantity,
source='account_module/payment_to_encash',
destination='account_module/payment_to_encash',
start_date=current_invoice.getStartDate(),
stop_date=current_invoice.getStopDate())
comment = translateString("Initialised by Entity_createPaymentTransaction.")
# Reindex with a tag to ensure that there will be no generation while the object isn't
# reindexed.
payment_tag ="sale_invoice_transaction_create_payment_%s" % current_invoice.getUid()
current_payment.activate(tag=payment_tag).immediateReindexObject()
# Call script rather them call confirm(), since it would set security and fail whenever
# start is called.
current_payment.AccountingTransaction_setReference()
comment = translateString("Initialised by Entity_createPaymentTransaction.")
current_payment.start(comment=comment)
# Set a flag on the request for prevent 2 calls on the same transaction
context.REQUEST.set(payment_tag, 1)
return current_payment
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>**kw</string> </value>
<value> <string>invoice_list, REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_getSlapOSPaymentRelated</string> </value>
<value> <string>Entity_createPaymentTransaction</string> </value>
</item>
</dictionary>
</pickle>
......
"""return a list of invoices with the following attributes:
- payment_request_uid: the uid of the invoice, we consider that payment request is the invoice.
- total_price: the amount left to pay for this invoice
- getTotalPrice: the original amount of this invoice.
Arguments:
- at_date (default None)
"""
portal = context.getPortalObject()
params = dict()
if at_date:
params['at_date'] = at_date
params['grouping_reference'] = None
object_list = []
for (idx, brain) in enumerate(portal.portal_simulation.getInventoryList(
mirror_section_uid=context.getUid(),
simulation_state=('stopped', 'delivered'),
node_uid=[x.uid for x in context.Base_getReceivableAccountList()] or -1,
**params )):
# XXX rewrap inventory list brain because they don't have a valid "uid" and cannot be used
# directly in listbox. We should probably add support for this in getInventoryList instead
# of this hack
# XXX In our case, this hould be always None.
payment_request_uid = brain.payment_request_uid
if not payment_request_uid:
payment_request_uid = brain.getObject().getExplanationUid()
payment_request = portal.portal_catalog.getObject(uid=payment_request_uid)
object_list.append(payment_request.asContext(
section_uid=brain.section_uid,
payment_request_uid=payment_request_uid,
node_uid=brain.node_uid,
node_relative_url=brain.node_relative_url,
total_price=brain.total_price,
uid='new_%s' % idx,))
return object_list
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>at_date=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_startWechatPayment</string> </value>
<value> <string>Entity_getOutstandingAmountList</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -4,35 +4,13 @@ if simulation_state != "started":
# The payment isn't started, so ignore it
return "Not Started"
portal = context.getPortalObject()
paid = False
def isNodeFromLineReceivable(line):
node_value = line.getSourceValue(portal_type='Account')
return node_value.getAccountType() == 'asset/receivable'
invoice = context.getCausalityValue()
if invoice is None:
# No invoice Related, so skip and ignore
return
line_found = False
line_list = invoice.getMovementList(portal.getPortalAccountingMovementTypeList())
if not len(line_list):
# Ignore since lines to group don't exist yet
return
for line in line_list:
if isNodeFromLineReceivable(line):
line_found = True
if line.hasGroupingReference():
paid = True
letter = line.getGroupingReference()
break
if line_found and paid:
letter = invoice.SaleInvoiceTransaction_isLettered()
if letter:
# We should ensure that the order builder won't create another document.
context.edit(payment_mode=None)
context.cancel(comment="Payment is cancelled since the invoice is payed by other document,\
......@@ -40,7 +18,6 @@ if line_found and paid:
# Should be safe now to fix everything XXXXXXX
invoice.SaleInvoiceTransaction_resetPaymentMode()
return "Payment Cancelled"
return "Skipped"
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped</string> </value>
<value> <string>PaymentTransaction_cancelIfSaleInvoiceTransactionIsLettered</string> </value>
</item>
</dictionary>
</pickle>
......
""" Create a reversal transaction from current payzen transaction. """
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
""" Create a reversal transaction from current transaction. """
portal = context.getPortalObject()
if not batch_mode and context.getPaymentMode() not in ["payzen", "wechat"]:
message = context.Base_translateString("The payment mode is unsupported.")
return context.Base_redirect(keep_items={'portal_status_message': message})
# Check that we are in state that we are waiting for user manual payment
assert context.getPortalType() == 'Sale Invoice Transaction'
assert context.getPaymentMode() == 'payzen'
assert context.getPaymentMode() in ('payzen', 'wechat')
assert context.getSimulationState() == 'stopped'
assert context.getTotalPrice() != 0
assert context.getSpecialise() in ("sale_trade_condition_module/slapos_aggregated_trade_condition",
"sale_trade_condition_module/slapos_aggregated_subscription_trade_condition")
paid = True
for line in context.getMovementList(portal.getPortalAccountingMovementTypeList()):
node_value = line.getSourceValue(portal_type='Account')
if node_value.getAccountType() == 'asset/receivable':
if not line.hasGroupingReference():
paid = False
break
assert not paid
# Dont create if the invoice is already paied
assert not context.SaleInvoiceTransaction_isLettered()
payment = portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
default_causality_uid=context.getUid(),
default_payment_mode_uid=portal.portal_categories.payment_mode.payzen.getUid(),
default_payment_mode_uid=[
portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()],
)
assert payment is not None
assert payment.getSimulationState() == 'started'
assert payment.getPaymentMode() == 'payzen'
assert payment.PaymentTransaction_getPayzenId()[1] is None
if payment is not None:
payment_mode = payment.getPaymentMode()
if payment_mode == 'payzen' and payment.PaymentTransaction_getPayzenId()[1] is not None:
# The payment transaction will be cancelled by a proper alarm.
raise ValueError("Payment Transaction is waiting for External Payzen confirmation!")
elif payment_mode == 'wechat' and payment.PaymentTransaction_getWechatId()[1] is not None:
# The payment transaction will be cancelled by a proper alarm.
raise ValueError("Payment Transaction is waiting for External Wechat confirmation!")
# Should be safe now to fix everything
context.SaleInvoiceTransaction_resetPaymentMode()
payment.edit(payment_mode=None)
reversal_transaction = context.Base_createCloneDocument(batch_mode=1)
payment.cancel(
comment="Reversal sale invoice transaction created %s" % reversal_transaction.getRelativeUrl())
reversal_transaction.edit(
title="Reversal Transaction for %s" % context.getTitle(),
......@@ -53,4 +50,9 @@ for line in reversal_transaction.getMovementList():
reversal_transaction.confirm(comment="Automatic because of reversal creation")
reversal_transaction.stop(comment="Automatic because of reversal creation")
return reversal_transaction
if batch_mode:
return reversal_transaction
message = context.Base_translateString("Reversal Transaction created.")
return reversal_transaction.Base_redirect(
keep_items={'portal_status_message': message})
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>REQUEST=None</string> </value>
<value> <string>batch_mode=False</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createReversalPayzenTransaction</string> </value>
<value> <string>SaleInvoiceTransaction_createReversalSaleInvoiceTransaction</string> </value>
</item>
</dictionary>
</pickle>
......
if context.getPaymentMode() == "payzen":
reversal_payment = context.SaleInvoiceTransaction_createReversalPayzenTransaction()
elif context.getPaymentMode() == "wechat":
reversal_payment = context.SaleInvoiceTransaction_createReversalWechatTransaction()
else:
message = context.Base_translateString("The payment mode is unsupported.")
return context.Base_redirect(keep_items={'portal_status_message': message})
message = context.Base_translateString("Reversal Transaction created.")
return reversal_payment.Base_redirect(keep_items={'portal_status_message': message})
<?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>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createSlapOSReversalTransaction</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
payment_transaction = context.SaleInvoiceTransaction_getSlapOSPaymentRelatedValue()
if payment_transaction is not None:
return payment_transaction.getRelativeUrl()
portal = context.getPortalObject()
return portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
default_causality_uid=context.getUid(),
default_payment_mode_uid=[portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()],
)
<?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>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_getSlapOSPaymentRelatedValue</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getObject()
line_list = context.getMovementList(
portal_type=portal.getPortalAccountingMovementTypeList())
if not len(line_list):
# Ignore since lines to group don't exist yet
return False
source_list = [i.getRelativeUrl() for i in context.Base_getReceivableAccountList()]
for line in line_list:
if line.getSource() in source_list:
if line.hasGroupingReference():
return line.getGroupingReference()
......@@ -52,9 +52,17 @@
<key> <string>_params</string> </key>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createReversalWechatTransaction</string> </value>
<value> <string>SaleInvoiceTransaction_isLettered</string> </value>
</item>
</dictionary>
</pickle>
......
payment_mode = context.getPaymentMode()
entity = context.getDestinationSectionValue()
def wrapWithShadow(entity, invoice):
return entity.Entity_createPaymentTransaction([invoice])
payment = entity.Person_restrictMethodAsShadowUser(
shadow_document=entity,
callable_object=wrapWithShadow,
argument_list=[entity, context])
if web_site is None:
web_site = context.getWebSiteValue()
if payment_mode == "wechat":
return context.PaymentTransaction_redirectToManualWechatPayment(web_site=web_site)
return payment.PaymentTransaction_redirectToManualWechatPayment(web_site=web_site)
elif payment_mode == "payzen":
return context.PaymentTransaction_redirectToManualPayzenPayment(web_site=web_site)
return payment.PaymentTransaction_redirectToManualPayzenPayment(web_site=web_site)
raise ValueError("%s isn't an acceptable payment mode" % payment_mode)
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_redirectToManualSlapOSPayment</string> </value>
<value> <string>SaleInvoiceTransaction_redirectToManualSlapOSPayment</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -35,7 +35,11 @@
</item>
<item>
<key> <string>action</string> </key>
<value> <string>SaleInvoiceTransaction_createSlapOSReversalTransaction</string> </value>
<value> <string>SaleInvoiceTransaction_createReversalSaleInvoiceTransaction</string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
......
# -*- coding: utf-8 -*-
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2002-2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
......@@ -1411,14 +1432,14 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase
self.tic()
self.assertNotEqual(
'Not visited by PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped',
'Not visited by PaymentTransaction_cancelIfSaleInvoiceTransactionIsLettered',
payment_transaction.getTitle())
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsLettered')
def test_payment_is_draft_payzen(self):
self._test_payment_is_draft(payment_mode="payzen")
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsLettered')
def test_payment_is_draft_wechat(self):
self._test_payment_is_draft(payment_mode="wechat")
......@@ -1440,14 +1461,14 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase
self.tic()
self.assertNotEqual(
'Not visited by PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped',
'Not visited by PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered',
payment_transaction.getTitle())
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_stopped_payzen(self):
self._test_payment_is_stopped(payment_mode="payzen")
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_stopped_wechat(self):
self._test_payment_is_stopped(payment_mode="wechat")
......@@ -1469,14 +1490,13 @@ class TestSlapOSCancelSaleTnvoiceTransactionPaiedPaymentListAlarm(SlapOSTestCase
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped',
'Visited by PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered',
payment_transaction.getTitle())
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_started_payzen(self):
self._test_payment_is_started(payment_mode="payzen")
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionIsGrouped')
@simulateByTitlewMark('PaymentTransaction_cancelIfSaleInvoiceTransactionisLettered')
def test_payment_is_started_wechat(self):
self._test_payment_is_started(payment_mode="wechat")
self._test_payment_is_started(payment_mode="wechat")
\ No newline at end of file
......@@ -6,12 +6,6 @@
</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>testSlapOSAccountingAlarm</string> </value>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
# -*- coding: utf-8 -*-
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin
class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
class TestSlapOSEntityCreatePaymentMixin(SlapOSTestCaseMixin):
def sumReceivable(self, payment_transaction):
quantity = .0
......@@ -22,7 +36,7 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
def assertPayment(self, payment, invoice):
self.assertEqual(self.sumReceivable(invoice), payment\
.PaymentTransaction_getTotalPayablePrice())
self.assertEqual('confirmed', payment.getSimulationState())
self.assertEqual('started', payment.getSimulationState())
self.assertSameSet([], payment.checkConsistency())
self.assertSameSet([invoice], payment.getCausalityValueList())
self.assertSameSet([], payment.getCausalityRelatedValueList(
......@@ -63,19 +77,14 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
'destination/account_module/payable',
'source/account_module/receivable'])
def emptyBuild(self, **kw):
delivery_list = self._build(**kw)
self.assertSameSet([], delivery_list)
return delivery_list
def fullBuild(self, person, invoice_list):
payment = person.Entity_createPaymentTransaction(invoice_list)
self.assertNotEqual(None, payment)
return payment
def fullBuild(self, **kw):
delivery_list = self._build(**kw)
self.assertNotEqual([], delivery_list)
return delivery_list
def _build(self, **kw):
return self.portal.portal_orders.slapos_payment_transaction_builder.build(
**kw)
def resetPaymentTag(self, invoice):
payment_tag = "sale_invoice_transaction_create_payment_%s" % invoice.getUid()
invoice.REQUEST.set(payment_tag, None)
def _test(self):
person = self.portal.person_module.template_member\
......@@ -87,12 +96,8 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=invoice.getUid())
payment = self.fullBuild(person, [invoice])
self.tic()
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
self.assertPayment(payment, invoice)
def _test_twice(self):
......@@ -105,13 +110,13 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=invoice.getUid())
payment = self.fullBuild(person, [invoice])
self.assertPayment(payment, invoice)
self.tic()
self.emptyBuild(uid=invoice.getUid())
self.resetPaymentTag(invoice)
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
# Create twice, generate 2 payments
payment = self.fullBuild(person, [invoice])
self.assertPayment(payment, invoice)
def _test_twice_transaction(self):
......@@ -124,13 +129,9 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=invoice.getUid())
self.emptyBuild(uid=invoice.getUid())
payment = self.fullBuild(person, [invoice])
self.assertRaises(ValueError, person.Entity_createPaymentTransaction, [invoice])
self.tic()
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
self.assertPayment(payment, invoice)
def _test_twice_indexation(self):
......@@ -143,17 +144,18 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=invoice.getUid())
payment = self.fullBuild(person, [invoice])
self.commit()
# the payment transaction is immediately indexed
self.assertEqual(1, len(payment_list))
self.emptyBuild(uid=invoice.getUid())
self.tic()
# Request was over, so emulate start a new one
self.resetPaymentTag(invoice)
# Should we take into account that a payment is ongoing?
payment2 = self.fullBuild(person, [invoice])
payment = payment_list[0].getObject()
self.tic()
self.assertPayment(payment, invoice)
self.assertPayment(payment2, invoice)
def _test_cancelled_payment(self):
person = self.portal.person_module.template_member\
......@@ -165,18 +167,13 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=invoice.getUid())
payment_list[0].cancel()
payment = self.fullBuild(person, [invoice])
payment.cancel()
self.tic()
self.portal.REQUEST.set("sale_invoice_transaction_order_builder_%s" % invoice.getUid(), None)
self.resetPaymentTag(invoice)
payment_list = self.fullBuild(uid=invoice.getUid())
payment = self.fullBuild(person, [invoice])
self.tic()
self.emptyBuild(uid=invoice.getUid())
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
self.assertPayment(payment, invoice)
def _test_two_invoices(self):
......@@ -195,7 +192,8 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice_2.confirm()
invoice_2.stop()
self.tic()
payment_list = self.fullBuild(uid=[invoice_1.getUid(), invoice_2.getUid()])
payment_list = [self.fullBuild(person, [invoice_1]),
self.fullBuild(person, [invoice_2])]
self.tic()
self.assertEqual(2, len(payment_list))
......@@ -236,21 +234,17 @@ class TestSlapOSPaymentTransactionOrderBuilderMixin(SlapOSTestCaseMixin):
invoice.confirm()
invoice.stop()
self.tic()
payment_list = self.fullBuild(uid=[invoice.getUid()])
payment = self.fullBuild(person, [invoice])
self.tic()
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
self.assertPayment(payment, invoice)
class TestSlapOSPaymentTransactionOrderBuilder(TestSlapOSPaymentTransactionOrderBuilderMixin):
payment_mode = "payzen"
class TestSlapOSEntityCreatePayment(TestSlapOSEntityCreatePaymentMixin):
payment_mode = "wire_transfer"
test = TestSlapOSPaymentTransactionOrderBuilderMixin._test
test_twice = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice
test_twice_transaction = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice_transaction
test_twice_indexation = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice_indexation
test_cancelled_payment = TestSlapOSPaymentTransactionOrderBuilderMixin._test_cancelled_payment
test_two_invoices = TestSlapOSPaymentTransactionOrderBuilderMixin._test_two_invoices
test_two_lines = TestSlapOSPaymentTransactionOrderBuilderMixin._test_two_lines
test = TestSlapOSEntityCreatePaymentMixin._test
test_twice = TestSlapOSEntityCreatePaymentMixin._test_twice
test_twice_transaction = TestSlapOSEntityCreatePaymentMixin._test_twice_transaction
test_twice_indexation = TestSlapOSEntityCreatePaymentMixin._test_twice_indexation
test_cancelled_payment = TestSlapOSEntityCreatePaymentMixin._test_cancelled_payment
test_two_invoices = TestSlapOSEntityCreatePaymentMixin._test_two_invoices
test_two_lines = TestSlapOSEntityCreatePaymentMixin._test_two_lines
......@@ -6,15 +6,9 @@
</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>testSlapOSWechatBuilder</string> </value>
<value> <string>testSlapOSEntityCreatePayment</string> </value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -24,7 +18,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSWechatBuilder</string> </value>
<value> <string>test.erp5.testSlapOSEntityCreatePayment</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
......@@ -6,4 +6,5 @@ test.erp5.testSlapOSContractSkins
test.erp5.testSlapOSAccountingInteractionWorkflow
test.erp5.testSlapOSAccountingSkins
test.erp5.testSlapOSAccountingAlarm
test.erp5.testSlapOSContractAlarm
\ No newline at end of file
test.erp5.testSlapOSContractAlarm
test.erp5.testSlapOSEntityCreatePayment
\ No newline at end of file
# -*- coding: utf-8 -*-
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2019 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
......@@ -432,23 +446,6 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# trigger the CRM interaction
self.stepCallSlaposCrmCreateRegularisationRequestAlarm()
self.tic()
......@@ -560,18 +557,7 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
payment_list = invoice.getCausalityRelatedValueList(
portal_type='Payment Transaction')
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
causality_list = payment.getCausalityValueList()
self.assertSameSet([invoice], causality_list)
self.assertEqual('started', payment.getSimulationState())
self.assertEqual('draft', payment.getCausalityState())
self.assertEqual(-1 * payment.PaymentTransaction_getTotalPayablePrice(),
invoice.getTotalPrice())
self.assertEqual(0, len(payment_list))
def assertPersonDocumentCoverage(self, person):
self.login()
......@@ -666,28 +652,7 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
self.assertEqual(None, to_click_message)
@changeSkin('RJS')
def useWechatManually(self, web_site, user_id, is_email_expected=True):
person = self.portal.portal_catalog.getResultValue(
portal_type="Person",
user_id=user_id)
self.assertNotEqual(person, None)
self.assertInvoiceNotification(person, is_email_expected)
# If you are using live test, be aware that the call of the alarm can be
# not enough for the number of objects on the site.
document_id = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
destination_section_uid=person.getUid()
).getId()
web_site.accounting_module[document_id].\
PaymentTransaction_redirectToManualWechatPayment()
@changeSkin('RJS')
def usePayzenManually(self, web_site, user_id, is_email_expected=True):
def usePaymentManually(self, web_site, user_id, is_email_expected=True, subscription_request=None):
person = self.portal.portal_catalog.getResultValue(
portal_type="Person",
user_id=user_id)
......@@ -695,17 +660,27 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
self.assertNotEqual(person, None)
self.assertInvoiceNotification(person, is_email_expected)
# Pay to payzen...
# If you are using live test, be aware that the call of the alarm can be
# not enough for the number of objects on the site.
document_id = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
destination_section_uid=person.getUid()
).getId()
invoice_list = person.Entity_getOutstandingAmountList()
self.login()
if subscription_request is not None:
expected_causality = subscription_request.getRelativeUrl()
filtered_invoice_list = []
for invoice in invoice_list:
spl = invoice.getCausalityValue()
if spl is not None and spl.getCausality() == expected_causality:
filtered_invoice_list.append(invoice)
self.assertEqual(len(filtered_invoice_list), 1)
invoice_list = filtered_invoice_list
else:
self.assertEqual(len(invoice_list), 1)
self.login(user_id)
document_id = invoice_list[0].getId()
web_site.accounting_module[document_id].\
PaymentTransaction_redirectToManualPayzenPayment()
SaleInvoiceTransaction_redirectToManualSlapOSPayment()
self.tic()
def assertSubscriptionStopped(self, person):
self.login()
......
......@@ -567,13 +567,13 @@ class SlapOSTestCaseMixin(testSlapOSMixin):
reference="TESTTRANS-%s" % new_id,
)
def createSaleInvoiceTransaction(self):
def createSaleInvoiceTransaction(self, **kw):
new_id = self.generateNewId()
return self.portal.accounting_module.newContent(
portal_type='Sale Invoice Transaction',
title="Invoice %s" % new_id,
reference="TESTSIT-%s" % new_id,
)
**kw)
def createPayzenEvent(self):
return self.portal.system_event_module.newContent(
......@@ -585,16 +585,11 @@ class SlapOSTestCaseMixin(testSlapOSMixin):
portal_type='Wechat Event',
reference='PAY-%s' % self.generateNewId())
def createPayzenSaleInvoiceTransaction(self, destination_section=None, price=2, payment_mode="payzen"):
new_title = self.generateNewId()
new_reference = self.generateNewId()
def createStoppedSaleInvoiceTransaction(self, destination_section=None, price=2, payment_mode="payzen"):
new_source_reference = self.generateNewId()
new_destination_reference = self.generateNewId()
invoice = self.portal.accounting_module.newContent(
portal_type="Sale Invoice Transaction",
title=new_title,
invoice = self.createSaleInvoiceTransaction(
start_date=DateTime(),
reference=new_reference,
source_reference=new_source_reference,
destination_reference=new_destination_reference,
destination_section=destination_section,
......@@ -614,23 +609,8 @@ class SlapOSTestCaseMixin(testSlapOSMixin):
source="account_module/receivable",
quantity=-3,
)
payment = self.portal.accounting_module.newContent(
portal_type="Payment Transaction",
payment_mode=payment_mode,
causality_value=invoice,
destination_section=destination_section,
created_by_builder=1 # to prevent init script to create lines
)
self.portal.portal_workflow._jumpToStateFor(payment, 'started')
return invoice
def createWechatSaleInvoiceTransaction(self, destination_section=None, price=2):
return self.createPayzenSaleInvoiceTransaction(destination_section=destination_section,
price=price,
payment_mode='wechat')
def createRegularisationRequest(self):
new_id = self.generateNewId()
return self.portal.regularisation_request_module.newContent(
......
......@@ -6,12 +6,6 @@
</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>SlapOSTestCaseMixin</string> </value>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
portal = context.getPortalObject()
person_uid_list = []
for (_, brain) in enumerate(portal.portal_simulation.getInventoryList(
simulation_state=('stopped', 'delivered'),
parent_payment_mode_uid = [
portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()],
group_by_mirror_section=True,
portal_type=portal.getPortalAccountingMovementTypeList(),
node_uid=[x.uid for x in context.Base_getReceivableAccountList()],
grouping_reference=None)):
payment_request_uid = brain.payment_request_uid
if not payment_request_uid:
payment_request_uid = brain.getObject().getExplanationUid()
payment_request = portal.portal_catalog.getObject(uid=payment_request_uid)
section_uid = payment_request.getDestinationSectionUid(portal_type="Person")
if section_uid is not None:
person_uid_list.append(section_uid)
portal.portal_catalog.searchAndActivate(
portal_type="Person",
validation_state="validated",
destination_section_related__portal_type="Payment Transaction",
destination_section_related__simulation_state="started",
uid=person_uid_list,
method_id='Person_checkToCreateRegularisationRequest',
activate_kw={'tag': tag}
)
......
......@@ -28,12 +28,12 @@ outstanding_amount = person.Entity_statSlapOSOutstandingAmount()
# Amount to be ignored, as it comes from the first invoice generated
# after the subscription. We do not take it into account as no service
# was provided yet.
unpaid_payment_amount = 0
for payment in person.Person_getSubscriptionRequestFirstUnpaidPaymentList():
unpaid_payment_amount += payment.PaymentTransaction_getTotalPayablePrice()
unpaid_invoice_amount = 0
for invoice in person.Person_getSubscriptionRequestFirstUnpaidInvoiceList():
unpaid_invoice_amount += invoice.getTotalPrice()
# It can't be smaller, we are considernig all open invoices are from unpaid_payment_amount
if round(float(outstanding_amount), 2) == round(-float(unpaid_payment_amount), 2):
if round(float(outstanding_amount), 2) == round(float(unpaid_invoice_amount), 2):
return ticket, None
if int(outstanding_amount) > 0:
......
......@@ -12,8 +12,8 @@ subscription_request_list = portal.portal_catalog(
creation_date=Query(creation_date=addToDate(DateTime(), to_add={'day': -20}), range="min"))
for subscription_request in subscription_request_list:
first_period_payment = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
if first_period_payment is not None and first_period_payment.getSimulationState() == "started":
unpaid_list.append(first_period_payment)
first_invoice = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
if first_invoice is not None and not first_invoice.SaleInvoiceTransaction_isLettered():
unpaid_list.append(first_invoice)
return unpaid_list
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_getSubscriptionRequestFirstUnpaidPaymentList</string> </value>
<value> <string>Person_getSubscriptionRequestFirstUnpaidInvoiceList</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -13,12 +13,12 @@ outstanding_amount = person.Entity_statSlapOSOutstandingAmount()
# Amount to be ignored, as it comes from the first invoice generated
# after the subscription. We do not take it into account as no service
# was provided yet.
unpaid_payment_amount = 0
for payment in person.Person_getSubscriptionRequestFirstUnpaidPaymentList():
unpaid_payment_amount += payment.PaymentTransaction_getTotalPayablePrice()
unpaid_invoice_amount = 0
for invoice in person.Person_getSubscriptionRequestFirstUnpaidInvoiceList():
unpaid_invoice_amount += invoice.getTotalPrice()
# It can't be smaller, we are considernig all open invoices are from unpaid_payment_amount
if round(float(outstanding_amount), 2) == round(-float(unpaid_payment_amount), 2):
if round(float(outstanding_amount), 2) == round(float(unpaid_invoice_amount), 2):
context.invalidate(comment="Automatically disabled as balance is %s" % outstanding_amount)
return
......
......@@ -27,6 +27,52 @@ from DateTime import DateTime
class TestSlapOSCRMCreateRegularisationRequest(SlapOSTestCaseMixin):
def createFinalInvoice(self, person):
template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredDefaultPrePaymentSubscriptionInvoiceTemplate())
current_invoice = template.Base_createCloneDocument(batch_mode=1)
current_invoice.edit(
destination_value=person,
destination_section_value=person,
destination_decision_value=person,
start_date=DateTime('2019/10/20'),
stop_date=DateTime('2019/10/20'),
title='Fake Invoice for Demo User Functional',
price_currency="currency_module/EUR",
reference='1')
cell = current_invoice["1"]["movement_0"]
cell.edit(quantity=1)
cell.setPrice(1)
current_invoice.plan()
current_invoice.confirm()
current_invoice.startBuilding()
current_invoice.reindexObject()
current_invoice.stop()
self.tic()
current_invoice.Delivery_manageBuildingCalculatingDelivery()
self.tic()
applied_rule = current_invoice.getCausalityRelated(portal_type="Applied Rule")
for sm in self.portal.portal_catalog(portal_type='Simulation Movement',
simulation_state=['draft', 'planned', None],
left_join_list=['delivery_uid'],
delivery_uid=None,
path="%%%s%%" % applied_rule):
if sm.getDelivery() is not None:
continue
root_applied_rule = sm.getRootAppliedRule()
root_applied_rule_path = root_applied_rule.getPath()
sm.getCausalityValue(portal_type='Business Link').build(
path='%s/%%' % root_applied_rule_path)
return current_invoice
def test_alarm_expected_person(self):
new_id = self.generateNewId()
person = self.portal.person_module.newContent(
......@@ -35,14 +81,7 @@ class TestSlapOSCRMCreateRegularisationRequest(SlapOSTestCaseMixin):
)
person.validate()
payment = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Payment Transaction for TestSlapOSCRMCreateRegularisationRequest person %s" % new_id,
destination_section=person.getRelativeUrl(),
start_date=DateTime()
)
payment.confirm()
payment.start()
self.createFinalInvoice(person)
self.tic()
alarm = self.portal.portal_alarms.\
......@@ -56,17 +95,9 @@ class TestSlapOSCRMCreateRegularisationRequest(SlapOSTestCaseMixin):
title="Test person %s" % new_id
)
person.validate()
self.createFinalInvoice(person)
person.invalidate()
payment = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Payment Transaction for TestSlapOSCRMCreateRegularisationRequest person %s" % new_id,
destination_section=person.getRelativeUrl(),
start_date=DateTime()
)
payment.confirm()
payment.start()
self.tic()
alarm = self.portal.portal_alarms.\
slapos_crm_create_regularisation_request
......@@ -79,23 +110,31 @@ class TestSlapOSCRMCreateRegularisationRequest(SlapOSTestCaseMixin):
title="Test person %s" % new_id
)
person.validate()
current_invoice = self.createFinalInvoice(person)
payment_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredDefaultPrePaymentTemplate())
payment = payment_template.Base_createCloneDocument(batch_mode=1)
for line in payment.contentValues():
if line.getSource() == "account_module/payment_to_encash":
line.setQuantity(-1)
elif line.getSource() == "account_module/receivable":
line.setQuantity(1)
payment = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Payment Transaction for TestSlapOSCRMCreateRegularisationRequest person %s" % new_id,
destination_section=person.getRelativeUrl(),
start_date=DateTime()
)
payment.confirm()
payment.start()
payment.setCausalityValue(current_invoice)
payment.setDestinationSectionValue(person)
payment.stop()
self.tic()
self.tic()
alarm = self.portal.portal_alarms.\
slapos_crm_create_regularisation_request
self._test_alarm_not_visited(alarm, person, "Person_checkToCreateRegularisationRequest")
def test_alarm_payment_confirmed(self):
def test_alarm_payment_started(self):
new_id = self.generateNewId()
person = self.portal.person_module.newContent(
portal_type='Person',
......@@ -103,18 +142,26 @@ class TestSlapOSCRMCreateRegularisationRequest(SlapOSTestCaseMixin):
)
person.validate()
payment = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Payment Transaction for TestSlapOSCRMCreateRegularisationRequest person %s" % new_id,
destination_section=person.getRelativeUrl(),
start_date=DateTime()
)
current_invoice = self.createFinalInvoice(person)
payment_template = self.portal.restrictedTraverse(
self.portal.portal_preferences.getPreferredDefaultPrePaymentTemplate())
payment = payment_template.Base_createCloneDocument(batch_mode=1)
for line in payment.contentValues():
if line.getSource() == "account_module/payment_to_encash":
line.setQuantity(-1)
elif line.getSource() == "account_module/receivable":
line.setQuantity(1)
payment.confirm()
payment.start()
payment.setCausalityValue(current_invoice)
payment.setDestinationSectionValue(person)
self.tic()
alarm = self.portal.portal_alarms.\
slapos_crm_create_regularisation_request
self._test_alarm_not_visited(alarm, person, "Person_checkToCreateRegularisationRequest")
self._test_alarm(alarm, person, "Person_checkToCreateRegularisationRequest")
class TestSlapOSCrmInvalidateSuspendedRegularisationRequest(SlapOSTestCaseMixinWithAbort):
......
......@@ -149,7 +149,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_crm_monitoring/Event_checkCustomerAsSourceOrDestinationConsistency',
'slapos_crm_monitoring/SupportRequest_checkCausalitySourceDestinationConsistency',
'slapos_crm_monitoring/SupportRequest_getLastEvent',
'slapos_crm/Person_getSubscriptionRequestFirstUnpaidPaymentList',
'slapos_crm/Person_getSubscriptionRequestFirstUnpaidInvoiceList',
'slapos_crm/RegularisationRequest_afterClone',
'slapos_crm/RegularisationRequest_getResourceItemList',
'slapos_crm/RegularisationRequest_init',
......
# -*- coding: utf-8 -*-
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
......@@ -237,23 +251,6 @@ class TestSlapOSDefaultScenario(DefaultScenarioMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# trigger the CRM interaction
self.stepCallSlaposCrmCreateRegularisationRequestAlarm()
self.tic()
......@@ -266,10 +263,10 @@ class TestSlapOSDefaultScenario(DefaultScenarioMixin):
self.assertPersonDocumentCoverage(person)
self.login(public_person.getUserId())
self.usePayzenManually(self.web_site, public_person.getUserId())
self.usePaymentManually(self.web_site, public_person.getUserId())
self.login(friend_person.getUserId())
self.usePayzenManually(self.web_site, friend_person.getUserId())
self.usePaymentManually(self.web_site, friend_person.getUserId())
class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
......@@ -283,6 +280,7 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
default_source_project_uid=person.getUid()
)
self.assertNotEqual(ticket, None)
event = self.portal.portal_catalog.getResultValue(
portal_type='Mail Message',
default_resource_uid=self.portal.service_module[service_id].getUid(),
......@@ -344,18 +342,7 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
payment_list = invoice.getCausalityRelatedValueList(
portal_type='Payment Transaction')
self.assertEqual(1, len(payment_list))
payment = payment_list[0].getObject()
causality_list = payment.getCausalityValueList()
self.assertSameSet([invoice], causality_list)
self.assertEqual('cancelled', payment.getSimulationState())
self.assertEqual('draft', payment.getCausalityState())
self.assertEqual(-1 * payment.PaymentTransaction_getTotalPayablePrice(),
invoice.getTotalPrice())
self.assertEqual(0, len(payment_list))
# Check reverse invoice
reverse_invoice_list = invoice.getCausalityRelatedValueList(
......@@ -468,23 +455,6 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# create the regularisation request
self.stepCallSlaposCrmCreateRegularisationRequestAlarm()
self.tic()
......@@ -525,13 +495,10 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
# Manually cancel the users invoice
payment = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
destination_section_uid=person.getUid(),
simulation_state="started")
invoice = payment.getCausalityValue(portal_type="Sale Invoice Transaction")
invoice.SaleInvoiceTransaction_createReversalPayzenTransaction()
invoice_list = person.Entity_getOutstandingAmountList()
self.assertEqual(len(invoice_list), 1)
sale_transaction_invoice = invoice_list[0].getObject()
sale_transaction_invoice.SaleInvoiceTransaction_createReversalSaleInvoiceTransaction(batch_mode=1)
self.tic()
......@@ -602,22 +569,5 @@ class TestSlapOSDefaultCRMEscalation(DefaultScenarioMixin):
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# check final document state
self.assertPersonDocumentCoverage(person)
......@@ -6,12 +6,6 @@
</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>testSlapOSERP5DefaultScenario</string> </value>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
......@@ -53,6 +53,7 @@
}
if (1 || (result.data.rows[i].hasOwnProperty("id"))) {
value = result.data.rows[i].value.AccountingTransaction_getPaymentStateAsHateoas;
value.jio_key = result.data.rows[i].id;
result.data.rows[i].value.AccountingTransaction_getPaymentStateAsHateoas = {
field_gadget_param : {
css_class: "",
......
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1667590788.38</float>
<float>1671074456.17</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -53,6 +53,7 @@
}
if (1 || (result.data.rows[i].hasOwnProperty("id"))) {
value = result.data.rows[i].value.AccountingTransaction_getPaymentStateAsHateoas;
value.jio_key = result.data.rows[i].id;
result.data.rows[i].value.AccountingTransaction_getPaymentStateAsHateoas = {
field_gadget_param : {
css_class: "",
......
......@@ -236,7 +236,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1004.6631.33059.11776</string> </value>
<value> <string>1004.12379.1853.8140</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1667930581.61</float>
<float>1671070228.44</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -160,7 +160,7 @@
"title": result[1][5],
"default": {
state: gadget.state.doc.payment_state,
payment_transaction: gadget.state.doc.payment_transaction
jio_key: gadget.state.jio_key
},
"css_class": "",
"required": 1,
......
......@@ -236,7 +236,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>999.2068.62564.17</string> </value>
<value> <string>1004.12196.28653.10734</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1667919626.06</float>
<float>1671070129.29</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -14,16 +14,15 @@
link;
return gadget.getTranslationDict(['Pay Now'])
.push(function (translation_dict) {
if ((gadget.state.payment_transaction !== null) &&
(gadget.state.payment_transaction !== undefined)) {
if (gadget.state.payment_state === 'Pay Now') {
link = domsugar("li", {},
[
domsugar("a", {
class: "ui-btn ui-first-child ui-btn-icon-center",
text: translation_dict["Pay Now"],
href: gadget.state.hateoas_url +
gadget.state.payment_transaction +
"/PaymentTransaction_redirectToManualSlapOSPayment"
gadget.state.jio_key +
"/SaleInvoiceTransaction_redirectToManualSlapOSPayment"
})
]);
} else {
......@@ -41,7 +40,7 @@
return gadget.getSetting("hateoas_url")
.push(function (hateoas_url) {
return gadget.changeState({
payment_transaction: options.value.payment_transaction,
jio_key: options.value.jio_key,
payment_state: options.value.state,
hateoas_url: hateoas_url
});
......
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1667929052.34</float>
<float>1671070046.11</float>
<string>UTC</string>
</tuple>
</state>
......
state = context.AccountingTransaction_getPaymentState()
payment_transaction = None
payment_mode = None
if state == "Pay Now":
payment_transaction_value = context.SaleInvoiceTransaction_getSlapOSPaymentRelatedValue()
payment_transaction = payment_transaction_value.getRelativeUrl()
payment_mode = payment_transaction_value.getPaymentMode()
payment_mode = context.getPaymentMode() # ???
return {"state": context.Base_translateString(state),
"payment_mode": payment_mode,
"payment_transaction": payment_transaction}
"payment_mode": payment_mode}
......@@ -100,7 +100,6 @@
<string>my_resource_title</string>
<string>my_payment_transaction_external_id</string>
<string>my_payment_state</string>
<string>my_payment_transaction</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_payment_transaction</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Payment Transaction</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: context.SaleInvoiceTransaction_getSlapOSPaymentRelated()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -76,12 +76,12 @@
</tr>
<tr>
<td>waitForElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
......@@ -165,12 +165,12 @@
</tr>
<tr>
<td>waitForElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
......@@ -284,12 +284,12 @@
</tr>
<tr>
<td>waitForElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'PaymentTransaction_redirectToManualSlapOSPayment')]</td>
<td> //div[contains(@data-gadget-url, 'gadget_slapos_invoice_state.html')]//a[contains(@href, 'SaleInvoiceTransaction_redirectToManualSlapOSPayment')]</td>
<td></td>
</tr>
<tr>
......
......@@ -74,5 +74,13 @@ if not unpaid:
"SimulationMovement_buildSlapOS",
"SaleInvoiceTransaction_forceBuildSlapOSAccountingLineList")
).SaleInvoiceTransaction_setFakeGroupingReference()
payment.activate(
after_method_id=(
"immediateReindexObject",
"_updateSimulation",
"Delivery_manageBuildingCalculatingDelivery",
"SimulationMovement_buildSlapOS",
"SaleInvoiceTransaction_forceBuildSlapOSAccountingLineList")
).SaleInvoiceTransaction_setFakeGroupingReference()
return 'Done.'
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_updatePayzenConfirmedPaymentTransaction</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_payzen_update_confirmed_payment</string> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute_frequency</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1288051200.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>sense_method_id</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Handles confirmed Payment Transactions with PayZen interface</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_triggerPaymentTransactionOrderBuilder</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_trigger_payment_transaction_order_builder</string> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute_frequency</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>548812800.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>sense_method_id</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Trigger Payment Transaction Order Builder</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category Movement Group" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>collect_order_group/delivery</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>category_movement_group_on_delivery</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category Movement Group</string> </value>
</item>
<item>
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>causality</string>
<string>destination_payment</string>
<string>destination_section</string>
<string>price_currency</string>
<string>resource</string>
<string>source_payment</string>
<string>source_section</string>
<string>specialise</string>
<string>payment_mode</string>
</tuple>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category Movement Group" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>collect_order_group/line</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>category_movement_group_on_line</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category Movement Group</string> </value>
</item>
<item>
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>destination</string>
<string>source</string>
</tuple>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Movement Group" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>collect_order_group/line</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>property_movement_group_on_line</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value> <int>2</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Movement Group</string> </value>
</item>
<item>
<key> <string>tested_property</string> </key>
<value>
<tuple>
<string>start_date</string>
<string>stop_date</string>
</tuple>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>property_movement_group_on_line</string> </value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
context.getPortalObject().portal_orders.slapos_payment_transaction_builder.build(
activate_kw={'tag': tag}
)
context.activate(after_tag=tag).getId()
<?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>tag, fixit, params</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_triggerPaymentTransactionOrderBuilder</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
portal.portal_catalog.searchAndActivate(
portal_type="Payment Transaction",
simulation_state=["confirmed"],
causality_state=["draft"],
payment_mode_uid=portal.portal_categories.payment_mode.payzen.getUid(),
method_id='PaymentTransaction_startPayzenPayment',
packet_size=1, # just one to minimise errors
activate_kw={'tag': tag}
)
context.activate(after_tag=tag).getId()
<?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>tag, fixit, params</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_updatePayzenConfirmedPaymentTransaction</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
newTempSimulationMovement = portal.portal_trash.newContent
select_dict = {
'causality_payment_transaction_related_uid': None,
'causality_subscription_request_related_uid': None,
}
select_kw = kwargs.copy()
select_kw.pop('portal_type', None)
select_kw.pop('delivery_relative_url_list', None)
select_kw.update(
portal_type='Sale Invoice Transaction',
simulation_state='stopped',
default_payment_mode_uid=(portal.portal_categories.payment_mode.payzen.getUid(),
portal.portal_categories.payment_mode.wechat.getUid()),
limit=10, # do only some in one shot
select_dict=select_dict,
left_join_list=select_dict.keys(),
causality_payment_transaction_related_uid=None,
causality_subscription_request_related_uid=None
)
default_source_uid=portal.restrictedTraverse('account_module/receivable').getUid()
movement_list = []
_id = 1
for invoice in portal.portal_catalog(**select_kw):
invoice.getObject().serialize() # in order to avoid selection on concurrent transactions
payment_tag = "sale_invoice_transaction_order_builder_%s" % invoice.getObject().getUid()
if context.REQUEST.get(payment_tag, None) is not None or \
context.portal_activities.countMessageWithTag(payment_tag) > 0:
# Invoice was selected before the payment be indexed or it was already created on this transaction
# so skip the invoice for now.
continue
quantity = 0.
for movement in invoice.searchFolder(portal_type='Sale Invoice Transaction Line',
default_source_uid=default_source_uid):
quantity += movement.getQuantity()
temp_movement_kw = dict(
portal_type="Simulation Movement",
causality=invoice.getRelativeUrl(),
source_section=invoice.getSourceSection(),
destination_section=invoice.getDestinationSection(),
resource=invoice.getResource(),
price_currency=invoice.getResource(),
start_date=invoice.getStartDate(),
stop_date=invoice.getStopDate(),
specialise=invoice.getSpecialise(),
payment_mode=invoice.getPaymentMode(),
source_payment='%s/bank_account' % invoice.getSourceSection(), # the other place defnied: business process
)
temp_movement_rec = newTempSimulationMovement(
temp_object=True, id=str(_id),
quantity=-1 * quantity,
source='account_module/receivable',
destination='account_module/payable',
**temp_movement_kw
)
_id += 1
temp_movement_bank = newTempSimulationMovement(
temp_object=True, id=str(_id),
quantity=1 * quantity,
source='account_module/payment_to_encash',
destination='account_module/payment_to_encash',
**temp_movement_kw
)
_id += 1
movement_list.extend([temp_movement_rec, temp_movement_bank])
return movement_list
<?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>*args, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>OrderBuilder_generateUnrelatedInvoiceList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from DateTime import DateTime
state = context.getSimulationState()
transaction_amount = int(round((context.PaymentTransaction_getTotalPayablePrice() * -100), 2))
if (state != 'confirmed') or (context.getPaymentMode() != 'payzen') or (transaction_amount == 0):
if (transaction_amount == 0):
invoice = context.getCausalityValue(portal_types="Sale Invoice Transaction")
if invoice is not None and round(invoice.getTotalPrice(), 2) == 0:
context.edit(payment_mode="wire_transfer")
return
else:
# Request manual payment
context.start(comment='Requested manual payment')
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort
from Products.ERP5Type.tests.utils import createZODBPythonScript
from DateTime import DateTime
class TestSlapOSPayzenUpdateConfirmedPayment(SlapOSTestCaseMixinWithAbort):
def _simulatePaymentTransaction_startPayzenPayment(self):
script_name = 'PaymentTransaction_startPayzenPayment'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PaymentTransaction_startPayzenPayment') """ )
self.commit()
def _dropPaymentTransaction_startPayzenPayment(self):
script_name = 'PaymentTransaction_startPayzenPayment'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
self.commit()
def test_alarm_confirmed_draft_payzen(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.tic()
self._simulatePaymentTransaction_startPayzenPayment()
try:
self.portal.portal_alarms.slapos_payzen_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startPayzenPayment()
self.tic()
self.assertEqual(
'Visited by PaymentTransaction_startPayzenPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_confirmed(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
self.tic()
self._simulatePaymentTransaction_startPayzenPayment()
try:
self.portal.portal_alarms.slapos_payzen_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startPayzenPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startPayzenPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_draft(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.portal.portal_workflow._jumpToStateFor(transaction, 'solved')
self.tic()
self._simulatePaymentTransaction_startPayzenPayment()
try:
self.portal.portal_alarms.slapos_payzen_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startPayzenPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startPayzenPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_payzen(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.tic()
self._simulatePaymentTransaction_startPayzenPayment()
try:
self.portal.portal_alarms.slapos_payzen_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startPayzenPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startPayzenPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def _simulatePaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""return 1""")
self.commit()
def _simulatePaymentTransaction_getZeroTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""return 0""")
self.commit()
def _dropPaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
self.commit()
def test_not_confirmed_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startPayzenPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_not_payzen_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startPayzenPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_zero_amount_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getZeroTotalPayablePrice()
try:
transaction.PaymentTransaction_startPayzenPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_expected_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="payzen",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startPayzenPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), 'started')
class TestSlapOSPayzenUpdateStartedPayment(SlapOSTestCaseMixinWithAbort):
def test_not_started_payment(self):
......
......@@ -6,12 +6,6 @@
</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>testSlapOSPayzenAlarm</string> </value>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from erp5.component.test.testSlapOSEntityCreatePayment import TestSlapOSEntityCreatePaymentMixin
class TestSlapOSEntityCreatePayment(TestSlapOSEntityCreatePaymentMixin):
payment_mode = "payzen"
test = TestSlapOSEntityCreatePaymentMixin._test
test_twice = TestSlapOSEntityCreatePaymentMixin._test_twice
test_twice_transaction = TestSlapOSEntityCreatePaymentMixin._test_twice_transaction
test_twice_indexation = TestSlapOSEntityCreatePaymentMixin._test_twice_indexation
test_cancelled_payment = TestSlapOSEntityCreatePaymentMixin._test_cancelled_payment
test_two_invoices = TestSlapOSEntityCreatePaymentMixin._test_two_invoices
test_two_lines = TestSlapOSEntityCreatePaymentMixin._test_two_lines
......@@ -7,14 +7,14 @@
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
<key> <string>default_reference</string> </key>
<value> <string>testSlapOSPayzenEntityCreatePayment</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSlapOSPayzenBuilder</string> </value>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -24,7 +24,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSPayzenBuilder</string> </value>
<value> <string>test.erp5.testSlapOSPayzenEntityCreatePayment</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -55,28 +55,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +74,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +83,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
accounting_module/template_sale_invoice_transaction
accounting_module/template_sale_invoice_transaction/**
portal_alarms/slapos_payzen_update_confirmed_payment
portal_alarms/slapos_payzen_update_started_payment
portal_alarms/slapos_trigger_payment_transaction_order_builder
portal_integrations/slapos_payzen_test_integration
portal_integrations/slapos_payzen_test_integration/Causality
portal_integrations/slapos_payzen_test_integration/Resource
portal_integrations/slapos_payzen_test_integration/Resource/**
portal_integrations/slapos_payzen_test_integration/SourceProject
portal_orders/slapos_payment_transaction_builder
portal_orders/slapos_payment_transaction_builder/**
portal_secure_payments/slapos_payzen_test
portal_secure_payments/slapos_payzen_test/**
sale_trade_condition_module/payzen_sale_trade_condition
......
test.erp5.testSlapOSPayzenBuilder
test.erp5.testSlapOSPayzenEntityCreatePayment
test.erp5.testSlapOSPayzenSkins
test.erp5.testSlapOSPayzenAlarm
test.erp5.testSlapOSPayzenWorkflow
\ No newline at end of file
......@@ -25,7 +25,7 @@ notification_mapping_dict = {
'name': recipient.getTitle(),
'subscription_title': context.getTitle(),
# Possible more actions goes here
'payment_relative_relative_url': payment.getRelativeUrl()}
'payment_relative_relative_url': invoice.getRelativeUrl()}
# Preserve HTML else convert to text
if notification_message.getContentType() == "text/html":
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>payment</string> </value>
<value> <string>invoice</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -32,19 +32,17 @@ if instance_tree is not None:
context.start(comment=comment)
context.stop(comment=comment)
first_period_payment = context.SubscriptionRequest_verifyPaymentBalanceIsReady()
if not first_period_payment:
# Payment isn't available for the user
invoice = context.SubscriptionRequest_verifyPaymentBalanceIsReady()
if not invoice:
# Invoice isn't available for the user to Pay
return "Skipped (Payment isn't ready)"
if not context.SubscriptionRequest_verifyInstanceIsAllocated():
# Only continue if instance is ready
return "Skipped (Instance isn't ready)"
invoice = first_period_payment.getCausalityValue()
# Link to be sent is the invoice one
if context.SubscriptionRequest_notifyPaymentIsReady(payment=invoice):
if context.SubscriptionRequest_notifyPaymentIsReady(invoice):
context.confirm(comment="Payment is ready for the user")
return "Payment is ready for the user"
......
portal = context.getPortalObject()
payment = context.SubscriptionRequest_verifyPaymentBalanceIsReady()
invoice = context.SubscriptionRequest_verifyPaymentBalanceIsReady()
if payment is not None:
if payment.getSimulationState() in ['stopped', 'delivered']:
# Payment Transaction is payed
if invoice is not None:
if invoice.SaleInvoiceTransaction_isLettered():
# Invoice is payed
return True
person = context.getDestinationSectionValue()
......@@ -18,5 +18,5 @@ if payment is not None:
not (person.Entity_statSlapOSOutstandingAmount() > 0)):
return True
# Payment Transaction isn't payed
# Invoice isn't payed
return False
......@@ -25,12 +25,10 @@ for packing_list in portal.portal_catalog(
):
for invoice in packing_list.getCausalityRelatedValueList(
portal_type="Sale Invoice Transaction"):
for payment in invoice.getCausalityRelatedValueList(
portal_type=["Payment Transaction", "Sale Invoice Transaction"]):
if payment.getSimulationState() in ["stopped", "delivered", "started"]:
# Invoice is already paied so we just move foward
if not payment.checkConsistency():
return payment
# XXX How to check if the invoice is ready to be payed
if invoice.getSimulationState() in ['stopped', 'delivered'] and \
not invoice.checkConsistency():
return invoice
# Payment isn't ready
return
# Invoice inst ready to be payed
......@@ -170,20 +170,6 @@ class TestSlapOSSubscriptionCancellationScenario(TestSlapOSSubscriptionScenarioM
self.assertEqual(invoice.getSimulationState(), "stopped")
self.assertEqual(invoice.getCausalityState(), "solved")
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm, this is mostly for
# live tests with date on the development sites.
if not len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
self.invokeBasicSimulationAlarmList()
# Call alarm to check payment and invoice and move foward to planned.
......
......@@ -6,12 +6,6 @@
</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>testSlapOSSubscriptionCancellationScenario</string> </value>
......@@ -61,28 +55,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -95,7 +74,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -104,7 +83,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
......@@ -338,9 +338,9 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
notification_message="subscription_request-payment-is-ready"):
self.checkSubscriptionRequest(subscription_request, email,
subscription_condition)
payment = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
self.assertNotEqual(payment, None)
self.assertEqual(payment.getSimulationState(), 'started')
invoice = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
self.assertNotEqual(invoice, None)
self.assertEqual(invoice.getSimulationState(), 'stopped')
# Assert instance is allocated and without error
self.assertEqual(True,
......@@ -375,17 +375,13 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
person = subscription_request.getDestinationSectionValue()
self.login(person.getUserId())
payment = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started")
sale_transaction_invoice = payment.getCausalityValue(
portal_type="Sale Invoice Transaction"
)
invoice_list = person.Entity_getOutstandingAmountList()
self.assertEqual(len(invoice_list), 1)
sale_transaction_invoice = invoice_list[0].getObject()
self.logout()
self.login()
sale_transaction_invoice.SaleInvoiceTransaction_createReversalPayzenTransaction()
sale_transaction_invoice.SaleInvoiceTransaction_createReversalSaleInvoiceTransaction(batch_mode=1)
def checkSubscriptionRequestPayment(self, subscription_request, authAmount):
......@@ -447,8 +443,11 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
quantity = subscription_request.getQuantity()
self.login(person.getUserId())
self.usePayzenManually(self.web_site, person.getUserId(), is_email_expected=False)
self.usePaymentManually(
self.web_site,
person.getUserId(),
is_email_expected=False,
subscription_request=subscription_request)
self.logout()
self.login()
......@@ -485,7 +484,10 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
quantity = subscription_request.getQuantity()
self.login(person.getUserId())
self.usePayzenManually(self.web_site, person.getUserId())
self.usePaymentManually(
self.web_site,
person.getUserId(),
subscription_request=subscription_request)
self.logout()
self.login()
......@@ -524,7 +526,11 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
self.portal.portal_secure_payments.slapos_wechat_test.setWechatMode("UNITTEST")
self.login(person.getUserId())
self.useWechatManually(self.web_site, person.getUserId(), is_email_expected=False)
self.usePaymentManually(
self.web_site,
person.getUserId(),
is_email_expected=False,
subscription_request=subscription_request)
self.logout()
self.login()
......@@ -556,7 +562,10 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
self.portal.portal_secure_payments.slapos_wechat_test.setWechatMode("UNITTEST")
self.login(person.getUserId())
self.useWechatManually(self.web_site, person.getUserId())
self.usePaymentManually(
self.web_site,
person.getUserId(),
subscription_request=subscription_request)
self.logout()
self.login()
......@@ -882,23 +891,6 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if not len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# trigger the CRM interaction
self.stepCallSlaposCrmCreateRegularisationRequestAlarm()
self.tic()
......@@ -989,9 +981,8 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
"TestSubscriptionSkins Notification Message %s %s" % (
subscription_request.getLanguage(), notification_message),
mail_message.getTitle())
payment = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
self.assertEqual(payment.getSimulationState(), 'started')
invoice = payment.getCausalityValue()
invoice = subscription_request.SubscriptionRequest_verifyPaymentBalanceIsReady()
self.assertEqual(invoice.getSimulationState(), 'stopped')
self.assertTrue(invoice.getRelativeUrl() in \
mail_message.getTextContent())
self.assertTrue(subscription_request.getDestinationSectionTitle() in \
......@@ -1407,23 +1398,6 @@ return dict(vads_url_already_registered="%s/already_registered" % (payment_trans
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
builder = self.portal.portal_orders.slapos_payment_transaction_builder
for _ in range(500):
# build the aggregated payment
self.stepCallSlaposTriggerPaymentTransactionOrderBuilderAlarm()
self.tic()
# If there is something unbuild recall alarm.
if not len(builder.OrderBuilder_generateUnrelatedInvoiceList()):
break
# start the payzen payment
self.stepCallSlaposPayzenUpdateConfirmedPaymentAlarm()
self.tic()
# stabilise the payment deliveries and expand them
self.stepCallSlaposManageBuildingCalculatingDeliveryAlarm()
self.tic()
# trigger the CRM interaction
self.stepCallSlaposCrmCreateRegularisationRequestAlarm()
self.tic()
......
......@@ -6,12 +6,6 @@
</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>testSlapOSSubscriptionScenario</string> </value>
......@@ -55,28 +49,13 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</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>
......@@ -89,7 +68,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -98,7 +77,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_updateWechatConfirmedPaymentTransaction</string> </value>
</item>
<item>
<key> <string>automatic_solve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_wechat_update_confirmed_payment</string> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute_frequency</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1288051200.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>sense_method_id</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Handles confirmed Payment Transactions with Wechat interface</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
portal.portal_catalog.searchAndActivate(
portal_type="Payment Transaction",
simulation_state=["confirmed"],
causality_state=["draft"],
payment_mode_uid=portal.portal_categories.payment_mode.wechat.getUid(),
method_id='PaymentTransaction_startWechatPayment',
packet_size=1, # just one to minimise errors
activate_kw={'tag': tag}
)
context.activate(after_tag=tag).getId()
<?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>tag, fixit, params</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_updateWechatConfirmedPaymentTransaction</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from DateTime import DateTime
state = context.getSimulationState()
transaction_amount = int(round((context.PaymentTransaction_getTotalPayablePrice() * -100), 2))
if (state != 'confirmed') or (context.getPaymentMode() != 'wechat') or (transaction_amount == 0):
if (transaction_amount == 0):
invoice = context.getCausalityValue(portal_types="Sale Invoice Transaction")
if invoice is not None and round(invoice.getTotalPrice(), 2) == 0:
context.edit(payment_mode="wire_transfer")
return
else:
# Request manual payment
context.start(comment='Requested manual payment')
""" Create a reversal transaction from current wechat transaction. """
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
# Check that we are in state that we are waiting for user manual payment
assert context.getPortalType() == 'Sale Invoice Transaction'
assert context.getPaymentMode() == 'wechat'
assert context.getSimulationState() == 'stopped'
assert context.getTotalPrice() != 0
assert context.getSpecialise() == "sale_trade_condition_module/slapos_aggregated_trade_condition"
paid = True
for line in context.getMovementList(portal.getPortalAccountingMovementTypeList()):
node_value = line.getSourceValue(portal_type='Account')
if node_value.getAccountType() == 'asset/receivable':
if not line.hasGroupingReference():
paid = False
break
assert not paid
payment = portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
default_causality_uid=context.getUid(),
default_payment_mode_uid=portal.portal_categories.payment_mode.wechat.getUid(),
)
assert payment is not None
assert payment.getSimulationState() == 'started'
assert payment.getPaymentMode() == 'wechat'
assert payment.PaymentTransaction_getWechatId()[1] is None
# Should be safe now to fix everything
context.SaleInvoiceTransaction_resetPaymentMode()
payment.edit(payment_mode=None)
reversal_transaction = context.Base_createCloneDocument(batch_mode=1)
payment.cancel(
comment="Reversal sale invoice transaction created %s" % reversal_transaction.getRelativeUrl())
reversal_transaction.edit(
title="Reversal Transaction for %s" % context.getTitle(),
causality_value=context,
description="Reversal Transaction for %s" % context.getTitle(),
specialise_value=portal.sale_trade_condition_module.slapos_manual_accounting_trade_condition,
)
for line in reversal_transaction.getMovementList():
line.edit(quantity=(-line.getQuantity()))
reversal_transaction.confirm(comment="Automatic because of reversal creation")
reversal_transaction.stop(comment="Automatic because of reversal creation")
return reversal_transaction
portal = context.getPortalObject()
return portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
simulation_state="started",
default_causality_uid=context.getUid(),
default_payment_mode_uid=portal.portal_categories.payment_mode.wechat.getUid(),
)
<?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>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_getWechatPaymentRelatedValue</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort
from Products.ERP5Type.tests.utils import createZODBPythonScript
from DateTime import DateTime
class TestSlapOSWechatUpdateConfirmedPayment(SlapOSTestCaseMixinWithAbort):
def _simulatePaymentTransaction_startWechatPayment(self):
script_name = 'PaymentTransaction_startWechatPayment'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PaymentTransaction_startWechatPayment') """ )
self.commit()
def _dropPaymentTransaction_startWechatPayment(self):
script_name = 'PaymentTransaction_startWechatPayment'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
self.commit()
def test_alarm_confirmed_draft_wechat(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.tic()
self._simulatePaymentTransaction_startWechatPayment()
try:
self.portal.portal_alarms.slapos_wechat_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startWechatPayment()
self.tic()
self.assertEqual(
'Visited by PaymentTransaction_startWechatPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_confirmed(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
self.tic()
self._simulatePaymentTransaction_startWechatPayment()
try:
self.portal.portal_alarms.slapos_wechat_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startWechatPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startWechatPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_draft(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.portal.portal_workflow._jumpToStateFor(transaction, 'solved')
self.tic()
self._simulatePaymentTransaction_startWechatPayment()
try:
self.portal.portal_alarms.slapos_wechat_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startWechatPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startWechatPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def test_alarm_not_wechat(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self.tic()
self._simulatePaymentTransaction_startWechatPayment()
try:
self.portal.portal_alarms.slapos_wechat_update_confirmed_payment.activeSense()
self.tic()
finally:
self._dropPaymentTransaction_startWechatPayment()
self.tic()
self.assertNotEqual(
'Visited by PaymentTransaction_startWechatPayment',
transaction.workflow_history['edit_workflow'][-1]['comment'])
def _simulatePaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""return 1""")
self.commit()
def _simulatePaymentTransaction_getZeroTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""return 0""")
self.commit()
def _dropPaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
self.commit()
def test_not_confirmed_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startWechatPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_not_wechat_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startWechatPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_zero_amount_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
simulation_state = transaction.getSimulationState()
modification_date = transaction.getModificationDate()
self._simulatePaymentTransaction_getZeroTotalPayablePrice()
try:
transaction.PaymentTransaction_startWechatPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), simulation_state)
self.assertEqual(transaction.getModificationDate(), modification_date)
def test_expected_payment(self):
new_id = self.generateNewId()
transaction = self.portal.accounting_module.newContent(
portal_type='Payment Transaction',
title="Transaction %s" % new_id,
reference="TESTTRANS-%s" % new_id,
payment_mode="wechat",
)
self.portal.portal_workflow._jumpToStateFor(transaction, 'confirmed')
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
transaction.PaymentTransaction_startWechatPayment()
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
self.assertEqual(transaction.getSimulationState(), 'started')
class TestSlapOSWechatUpdateStartedPayment(SlapOSTestCaseMixinWithAbort):
def test_not_started_payment(self):
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
#
##############################################################################
from erp5.component.test.testSlapOSPayzenBuilder import TestSlapOSPaymentTransactionOrderBuilderMixin
class TestSlapOSWechatPaymentTransactionOrderBuilder(TestSlapOSPaymentTransactionOrderBuilderMixin):
payment_mode = "wechat"
test = TestSlapOSPaymentTransactionOrderBuilderMixin._test
test_twice = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice
test_twice_transaction = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice_transaction
test_twice_indexation = TestSlapOSPaymentTransactionOrderBuilderMixin._test_twice_indexation
test_cancelled_payment = TestSlapOSPaymentTransactionOrderBuilderMixin._test_cancelled_payment
test_two_invoices = TestSlapOSPaymentTransactionOrderBuilderMixin._test_two_invoices
test_two_lines = TestSlapOSPaymentTransactionOrderBuilderMixin._test_two_lines
\ No newline at end of file
image_module/wechat**
portal_alarms/slapos_wechat_update_confirmed_payment
portal_alarms/slapos_wechat_update_started_payment
portal_integrations/slapos_wechat_test_integration
portal_integrations/slapos_wechat_test_integration/Causality
......
test.erp5.testSlapOSWechatSkins
test.erp5.testSlapOSWechatWorkflow
test.erp5.testSlapOSWechatAlarm
test.erp5.testSlapOSWechatBuilder
\ No newline at end of file
test.erp5.testSlapOSWechatEntityCreatePayment
\ No newline at end of file
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