From deb076612f3ff334147180049fbd19704b7c9890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Margueron?= Date: Wed, 9 Feb 2022 11:10:59 +0100 Subject: [PATCH 1/5] CS-717 tax receipt for company/contact is wrong add invoices from the company, when the tax receipt is computed from an employee --- report_compassion/models/res_partner.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/report_compassion/models/res_partner.py b/report_compassion/models/res_partner.py index 101ccc777..278bd452a 100644 --- a/report_compassion/models/res_partner.py +++ b/report_compassion/models/res_partner.py @@ -40,9 +40,16 @@ def get_receipt(self, year): ("last_payment", "<=", end_date), ("state", "=", "paid"), ("product_id.requires_thankyou", "=", True), + # invoice from either the partner, the company or the employee + # to obtain the same results when tax receipt is computed from companies or employees "|", + # invoice from the partner (when self is either an company or an employee) ("partner_id", "=", self.id), + "|", + # invoice from the company (when self is an employee) ("partner_id.parent_id", "=", self.id), + # invoice from the employees (when self is a company) + ("partner_id.child_ids", "=", self.id), ] ) return sum(invoice_lines.mapped("price_subtotal")) From dc21bd3cbb89a0a29b888e58482b61000844ca13 Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 10 Mar 2022 16:35:22 +0100 Subject: [PATCH 2/5] CO-3909 Add wizard for auto-reconciling web donations - Use Postfinance Checkout API to retrieve transactions and find matches --- account_reconcile_compassion/__manifest__.py | 7 +- .../views/reconcile_1015_wizard_view.xml | 30 +++ .../wizards/__init__.py | 1 + .../wizards/reconcile_1015_wizard.py | 196 ++++++++++++++++++ requirements.txt | 1 + 5 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 account_reconcile_compassion/views/reconcile_1015_wizard_view.xml create mode 100644 account_reconcile_compassion/wizards/reconcile_1015_wizard.py diff --git a/account_reconcile_compassion/__manifest__.py b/account_reconcile_compassion/__manifest__.py index 05477f040..9d1eea13d 100644 --- a/account_reconcile_compassion/__manifest__.py +++ b/account_reconcile_compassion/__manifest__.py @@ -29,7 +29,7 @@ # pylint: disable=C8101 { "name": "Bank Statement Reconcile for Compassion CH", - "version": "12.0.1.0.0", + "version": "12.0.1.1.0", "author": "Compassion CH", "license": "AGPL-3", "category": "Finance", @@ -40,7 +40,11 @@ "sponsorship_switzerland", # compassion-switzerland, "mobile_app_connector", # compassion-modules "account_bank_statement_import_camt_details", # OCA/bank-statement-import + "payment_postfinance_flex" # paid-addons ], + "external_dependencies": {"python": [ + "postfinancecheckout" + ]}, "data": [ "data/statement_operation.xml", "views/account_reconcile_compassion.xml", @@ -53,6 +57,7 @@ "views/statement_view.xml", "views/statement_operation_view.xml", "views/view_bank_statement_form.xml", + "views/reconcile_1015_wizard_view.xml", ], "qweb": ["static/src/xml/account_move_reconciliation.xml"], "auto_install": False, diff --git a/account_reconcile_compassion/views/reconcile_1015_wizard_view.xml b/account_reconcile_compassion/views/reconcile_1015_wizard_view.xml new file mode 100644 index 000000000..019344828 --- /dev/null +++ b/account_reconcile_compassion/views/reconcile_1015_wizard_view.xml @@ -0,0 +1,30 @@ + + + + reconcile.1015.wizard.form + reconcile.1015.wizard + +
+ + + + + +
+
+
+
+
+ + + + Reconcile 1015 + reconcile.1015.wizard + form + form + new + + + +
diff --git a/account_reconcile_compassion/wizards/__init__.py b/account_reconcile_compassion/wizards/__init__.py index 686794271..17e2166f6 100644 --- a/account_reconcile_compassion/wizards/__init__.py +++ b/account_reconcile_compassion/wizards/__init__.py @@ -11,3 +11,4 @@ from . import reconcile_fund_wizard from . import reconcile_split_payment_wizard from . import change_attribution_wizard +from . import reconcile_1015_wizard diff --git a/account_reconcile_compassion/wizards/reconcile_1015_wizard.py b/account_reconcile_compassion/wizards/reconcile_1015_wizard.py new file mode 100644 index 000000000..8c6eca8cb --- /dev/null +++ b/account_reconcile_compassion/wizards/reconcile_1015_wizard.py @@ -0,0 +1,196 @@ +############################################################################## +# +# Copyright (C) 2022 Compassion CH (http://www.compassion.ch) +# Releasing children from poverty in Jesus' name +# @author: Emanuel Cino +# +# The licence is in the file __manifest__.py +# +############################################################################## +from datetime import datetime, timedelta +from enum import Enum +from postfinancecheckout import Configuration +from postfinancecheckout.api.transaction_service_api import TransactionServiceApi + +from odoo import api, models, fields, _ +from odoo.tools import ormcache + + +class Provider(Enum): + WORLDLINE = "WORLDLINE SCHWEIZ AG" + PF_CARD = "POSTFINANCE CARD" + TWINT = "TWINT" + E_FINANCE = "POSTFINANCE E-FINANCE" + + +PF_MAPPING = { + Provider.WORLDLINE: "SIX Acquiring", + Provider.PF_CARD: "PostFinance Acquiring - PostFinance Card", + Provider.E_FINANCE: "PostFinance Acquiring - PostFinance E-Finance", + Provider.TWINT: "TWINT - TWINT Connector" +} + + +class ReconcileFundWizard(models.TransientModel): + + _name = "reconcile.1015.wizard" + _description = "Wizard reconcile 1015 account" + + account_id = fields.Many2one( + "account.account", "Reconcile account", + default=lambda s: s.env["account.account"].search([ + ("code", "=", "1015")], limit=1) + ) + full_reconcile_line_ids = fields.Many2many( + "account.move.line", "reconcile_1015_reconciled", string="Reconciled lines", + readonly=True + ) + partial_reconcile_line_ids = fields.Many2many( + "account.move.line", "reconcile_1015_partial_reconciled", + string="Partial reconciled lines", readonly=True + ) + missing_donation_line_ids = fields.Many2many( + "account.move.line", "reconcile_1015_missing_invoice", + string="Leftover donations", + readonly=True + ) + + def reconcile_1015(self): + mvl_obj = self.env["account.move.line"] + credit_lines = mvl_obj.search([ + ("account_id", "=", self.account_id.id), + ("full_reconcile_id", "=", False), + ("credit", ">", 0), + ("date", ">=", "2022-02-09") # Date of activation of pf_checkout + ]) + for cl in credit_lines: + self.reconcile_using_pf_checkout(cl) + + # Compute results + oldest_credit = min(self.partial_reconcile_line_ids.mapped("date")) + self.missing_donation_line_ids = mvl_obj.search([ + ("account_id", "=", self.account_id.id), + ("full_reconcile_id", "=", False), + ("debit", ">", 0), + ("date", "<=", fields.Date.to_string(oldest_credit)), + ("date", ">=", "2022-02-09") # Date of activation of pf_checkout + ]).filtered(lambda m: not m.matched_credit_ids) + self.env.user.notify_success( + message=_("Successfully reconciled %s entries") + % len(self.full_reconcile_line_ids)) + return { + "type": "ir.actions.act_window", + "view_mode": "tree,form", + "view_type": "form", + "res_model": "account.move.line", + "target": "current", + "context": self.env.context, + "domain": [("id", "in", (self.partial_reconcile_line_ids + + self.missing_donation_line_ids).ids)] + } + + @api.multi + def reconcile_using_pf_checkout(self, move_line): + # Transactions are grouped by dates + date_position = -1 + date_length = 8 + search_days_delta = 0 + if Provider.WORLDLINE.value in move_line.name: + date_position = self._search_in_credit_string(move_line, "REFERENCES: ") + search_days_delta = -9 + provider = Provider.WORLDLINE + elif Provider.TWINT.value in move_line.name: + date_position = self._search_in_credit_string(move_line, "Payout ") + search_days_delta = -1 + provider = Provider.TWINT + elif Provider.PF_CARD.value in move_line.name: + date_position = self._search_in_credit_string(move_line, " TRAITEMENT DU ") + date_length = 10 + provider = Provider.PF_CARD + elif Provider.E_FINANCE.value in move_line.name: + date_position = self._search_in_credit_string(move_line, " TRAITEMENT DU ") + date_length = 10 + provider = Provider.E_FINANCE + if date_position == -1: + self.missing_line_ids += move_line + return False + date_transactions = datetime.strptime( + move_line.name[date_position:date_position + date_length], + "%Y%m%d" if date_length == 8 else "%d.%m.%Y" + ) + timedelta(days=search_days_delta) + pf_service, space_id = self.get_pf_service() + debit_match = self.env["account.move.line"] + pf_filter = self.get_pf_filter(date_transactions, provider) + for transaction in pf_service.search(space_id, pf_filter): + # Some references have this TEMPTR- prefix that should be ignored + ref = transaction.merchant_reference.replace("TEMPTR-", "").split("-")[0] + debit_match += self.env["account.move.line"].search([ + ("ref", "like", ref), + ("debit", ">", 0), + ("full_reconcile_id", "=", False), + ("account_id", "=", self.account_id.id) + ]).filtered(lambda m: not m.matched_credit_ids) + # Perform a partial or full reconcile + (move_line + debit_match).reconcile() + if sum(debit_match.mapped("debit")) == move_line.credit: + self.full_reconcile_line_ids += move_line + else: + self.partial_reconcile_line_ids += move_line + return True + + @api.model + def _search_in_credit_string(self, move_line, search_string): + string_position = move_line.name.find(search_string) + if string_position > -1: + string_position += len(search_string) + return string_position + + @ormcache() + def get_pf_service(self): + pf_acquirer = self.env.ref( + "payment_postfinance_flex.payment_acquirer_postfinance") + config = Configuration( + user_id=pf_acquirer.postfinance_api_userid, + api_secret=pf_acquirer.postfinance_api_application_key) + return TransactionServiceApi(configuration=config),\ + pf_acquirer.postfinance_api_spaceid + + @api.model + def get_pf_filter(self, date_search=None, provider=None, state="FULFILL"): + if date_search is None: + date_search = datetime.today() + stop = date_search.replace(hour=23, minute=0, second=0, microsecond=0) + start = stop + timedelta(days=-1) + domain = { + "filter": { + "children": [ + { + "fieldName": "createdOn", + "operator": "GREATER_THAN", + "type": "LEAF", + "value": start.isoformat(), + }, + { + "fieldName": "createdOn", + "operator": "LESS_THAN_OR_EQUAL", + "type": "LEAF", + "value": stop.isoformat(), + }, + { + "fieldName": "state", + "operator": "EQUALS", + "type": "LEAF", + "value": state, + } + ], + "type": "AND", + } + } + if provider is not None: + domain["filter"]["children"].append({ + "fieldName": "paymentConnectorConfiguration.name", + "operator": "CONTAINS", + "type": "LEAF", + "value": PF_MAPPING.get(provider), + }) + return domain diff --git a/requirements.txt b/requirements.txt index 78af644ca..0a3c664a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ matplotlib mysqlclient odoo-sphinx-autodoc git+git://github.com/OCA/openupgradelib +git+git://github.com/pfpayments/python-sdk opencv-python opencv-contrib-python pandas From 576cf5ddced720c8745d07fce7f84a53fcafb689 Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 17 Mar 2022 10:52:31 +0100 Subject: [PATCH 3/5] FIX requirements.txt --- requirements.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0a3c664a4..c64b8817c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,10 @@ bs4 firebase_admin geojson jwt -matplotlib mysqlclient odoo-sphinx-autodoc -git+git://github.com/OCA/openupgradelib -git+git://github.com/pfpayments/python-sdk +git+https://github.com/OCA/openupgradelib +git+https://github.com/pfpayments/python-sdk opencv-python opencv-contrib-python pandas @@ -37,5 +36,4 @@ timezonefinder unidecode wand PyMuPDF -boxdetect fintech \ No newline at end of file From 1d7b7d5b1f9efc1d20f5034f8f6e1abbbf7e04f7 Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 17 Mar 2022 11:06:42 +0100 Subject: [PATCH 4/5] CO-3909 FIX possible outcome messages --- .../wizards/reconcile_1015_wizard.py | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/account_reconcile_compassion/wizards/reconcile_1015_wizard.py b/account_reconcile_compassion/wizards/reconcile_1015_wizard.py index 8c6eca8cb..69ce8415a 100644 --- a/account_reconcile_compassion/wizards/reconcile_1015_wizard.py +++ b/account_reconcile_compassion/wizards/reconcile_1015_wizard.py @@ -67,17 +67,28 @@ def reconcile_1015(self): self.reconcile_using_pf_checkout(cl) # Compute results - oldest_credit = min(self.partial_reconcile_line_ids.mapped("date")) - self.missing_donation_line_ids = mvl_obj.search([ - ("account_id", "=", self.account_id.id), - ("full_reconcile_id", "=", False), - ("debit", ">", 0), - ("date", "<=", fields.Date.to_string(oldest_credit)), - ("date", ">=", "2022-02-09") # Date of activation of pf_checkout - ]).filtered(lambda m: not m.matched_credit_ids) - self.env.user.notify_success( - message=_("Successfully reconciled %s entries") - % len(self.full_reconcile_line_ids)) + if self.partial_reconcile_line_ids: + oldest_credit = min(self.partial_reconcile_line_ids.mapped("date")) + self.missing_donation_line_ids = mvl_obj.search([ + ("account_id", "=", self.account_id.id), + ("full_reconcile_id", "=", False), + ("debit", ">", 0), + ("date", "<=", fields.Date.to_string(oldest_credit)), + ("date", ">=", "2022-02-09") # Date of activation of pf_checkout + ]).filtered(lambda m: not m.matched_credit_ids) + if self.full_reconcile_line_ids: + self.env.user.notify_success( + message=_("Successfully reconciled %s entries") + % len(self.full_reconcile_line_ids), + sticky=True + ) + else: + if not credit_lines: + self.env.user.notify_success( + message=_("Every credit entry is reconciled")) + else: + self.env.user.notify_warning( + message=_("0 credit entry could be fully reconciled")) return { "type": "ir.actions.act_window", "view_mode": "tree,form", From f972cfca55c52e5b850d013bb5bab9260329adbd Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 17 Mar 2022 11:16:16 +0100 Subject: [PATCH 5/5] Track zoom participant state change --- .../models/res_partner_zoom_attendee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partner_communication_switzerland/models/res_partner_zoom_attendee.py b/partner_communication_switzerland/models/res_partner_zoom_attendee.py index 7669bd64a..e35406bbc 100644 --- a/partner_communication_switzerland/models/res_partner_zoom_attendee.py +++ b/partner_communication_switzerland/models/res_partner_zoom_attendee.py @@ -46,7 +46,7 @@ class ZoomAttendee(models.Model): ("attended", "Attended"), ("missing", "Didn't show up"), ("declined", "Declined") - ], default="invited", group_expand="_expand_states") + ], default="invited", group_expand="_expand_states", track_visibility="onchange") optional_message = fields.Text() color = fields.Integer(compute="_compute_color")