diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 94885575331f8f372d84c95b09ae081cfd3dba4a..1c791034c63cc47a275b1d347278e71bd4e24440 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -366,7 +366,7 @@ "table_fieldname": "references" } ], - "modified": "2024-01-20 00:37:06.988919", + "modified": "2024-01-21 00:37:06.988919", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 15208cc10e8dae9664e1017025a7131ea1e8e79c..6276d5cd7ad932c97d0087742b52707d19824e5b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -10,6 +10,7 @@ from frappe.model.document import Document from frappe.query_builder.functions import Sum from frappe.utils import flt, get_url, nowdate from frappe.utils.background_jobs import enqueue +from payments.utils.utils import can_make_immediate_payment from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -20,9 +21,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( ) from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_account_currency -from erpnext.erpnext_integrations.doctype.integration_references.integration_references import ( - can_make_immediate_payment, -) from erpnext.utilities import payment_app_import_guard, webshop_app_import_guard @@ -373,19 +371,13 @@ class PaymentRequest(Document): return controller = _get_payment_gateway_controller(self.payment_gateway) - if frappe.db.exists("Integration References", dict(customer=self.customer)): - doc = frappe.get_doc("Integration References", dict(customer=self.customer)) - else: - doc = frappe.new_doc("Integration References") - - if controller.doctype == "Stripe Settings": - doc.stripe_customer_id = controller.get_customer_id(reference_no) - if not doc.stripe_customer_id: - return - doc.stripe_settings = controller.name - - doc.customer = self.customer - doc.save(ignore_permissions=True) + if customer_id := controller.run_method("get_customer_id", reference_no): + customer = frappe.get_cached_doc("Customer", self.customer) + customer.append( + "payment_gateways_references", + {"payment_gateway": self.payment_gateway, "customer_id": customer_id}, + ) + customer.save() def create_payment_entry(self, submit=True, reference_no=None): """create entry""" diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js deleted file mode 100644 index 21687eaa57bb07a661bb49e562f3981b3c38c999..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Integration References', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json deleted file mode 100644 index 9fd24f5282b308a6aba448a18da351743cf07837..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "allow_rename": 1, - "creation": "2019-08-21 17:10:07.246025", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "customer", - "column_break_2", - "customer_name", - "stripe_section", - "stripe_customer_id", - "column_break_6", - "stripe_settings", - "gocardless_section", - "gocardless_customer_id", - "column_break_8", - "gocardless_settings" - ], - "fields": [ - { - "fieldname": "customer", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Customer", - "options": "Customer", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fetch_from": "customer.customer_name", - "fieldname": "customer_name", - "fieldtype": "Data", - "label": "Customer Name", - "read_only": 1 - }, - { - "fieldname": "stripe_section", - "fieldtype": "Section Break", - "label": "Stripe" - }, - { - "fieldname": "stripe_customer_id", - "fieldtype": "Data", - "label": "Customer ID" - }, - { - "fieldname": "gocardless_section", - "fieldtype": "Section Break", - "label": "GoCardless" - }, - { - "fieldname": "gocardless_customer_id", - "fieldtype": "Data", - "label": "Customer ID" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "fieldname": "gocardless_settings", - "fieldtype": "Link", - "label": "GoCardless Settings", - "options": "GoCardless Settings" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "stripe_settings", - "fieldtype": "Link", - "label": "Stripe Settings", - "options": "Stripe Settings" - } - ], - "modified": "2019-08-22 19:30:55.564180", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Integration References", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "customer_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py deleted file mode 100644 index ca568f98375fe826db37819e645ac76ab62fada5..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2023, Dokos SAS and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - -from payments.payment_gateways.doctype.stripe_settings.api.customer import StripeCustomer - -class IntegrationReferences(Document): - pass - -def can_make_immediate_payment(payment_request, controller): - customer = payment_request.get_customer() - if not customer: - return - - if controller.doctype == "Stripe Settings": - stripe_customer_id = frappe.db.get_value( - "Integration References", - dict(customer=customer, stripe_settings=controller.name), - "stripe_customer_id", - ) - - if stripe_customer_id: - stripe_customer = StripeCustomer(controller).get(stripe_customer_id) - has_default_source = bool(stripe_customer.get("default_source")) - has_default_payment_method = bool(stripe_customer.get("invoice_settings", {}).get("default_payment_method")) - return has_default_source or has_default_payment_method - - elif controller.doctype == "GoCardless Settings": - return bool(controller.check_mandate_validity(payment_request.get_customer()).get("mandate")) - diff --git a/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py b/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py deleted file mode 100644 index dac40e43b74efd88d542f2cec3c7105c6e1fe3f7..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - - -# import frappe -import unittest - - -class TestIntegrationReferences(unittest.TestCase): - pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d033ddaa4c85233138b0d141e052d5442d59513a..1a15e6cec79d958f8df8f91f8241d86f232032a3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -383,6 +383,8 @@ execute:frappe.delete_doc_if_exists("DocType", "Subscription Event") erpnext.patches.dokos.v4_0.set_mode_of_payment_in_payment_requests erpnext.patches.dokos.v4_0.add_customer_to_payment_request erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to_account_table +erpnext.patches.dokos.v4_0.migrate_integration_references_to_customer +execute:frappe.delete_doc_if_exists("DocType", "Integration References") # @dokos diff --git a/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py b/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py new file mode 100644 index 0000000000000000000000000000000000000000..7eca1d2e70c9f289431fdc84a71981f69c24a200 --- /dev/null +++ b/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py @@ -0,0 +1,41 @@ +import frappe + + +def execute(): + for ir in frappe.get_all("Integration References"): + doc = frappe.get_doc("Integration References", ir.name) + customer = frappe.get_doc("Customer", doc.customer) + + if not doc.stripe_customer_id and not doc.gocardless_customer_id: + return + + if doc.stripe_customer_id: + customer.append( + "payment_gateways_references", + { + "payment_gateway": frappe.db.get_value( + "Payment Gateway", + dict(gateway_settings="Stripe Settings", gateway_controller=doc.stripe_settings, disabled=0), + ), + "customer_id": doc.stripe_customer_id, + }, + ) + + if doc.gocardless_customer_id: + customer.append( + "payment_gateways_references", + { + "payment_gateway": frappe.db.get_value( + "Payment Gateway", + dict( + gateway_settings="GoCardless Settings", + gateway_controller=doc.gocardless_settings, + disabled=0, + ), + ), + "customer_id": doc.gocardless_customer_id, + }, + ) + + customer.flags.ignore_mandatory = True + customer.save() diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index dbdaaeb2907710f7224b3039b44dc51ad8fc62fb..fbf00644723080f24a2596e80d62f6b3ba78aa8a 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -87,7 +87,9 @@ "disabled", "status", "portal_users_tab", - "portal_users" + "portal_users", + "payments_tab", + "payment_gateways_references" ], "fields": [ { @@ -594,6 +596,17 @@ "fieldtype": "Link", "label": "Default Mode of Payment", "options": "Mode of Payment" + }, + { + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments" + }, + { + "fieldname": "payment_gateways_references", + "fieldtype": "Table", + "label": "Payment Gateways References", + "options": "Payment Gateways References" } ], "icon": "fa fa-user", @@ -607,7 +620,7 @@ "link_fieldname": "party" } ], - "modified": "2024-03-16 19:41:47.971815", + "modified": "2024-04-23 12:04:01.191169", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 1e76fe122f7e5b7c8b3e2f80e54b2027851e12e2..7968debd59898855f7133d3f152b6f9b76f79170 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -38,6 +38,9 @@ class Customer(TransactionBase): from erpnext.selling.doctype.customer_credit_limit.customer_credit_limit import ( CustomerCreditLimit, ) + from erpnext.selling.doctype.payment_gateways_references.payment_gateways_references import ( + PaymentGatewaysReferences, + ) from erpnext.selling.doctype.sales_team.sales_team import SalesTeam from erpnext.utilities.doctype.portal_user.portal_user import PortalUser @@ -74,6 +77,7 @@ class Customer(TransactionBase): mode_of_payment: DF.Link | None naming_series: DF.Literal["CUST-.YYYY.-"] opportunity_name: DF.Link | None + payment_gateways_references: DF.Table[PaymentGatewaysReferences] payment_terms: DF.Link | None portal_users: DF.Table[PortalUser] primary_address: DF.Text | None @@ -254,7 +258,7 @@ class Customer(TransactionBase): self.db_set("mobile_no", self.mobile_no) self.db_set("email_id", self.email_id) elif self.customer_primary_contact: - frappe.set_value("Contact", self.customer_primary_contact, "is_primary_contact", 1) # ensure + frappe.db.set_value("Contact", self.customer_primary_contact, "is_primary_contact", 1) # ensure def validate_primary_contact(self): primary_contacts = frappe.get_all( @@ -853,3 +857,11 @@ def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: last_name = names[-1] if len(names) > 1 else None return first_name, middle_name, last_name + + +def get_customer_from_integration_id(payment_gateway, customer_id): + return frappe.db.get_value( + "Payment Gateways References", + dict(parenttype="Customer", payment_gateway=payment_gateway, customer_id=customer_id), + "parent", + ) diff --git a/erpnext/erpnext_integrations/doctype/integration_references/__init__.py b/erpnext/selling/doctype/payment_gateways_references/__init__.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/integration_references/__init__.py rename to erpnext/selling/doctype/payment_gateways_references/__init__.py diff --git a/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json new file mode 100644 index 0000000000000000000000000000000000000000..7458d13b10237cddaa90df68ba264fefa88b9b49 --- /dev/null +++ b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-04-23 12:00:18.764044", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_gateway", + "customer_id", + "payment_method", + "payment_method_id" + ], + "fields": [ + { + "fieldname": "payment_gateway", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Payment Gateway", + "options": "Payment Gateway", + "reqd": 1 + }, + { + "fieldname": "customer_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Customer ID", + "reqd": 1 + }, + { + "fieldname": "payment_method", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Default Payment Method" + }, + { + "fieldname": "payment_method_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Default Payment Method ID" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-04-23 12:03:24.787858", + "modified_by": "Administrator", + "module": "Selling", + "name": "Payment Gateways References", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py new file mode 100644 index 0000000000000000000000000000000000000000..2706287684d6d66b5c05e8bc673f86e51e2cc9b0 --- /dev/null +++ b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py @@ -0,0 +1,26 @@ +# Copyright (c) 2024, Dokos SAS and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PaymentGatewaysReferences(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + customer_id: DF.Data + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + payment_gateway: DF.Link + payment_method: DF.Data | None + payment_method_id: DF.Data | None + # end: auto-generated types + + pass diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 97107d315ea2e3cf7a671b31aa1fd076eb7849fd..da958f9879046070ca0df291f2413202f16847fa 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -5444,7 +5444,6 @@ Insurer,Assureur, Integrated Tax,Tax intégrée, Integration Details,Détails d'intégration, Integration ID,ID d'intégration, -Integration References,Références d'intégration, Integrations Settings,Paramètres des intégrations, Inter Company Account,Compte inter-sociétés, Inter Company Invoice Reference,Référence de facture inter-sociétés, diff --git a/erpnext/www/me.py b/erpnext/www/me.py index 41cae65e8cce4e74ce616ba4447be80db950d963..692b20cb161f775f2d1609c5ae8179796121a61d 100644 --- a/erpnext/www/me.py +++ b/erpnext/www/me.py @@ -33,39 +33,54 @@ def get_context(context): ) context.third_party_apps = True if active_tokens else False - customers, suppliers = get_customers_suppliers("Integration References", frappe.session.user) + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) if customers: - try: - if frappe.db.exists("Integration References", dict(customer=customers[0])): - references = frappe.get_doc("Integration References", dict(customer=customers[0])) - if references.get("stripe_customer_id") and references.get("stripe_settings"): - context.enable_stripe = True - context.stripe_payment_methods = get_customer_payment_methods(references) - context.publishable_key = get_api_key(references.stripe_settings) - except Exception: - pass - - context.credits_balance = get_balance(customers[0]) - - -def get_customer_payment_methods(references): + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + context.enable_stripe = True + context.stripe_payment_methods = get_customer_payment_methods( + stripe_references, customer.name, gateway_controller + ) + context.publishable_key = get_api_key(gateway_controller) + + context.credits_balance = get_balance(customer.name) + + +def get_customer_payment_methods(controller, customer, stripe_customer_id): try: - stripe_settings = frappe.get_doc("Stripe Settings", references.stripe_settings) - return StripePaymentMethod(stripe_settings).get_list(references.stripe_customer_id) + stripe_settings = frappe.get_doc("Stripe Settings", controller) + return StripePaymentMethod(stripe_settings).get_list(stripe_customer_id) except Exception: frappe.log_error( - _("[Portal] Stripe payment methods retrieval error for {0}").format(references.customer), + _("[Portal] Stripe payment methods retrieval error for {0}").format(customer), ) @frappe.whitelist() def remove_payment_card(id): try: - customers, suppliers = get_customers_suppliers("Integration References", frappe.session.user) - account = frappe.get_doc("Integration References", dict(customer=customers[0])) - if account.stripe_settings: - stripe_settings = frappe.get_doc("Stripe Settings", account.stripe_settings) - return StripePaymentMethod(stripe_settings).detach(id) + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) + if customers: + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + return StripePaymentMethod(stripe_settings).detach(id) except Exception: frappe.log_error(_("[Portal] Stripe payment source deletion error")) @@ -74,11 +89,22 @@ def remove_payment_card(id): @frappe.whitelist() def add_new_payment_card(payment_method): try: - customers, suppliers = get_customers_suppliers("Integration References", frappe.session.user) - account = frappe.get_doc("Integration References", dict(customer=customers[0])) - if account.stripe_settings: - stripe_settings = frappe.get_doc("Stripe Settings", account.stripe_settings) - return StripePaymentMethod(stripe_settings).attach(payment_method, account.stripe_customer_id) + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) + if customers: + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + return StripePaymentMethod(stripe_settings).attach( + payment_method, stripe_references[0].customer_id + ) except Exception: frappe.log_error(_("[Portal] New stripe payment source registration error"))